mozilla / persona

Persona is a secure, distributed, and easy to use identification system.
https://login.persona.org
Other
1.83k stars 264 forks source link

Delegating Authority and Fallback IdPs #3453

Closed jaredhanson closed 11 years ago

jaredhanson commented 11 years ago

I'm implementing a local verification strategy for Passport.js (work-in-progress on the local-verify branch).

I've run into an issue regarding verifying the authority delegation chain when fallback IdPs are in use (including login.persona.org itself).

In Persona's verifier, anytime the issuer doesn't match the email domain, there's a check to verify that authority is delegated (see code). One exception to that is when the hostname of the verifier is also the issuer, which is nearly all the time currently, since Persona's fallback IdP is responsible for both issuing and verifying tokens.

That rule breaks if verification is done locally. For instance, my test app receives assertions issued by login.persona.org that cover gmail.com as a domain. Obviously, there is no delegation chain from gmail.com to login.persona.org. You can see my note on this here.

Is there a recommended practice for this situation? Should fallback IdPs be whitelisted as acceptable to issue assertions for any domain? Is verifying this chain even necessary? Presumably there'd have to have been a problem in the provisioning process if this were to arise.

jaredhanson commented 11 years ago

I take back my comment about the necessity of verifying this chain. Thinking it through a bit more, the chain seems quite critical. Is there any other safeguard preventing a malicious fallback IdP from generating backed assertions for any email address, listing itself as the issuer?

callahad commented 11 years ago

I'm sitting on a 90% complete update of the spec that addresses this, but that last 10% has been putting up a good fight.

In brief:

  1. Start discovery from the email's domain. If you find a native IdP in that chain, then you should only trust certs from that IdP.
  2. If you do not find a native IdP, then re-start discovery from the fallback domain.

Basically, if you're willing to trust login.persona.org, then just imagine that every failed discovery really ends in a delegation to login.persona.org, and continue from there. So it's not strictly a whitelist. If my domain is a native IdP, then you should not trust things signed by the fallback.

jaredhanson commented 11 years ago

In case 2, how do you know what fallback domain to start from, particularly whether or not it is trustworthy.

Presumably if case 1 fails, then the assertion was issued from a fallback domain. If someone is doing something malicious, the fallback is the issuer, which could then be self-asserted by the entity initiating the request.

callahad commented 11 years ago

Each verifier / relying party should explicitly elect to trust specific fallbacks. Right now, there is only one, login.persona.org, and the reference implementation trusts it by default. Which is another way of saying: If you're willing to trust Mozilla, then passport-browserid should define const fallbackIdP = 'login.persona.org' and go from there.

Pragmatically, the number of fallbacks can't / shouldn't fall below 1, nor should it grow much larger than 1. Without any fallbacks, the system can't bootstrap itself. With too many fallbacks, it becomes difficult for users to get an identity certificate that will be honored by sufficiently many sites.

callahad commented 11 years ago

