Iterating on the protocol

35 views
Skip to first unread message

Dirkjan Ochtman

unread,
Jun 11, 2016, 11:17:53 AM6/11/16
to lets...@googlegroups.com
All,

After implementing the Rust version of the daemon [1] based on onli
and Dan's authbackend [2], I wrote down some notes on how the protocol
currently works [3]. In order to make progress on this project, I
think it's important that we validate the current protocol and see if
it's going in the right direction, or if there are open issues that
are at odds with how we want to build the project.

As I understand it, the protocol is mainly two separate OpenID Connect
flows: one between the RP and the LA daemon, and the other is between
the LA daemon and the user's identity provider if that identity
provider is "famous" (in my implementation, gmail.com or hotmail.com).

Gap 1: it seems like a secure implementation of OpenID Connect
requires a pre-shared secret between the relying party and the
identity provider. This establishes a relationship between these two,
which means a true OpenID Connect IdP can never send an Identity Token
to a random host pointed to by some web form. Currently, it's
trivially possible to have an Identity Token for some user be sent
from LA to a random, different host than the user might think, which
seems bad to me.

Gap 2: what do we do for "native" LA identity providers? One simple
solution would be to use OpenID Connect for that case, too. The
reliance of OIDC on pre-shared secrets however makes this less
attractive if we want to support identity providers without central
registration. While it's okay to maintain a list of secrets and client
identifiers for a handful of famous IdPs (the Rust implementation can
read these from a configuration file), I think we'd like to allow any
domain to be an IdP, and the requirement to have pre-shared secrets
makes it annoying to scale this up.

Gap 3: as noted in the protocol write-up I did, we don't yet have an
established path of returning errors to the RP. Right now, the demo-RP
that Dan worked on [4] and that I have been testing on pretty much
only supports the "happy path". I think isn't so hard, but we do need
consensus here.

Curious to hear your thoughts.

Cheers,

Dirkjan

[1] https://github.com/djc/ladaemon
[2] https://github.com/callahad/authbackend/
[3] https://github.com/djc/ladaemon/blob/master/protocol.md
[4] https://github.com/letsauth/demo-rp

Stavros Korokithakis

unread,
Jun 11, 2016, 12:26:02 PM6/11/16
to lets...@googlegroups.com
Hey Dirkjan,
since #1 and #2 seem to both be caused by the need for pre-shared
credentials, I'll consider them one issue in my reply.

As far as I know, even though we're using the Authorization Code OIDC
flow right now, the Implicit flow is also available, for clients that
can't/won't maintain secrets. Since we won't be doing any API access
later on (i.e. we won't be using OIDC for authorization, e.g. to
retrieve user info *after* authentication), we don't need refresh tokens
and the like, so we can use one of the other flows (Implicit or Hybrid),
although Implicit sounds like a better fit.

I'm not very up to date on my OIDC, though, so please correct me if I'm
wrong or if I'm missing any information.

Thanks,
Stavros

Ryan Kelly

unread,
Jun 11, 2016, 9:50:54 PM6/11/16
to lets...@googlegroups.com
On 12/06/2016 01:17, Dirkjan Ochtman wrote:
>
> Gap 1: it seems like a secure implementation of OpenID Connect
> requires a pre-shared secret between the relying party and the
> identity provider. This establishes a relationship between these two,
> which means a true OpenID Connect IdP can never send an Identity Token
> to a random host pointed to by some web form. Currently, it's
> trivially possible to have an Identity Token for some user be sent
> from LA to a random, different host than the user might think, which
> seems bad to me.

I don't believe a secret is required in order to ensure security,
although it does make it easier to get the security right.

IIUC the security of the secret-free OIDC "implicit flow" depends
crucially on knowing what redirect_uri values can be used with what
client_id values, and on having both sides of the dance check this
correspondence.

You start the OIDC login by specifying a client_id and redirect_uri, and
it's up to the provider to check that the (client_id, redirect_uri) pair
is valid.

You end the OIDC login by having the user land on the specified
redirect_uri, with an id_token that's scoped to a particular client_id.
At this point the relier must also check that the (client_id,
redirect_uri) pair is valid, i.e. that they represent values for this
relier and not someone else.

What does it mean for a (client_id, redirect_uri) pair to be valid? Well...

> Gap 2: what do we do for "native" LA identity providers? One simple
> solution would be to use OpenID Connect for that case, too. The
> reliance of OIDC on pre-shared secrets however makes this less
> attractive if we want to support identity providers without central
> registration. While it's okay to maintain a list of secrets and client
> identifiers for a handful of famous IdPs (the Rust implementation can
> read these from a configuration file), I think we'd like to allow any
> domain to be an IdP, and the requirement to have pre-shared secrets
> makes it annoying to scale this up.

