Introducing MockMyId.com

As a exercise, I recently built my own BrowserID Identity Provider (IdP), also known as a “Primary.” I didn’t want to actually deal with emails or an account database, so I decided to create an IdP that would happily hand out credentials to anyone that asked. Such an IdP would be useful for testing or demoing sites that rely on BrowserID, since it allows for the creation of new identities on demand.

Thus, MockMyId.com was born. You can log into any BrowserID Relying Party with anything_you_want@mockmyid.com, and it should Just Work. Try it out at My Favorite Beer! The code is on GitHub.

Building MockMyID

Note: The implementation details herein may be out of date! You should always refer to MDN for current information.

Once I settled on the idea of an approve-anything IdP, I got to thinking about Mozilla’s use of NodeJS for the BrowserID project. Since I didn’t need to keep the keypair secret, and I didn’t need to do any work on the backend, I decided to try and implement everything client-side.

So, where to start? I initially went to the BrowserID page on MDN but couldn’t find any relevant information aside from “Development Tips” for becoming an IdP. Unfortunately, the tips weren’t terribly useful since they presumed a lot of pre-existing knowledge about the authentication flow.

With MDN a bust, I headed over to the BrowserID spec and skipped down to the section on “Primary Authority Compliance,” where I learned that I only needed three documents on my server: a support document at /.well-known/browserid, a page for authenticating users, and a page for creating new keys.

The Support Document

First things first, I created a new json document at /.well-known/browserid and configured Nginx to serve it as Content-Type: application/json. The document just needed three keys, as per the spec: public-key, authentication, and provisioning. I stubbed out the authetication and provisioning values and then turned to the public-key field.

The spec simply said that the key needs to be a “Public Key serialized as a JSON object, as defined above.” I would’ve appreciated a link to the whole “above” part, but it was easy enough to find the “Public Key” subsection under the “Objects / Messages” header. Where to go from there, however, proved to be a little more challenging. The spec mentions RSA keys and proffers a link to JOSE, the IETF’s Javascript Object Signing and Encryption working group.

Landing on the homepage of an IETF working group isn’t the most helpful, but I found a link therein to the JSON Web Key (JWK) draft… which 404’d. Thankfully, there’s a working link to the JWK spec at the bottom of the BrowserID spec.

The JWK spec mentioned RSA keys, so I generated a keypair using openssl on the command line and spent an hour or so working out how to encode the public key as a JWK. It was only then that I noticed that the example in the JWK spec looked nothing like the one in the BrowserID spec, and the one in the BrowserID spec looked nothing like the one used at eyedee.me, Lloyd’s proof-of-concept IdP. Awesome.

At this point I gave up on the docs and fell back on the knowledge that Ben Adida had been working on jwcrypto, an implementation of the various standards coming out of JOSE. I grepped through the source, found the RSAKey.readPublicKeyFromPEMString and RSAKey.readPrivateKeyFromPEMString functions, but wasn’t able to figure out how to take those and serialize them back out to the JSON format.

I eventually stumbled upon jwk.KeyPair.generate and used that to create a new JWK KeyPair, which I then dumped to JSON with the toSimpleObject method on the KeyPair’s publicKey and privateKey members. The output looked like the public key on eyedee.me and not like the one in the JWK or BrowserID specs, but it was at least a proper keypair! I dumped my old openssl-derived keys and forged ahead with these new keys, plugging the public key into its place in .well-known/browserid.

Now for the two other attributes: authentication and provisioning.

The Authentication Page

The “Identity Provisioning Flow” section of the spec was pretty straightforward: The user agent tries to provision a new key by loading the provisioning URL in an invisible iframe, and if the user isn’t sufficiently authenticated with the IdP, the provisioning page raises a provisioning failure and the user gets taken to the authentication page.

Since MockMyID never requires any authentication, I just stubbed out a mostly-empty HTML document and saved it at /browserid/sign_in.html. No one should ever see that page, but at least it won’t 404.

The Provisioning Page

The BrowserID Spec does a good job of outlining what needs to happen on the provisioning page under the “Certifying Users” subheading, but there was a bunch of useful information scattered in other sections. Everything starts with a call to navigator.id.beginProvisioning(callback), with the callback described as “a function that accepts an email address as parameter.” However, under the “User-Agent Compliance” section, that same function is described as needing to “accept parameters email and cert_duration_s,” and it’s used in this manner in the example in the “Identity Provisioning Flow” section earlier in the document.