(The list of fallback IdP's that your library trusts should be configured beforehand -- when you get an assertion, it should either be signed by someone in the direct chain of authority from that domain, or in the direct chain of authority from a fallback you've decided to trust. If it's neither of those, something fishy is going on.)

jaredhanson commented 11 years ago

Right. When a fallback is in use, the delegation chain will only ever be 1 (the domain of the fallback itself).

If that's correct, then for case 2, there's no need to restart discovery. Instead, simply check the whitelist of allowed fallbacks.

If that's not correct, what scenario would lead to a larger chain when a fallback was used?

callahad commented 11 years ago

I really need to get this damned spec updated. I'm really sorry about that.

When you're doing discovery for foo@example.com, pass ?domain=example.com whenever you request a /.well-known/browserid file during discovery. In most cases, IdPs will ignore this. In the login.persona.org's case, this lets us separate out signing keys used for different domains.

Case in point:

https://login.persona.org/.well-known/browserid ← General case, covers most domains https://login.persona.org/.well-known/browserid?domain=yahoo.com ← Delegates to the Yahoo identity bridge

jaredhanson commented 11 years ago

Also, I imagine the number of "trusted" fallbacks would be roughly equal to the number of browser vendors. If Google or Apple were to ever natively implement navigator.id, I'd take the bet that they'd be their own fallback.

callahad commented 11 years ago

That's probably a safe bet. A world with 2-3 fallbacks would probably be manageable, but more than that would get rough.

That's part of the reason we went with persona.org instead of a mozilla.org subdomain -- we're hopeful that we'll be able to collaborate with other browser vendors on a neutral domain.

jaredhanson commented 11 years ago

Hmm, I don't immediately understand how the domain argument is useful in this case.

For example: https://login.persona.org/.well-known/browserid?domain=jaredhanson.net

That's doesn't delegate, but yet login.persona.org is perfectly capable of issuing assertions for that domain, by emailing me tokens.

jaredhanson commented 11 years ago

(To clarify, there's no chain from login.persona.org to jaredhanson.net. How does the domain param help "restart discovery" vs just saying "hey, I trust login.persona.org for damn near everything".)

callahad commented 11 years ago

The domain param is just there as a hook so that targets of delegation can (optionally) respond differently for each domain under their jurisdiction.

The fallback uses this to facilitate identity bridging: we're building an IdP that uses Gmail's OpenID endpoint. When it's ready, we want to use that to certify users instead of the email link / password challenge that we're currently using.

So, some time in the next 4-6 weeks, the response to https://login.persona.org/.well-known/browserid?domain=gmail.com will change from its current document to one that delegates to this standalone Google OpenID / Persona bridge.

jaredhanson commented 11 years ago

OK, got it. So let's say I sign in using Google OpenID and identity bridging through Persona. What form does the resulting email address take? I'm guessing user@gmail.login.persona.org from the convention on the yahoo domain?

In that case, you're not really a fallback in the sense that your not asserting addresses outside your own domain, and you'll fall into case 1 above. I'd still like clarification on case 2, and why it should just be "trust these pre-configured domains to issue for any domain".

Relatedly, what happens to users of Persona who go through the identity bridge to sign in, and then all of a sudden Google sets up BrowserID support on gmail.com. If a user previously established their account as user@gmail.login.persona.org, how do they get back to their account when the verification will now be user@gmail.com?

callahad commented 11 years ago

There's no address munging -- it's just a question of who the "issuer" is in an identity certificate. Since it's live in production right now, let's consider Yahoo users.

Normally, you'd expect to see certificates from the fallback that look like this:

{ "principal": { "email": "bar@example.com" }, "iss": "login.persona.org", ... }

A Yahoo user, on the other hand, will end up with a certificate that looks like this:

{ "principal": { "email": "foo@yahoo.com" }, "iss": "yahoo.login.persona.org", ... }

The question is: is it OK that yahoo.login.persona.org issued the certificate for this identity?

With the ?domain parameter

  1. GET https://yahoo.com/.well-known/browserid?domain=yahoo.com
    → 404
  2. GET https://login.persona.org/.well-known/browserid?domain=yahoo.com
    {"authority":"yahoo.login.persona.org"}
  3. GET https://yahoo.login.persona.org/.well-known/browserid?domain=yahoo.com
    { "public-key": ..., "authentication": ..., "provision": ... }

Awesome! It's OK that yahoo.login.persona.org signed the cert, since that's what the trusted fallback delegates to.

Without the ?domain parameter

  1. GET https://yahoo.com/.well-known/browserid
    → 404
  2. GET https://login.persona.org/.well-known/browserid
    { "public-key": ..., "authentication": ..., "provision": ... }

Oh no! It's Not OK that yahoo.login.persona.org signed the cert, since that domain does not explicitly appear in the trust chain.

Note that the two services, login.persona.org and yahoo.login.persona.org are completely independent and use totally different signing keys.

jaredhanson commented 11 years ago

Thanks for the clarification that there's no address munging!

In the "With domain parameter case", what I still don't understand is how or why step 2 GET https://login.persona.org/.well-known/browserid?domain=yahoo.com would get inserted into the authority verification step. yahoo.com is the email domain, and a failure there should terminate. The only reason to introduce a non-yahoo domain would be due to some pre-configured notion to do it.

In other words, I can't get from yahoo.com to yahoo.login.persona.org, without explicitly introducing login.persona.org into the chain myself. And if we resort to that, we've effectively just declared login.persona.org as trustworthy for anything (which also means the ?domain parameter convention not necessary).

callahad commented 11 years ago

The only reason to introduce a non-yahoo domain would be due to some pre-configured notion to do it.

Bingo! The fallback is, by definition, a pre-configured notion. ;) If all else fails, check in with the fallback.

we've effectively just declared login.persona.org as trustworthy for anything.

Yep. Except for domains that are either their own IdP, or which explicitly delegate to another IdP.

jaredhanson commented 11 years ago

Right, but in my opinion then, it should just be:

  1. GET https://yahoo.com/.well-known/browserid → 404
  2. Is login.persona.org whitelisted to assert other domains? yes = success : no = fail

Remember, by the time we get to verifying authority delegation, we've already established that the assertion was correctly signed using persona.org's keys. We just want to know if it is acceptable that it does so, and there's no need to add another HTTP roundtrip to that process.

callahad commented 11 years ago

Remember, by the time we get to verifying authority delegation, we've already established that the assertion was correctly signed using persona.org's keys.

How are you getting there? Don't you have to do the whole delegation chain thing first, to find out what keys persona.org should be using?

jaredhanson commented 11 years ago

Also, with the current Yahoo bridge, now both login.persona.org and yahoo.login.persona.org need to be whitelisted for cross-domain assertions. Or use a wildcard.

callahad commented 11 years ago

Check out the "With the ?domain parameter" section again -- you only have to explicitly trust login.persona.org. Everything else flows from login.persona.org delegating to another domain.

callahad commented 11 years ago

(Since we've been going back and forth in pretty much real time, I'll note that I'm going to go get some sleep and I'll continue responding here in the morning. Thank you for being interested in Persona, and motivated enough to want to hack on a native verifier for the Passport module. You've got my full attention and support - let me know what you need to make this thing a reality.)

callahad commented 11 years ago

You can think of imbuing your verifier with the following principles:

  1. For a user @example.com, I will trust example.com, or anything to which example.com delegates.
  2. Failing that, I will trust login.persona.org, or anything to which login.persona.org delegates.

With those two principles, you're golden. You don't have to care about the Yahoo bridge explicitly, because you'll naturally find it as a consequence of the second principle whenever you need it.

jaredhanson commented 11 years ago

No, the verification starts with the assertion, which is issued by the authoritative domain (login.persona.org or yahoo.login.persona.org). This is passed to verifyBundle code. The first thing that does is fetch the public key for the issuer to verify the assertion.

In effect, if there were any delegations used when provisioning, those are unknown at the point the assertion is received by the relying party. It's only after doing this that the assertion's signatures can be validated and we can trust the contents that specify the issuer and principal. From that point, we have to work backwards and see if there is a delegation chain from the principal domain to the issuer (if they are different) code

jaredhanson commented 11 years ago

Ok, get some sleep :)

I'm pretty confident in my analysis on this one though. I think this domain stuff is unnecessary overhead, and a whitelist is the correct approach.

callahad commented 11 years ago

Get some sleep yourself ;)

