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

Thomas Broyer <> Sat, 26 October 2013 20:34 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id 6928211E8176 for <>; Sat, 26 Oct 2013 13:34:47 -0700 (PDT)
X-Virus-Scanned: amavisd-new at
X-Spam-Flag: NO
X-Spam-Score: -1.968
X-Spam-Status: No, score=-1.968 tagged_above=-999 required=5 tests=[AWL=-0.569, BAYES_00=-2.599, HTML_MESSAGE=0.001, J_CHICKENPOX_36=0.6, J_CHICKENPOX_43=0.6, NO_RELAYS=-0.001]
Received: from ([]) by localhost ( []) (amavisd-new, port 10024) with ESMTP id 0MGnOEqvOpUu for <>; Sat, 26 Oct 2013 13:34:46 -0700 (PDT)
Received: from ( [IPv6:2607:f8b0:400c:c02::231]) by (Postfix) with ESMTP id C639F11E8202 for <>; Sat, 26 Oct 2013 13:34:45 -0700 (PDT)
Received: by with SMTP id w16so3280897vbb.8 for <>; Sat, 26 Oct 2013 13:34:45 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type; bh=kionpubTdjUtOr05zIGffkqQvEz+n889E1kmQTy0sVc=; b=Ztc0L1ZfVbsLH+l45LZRIOAt8mZAVIxGLUkhn2hXDWGQoBqehTEZ6Bk+aMi0CprMlt PNdcYvLX1YV16Oo3Owrdwg46tZzlN0BkvpVdfivwHsh747RMBlI4cq/Zhco6EP02AX4S m3ZpOaxthSmKtr1dHWZ4Xy3RlD+FmG/3LL60nDvHOcwHIUr0qbt8ztF/KP3uKKB+5eb4 pSEL45ODUC6a1KgeslAQdnkjO4L9QEGvh5EsOUz/sClmO70L3zB1AFDeV2YZm/unyOk/ zDSYvS9SLojMDB+xmx2GeS2PKRGtmd+eIeVzmGH9HvzSC6SFrv9p60E+D/lYQFdLfnxE IzLg==
X-Received: by with SMTP id zf3mr735813vec.37.1382819685096; Sat, 26 Oct 2013 13:34:45 -0700 (PDT)
MIME-Version: 1.0
Received: by with HTTP; Sat, 26 Oct 2013 13:34:24 -0700 (PDT)
In-Reply-To: <>
References: <> <> <> <> <> <> <> <>
From: Thomas Broyer <>
Date: Sat, 26 Oct 2013 22:34:24 +0200
Message-ID: <>
To: "Richer, Justin P." <>
Content-Type: multipart/alternative; boundary=089e0153798a1e616504e9aacc4b
Cc: "<>" <>
Subject: Re: [OAUTH-WG] Comments on draft-richer-oauth-introspection-04
X-Mailman-Version: 2.1.12
Precedence: list
List-Id: OAUTH WG <>
List-Unsubscribe: <>, <>
List-Archive: <>
List-Post: <>
List-Help: <>
List-Subscribe: <>, <>
X-List-Received-Date: Sat, 26 Oct 2013 20:34:47 -0000

On Sat, Oct 26, 2013 at 6:03 PM, Richer, Justin P. <>wrote;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/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
> 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/1.1
> Accept: application/json
> X-Other-Header: SomeHeaderValue
>  Authorization:
> SignedOAuth eyheaderstufffooo.eybodystuffbaaar.signatureblob
>  or:
>   GET
> 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

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