Extended Association Protocol for Joint OpenID/OAuth

Current State

OpenID

Currently, OpenID provides adequate mechanisms for the relying party/consumer (RP/CS) to bind a shared key to an identity provider (IDP/OP/SP):

    • The RP initiates connection to OP endpoint. So the key binding is at least as reliable as the DNS service.

    • If the OP endpoint is an SSL endpoint, then the RP additionally acquires PKI-level confidence about the binding.

By contrast, the OP has very little to bind the key to a realm. This could lead to problems:

    • The relying party has an open redirector at http://example.com/open_redictor: then evil.org could initiate an association request, then redirect a user with parameters realm = http://example.com; return_to = http://example.com/open_redirector/evil.org ; the provider will ask the user to trust "example.com" and effectively convey information to evil.org; This makes OpenID unsuitable for extensions that disclose user-sensitive data, in particular makes it unsuitable for use in combination with authorization protocols that enable access to user resources.

    • The relying party does not have an open redirector, but uses the first request to bind a new key to a realm, which it uses in subsequent requests. Then evil.org associates, redirects a user with example.com as the realm and the new key. The OP sends the user to example.com, which does not understand the response (signed with a key it doesn't know) and shows the user a broken page (or continues in stateless mode). The OP creates a "binding" between the realm and the key. Later if the user has authorized access to resources to example.com, evil.org might be able to guess or steal user tokens that access resources and issue signed access requests impersonating the realm. (For more context on this attack it is useful to understand OAuth and other similar resource authorization protocols.)

OAuth

On the other hand, OAuth and other authentication protocols do not provide for automated mechanisms for resource providers and consumers to exchange keys, unlike OpenID. So, endowing OpenID with a mechanism to validate the binding of shared keys to relying parties (consumers) is the missing piece which allows for integrated use of open identification/authorization and supports user-friendly data portability.

Proposals

There are two proposals, one named Authentication-based, the other Association-based. Both seem secure, both require the same number of round trips, both can be stateless, and both seem like they could be implemented.

Other common features between the two proposals:

    • Relying Parties/Data consumers may implement the combined protocol either in stateful mode or stateless mode.

    • Stateless RPs do not need to register keys or to store authentication tokens outside a user's session. Stateful RPs register keys in advance and may also store the authentication tokens.

    • These key registration protocols can also be used for stateless RPs simply to register the scope of their association requests if an IDP requires scope to be declared in advance.

    • Policy issue: IDPs may wish to assign different levels of trust to the key bindings obtained through SSL as opposed to over plaintext. In this case, both the realm where discovery was performed AND the key registration server would need to be SSL endpoints for the IDP to obtain assurances of SSL-level certification. The initial request from the RP may still be over HTTP.

Advantage for the association-based problem:

    • does not require any DH if both parties support https.

    • reuses the OpenID assocation messages for both legs of the protocol.

    • fewer code differences on the IdP side for clients that support proof of identity via ID endpoint vs those that don't.

Advantage for authentication-based protocol

    • requires a single DH exchange (if both sides use HTTPS, this is less efficient than the association-based protocol, if neither side uses HTTPS, then this is more efficient than the association-based protocol, but is also subject to a man-in-the-middle attack).

    • binds the OpenID key as well as the OAuth Consumer secret to each party. The association-based protocol binds the Consumer Secret to both parties, but only binds the OpenID shared key to the IdP. So, for consumers interested only in issuing authentication requests, the association-based protocol does not provide additional security over the regular OpenID association protocol.

    • reuses the OpenID association messages for one leg of the protocol and the OpenID signature verification protocol for the other leg. In both cases, this requires simply porting RP-side code to the IdP codebase and adding awareness of the additional parameters.

Both protocols have a similar flow, labeled 1 through 4 on the diagram:

Authentication-Based Flow

(See diagram below with the steps drawn in chronological order from top to bottom):

    • Step 1: The key registration request is initiated by the RP. It sends a POST file in a format similar to the OpenID association protocol, but with mode "associate". In addition to the regular association parameters, it provides a "key handle", any oauth association parameters (if applicable), the Diffie-Hellman association parameters (which are now required even over SSL), and a list of signed fields (which must include at least the mode, realm, the Diffie-Hellman parameters and each oauth association parameter that is present).

      • The field "realm" is to be understood as a realm in the OpenID specification.

    • Step 2: The IDP performs discovery in the realm (not shown), and finds the location of the key registration server. It then starts a signature verification request against the key registration server. It forwards all signed fields in the key registration request, i.e., message sent in Step 1., as well as the signature itself.

    • Step 3: The key registration server indicates whether the signature is valid, as in the OpenID protocol specification.

    • Step 4: If verification succeeds the negotiation may proceed. Otherwise, the IDP sends a failure message to the RP and terminates the connection.

      • After successful verification, the IDP responds to the initial request from the RP by sending an association handle and a MAC key encrypted using the DH shared secret as per the OpenID specification. It also sends an encrypted blob which contains an XRDS document with OAuth configuration information, as well as a consumer secret to be used by the RP (in its role as the Consumer in OAuth).

  • Properties:

      • Note that the RP can safely bind all keys to the IDP because it has initiated the connection in Step 1. Therefore, to the extent that it can trust its DNS resolvers, and intermediate routers, it can be confident that it is communicating with the correct IDP. More significantly, if the request is an SSL session, then the initial exchange took place over a cryptographically half-authenticated channel and in this case the RP can be assured of the IDP's identity even in the absence of confidence in either DNS or routing.

      • Similarly, the IDP can bind the key to the RP because it has initiated the signature verification request itself: Same considerations as in the previous statement about the RP initiating the key registration request.

      • The key used to encrypt the XRDS document and OAuth consumer secret is obtained through the application of a key derivation function on the DH secret negotiated through the OpenID protocol. The type of key derivation function to be used is indicated in the oauth_assoc_type parameter. Definition and standardization of OAuth key derivation functions are out of scope for this document.

      • The signature method used by the RP to verify its own signatures is out of scope of this document. This document only specifies that the assoc_type is encoded in the same format as assoc_handle's in the OpenID specification. IDPs should be able to accept assoc_handle's of at least xxx characters in length.

      • The IDP SHOULD verify that the Diffie-Hellman parameters are present always and it SHOULD verify that they are included in the signature.

      • RPs must support very large enc_bobs. These will be encrypted XRDS documents with all the IDP's endpoints and could exceed 16KB of data.

Association-Based Flow

    • Step 1: RP sends an OpenID association session request to the IdP and includes the following additional parameters

    • openid.oauth.consumerhost: http[s]://www.relyingparty.com/identity_endpoint

    • This is the web site the relying party claims to own.

    • openid.oauth.scope: <comma separated list of data items>

    • These are the data items the relying party wishes to access.

    • openid.oauth.encryption_types: <comma separated list of encryption+authentication algorithms>

    • This is the list of acceptable mechanisms for encrypting the OAuth configuration file (which includes the consumer key and secret.). Examples: aes-128-cbc-hmac-sha1, aes-256-cbc-hmac-sha256.

    • openid.oauth.state: <opaque blob of data> [optional]

This message uses the DH mode of the OpenID association session request if the IdP endpoint is http.

  • Step 2: IdP sends an OpenID association session request to the relying party consumer host endpoint and includes the following additional parameters:

      • openid.mode: associate-proof

      • openid.oauth.encryption_type: <one of the encryption types specified by the relying party that is also acceptable to the IdP>

      • openid.oauth.state: <opaque blob from step 1>

This message uses the DH mode of the OpenID association session request if the IdP endpoint is http.

  • Step 3: RP sends an OpenID association session response to the IdP endpoint.

  • The RP may check the openid.oauth.state value to verify that the relying party really initiated the assocation process. (This may be useful to prevent abuse of the ID endpoint, but is not otherwise necessary for the security of the protocol.)

  • For an https request, the RP generates a cryptographically strong random string of bytes.

  • For an http request, the RP generates a DH server public key and uses that to calculate the secret Diffie-Hellman value.

  • The resulting value is called the "oauth master secret"

  • The RP encrypts the oauth master secret using any secure algorithm, with a key known only to the relying party endpoints.

  • The RP then sends the response to the IdP endpoint with the following additional parameters

      • openid.oauth.rpstate = base64(encrypted(oauth master secret))

  • For an https request, the oauth master secret is returned directly

      • openid.oauth.master_secret = base64(oauth master secret)

  • For an http request, the RP returns the DH server public key values instead.

  • Step 4: The IdP endpoint receives the response from the relying party ID endpoint and sends an association response to the relying party.

  • The IdP creates a consumer key and secret for the RP, and inserts the key and secret into an OAuth discovery XRDS file.

  • The XRDS file is then encrypted using the encryption type from step 2 with the master secret from step 3.

  • The IdP then returns an association session response to the RP with the following additional parameters:

      • openid.oauth.discovery_document = base64(encrypted_and_maced(XRDS file))

      • openid.oauth.rpstate = <rpstate from step 3>

  • The RP endpoint receives the response and decrypts the openid.oauth.rpstate parameter to retrieve the OAuth master key. The RP endpoint can then decrypt the XRDS file to determine the OAuth consumer key, secret, and other information about the service provider.

Notes on Association-Based Flow:

Q: What are the encryption types, and what are they for?

A: the OAuth service provider needs a mechanism to return large (kB) chunks of encrypted data to the RP. The OpenID DH+HASH+XOR based encryption scheme tops out at 256 bytes, so it doesn't work for distributing OAuth discovery documents. To work around this, we need to define an encryption+authentication algorithm or algorithms.

The input key to the algorithm will either be a DH agreed key (for the http case) or a large random byte string chosen by the RP ID endpoint (for the https case.) Encryption and authentication keys must be derived from this master key.

The input data to the algorithm will be a string of bytes that happens to be an XRDS document.

The algorithm will then return the encrypted document.

Q: Why not just define one encryption+authn type and tell everybody to use that?

A: Because we'll be screwed once someone breaks that algorithm. We need room to upgrade.

Q: what would a simple implementation look like?

I'm not qualified to do this, but here's one possible implementation for AES-128-CBC with a SHA1 HMAC. The '+' sign indicates concatenation.

encryption-key = HMAC-SHA1(oauth master secret, "encrypt");

authentication-key = HMAC_SHA1(oauth master secret, "authenticate");

encrypted data = aes-128-cbc(encryption-key, data)

signature = HMAC-SHA1(authentication-key, encrypted data) (is it ok to sign secret data this way?)

return encrypted-data + signature