I strongly agree that pre-registration is something to be avoided, and
I'm hopeful we can find a way to make it work.

OpenID Connect has a standard that's designed to solve this problem, the
"dynamic client registration" spec:

http://openid.net/specs/openid-connect-registration-1_0.html

It's been a while since I read it in detail, but I remember having two
broad take-aways from it:

1) The data-model and security-model is pretty good.

2) The way it portions out work between reliers and IdPs creates
weak incentives for either side to actually implement it.

This spec basically provides an API for each relier to register itself
with one or more IdPs. Automating that registration is good, but
getting rid of it entirely would be even better.

What I think would work well, is if we took the data model from this
spec and combined it with the idea of discovery ala persona.

Some quick thoughts about a half-baked proposal:

* Identify clients by URL. IIUC the current protocol and prototype does
this already. But the key point is that it's a full URL, including
path component, not just an origin.

* Rather than requiring reliers to register themselves to the IdP,
have the IdP discover public metadata about each relier by looking
up a support document.

It could work like this:

1) The relier starts the flow by hitting the LA authentication endpoint
like it does now:

POST /auth HTTP/1.1

login_hint=m...@example.com&
scope=openid%20email&
response_type=id_token&
client_id=https://rp.info/&
redirect_uri=https://rp.info/login

2) The IdP requests the OpenID configuration document from the relier,
in order to find its metadata as specced at [1]:

GET https://rp.info/.well-known/openid-configuration

3) The IdP validates that the redirect_uri is one that's listed in the
discovered metadata, along with a bunch of other security checking
rules.

4) The flow proceeds from here as specified in the current LA protocol.

N) At the end of all this, the relier must check that it has received
an id_token that's propery scoped to its client_id URL.

I think this would give us the same level of security as the existing
OIDC client registration spec, while allowing clients to come into
existing merely by publishing something at a URL, rather than
registering for a shared secret.

Two interesting things that could come out of this:

* The same protocol could be used between the relier and "native" LA
IdPs. The native IdP could follow all the same discovery steps as
the LA daemon, and the relier doesn't have to do anything at all
in order to be compatible with it.

* We could probably define a sensible default suite of metadata for
reliers that don't publish a full OpenID configuration. Among
other things, it would restrict the allowed value of redirect_uri
to precisely match the client_id.

I'm about to go jump on a plane, but I'll see if I can use the flight
time to flesh out the details here a little more and post a concrete
protocol proposal...


[1]
http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata


> Gap 3: as noted in the protocol write-up I did, we don't yet have an
> established path of returning errors to the RP. Right now, the demo-RP
> that Dan worked on [4] and that I have been testing on pretty much
> only supports the "happy path". I think isn't so hard, but we do need
> consensus here.

One thing to be careful of here is open redirects. The OAuth2 spec
essentially says that you should handle errors by redirecting back to
the client's redirect_uri with URL params describing the error.
Combined with dynamic regisreation/discovering or redirect uris this is
pretty much a spec-mandated open redirect vulnerability :-/



Cheers,

Ryan

Ryan Kelly

unread,
Jun 12, 2016, 3:28:21 AM6/12/16
to lets...@googlegroups.com
On 12/06/2016 11:50, Ryan Kelly wrote:
>
>> Gap 2: what do we do for "native" LA identity providers? One simple
>> solution would be to use OpenID Connect for that case, too. The
>> reliance of OIDC on pre-shared secrets however makes this less
>> attractive if we want to support identity providers without central
>> registration. While it's okay to maintain a list of secrets and client
>> identifiers for a handful of famous IdPs (the Rust implementation can
>> read these from a configuration file), I think we'd like to allow any
>> domain to be an IdP, and the requirement to have pre-shared secrets
>> makes it annoying to scale this up.
>
> OpenID Connect has a standard that's designed to solve this problem, the
> "dynamic client registration" spec:
>
> http://openid.net/specs/openid-connect-registration-1_0.html
>
> [..snip..]
>
> What I think would work well, is if we took the data model from this
> spec and combined it with the idea of discovery ala persona.

I had a bit of a go at writing this out, and I think it came out OK -
see attached.

It could make for a decent protocol between LA and native identity
providers, between reliers and LA, or even between reliers and native
providers without using LA as a middleman.

But that means it may also be a bit too open-ended and generic for just
the LA use-case needed here :-)

Anyway, it's something that's been kicking around in my head for a while
and seemed like it might be a good fit here.