And ponder: Right now, login.persona.org should not be able to issue valid certificates for yahoo.com users, because it's delegated that to another domain. How would a whitelist handle that?

jaredhanson commented 11 years ago

Right now, login.persona.org should not be able to issue valid certificates for yahoo.com users, because it's delegated that to another domain.

What? How did login.persona.org delegate for a domain it doesn't control? Remember, this is a concern for fallback domains which by definition are outside of any delegation chain. And that's why the whitelist is needed.

jaredhanson commented 11 years ago

Didn't mean to close this. It's getting late and I'm pressing random buttons...

callahad commented 11 years ago

Correct, the fallback is outside of the original domain's delegation chain.

Being outside the first delegation chain is the only thing that makes the fallback special. It's a normal IdP in all other regards. Normal IdPs can delegate authority to other domains, ergo, so can the fallback. This is done by publishing a special ./well-known/browserid file.

In pseudocode, authority discovery should work like this:

def find_authority(email):
    user, _, domain = email.partition('@')

    authority = follow_chain(domain, domain)
    if authority is None:
        # Try the fallback
        authority = follow_chain('login.persona.org', domain)

    return authority       

Notice how I'm using the same algorithm for both chains, and only traversing the second (fallback) chain if the first one fails?

That traversal would look something like this:

def follow_chain(lookup_domain, email_domain):
    resp = http.get('https://' + lookup_domain + '/.well-known/browserid?domain=' + email_domain)
    document = parse(resp)

    if document.type == 'invalid' or document.type == 'disabled':
        return None
    elif document.type == 'delegates':
        return follow_chain(document.delegate, email_domain)
    elif document.type == 'primary':
        return lookup_domain
    else:
        return None

Here are a few examples of what you should get in the real world:

