What you suggest below is essentially what John Bradley proposed as a "holder of key" JWT a while ago. The main difference between the two approaches is that mine can also cover aspects of the HTTP request itself in the signature. I think you could do both of them together by simply making the "signed" and "hash" bits optionally empty, which would effectively give you the functionality you're after.

 -- Justin

On 10/26/2013 04:34 PM, Thomas Broyer wrote:


On Sat, Oct 26, 2013 at 6:03 PM, Richer, Justin P. <jric...@mitre.org <mailto:jric...@mitre.org>> wrote:

    >> On our backlog is also support for "service accounts" (to use
    Google's terminology), so clients will likely need to do some
    crypto-related work. Asking them to do it for each and every
    request to sign the access token might not be that
    >
    >
    > I assume you mean signing the request or at least some request
    data. Just signing the access token won't help.

    I meant signing the access token + PR identifier/URI + some
    timestamp, at a minimum.
    I explained it better in my answer to Justin; something like
    jwt-bearer applied to access tokens.



    I suggested to the group that we try something like this a while
    ago but it didn't get traction at the time. I never really wrote
    down the whole process I had in mind, so here's a quick overview:


    First, the client gets a token from the auth server using any
    normal oauth mechanism (or any abnormal mechanism for that
    matter), and the response looks something like this:

    {
      token_type: signed,
      access_token: <public token value>
      token_key: <private token secret used for signing, maybe as a JWK?>
      alg: <JWS algorithm to use for signing requests to the RS, I'll
    use HS256 for my example below but we could probably tweak this to
    use asymmetric keys too>
      expires_in: 3600
      …
    }


    Next, the client figures out the request it wants to make:

    GET http://foo.bar/baz?quxx=bob&woz=whynot HTTP/1.1
    Accept: application/json
    X-Other-Header: SomeHeaderValue


    Treating this request as a structure, we've got a verb, a url
    (with scheme, host, path, parameters), and some headers. We want
    the client to be able to sign some subset of this with the
    request, so let's say this client wants to sign the verb,
    scheme+host+port+path, the parameters, and one of the headers. So
    the way I see it we've got a couple options. First is to repeat
    all the items to be signed within a JSON structure and use JWS.
    This is messy and it ignores some of the best stuff about using
    HTTP/REST these days. Second, we can combine, normalize, and hash
    the signable items. With this approach and some sufficient
    handwavium (because I don't really want to get into specifics of
    serialization and normalization here), we get something like this
    for a base string:

    GET
    http://foo.bar/baz
    quux=bob
    woz=whynot
    X-Other-Header=SomeHeaderValue

    which we can hash using a defined algorithm of the same bit size
    as our signing algorithm. So say we're using HMAC 256, we get a
    base64url encoded hash blob like this fake one I just made up:
    HJ23dfjoa32fasd23lajkds

    So great, we've got a hash and we've got a set of data that was
    hashed. So far we're in the same boat that got a lot of OAuth 1.0
    implementations in trouble, due to oddities about normalization.
    So let's take this a step further and tell the server what we're
    hashing in our signed content, but by repeating just the *keys* to
    this content and not the content itself, since the keys will be
    shorter in many cases and less redundant:

    signed: [ "verb", "scheme+host+path", {"query": ["quux", "woz"]},
    {"header": ["X-Other-Header"]} ]

    Note that this uses arrays, which is important because arrays
    preserve order in JSON. Now we have a pretty programmatic way of
    telling the server which bits we care about signing and what order
    we put them in, with normalizing those aspects. This makes us
    robust against stuff getting reordered and new headers or query
    parameters being inserted (which often happens with various web
    frameworks). Of course, after verifying a signature, an app would
    want to make sure that important parameters were covered by that
    signature, but that's up to the app. Any decent library would make
    the list available to the app easily enough for a quick check.

    OK, so now we've got our hash and our note on what we hashed.
    Let's put those into a JSON object:

    {
    hash: HJ23dfjoa32fasd23lajkds,
    signed: [ "verb", "scheme+host+path", {"query": ["quux", "woz"]},
    {"header": ["X-Other-Header"]} ],
    token_id: <public token value>
    }

    And we'll make our normal JWS header, use the above JSON object as
    the bode, and sign it with JWS using the secret/key that we got up
    in the token response:

    eyheaderstufffooo.eybodystuffbaaar.signatureblob


    [Side note: Maybe if you really wanted to you could also sign it
    using the client secret and include the client id in the signed
    data, like OAuth 1.0 does.]

    And *now* we can send this over using any method comparable to
    those defined in RFC6750, since it's all a single, self-contained
    value.

    GET http://foo.bar/baz?quxx=bob&woz=whynot HTTP/1.1
    Accept: application/json
    X-Other-Header: SomeHeaderValue
    Authorization:
    SignedOAuth eyheaderstufffooo.eybodystuffbaaar.signatureblob


    or:

    GET
    
http://foo.bar/baz?quxx=bob&woz=whynot&signed_access_token=eyheaderstufffooo.eybodystuffbaaar.signatureblob
 HTTP/1.1
    Accept: application/json
    X-Other-Header: SomeHeaderValue


    Note that in the both cases, the newly introduced header and query
    parameter are automatically excused from the signature calculation
    because they don't show up in the signed lists.


    OK, so the RS gets this request, and what does it do? Easy:

    First, check the signature on the token. This is self-contained
    and is a quick JWS operation. [Side note: how does the RS get the
    private signing/checking key from the AS? I don't care, and
    neither should you, because it's orthogonal to this part of the
    spec family.]

    Second, the RS parses the body and reads the "signed" member. This
    "signed" member tells the RS which parts of the original request
    it needs to check. Even with extra parameters and bits you end up
    pulling only the parts that you need. And you know what order to
    smash them together, too, without doing any kind of sorting!

    Third, the RS calculates the hash of this string and compares it,
    literally, to the "hash" parameter that was sent.

    Fourth, the RS makes sure any "important" parameters and headers
    and other bits are actually covered by the hash.



    Anyway, I think this method is worlds simpler than what we've got
    in http-mac right now and it goes back to solving a number of the
    use cases that have been brought up, including my own of simply
    protecting an HTTP message apart from tokens.


Couldn't we have something middle-ground between bearer tokens "in the clear" (RFC6750) and signing the request like http-mac or your proposal?

What I'd need is *just* a way so that a "thing" passed from the Client to the PR couldn't be reused with other PRs (should contain something that identifies the PR), and ideally couldn't also be "replayed" (let's give it a validity period, maybe also a nonce). Doesn't that nicely matches with JWT's "aud", "iat", "exp", "nbf" and possibly "jti" of JWT, as already used by jwt-bearer? Let's just use the client_id as "iss" (as in jwt-bearer) and the "access token" as "sub", and sign that. It's assumed that the AS already knows the public key of the Client so it can verify the signature; the access token response from the AS would probably include the signature algorithm that the Client is expected to use, but that could also have been "negotiated" before (just as with jwt-bearer, you have to know what the AS expects you to use). A compromised PR, despite knowing the access token (unless the JWT is encrypted rather than just signed), couldn't access other resource servers as it couldn't Just as with bearer tokens, it's assumed that PRs are reached through TLS (if you need OAuth, it means you're accessing data of the End-User, so you should use TLS anyway for privacy concerns). In the event there's an eavesdropper nevertheless, the "iat", "exp" and "nbf" limit the reuse of the token, and "jti" (if used, and used correctly) totally mitigates the risk of replay attacks.

The major remaining risk is a compromised client (and of course compromised AS). I don't think we can do much things about it though, apart from heavy monitoring at the AS and blacklisting the Client whenever some bad use is detected.

Remember I'm not a security guy though. Would that be too insecure? (I doubt it, everybody uses bearer tokens nowadays, and the approach is the same as with jwt-bearer) Have I missed something?


--
Thomas Broyer
/tɔ.ma.bʁwa.je/ <http://xn--nna.ma.xn--bwa-xxb.je/>

_______________________________________________
OAuth mailing list
OAuth@ietf.org
https://www.ietf.org/mailman/listinfo/oauth

Reply via email to