In one of the
previous posts, we explained SSO
on the example of SAML.In this post, we will introduce another
popular and widely deployed SSO Protocol: OpenID
(if you are not familiar with SSO, we recommend you to read SSO
on the example of SAML first).
The OpenID protocol exists in different versions. While OpenID 1.0
and 1.1 are still supported and deployed in the WWW, most
implementations concentrate on OpenID 2.0 – so does this post.Notation
We use the following notation for a cleared parameter intent. If you have worked on OpenID before, you may be more familiar with the OpenID specific parameter values, so here is an overview:
URL.IDC | openid.claimed_id | The identity requested by the Client C. In the context of OpenID, the identity is a URL e.g https://me.yahoo.com/a/identityname |
URL.SP | openid.return_to | The URL of the Service Provider (SP), e.g. https://www.openstreetmap.org/login |
URL.IdP | openid.op_endpoint | The URL of the Identity Provider (IdP), e.g. https://open.login.yahooapis.com/openid/op/auth |
α | openid.assoc_handle | The value that identifies the signature verification key stored on the SP as well as on the IdP. |
σ | openid.sig | This parameter contains the value of the token's signature. In fact, OpenID uses a Hash MAC (and not a signature), but has the OpenID specification uses the term “signature”, this Post will do this as well. |
There are a lot more parameters in OpenID:
- openid.ns defines the used protocol version, e.g.
http://specs.openid.net/auth/2.0
- openid.response_nonce contains a timestamp suffixed with a
nonce value
- openid.signed holds the parameter names that are signed, e.g.
claimed_id, return_to,..
- openid.ax.*, openid.sreg.* are extension parameters that can
be used to transfer additional information, e.g. an email address or
a birthday.
OpenID Protocol Flow
The OpenID protocol can be separated into three phases:
- In the discovery phase, the Service Provider (SP)
collects information about the user (Client C) who wants to login on
it.
- The association phase takes place next. This phase
establishes a shared secret between SP and the OpenID Identity
Provider (IdP).
- The token processing phase includes the creation of
the SSO token and its transport to the SP via C's user agent
(browser). The SP then verifies the token and the user is logged in
if it was valid.
- The Client C using his User Agent (UA) who wants to
authenticate on the SP, e.g. http://www.openstreetmap.org/.
- The ID Server and C's IdP. For a lot of publicly available
OpenID Providers, this is the same Server, e.g. Google. But OpenID
allows to separate them to have more flexibility. This is explained
more detailed at the end of this article.
- Step 1: C wants to login at SP and sends its Identity
URL.IDC.
- Step 2: The SP starts to discover URL.IDC by
requesting the URL on the ID-Server.
- Step 3: The ID-Server returns a document containing the URL
of C's Identity Provider, i.e. URL.IdPC.
- Step 4: The SP can optionally use URL.IdPC to
establish a shared secret with the IdP. Basically, this is a
Diffie-Hellman key exchange. If this step is performed, IdP chooses
a value α to identify the key material. α is transferred to SP, so
that both entities use the same identifier to store the key. Note
that α does not contain any key material, it is just a reference to
it.
In this step, SP and IdP are communicating directly to each other, thus, the exchanged messages cannot be seen in the user's browser.
- Steps 5-6: The SP now responds to C's initial login request
(Step 1) and sends an authentication request that contains URL.IdPC,
URL.SP and, if Step 4 was performed, α. This is an HTTP redirect
instruction and C's UA forwards this message to URL.IdPC.
- Step 7: If C is not yet logged in on his IdP, he now must
authenticate on it.
- Step 8-9: IdPC creates a token t that
contains C's identity URL.IDC, plus its own URL address
URL.IdPC and URL.SP.
IdPC then signs t by using the key identified by α. token t together with its signature σ is then sent back to C who forwards the message to the SP.
- Steps 10-11: The SP can optionally start a rediscovery. These
steps are identical to Steps 2-3. Rediscovery is used to verify,
that the URL.IdP parameter returned in Step 11 is equal to the
URL.IdP parameter contained in the token t.
- Step 13-14 are described separately below.
- Step 14: If the signature σ is valid, the SP maps URL.IDC
to a local username and grants access to C.
Direct Verification
Step 4 is optional.Consequently,an SP does not need to establish a shared secret with the IdP. This is for example useful, if the SP is unable to store the key material (e.g., it has no database).
The question raised from this fact is: How can the SP verify a tokens signature without knowing the key?
The answer is called direct
verification.
The protocol flow in this case is
slightly changed. If no association is established in Step 4, α is
not contained in Step 5-6. This leads IdPC to create a
fresh secret key in Step 8 to sign the token t. The secret key
itself is then only stored at the IdP. The IdP additionally creates a
value α as a key identifier and puts α into t. If the SP
receives the token in Step 9, it sends the whole token together with
σ to IdPC in Step 12. Then, IdPC verifies the
token and sends the result back to SP in Step 13.
Again, this is direct communication
between the SP and the IdP and the OpenID specification assumes that
there is no Man-in-the-Middle attacker (the token should be exchanged
over TLS).
Discovery in Detail
In Step 3 of the protocol, SP receives a document that contains URL.IdPC. This document can either be HTML or an XRDS document. A minimal example of an HTML document has the following structure:
<html>
<head>
<title/>
<link rel="openid2.provider" href="https://myidp.com/" />
</head>
<body/>
</html>
The href attribute in the link element contains URL.IdPC.
XRDS document contains the same information, but stored in XML data format:
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)" xmlns:openid="http://openid.net/xmlns/1.0">
<XRD version="2.0"> <Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<URI>http://myidp.com/</URI>
</Service>
</XRD>
</xrds:XRDS>
An interesting side note on the XRDS discovery is, that the use of XML as a data description language offers an attacker trying to inject XML Entities. We have already described, how to apply XXE Attack on SAML, and the OpenID Discovery phase allows to apply this technique to OpenID. Reginaldo Silva applied the XXE Attack on Facebook's password recovery feature, that uses OpenID in the background and earned $33,500 as a bug bounty.
ID Server
Regarding the discovery documents described above, this explains the flexible design of OpenID. It allows one to store such a discovery document on its own server (http://mysite.com) but declares Google, and Yahoo as an Identity Provider. When the user then wants to login, he can simply use his own website (http://mysite.com). He is then automatically forwarded to his IdP (e.g. Google or Yahoo).
One may have noticed, that URL.IDC is not contained in Step 5.
This can be optionally included in the discovery document, so that the SP can include it in Step 5.
For HTML discovery, this is possible via a further link attribute:
<link rel="openid2.provider" ref="http://xml.nds.rub.de/simpleid/www" />
In XRDS documents, this is also possible:
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)" xmlns:openid="http://openid.net/xmlns/1.0">
<XRD version="2.0">
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<URI>http://myidp.com</URI>
<LocalID>http://myidp.com/its_me</LocalID>
</Service>
</XRD>
</xrds:XRDS>
Note that in the XRDS case, the Type parameter has changed from “server” to “signon”.
Authors of this Post
Vladislav MladenovChristian Mainka (@CheariX)