Valid HTML 4.01 Transitional

Setting Up and Debugging OpenID

James F. Carter <jimc@math.ucla.edu>, 2012-09-03

A major problem for a digital citizen like myself is identity management. I have 48 accounts listed in my password management file (a flat file encrypted with PGP, not automated), plus 13 obsolete ones which may or may not still exist, plus quite a number of non-recorded accounts using a generic low-security password. Managing these accounts is a hassle. The vendors or servers with generic accounts are not trusted to do a good job keeping out thieves, and sooner or later the generic password is going to be revealed.

Introduction to OpenID

OpenID is an alternative way to manage your identity. Here is some terminology:

The RP does not have to trust the OP. The EU is the one who trusts the OP to honestly and securely identify him and report his identity to the RP. In other words, the EU considers the OP to have adequate security to protect his identity when working with the particular RP. The RP is not going to get any assurance from the OP of the EU's real identity, for example whether his tax identification number is correct. Thus OpenID is appropriate where the RP needs to be sure that the same EU appears time after time. For example, the EU stores files on the RP's machine, and then the EU and nobody else may retrieve them. Or the EU deposits money with the RP (e.g. a game site) and then incurs charges against this credit balance.

A bank or an online brokerage needs a real-world identity for the customer, and will not get it from OpenID. This RP needs to use other means to assure itself who the customer is, such as a scanned image of a manual signature under penalty of perjury, which in most jurisdictions is considered to be proof of identity. Some nations provide their citizens with X.509 certificates of identity, which can be used for this purpose. However, the brokerage RP can be assured via OpenID that an EU authenticated previously in the real world is still the same person in future transactions.

The way of using OpenID intended by the developers goes like this:

My OpenID Provider

I have signed up with StartSSL.com for a free X.509 identity certificate, which includes OpenID service. Their OP login page demands this certificate, and authentication is possible only if the EU's (my) browser can wield the private key whose corresponding public key was signed to make the certificate. Needless to say, the private key is encrypted; the key and the password never leave my machine. My OpenID URL is https://luser.startssl.com/ and the hostname resolves to the OP's address, so it can be used also as the server URL.

For the examples in this document I have turned on security by obscurity, referring to my server as www.example.net and to my own loginID as luser.

There are quite a number of other OpenID OPs including Google, Yahoo!, AOL, and Wordpress. Of these, Google is the most professional. See Google's documentation: Federated Login for Google Account Users. You specify https://www.google.com/accounts/o8/id as the OpenID URL. Download the content at that URL, which is a YADIS document, and see the URI element for the server's URL. Google recommends that the RP's login form should have an icon to fill in Google's URL and submit the form. If you have authenticated to your Google account and have a recent cookie, Google will believe in it; without the cookie Google will ask for your Google ID and password. Then Google will ask you to confirm logging in, and will send back your OpenID as they know it, which contains a randomly generated ID string (always the same for any one EU). If Google sends back personal data, I haven't figured out how to turn this on. ( Andrew Arnott says: (2010-04-05) The RP needs to use the AX extension, not SREG, and must require the e-mail address.)

Google would prefer that you authenticate to Google first, not giving your Google ID and password when logging in to the RP's site, to avoid phishing: a malicious RP redirects you to their malicious OP which puts up a page simulating your OP (Google), and if you aren't on your toes you will give your credential to the identity thief. Other non-password methods, like the X.509 certificate or Kerberos, similarly resist this phishing attack.

Here's another vulnerability (OpenID-1.1 only): when authentication is finished, the OP redirects the user's browser to the RP, with appended authentication info. If someone snatches this redirection off the wire, he can re-play it later and be admitted. Moral: always use HTTPS. OpenID-2.0 includes a nonce value so each confirmation URL can be used only once, but HTTPS is still recommended.

Details of the Delegation Document

The Claimed Identifier is the identifier which you, the EU, want the RP to believe is your identity. This identifier may be an XRI (not discussed further here) or a URL (most common). There are three variants for the referent of this URL:

I am going to use the third method, posting a delegation document on my own server and using its wild-side URL as my Claimed Identifier (OpenID). Its URL on my system: https://www.example.net:1443/~ooba/openid.rds

For the delegation document, the most flexible is a XRDS (YADIS) document. See this Wikipedia article about YADIS for the format of the YADIS document and the available parameters. It needs to have a mime-type of application/xrds+xml. To make Apache report this mime-type you can append to /etc/mime.types a line

application/xrds+xml rds

where I have arbitrarily picked rds as the extension for this type of file. Alternatively you can add to your Apache configuration file this line in the global scope or the VirtualHost that is going to be serving the document (assuming mod_mime is loaded):