>>> find_authority('foo@eyedee.me')
'eyedee.me'
>>> find_authority('foo@mockmyid.com')
'mockmyid.com'
>>> find_authority('foo@delegat.es')
'mockmyid.com
>>> find_authority('foo@example.com')
'login.persona.org'
>>> find_authority('foo@yahoo.com')
'yahoo.login.persona.org'

Certificates should only be valid if they're signed by the domain returned by find_authority.

jaredhanson commented 11 years ago

It's a normal IdP in all other regards. Normal IdPs can delegate authority to other domains, ergo, so can the fallback.

Except its not. Adding a query param named domain, does not make any random web host authoritative for that domain. Only control of the actual domain does. Therefore, it has no ability to delegate authority. The only thing that gives the fallback any sort of trust, is that the relying party imbued it with that ability.

I get what you're trying to do. However, I still think its not needed.

Let's walk through the Yahoo bridge case and compare it for something that doesn't have identity bridging.

certIssuer = yahoo.login.persona.org
find_authority('foo@yahoo.com')
GET https://yahoo.com/.well-known/browserid?domain=yahoo.com -> 404
*** BEGIN FALLBACK ***
GET https://login.persona.org/.well-known/browserid?domain=yahoo.com
-> {"authority":"yahoo.login.persona.org"}
GET https://yahoo.login.persona.org/.well-known/browserid?domain=yahoo.com
→ { "public-key": ..., "authentication": ..., "provision": ... }
*** OK *** (certIssuer = authority)

Consider this case:

certIssuer = login.persona.org
find_authority('foo@jaredhanson.net')
GET https://jaredhanson.net/.well-known/browserid?domain=jaredhanson.net -> 404
*** BEGIN FALLBACK ***
GET https://login.persona.org/.well-known/browserid?domain=jaredhanson.net
→ { "public-key": ..., "authentication": ..., "provision": ... }
*** ??? ***

What happens here. Nowhere has jaredhanson.net been delegated to the certIssuer. Should it fail? Yes, if I don't trust login.persona.org. But I do, so I accept it because its on my whitelist.

Let's reattempt that case, but do something slightly different on the Persona side. Namely, change the issuing domain.

certIssuer = jaredhanson.login.persona.org  // <-- note the change
find_authority('foo@jaredhanson.net')
GET https://jaredhanson.net/.well-known/browserid?domain=jaredhanson.net -> 404
*** BEGIN FALLBACK ***
GET https://login.persona.org/.well-known/browserid?domain=jaredhanson.net
-> {"authority":"jaredhanson.login.persona.org"}
GET https://jaredhanson.login.persona.org/.well-known/browserid?domain=jaredhanson.net
→ { "public-key": ..., "authentication": ..., "provision": ... }
*** OK *** (certIssuer = authority)

OK, now were acceptable according to the above algorithm. And this would be easy enough to do with some host wildcarding for *.login.persona.org.

But, we arrive back at the same point. If we trust the fallback (which is not the email domain) enough to vouch for a domain accepted as a query param, then we have granted it the ability to be authoritative for any and all domains. That trust only comes from pre-configuration by the relying party, and nothing else.

That reduces down to a whitelist, which makes the fallback requests unnecessary. The fallback algorithm can simply consult a whitelist.

I can't state this enough: login.persona.org is not and will not ever be authoritative for yahoo.com, if you start from a domain that is not yahoo.com (which is what you're proposing in the fallback scenario).

This is important, and I've got some more scenarios to walk through. I may not get them posted until tonight though.

jaredhanson commented 11 years ago

Another case, this time where there are multiple fallback providers. Lets say login.persona.org and login.googleid.com for sake of conversation:

I run a website, where people are using Chrome (with native navigator.id falling back to Google ID). And, of course, Persona for Firefox and shimed browsers.

Sue uses both Firefox and Chrome. She has a Yahoo address, which doesn't support BrowserID natively. So assertions for her identity could be either:

I go to verify a cert issued by googleid.com, and have persona.org configured as a fallback:

certIssuer = yahoo.login.googleid.com
find_authority('foo@yahoo.com')
GET https://yahoo.com/.well-known/browserid?domain=yahoo.com -> 404
*** BEGIN FALLBACK ***
GET https://login.persona.org/.well-known/browserid?domain=yahoo.com
-> {"authority":"yahoo.login.persona.org"}
GET https://yahoo.login.persona.org/.well-known/browserid?domain=yahoo.com
→ { "public-key": ..., "authentication": ..., "provision": ... }
*** FAIL *** (certIssuer != authority)

