On Sat, Oct 26, 2013 at 6:03 PM, Richer, Justin P. <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