Cheers,

Ryan
oidc_with_client_discovery.txt

Dirkjan Ochtman

unread,
Jun 15, 2016, 3:42:42 PM6/15/16
to Ryan Kelly, Let's Auth
On Sun, Jun 12, 2016 at 9:28 AM, Ryan Kelly <ry...@rfk.id.au> wrote:
>> What I think would work well, is if we took the data model from this
>> spec and combined it with the idea of discovery ala persona.
>
> I had a bit of a go at writing this out, and I think it came out OK -
> see attached.

I had a look at it, but I think it doesn't quite cover the concern I
have. Yes, the provider can verify that the callback URL in the
authentication request is valid according to the relier's
configuration document; but since this information is publicly
available, it still does not prevent some malevolent party from
triggering a user's login on that relier. Do you think that's not a
problem?

Cheers,

Dirkjan

Dan Callahan

unread,
Jun 15, 2016, 4:56:08 PM6/15/16
to Dirkjan Ochtman, Ryan Kelly, Let's Auth
On 6/15/16 14:42, Dirkjan Ochtman wrote:

> available, it still does not prevent some malevolent party from
> triggering a user's login on that relier. Do you think that's not a
> problem?

This seems congruent to someone going to
https://github.com/password_reset and entering my email address.
Potentially annoying, but also the status quo for the world.

-Dan

Ryan Kelly

unread,
Jun 15, 2016, 5:46:28 PM6/15/16
to Dirkjan Ochtman, Let's Auth
I think it would be useful to spell out the threat model a little more.
As long as the user has to actively consent to the login before it'll go
through, then it doesn't seem too bad to me - but I could easily be
missing something.

Cheers,

Ryan

Dirkjan Ochtman

unread,
Jun 16, 2016, 3:25:08 AM6/16/16
to Ryan Kelly, Let's Auth
On Wed, Jun 15, 2016 at 11:46 PM, Ryan Kelly <ry...@rfk.id.au> wrote:
> I think it would be useful to spell out the threat model a little more.
> As long as the user has to actively consent to the login before it'll go
> through, then it doesn't seem too bad to me - but I could easily be
> missing something.

In the implementation I have right now, a user authenticating with
GMail for the second time (independent of RP) does not have to
actively consent. Are we saying this is something that would have to
change?

By the way, your document seems to say that we'll have RPs rely on an
external verification service (like Persona). It's been my thinking
that the remote verification was not great, and we shouldn't
necessarily offer a remote verifier. I guess this is something else
that we'll need to discuss.

Cheers,

Dirkjan

Dan Callahan

unread,
Jun 16, 2016, 5:07:39 AM6/16/16
to Dirkjan Ochtman, Ryan Kelly, Let's Auth


On 6/16/16 02:24, Dirkjan Ochtman wrote:
> On Wed, Jun 15, 2016 at 11:46 PM, Ryan Kelly <ry...@rfk.id.au> wrote:
>> I think it would be useful to spell out the threat model a little more.
>> As long as the user has to actively consent to the login before it'll go
>> through, then it doesn't seem too bad to me - but I could easily be
>> missing something.
> In the implementation I have right now, a user authenticating with
> GMail for the second time (independent of RP) does not have to
> actively consent. Are we saying this is something that would have to
> change?

Strawman: We should require consent before each log-in. Otherwise, it
might be possible for websites to probe for identities without user
interaction.

> By the way, your document seems to say that we'll have RPs rely on an
> external verification service (like Persona). It's been my thinking
> that the remote verification was not great, and we shouldn't
> necessarily offer a remote verifier.

A remote verification API is crucial to LA's long-term success and
security. It's too easy to get JWT verification wrong; we should
implement that once, in the daemon, and encourage its re-use.

-Dan

Dirkjan Ochtman

unread,
Jun 16, 2016, 5:42:44 AM6/16/16
to Dan Callahan, Ryan Kelly, Let's Auth
On Thu, Jun 16, 2016 at 11:07 AM, Dan Callahan <dan.ca...@gmail.com> wrote:
>> In the implementation I have right now, a user authenticating with
>> GMail for the second time (independent of RP) does not have to
>> actively consent. Are we saying this is something that would have to
>> change?
>
> Strawman: We should require consent before each log-in. Otherwise, it might
> be possible for websites to probe for identities without user interaction.

Strawman: logging into Mozilla's Bugzilla with GitHub is particularly
smooth because there is no prompt that I have to click through.