Whoops. That's not good. I actually do trust login.googleid.com, so I repeat the fallback algorithm, rooted at a different domain

certIssuer = yahoo.login.googleid.com
find_authority('foo@yahoo.com')
GET https://yahoo.com/.well-known/browserid?domain=yahoo.com -> 404
*** BEGIN FALLBACK ***
GET https://login.googlid.com/.well-known/browserid?domain=yahoo.com
-> {"authority":"yahoo.login.googleid.com"}
GET https://yahoo.login.googleid.com/.well-known/browserid?domain=yahoo.com
→ { "public-key": ..., "authentication": ..., "provision": ... }
*** OK *** (certIssuer == authority)

Should my fallback algorithm loop through multiple providers, attempting to get success from a single one, and failing if all fail? Maybe, but that's not optimal. Again this reduces down:

All I have to do is check if whitelist.contains(certIssuer). If yes, trust the assertion, otherwise fail.

callahad commented 11 years ago

@jaredhanson Just to check in real quick: this started with a question about how things work today, but we've ended up with a proposal to simplify things via wildcards and white-listing, right?

callahad commented 11 years ago

And if so, is this an accurate summary?

(This part about today should be accurate -- it's the current state of the protocol. Any other behavior is likely to be non-conforming.)

Today

As part of the protocol's contract, fallbacks may directly vouch for users at any domain or they may delegate that power to any other domain.

The set of trusted fallbacks is preconfigured within each verifier.

The set of trusted fallbacks is recommended to be ['login.persona.org'].

A fallback may not vouch for users at a domain if there is a valid chain of authority rooted at the email's domain itself.

Proposed Simplification

As part of the protocol's contract, fallbacks may directly vouch for users at any domain.

Fallbacks may not delegate authority. However, a whitelist of several fallbacks may be specified, which may contain wildcards.

The fallback whitelist is preconfigured within each verifier.

The whitelist is recommended to be ['login.persona.org', '*.login.persona.org'].

A fallback may not vouch for users at a domain if there is a valid chain of authority rooted at the email's domain itself.

jaredhanson commented 11 years ago

Yep, that's an accurate summary and I'm in favor of the proposed simplification. Thanks for taking the time to discuss!

callahad commented 11 years ago

@jaredhanson What are your thoughts on limiting the authority of the subdomains?

In the current model, an attacker who compromises yahoo.login.persona.org could only forge certificates for users at Yahoo. In a wildcard model, couldn't it be used to forge certs for Gmail users, too?

jaredhanson commented 11 years ago

Yes, that's true. In the simplified proposal, there's no reason to issue certificates from yahoo.login.persona.org. They could just as well be issued by login.persona.org directly, which would reduce the whitelist by one domain or avoid the need for a wildcard.

I do see some benefit to limiting the scope of domains for which a fallback IdP can issue assertions, of course. As an initial suggestion, I would recommend putting that in the support document, like so:

yahoo.login.persona.org/.well-known/browserid:

{
    "public-key": {},
    "authentication": "/authentication",
    "provisioning": "/provision",
    "domain": "yahoo.com"
}

Primary IdPs would omit the domain, as it would be implicit.

yahoo.com/.well-known/browserid:

{
    "public-key": {},
    "authentication": "/authentication",
    "provisioning": "/provision"
}

For a primary IdP, the domain is equal to the domain the support document was served from.

Now, if someone compromises yahoo.login.persona.org they can still issue certs for any domain they want to, but only a single domain, not all domains. They could cycle that domain, of course, using gmail.com for a few hours, and then hotmail.com.

If this approach were taken, there would be two classes of fallback IdPs. "Root" fallback IdPs, like login.persona.org, and "domain-specific" fallback IdPs. Given the cycling issue mentioned above, this suggestion may not be worth the complication.

The existence of any trusted, fallback IdP (including login.persona.org) creates a highly-privileged class of identity providers, and that needs to be carefully noted in the security considerations for BrowserID as a protocol (if fallbacks are in use). As a "root identity issuer" login.persona.org becomes a highly valuable target for attacks. A compromise at that point, compromises all domains.

The ultimate solution, where domains are authoritative only for themselves, is to eliminate fallbacks. That is unlikely to happen, but with a whitelist approach, relying parties are free to do that by clearing the whitelist.

I'm not sure of any technical solution to the problem of trusting fallbacks, though I'd love to hear about it if there are. Establishing verifiable and revocable trust at the fallback level may require more of a social and political solution.

For instance, what if there were 4 or more separate organizations that acted as "trust enforcement" for fallback IdPs. Lets say login.persona.org, login.appleid.com, login.googleid.com, login.verisign.com. These domains could each publish a list of privileged, root-level fallbacks.

Relying parties who want a "trustworthy" list of fallbacks could subscribe to the list of fallback IdPs that each publish. If any domain is listed on 3 of 4 (or some acceptable number to establish an acceptable level of assurance), the relying party would add that domain to their whitelist. Ideally the set is chosen so that there's enough competitive interest among them all that there'd be no reason for some to collude, and therefore exclude one of the others from being trusted.

Now, if there is any compromise, that would get removed quickly from at least 2 of the lists, and propagate down. There's some propagation time where there is an open attack surface. Again, I'd note that that surface comes into existence due the the existence of fallbacks in the first place.

Relying parties have to assess this against their requirements. A government organization, for instance, should choose not to trust fallbacks. Other sites may whitelist without checking for any quorum. Some may choose to respect the decisions of the quorum.

I think I've rambled sufficiently on this for now. This is an important consideration for BrowserID, so I'd encourage others to share their thoughts on the matter.

jaredhanson commented 11 years ago

One other note on limiting the scope of the whitelist. It could be configured by the relying party like:

login.persona.org -> * yahoo.login.persona.org -> yahoo.com

This way the configuration can be more fine-grained with respect to the authority given to fallback IdPs.

jaredhanson commented 11 years ago

Yet another note on adding a domain field to the support document for a fallback IdP:

yahoo.login.persona.org/.well-known/browserid:

{
    "public-key": {},
    "authentication": "/authentication",
    "provisioning": "/provision",
    "domain": "yahoo.com"
}

Relying parties could use this to built a immutable provisioning map. Upon first seeing a domain-specific fallback IdP, they could tie that to the domain listed in the support document. If it were ever observed that the domain listed in the support document has changed, that would immediately signal a potential attack and trust of yahoo.login.persona.org could be revoked.

Again, not sure this is worth the complication, but it would allow the relying party to detect attack vectors.

ozten commented 11 years ago

We want to keep discovery as simple as possible, while meeting the requirements of the BrowserID protocol.

I don't see how the proposed change simplifies the protocol, nor how it improves security.

Let's assume one fallback, we will work hard to work with other vendors to keep it to one fallback.

Capturing this in a whitelist config is a great option for PassportJS, having it be a list, hedges your bets down the road.

The dynamic domain piece of discovery has several use cases and is the simplest mechanism we could find to meet those requirements. Another example is a shared IdP which uses different public/private keys per domain. Imagine twofactorpersona.org which will let you delegate to them for an annual service fee. Each customer gets security scoped to their domain.

I agree with callahad, let's take this proposal to the mailing list, which is the usual means of working through proposals.

I'm super excited for local verification in Passport, great stuff Jared!

jaredhanson commented 11 years ago

Hey Austin -

Thanks for checking in.

I just want to reiterate that I'm not proposing any changes to the discovery process. As far as primary IdPs go, BrowserID is top notch. The PKI advantages make it the best identity protocol going, especially in regards to privacy.

Your shared IdP example is a good use case. But, this falls under the primary IdP use case. The "domain" param here is a useful hint for a shared IdP, and they can't change the context from the original domain in question.

This discussion all relates to how fallback is handled. I don't believe an RFC could mandate this aspect as a MUST, given that local policies need to be assessed. So, I will definitely be implementing a whitelist in Passport.js, as that gives flexibility and control to the relying party.

I do think the "domain" parameter as used by a fallback in this case is misleading at best. Establishing a precedent for trusting delegation chains that don't originate from the domain in question is just bad practice, in my opinion.

The whitelist would short-circuit this, be just as deterministic and limit potential abuse.

Of course, I also want to be compliant with the protocol, so I wanted to raise this discussion. Happy to move it to the mailing list.

callahad commented 11 years ago

I'm going to close down this bug -- let's move the specific fallback / delegation proposal to the mailing list.

For this to be seriously considered, it will have to be able to fully replace the current delegation model, without causing any regressions in security or maintainability for all involved parties. Specifically, with the current system:

  1. Yahoo, Google, and Hotmail identity bridges can run on independent domains, using independent keypairs, and without knowing anything about the fallback itself.
  2. The fallback is able to act as a kill-switch on the identity bridges, currently by no longer delegating in its responses to discovery queries.
  3. Identity bridges may not sign for domains outside of their purview, even when completely compromised.
  4. Domain names for the bridges may be completely unrelated to the domains they sign for.
  5. Relying Parties don't have to regularly manage or curate a list of fallbacks / identity bridges.

I personally do not see how a whitelist improves on the status quo, but if you can figure out how to retain the current properties of the system, I'm game.

Until then, directly implementing a whitelist approach in passport-browserid contradicts the current behavior of the system. It will break things and expose you to additional vulnerabilities. Please don't do that to your users.

jaredhanson commented 11 years ago

Until then, directly implementing a whitelist approach in passport-browserid contradicts the current behavior of the system. It will break things and expose you to additional vulnerabilities. Please don't do that to your users.

How? I'll continue this discussion anywhere, but I'm of the exact opposite conclusion. A whitelist preserves all those properties and give more control to the RP.

jaredhanson commented 11 years ago

Addressing those issues in the context of a whitelist:

  1. A whitelist can point to Yahoo, Google, and Hotmail identity bridges running on independent domains, using independent keypairs, and without knowing anything about the fallback itself.
  2. A whitelist is able to act as a kill-switch on any fallback or identity bridges, by removing the whitelisted domain.
  3. A whitelist can limit the domains for which a specified fallback or identity bridge can issue certificates.
  4. Domains on a whitelist may be completely unrelated to the domains they sign for.
callahad commented 11 years ago

A whitelist is able to act as a kill-switch on any fallback or identity bridges, by removing the whitelisted domain.

Wouldn't that require every RP / library updating their configuration? Using delegation, the fallback at login.persona.org can simply cut them off by no longer delegating.

callahad commented 11 years ago

I'm honestly not trying to move the goalposts here -- probably another good reason to take it to the mailing list.

How would turning on a new bridge work? Turning it off? Expanding its scope to cover additional domains, like ccTLDs?

If we have to rely on configurations to get updated everywhere, that won't work.

If the whitelists have a wildcard, then I can't see how we could adjust the scope of each bridge.

If we sign with the same keypair as login.persona.org, we've given the bridges the ability to sign for other domains, too.

Maybe we could maintain a centralized whitelist as part of login.persona.org's .well-known/browserid file, but I don't think that actually improves things.

jaredhanson commented 11 years ago

I agree that all the questions you raise are valid considerations. However, I also think they are very focused on Mozilla's objectives with the persona.org infrastructure and the operation of the fallback. That is very important of course; however, I am raising my concerns from the perspective of the relying party, which needs to be given just as much (if not more) consideration for BrowserID to meet its objectives.

Specifically, there are enterprises using Passport (mostly with SAML), who do have specific arrangements where, for example: employees of corpa.com can log in to binc.com. But, only if that's done through approved channels. It would not be acceptable if assertions from a non-approved third-party (including login.persona.org) were used.

Note, I'm not trying to "enterprise-up" this discussion. I have no interest in that direction. BrowserID as a protocol is already very satisfactory for these use cases. Good enough, in fact, that I could see internal identity bridges set up between corporations to bridge over existing trusted channels.

In any case, my main concern here is to get an understanding of the fallback process, so that there is some level of transparency and control.

I know you're still working on the spec, but I'd like to walk through this again in the hopes that it can help clarify things. Let's take the case of verifying an assertion issued by yahoo.login.persona.org:

  1. client attempts authentication with example.com by sending an assertion.
  2. example.com checks the support document of yahoo.login.persona.org for the public key to verify the assertion (check mark: satisfied different key pairs for issuers)
  3. GET https://yahoo.com/.well-known/browserid?domain=yahoo.com -> 404
  4. * BEGIN FALLBACK *
  5. GET https://login.persona.org/.well-known/browserid?domain=yahoo.com -> {"authority":"yahoo.login.persona.org"}
  6. (NOT NECESSARY) GET https://yahoo.login.persona.org/.well-known/browserid?domain=yahoo.com → { "public-key": ..., "authentication": ..., "provision": ... }

Note: step 6 is not necessary. There's no information needed in the response, and we've already found that login.persona.org delegates to the issuer. If you want delegation by the fallback, I don't think the spec needs to require this.

What would the response look like if yahoo.login.persona.org had the kill switch thrown? Would it simply delegate to a different domain? If we traced this delegation and didn't get to the issuer, that suggests a compromise of some degree at the fallback, which could well extend beyond just yahoo.login.persona.org. This fact should be raised to the relying party as a serious issue.

Now, repeat this process using jaredhanson.net as a domain for which there is no identity bridge. I still haven't seen clarification on what the spec says with regards to trusting that. Authority was not delegated. Is a response containing a "normal" support document enough? If so fine, just clarify that.

Maybe we could maintain a centralized whitelist as part of login.persona.org's .well-known/browserid file, but I don't think that actually improves things.

I think that would actually be good. Some RPs may want to approve these, or at least be aware that new issuers are about to appear. They may also only want to accept assertions from some subset of identity bridges.

As a final point, I'd note that whatever persona.org chooses to do, it very easy to override, simply by changing the local fallback URL. For instance:

  1. * BEGIN FALLBACK *
  2. GET https://browserid.internal/.well-known/browserid?domain=yahoo.com -> {"authority":"login.persona.org"}

Now, the RP is entirely back in control. They can redelegate to login.persona.org if they wish, or implement their own policies. Again, back to a whitelist.

I know my users will want this. From Passport's perspective, all I'm doing in this regard is moving the whitelist into configuration supported by the module, rather than requiring the setup of a "redelegating" BrowserID fallback.

ozten commented 11 years ago

What would the response look like if yahoo.login.persona.org had the kill switch thrown?

There are a couple ways to disable yahoo.login.persona.org, which are implementation details, but the key takeaway is that step 5 (GET https://login.persona.org/.well-known/browserid?domain=yahoo.com) would no longer delegate authority. It would respond with a public key and act as the issuer.

Sorry, if I missed the point of the proposal earlier.

I agree with many of the points you are making Jared, but I want to make sure the current flow is clear. What has been in production for a few months, and what is lagging in documentation is this:

There is a discovery protocol with several steps which include things like /.well-known/browserid?domain={domain.tld}. This discovery is to be attempted on the domain portion of an email address. If this fails, the same discovery protocol is used against login.persona.org.

There is no extra or different flow for the fallback IdP.

Thanks for hammering out these ideas guys!

jaredhanson commented 11 years ago

the key takeaway is that step 5 (GET https://login.persona.org/.well-known/browserid?domain=yahoo.com) would no longer delegate authority. It would respond with a public key and act as the issuer.

OK then. This specifically makes it much harder to craft a whitelist.

I'll make one major point here: trusting a fallback, and trusting a fallback that can re-delegate authority are two very different levels of trust. Specifically, much more trust must be placed in the re-delegating fallback. Which, incidentally makes it less likely that the fallback is acceptable to use.

There will remain a need for relying parties to reliably choose who to trust. If the fallback can delegate, then any policy statements at the RP will need to take that into account. Specifically, now there needs to be policy statements about following delegations.

For example:

allow delegation from login.persona.org to *.login.persona.org; deny other

Without such policy in place, there exits the potential that login.persona.org could delegate to an authority that the RP does not trust.

jaredhanson commented 11 years ago

the key takeaway is that step 5 (GET https://login.persona.org/.well-known/browserid?domain=yahoo.com) would no longer delegate authority. It would respond with a public key and act as the issuer.

Also, this makes a "successful" response have two potentially different meanings.

GET https://login.persona.org/.well-known/browserid?domain=jaredhanson.net -> { "public-key": ..., "authentication": ..., "provision": ... }

Trustworthy, and what I expect from the known-good configuration of login.persona.org.

GET https://login.persona.org/.well-known/browserid?domain=yahoo.com -> {"authority":"yahoo.login.persona.org"}

Trustworthy, and what I expect from the known-good configuration of login.persona.org.

GET https://login.persona.org/.well-known/browserid?domain=yahoo.com -> { "public-key": ..., "authentication": ..., "provision": ... }

Hmmm, seemingly trustworthy but potentially suspect. This doesn't match the known-good configuration. Why is this response no longer delegated. Was yahoo.login.persona.org compromised and the kill switch flipped? Is Mozilla just adjusting their internal deployments for innocent reasons?

I do, of course, need to check the cert issuer with the final authority for this particular assertion, but the questions remain. I may want to invalidate the login sessions of anyone who authenticated with certs from yahoo.login.persona.org in the past hour. If I'm really paranoid, I may want to clear sessions from any delegation chain rooted at login.persona.org, under the precaution that if one of them is broken, others potentially are as well.

As a sensitive RP, I want an issue raised so that my security team can investigate.