Re: [OAUTH-WG] Comments on draft-richer-oauth-introspection-04

"Richer, Justin P." <jricher@mitre.org> Sat, 26 October 2013 16:03 UTC

Return-Path: <jricher@mitre.org>
X-Original-To: oauth@ietfa.amsl.com
Delivered-To: oauth@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id AADE311E81AD for <oauth@ietfa.amsl.com>; Sat, 26 Oct 2013 09:03:36 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -5.254
X-Spam-Level:
X-Spam-Status: No, score=-5.254 tagged_above=-999 required=5 tests=[AWL=-1.345, BAYES_05=-1.11, HTML_MESSAGE=0.001, J_CHICKENPOX_36=0.6, J_CHICKENPOX_43=0.6, RCVD_IN_DNSWL_MED=-4]
Received: from mail.ietf.org ([12.22.58.30]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id jrmn8uk5r1ak for <oauth@ietfa.amsl.com>; Sat, 26 Oct 2013 09:03:30 -0700 (PDT)
Received: from smtpksrv1.mitre.org (smtpksrv1.mitre.org [198.49.146.77]) by ietfa.amsl.com (Postfix) with ESMTP id 6CAF711E81A0 for <oauth@ietf.org>; Sat, 26 Oct 2013 09:03:30 -0700 (PDT)
Received: from smtpksrv1.mitre.org (localhost.localdomain [127.0.0.1]) by localhost (Postfix) with SMTP id C0B331F092C; Sat, 26 Oct 2013 12:03:29 -0400 (EDT)
Received: from IMCCAS01.MITRE.ORG (imccas01.mitre.org [129.83.29.78]) by smtpksrv1.mitre.org (Postfix) with ESMTP id A54CE1F0557; Sat, 26 Oct 2013 12:03:29 -0400 (EDT)
Received: from IMCMBX01.MITRE.ORG ([169.254.1.251]) by IMCCAS01.MITRE.ORG ([129.83.29.68]) with mapi id 14.03.0158.001; Sat, 26 Oct 2013 12:03:29 -0400
From: "Richer, Justin P." <jricher@mitre.org>
To: Thomas Broyer <t.broyer@gmail.com>
Thread-Topic: [OAUTH-WG] Comments on draft-richer-oauth-introspection-04
Thread-Index: AQHO0CU84Zn8ICoUOUaw6JzqL1n5yZoFJoYmgADLaoCAACrEgIABT7mA
Date: Sat, 26 Oct 2013 16:03:28 +0000
Message-ID: <AEDCC30D-5BE8-464A-AA44-A366049015CF@mitre.org>
References: <CAEayHENijdeTVu9-OxsnrJEh0JQBrvQo0eKWSjFvXSLqwzVRWg@mail.gmail.com> <63692DF5-4616-4F53-B12E-518397CFEFB3@mitre.org> <CAEayHEMZdOY5G=A5nc_pA14gUcyNpbbeb-pVpo7Cf_yB70Mjjw@mail.gmail.com> <9adcccce-7c48-47b8-b9b8-77a4ce439254@email.android.com> <CAEayHEN5LoX70OObz2jj-7wmH4qqpSQmhiTbe4kwgwpEt4PMkQ@mail.gmail.com> <526AAA2F.5020705@lodderstedt.net> <CAEayHEPSvpaiAiq-eBQodxvoCRgK7TZ45i1gm=sXKs0HT1qj1g@mail.gmail.com>
In-Reply-To: <CAEayHEPSvpaiAiq-eBQodxvoCRgK7TZ45i1gm=sXKs0HT1qj1g@mail.gmail.com>
Accept-Language: en-US
Content-Language: en-US
X-MS-Has-Attach:
X-MS-TNEF-Correlator:
x-originating-ip: [172.31.3.191]
Content-Type: multipart/alternative; boundary="_000_AEDCC30D5BE8464AAA44A366049015CFmitreorg_"
MIME-Version: 1.0
Cc: "<oauth@ietf.org>" <oauth@ietf.org>
Subject: Re: [OAUTH-WG] Comments on draft-richer-oauth-introspection-04
X-BeenThere: oauth@ietf.org
X-Mailman-Version: 2.1.12
Precedence: list
List-Id: OAUTH WG <oauth.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/oauth>, <mailto:oauth-request@ietf.org?subject=unsubscribe>
List-Archive: <http://www.ietf.org/mail-archive/web/oauth>
List-Post: <mailto:oauth@ietf.org>
List-Help: <mailto:oauth-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/oauth>, <mailto:oauth-request@ietf.org?subject=subscribe>
X-List-Received-Date: Sat, 26 Oct 2013 16:03:36 -0000

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

 -- Justin