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/>