AddType application/xrds+xml rds

You can also do double indirection, but I'm not sure what the advantage of this is. Create a HTML document at your Claimed Identifier URL and insert (in the head) a line like this giving the URL of the real YADIS document:

<meta http-equiv="X-XRDS-Location" content="http://example.com/yadis.rds" />

You can bypass YADIS entirely by inserting these lines in the head of the HTML document at your Claimed Identifier URL (this is for OpenID-2.0):

<link rel="openid2.provider" href="https://openid.example.com/" />
<link rel="openid2.local_id" href="https://myname.example.com/" />

The first one is the URL of the OP's server, and is required. The second, which some OPs don't require, is the Local_ID which the OP is willing to certify. Some OP servers can infer the Claimed Identifier by other means, e.g. a cookie or a X.509 certificate.

Special cases for the Claimed Identifier:

Testing the OpenID

Before I put my various OpenIDs into production I want to test them, so I can be sure that any failures are not due to defects in the OpenID. What OpenID test sites are available, beyond something I put up myself?

Jimc's Own OpenID Test Site

I need to test my OpenID before putting it into production, and this has turned out to be surprisingly difficult. It looks like I am going to have to set up my own RP for testing. (Note: I have only one server to test this on, and it contains production data, so it is not available to the general public. Sorry.) What software is available?

I'm basing my test program on Net::OpenID::Consumer. This package is notorious for the variety of its dependencies, direct and indirect. Here is a list of what I had to obtain (all from the SuSE Build Service). Don't be surprised if there are other dependencies not on the list: I've done some other web projects and have already installed web-related packages. These are all for OpenSuSE 11.4, either noarch or i586. For i586 substitute x86_64 if your webserver is a 64bit machine.

Links for testing:

Outcomes:

https://luser.startssl.com/

Success, and returns the full name, nickname, e-mail, country and postcode (zip code, in USA). Startssl's X.509 identity certificate is required for admission, and on repeated logins you don't see the StartSSL page as long as the TLS context is maintained; however, on the initial connection you get their login page and you need to click on Authenticate (vs. New User) and authorize your browser to send the certificate. There is no page identifying the RP and asking whether you want to send SREG fields; it just sends them.

A delegated OpenID, i.e. the URL of the delegation document, is honored with the same outcome as above. In my case the HTML delegation document contains (in its head) these links:

<link rel="openid2.provider" href="https://www.startssl.com/id.ssl">
<link rel="openid2.local_id" href="https://luser.startssl.com/">

A XRDS delegation document is also honored. Here is mine. Notice the Type element: this specifies version 2.0; the corresponding Type for version 1.x (deprecated) would be http://openid.net/server/1.0

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)"
xmlns:openid="http://openid.net/xmlns/1.0">
  <XRD>
    <Service priority="10">
      <Type>http://specs.openid.net/auth/2.0/signon</Type>
      <URI>https://www.startssl.com/id.ssl</URI>
      <LocalID>https://luser.startssl.com/</LocalID>
    </Service>
  </XRD>
</xrds:XRDS>
http://luser.myopenid.com/

This OpenID, entered directly in Setup mode, was certified by the OP and the RP (tester) finished the interaction successfully. All the SREG fields configured on the OP were received and readable on the RP. The MyOpenID URL was not tested as part of the delegation documents but it's expected that it would work equally well.

https://www.google.com/accounts/o8/id

In immediate mode, it returns The openid.mode argument is not correct (code bad_mode). It fails to cause the RP to retry in setup mode, which is what it should have done if unwilling to use immediate mode.

When started in setup mode, Google displays a confirmation page, then certifies an identity. It does not offer to send any SREG fields, and in fact sends none, not even the Gmail address. The claimed identity, invariant per EU, is generated arbitrarily by Google from an alphabet apparently of [-_0-9A-Za-z] (word characters plus hyphen, 64 possibilities), for example

https://www.google.com/accounts/o8/id?id=qwERtyiop... (39 random bytes)

Given a delegation document specifying the above URL as the server, the tester redirects to it, but the result is a XRDS document specifying the real server, and the EU's browser downloads and saves it, which is kind of useless.

If the delegation document specifies the server that's in the XRDS document, the tester (in setup mode) will behave just as it would if the XRDS URL had been entered as the OpenID. In particular, the verified OpenID URL will be the one returned by Google with the 39 random bytes, not the URL of the delegation document.

Thus, Google's OpenID is functional on the test site, taking into account Google's idiosyncratic way of using OpenID, which locks the EU into Google as an OP.

OpenID Apache Modules