If the user is properly authenticated (which is always true in mockmyid’s case), the provisioning page then needs to call navigator.id.genKeyPair(callback) to request that the client generates a keypair for the site to sign. If everything checks out the site is supposed to build a signed Identity Certificate and pass that to navigator.id.registerCertificate.

I stubbed out everything as per the example in the spec, but before diving into generating signed certificates, I figured I should make sure that everything else was at least set up properly. I added a self-signed SSL certificate to Nginx and tried to log in at dev.myfavoritebeer.org, which was mentioned in the “IdP Developer Tips and Tricks” article on MDN. Watching the JavaScript console, I saw an early problem: navigator.id.beginProvisioning wasn’t defined anywhere! Oops! From building a site that used BrowserID for authentication, I knew about the include.js file provided by browserid.org, so I went and added that to a script tag and tried again. Same problem.

It turns out, include.js doesn’t include definitions for provisioning stuff. Rather, there’s another file at on browserid.org called provisioning_api.js that has all of that in it. I changed the script tag and tried again. Success! My callback was getting a key when I ran navigator.id.genKeyPair!

(It’s worth noting that, as per the Developer Tips, I wasn’t actually loading the scripts from browserid.org, but rather from dev.diresworb.org.)

Now I just had to create the certificate and sign it. I started by typing out a literal JSON structure similar to that in the example, but I couldn’t quite figure out how to sign it. The spec just says that “when signed, [the certificate] becomes a base64url-encoded data structure.” Helpful. From looking at the JOSE stuff, there’s mention of “JSON Web Signatures,” but that link is also broken. Googling turned up a working link, but given the discrepancies I saw between the BrowserID and JWK specs, I ended up just going straight to eyedee.me’s source.

Therein I discovered the sign method on JWCert objects, and a simple constructor for said objects. Sold! The only thing I needed to do was find a way to actually have the jwcrypto libraries available client-side. Fortunately, the readme notes that “Browserify is used to bundle it all up,” and the repo includes a convenient build.sh script to create vepbundle.js with all of the crypto libraries concatenated together.

After adding that to the head of my provisioning page, I was able to take the rest of the flow from eyedee.me and have a mostly-complete IdP. Folks on IRC had pointed me to the check_primary_support script in the BrowserID repo, which reported an all-clear.

I tried to log into dev.myfavoritebeer.org.

It worked!

Making it Work in Production

Knowing that I had a functional IdP, I switched all of the dev.diresworb.org references over to browserid.org, went to the standard myfavoritebeer.org site, and… it failed. I got the “this looks like a new email address!” login flow used with non-IdP email addresses.

Looking at the network flow, I saw request to https://browserid.org/wsapi/address_info?email=dan@mockmyid.com which returned the JSON structure {"type":"secondary","known":false}. Yet hitting that same URL on dev.diresworb.org showed the correct information: {"auth":"https://mockmyid.com/browserid/sign_in.html","prov":"https://mockmyid.com/browserid/provision.html","type":"primary"}

After scratching my head for a while, I realized that the production site requires a properly signed SSL certificated, and thus my self-signed one just wouldn’t do. The failure mode involved gracefully degrading the the email-based verification workflow, but I would’ve appreciated it if either check_primary_support or the wsapi call had given me some clue as to this being a problem.

Everything worked flawlessly after I installed a proper SSL certificate. I was done!

MockMyID is live, and you can use it right now.

Next Steps

  1. I would’ve really appreciated an abbreviated guide to building an IdP. The Spec is nice, but there needs to be somewhere that we can say “Oh, and by the way, we’ve totally built a JavaScript implementation of this part for you. It’s available over here.”

  2. Explicitly pointing people to check_primary_support and /wsapi/address_info would be useful.

  3. I need to figure out what’s going on with the discrepancies between the BrowserID spec and the JOSE specs.

I’ll be working on the documentation in the coming weeks, but I can’t do it all alone! If you’re interested in improving the status quo, contributions are always welcome on the BrowserID section of MDN. It’s a wiki, so anyone can help out!

Resources