>> By the way, your document seems to say that we'll have RPs rely on an
>> external verification service (like Persona). It's been my thinking
>> that the remote verification was not great, and we shouldn't
>> necessarily offer a remote verifier.
>
> A remote verification API is crucial to LA's long-term success and security.
> It's too easy to get JWT verification wrong; we should implement that once,
> in the daemon, and encourage its re-use.

It's also a single point of failure (and the Persona verifier actually
did have vulnerabilities). Having the party who created a JWT also
verify it seems pretty pointless from a security point of view.

Cheers,

Dirkjan

Ryan Kelly

unread,
Jun 16, 2016, 6:18:21 AM6/16/16
to lets...@googlegroups.com
On 16/06/2016 10:42, Dirkjan Ochtman wrote:
> On Thu, Jun 16, 2016 at 11:07 AM, Dan Callahan <dan.ca...@gmail.com> wrote:
>>> In the implementation I have right now, a user authenticating with
>>> GMail for the second time (independent of RP) does not have to
>>> actively consent. Are we saying this is something that would have to
>>> change?
>>
>> Strawman: We should require consent before each log-in. Otherwise, it might
>> be possible for websites to probe for identities without user interaction.
>
> Strawman: logging into Mozilla's Bugzilla with GitHub is particularly
> smooth because there is no prompt that I have to click through.

I think it's valuable to prompt the user for consent at least once for
each (user, relier) pair.

It's not difficult to discover the redirect_uris for existing OAuth
services, I wonder if there's any prior art for how OAuth deals with
this issue, if at all.

>>> By the way, your document seems to say that we'll have RPs rely on an
>>> external verification service (like Persona). It's been my thinking
>>> that the remote verification was not great, and we shouldn't
>>> necessarily offer a remote verifier.
>>
>> A remote verification API is crucial to LA's long-term success and security.
>> It's too easy to get JWT verification wrong; we should implement that once,
>> in the daemon, and encourage its re-use.
>
> It's also a single point of failure (and the Persona verifier actually
> did have vulnerabilities). Having the party who created a JWT also
> verify it seems pretty pointless from a security point of view.

Unlike Persona, the spec for verifying OpenID Connect tokens is complete
and published, so there's no reason not to encourage reliers to do their
own local verification for extra reliability/security.

I agree that a hosted verifier would be useful to help reliers get up
and running quickly though.

Cheers,

Ryan

Dirkjan Ochtman

unread,
Jul 4, 2016, 2:36:23 PM7/4/16
to Ryan Kelly, Let's Auth
I have the feeling that code is now less of a blocking issue than the
actual spec, so reviving this thread to see if we can make some
progress.

On Thu, Jun 16, 2016 at 12:18 PM, Ryan Kelly <ry...@rfk.id.au> wrote:
>>> Strawman: We should require consent before each log-in. Otherwise, it might
>>> be possible for websites to probe for identities without user interaction.
>>
>> Strawman: logging into Mozilla's Bugzilla with GitHub is particularly
>> smooth because there is no prompt that I have to click through.
>
> I think it's valuable to prompt the user for consent at least once for
> each (user, relier) pair.
>
> It's not difficult to discover the redirect_uris for existing OAuth
> services, I wonder if there's any prior art for how OAuth deals with
> this issue, if at all.

Prompting once for a (user, relier) pair would make the daemon
stateful, which is something we've been trying to avoid.

> Unlike Persona, the spec for verifying OpenID Connect tokens is complete
> and published, so there's no reason not to encourage reliers to do their
> own local verification for extra reliability/security.
>
> I agree that a hosted verifier would be useful to help reliers get up
> and running quickly though.

I guess we can do this, although I don't think it's very high priority.

Now that the daemon code is much more modular, I'm thinking about how
we should improve the protocol further. Mainly, I've been thinking
about how we can bind the RP and LA together a little tighter. Notice
that, right now, the RP-LA side is using the Implicit Flow, whereas
we're using the Authorization Code Flow for authenticating to famous
IdPs. The Implicit Flow REQUIREs an additional "nonce" parameter that
binds together the request and the actual id_token, I think this is
exactly what we need. The RP would have to generate a nonce for each
request, and save it in a server-side database; on receiving the
callback, it can check that the authentication represented by the
id_token was actually initiated using a nonce that it generated.

Also, was there any reason not to use Implicit Flow with upstream
IdPs? It seems easier than using the authorization code exchange dance
(one less round-trip, for one thing).

Cheers,

Dirkjan

Dan Callahan

unread,
Aug 17, 2016, 2:05:49 PM8/17/16
to por...@googlegroups.com
On Mon, 4 Jul 2016 20:36:03 +0200
Dirkjan Ochtman <dir...@ochtman.nl> wrote:

> Also, was there any reason not to use Implicit Flow with upstream
> IdPs? It seems easier than using the authorization code exchange dance
> (one less round-trip, for one thing).

No; the demo daemon was just using off-the-shelf libraries for a proof
of concept.

I'm game for using the implicit flow pervasively.

-Dan

onli onli

unread,
Aug 17, 2016, 2:15:13 PM8/17/16
to Portier


On Monday, July 4, 2016 at 8:36:23 PM UTC+2, Dirkjan Ochtman wrote:

On Thu, Jun 16, 2016 at 12:18 PM, Ryan Kelly <ry...@rfk.id.au> wrote:
>>> Strawman: We should require consent before each log-in. Otherwise, it might
>>> be possible for websites to probe for identities without user interaction.
>>
>> Strawman: logging into Mozilla's Bugzilla with GitHub is particularly
>> smooth because there is no prompt that I have to click through.
>
> I think it's valuable to prompt the user for consent at least once for
> each (user, relier) pair.
>
> It's not difficult to discover the redirect_uris for existing OAuth
> services, I wonder if there's any prior art for how OAuth deals with
> this issue, if at all.

Prompting once for a (user, relier) pair would make the daemon
stateful, which is something we've been trying to avoid.
 
I absolutely agree.

Now that the daemon code is much more modular, I'm thinking about how
we should improve the protocol further. Mainly, I've been thinking
about how we can bind the RP and LA together a little tighter. Notice
that, right now, the RP-LA side is using the Implicit Flow, whereas
we're using the Authorization Code Flow for authenticating to famous
IdPs. The Implicit Flow REQUIREs an additional "nonce" parameter that
binds together the request and the actual id_token, I think this is
exactly what we need. The RP would have to generate a nonce for each
request, and save it in a server-side database; on receiving the
callback, it can check that the authentication represented by the
id_token was actually initiated using a nonce that it generated.

So  the nonce-part is actually correct. https://github.com/onli/sinatra-browserid/blob/letsauth/lib/sinatra/browserid.rb#L40 does not check for a nonce and it doesn't generate one. If we add a nonce the result would be that only login flows can be finished that started on the RP. Well, that's the idea. I'm not sure we need that. You need something like that if a rogue LA-instance could send wrong assertions to sites. But in our scheme, one RP is trusting exactly one LA instance, anf if that is rogue there is a problem anyway. Which means I'm not sure the nonce would help. Replay-Attacks? Problems with the  signature if there is no nonce inside?

Ah, yes, maybe – if someone catches an LA-assertion sent to the RP, he could log in as the user that just logged in as long as the assertion is not expired yet. A nonce would help here – but it would not help against using such assertion first. Is that a threat model we have to consider? Or, rather: Is that something we can consider?
 
Also, was there any reason not to use Implicit Flow with upstream
IdPs? It seems easier than using the authorization code exchange dance
(one less round-trip, for one thing).
 
I don't know of one. But frankly, I did not look at that part of the daemon in detail. If no one knows of a reason not to use an easier and faster flow, then we should just go for it.

Dirkjan Ochtman

unread,
Aug 17, 2016, 2:57:09 PM8/17/16
to onli onli, Portier
On Wed, Aug 17, 2016 at 8:15 PM, onli onli <onl...@gmail.com> wrote:
> So the nonce-part is actually correct.
> https://github.com/onli/sinatra-browserid/blob/letsauth/lib/sinatra/browserid.rb#L40
> does not check for a nonce and it doesn't generate one. If we add a nonce
> the result would be that only login flows can be finished that started on
> the RP. Well, that's the idea. I'm not sure we need that. You need something
> like that if a rogue LA-instance could send wrong assertions to sites. But
> in our scheme, one RP is trusting exactly one LA instance, anf if that is
> rogue there is a problem anyway. Which means I'm not sure the nonce would
> help. Replay-Attacks? Problems with the signature if there is no nonce
> inside?

So I think it's a problem that anyone can trigger a login to whatever
RP. If the RP does not set a nonce in advance, this means some
attacker can trigger a login on any Portier-using site, which might
make a whole bunch of stuff observable to the attacker.

> Ah, yes, maybe – if someone catches an LA-assertion sent to the RP, he could
> log in as the user that just logged in as long as the assertion is not
> expired yet. A nonce would help here – but it would not help against using
> such assertion first. Is that a threat model we have to consider? Or,
> rather: Is that something we can consider?

Yes, I think nonces would also prevent replay attacks like this, and
it's important that we prevent them.

Cheers,

Dirkjan
Reply all
Reply to author
Forward
0 new messages