To put OpenID into production on my site, I need to actually restrict some content (it's my webmail server, initially), and to do that I need to install and configure an Apache webserver module. For a programmatic web app (versus static pages) an alternative is to hack it to do the OpenID interaction itself, but this is a lot of work, and a pre-made module at the HTTP level is much more attractive. Further, it is common for various mechanisms to be used to authenticate to the same service, e.g. OpenID, X.509 certificates, Kerberos, Shibboleth (SAML), LDAP, etc. etc. To make a laundry list of mechanisms happen requires support in the webserver.

The Apache webserver has available two modules for OpenID authentication. One is called mod_auth_openid. Project website for mod_auth_openid. Project inception is before 2008-05-29 (the date of their oldest release remaining on the download site). I have had a lot of trouble setting it up; it rejects all the OpenIDs I've used to test it, both on my own server and on their test site. Hence I'm trying a different module.

Mod_auth_oid also handles OpenID-2.0 authentication for Apache.

Mod_auth_oid also depends on mod_parp (for parameter parsing).

One of the advantages of mod_auth_oid is that the administrator can configure a map from an arbitrary OpenID (including the mangled kind certified by Google) to a local loginID, whereas with mod_auth_openid the web app needs to handle this.

Here are my experiences installing mod_auth_oid. Unfortunately nobody has made it available on the SuSE Build Service (and I am motivated enough to try it out but not motivated enough to take responsibility for it on the Build Service).

Configuration for Mod_auth_oid (Fails)

I'm providing HTTP authentication to my webmail server, SquirrelMail. Using the login_auth plugin, it honors the REMOTE_USER variable if present, and solicits a loginID and password otherwise. To keep things simple I'm showing only the OpenID and Kerberos mechanisms, though I implemented all the ones listed previously. The Kerberos, LDAP and X.509 mechanisms all work in this framework. Shibboleth would work if I had permission from the IdP.

Here is the directory structure, shown as Locations, i.e. the corresponding directories are directly under /home/httpd/htdocs:

The virtual host includes this configuration (showing mod_auth_oid material only, not Kerberos etc.):

# OpenID authentication for the SquirrelMail directory
<Location /squirrelmux/openid>
    AuthType OpenID
    Order allow,deny
    Allow from all
    Require valid-user
</Location>

# SSL infrastructure for OpenID
SSLProxyEngine    	    on
SSLProxyCACertificatePath    /etc/ssl/certs
# AOID_LogLevel debug [didn't give any useful messages]

# OpenID login form
# Location (URL) of login handler (required)
AOID_LoginPath      /oid-forms/login
AOID_LoginSuffix    .shtml
<Location /oid-forms>
    Options    	    +Includes
    AddType    	    text/html .shtml
    AddOutputFilter INCLUDES .shtml
</Location>
# Static mapping of the admin user (DEBUG)
AOID_User https://www.example.net:1443/~ooba/openid.html    luser
AOID_User http://luser.myopenid.com/    	    	luser

# This file maps OpenID to loginID.  It is shared among all OpenID 
# apps.  It should be writeable by the Apache user (wwwrun) to enable
# administration.  '=' separates the OpenID from the loginID.  Sample:
# http://luser.myopenid.com/=luser
<IfModule mod_auth_oid_file.c>
    AOID_File_DB    	/home/httpd/data/openid2user.txt
    AOID_DB_Delimiter    	=
    AOID_File_AdminPage    /home/httpd/htdocs/oid-forms/file_mapper.html

<Location /oid-admin>
    SetHandler    	auth-oid-file
    AuthType    	OpenID
    # Lacking AOID_File_UserOnly, this user can edit any map row.
    Require    	    user luser
</Location>
<Location /oid-map>
    SetHandler    	auth-oid-file
    AuthType    	OpenID
    Require    	    valid-user
    # User can edit only OIDs mapping to itself
    AOID_File_UserOnly    on
    # Requires form parameter parsing, mod_parp:
    SetEnvIf    	Request_Method GET  parp
    SetEnvIf    	Request_Method POST parp
</Location>
</IfModule>

Conclusion

I have now confirmed that my StartSSL.com OpenID actually works, which was the goal of this project.

However, my motivation to work on OpenID was to set up an account on a popular social network site, but to avoid the ridiculous proliferation of account passwords. The site appeared to use OpenID, but when I dug deeper I discovered that this was an illusion: it neither accepts nor provides OpenID service. I was very disappointed, but I finished the OpenID project anyway to learn about the protocol.

I would very much like OpenID to become widely accepted by RPs, but I see several black clouds on its horizon:

Bibliography