diff --git a/browserid/app.js b/browserid/app.js index 1e9b330eece7a905abdc35612cfb777dd2eb63fa..35d8a4f618b09cf36dc463d396379ab2b64e472d 100644 --- a/browserid/app.js +++ b/browserid/app.js @@ -58,8 +58,11 @@ db.open(configuration.get('database')); const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', configuration.get('var_path')); const COOKIE_KEY = 'browserid_state'; -function internal_redirector(new_url) { +function internal_redirector(new_url, suppress_noframes) { return function(req, resp, next) { + if (suppress_noframes) + resp.removeHeader('x-frame-options'); + req.url = new_url; return next(); }; @@ -84,10 +87,10 @@ function router(app) { }); // simple redirects (internal for now) - app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html')); + app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true)); // Used for a relay page for communication. - app.get(/^\/relay(\.html)?$/, function(req,res, next) { + app.get("/relay", function(req,res, next) { // Allow the relay to be run within a frame res.removeHeader('x-frame-options'); res.render('relay.ejs', { diff --git a/browserid/static/dialog/qunit.html b/browserid/static/dialog/qunit.html index a22393863716be5e9f6b976a45a791fbd411f96e..8b7142e7746ea2a5a0b69dc5350c7f27ac6f3699 100644 --- a/browserid/static/dialog/qunit.html +++ b/browserid/static/dialog/qunit.html @@ -5,6 +5,7 @@ <script type='text/javascript'> steal = {ignoreControllers: true} </script> + <script type='text/javascript' src='/vepbundle'></script> <script type='text/javascript' src='/steal/steal.js?/dialog/test/qunit'></script> </head> <body> diff --git a/browserid/static/dialog/register_iframe.html b/browserid/static/dialog/register_iframe.html index 0365a0de3b05ad4f96102cf237c9f1ddd52c57c4..390bf33901afdb3ae314b4d01847f40504636ca3 100644 --- a/browserid/static/dialog/register_iframe.html +++ b/browserid/static/dialog/register_iframe.html @@ -1,4 +1,6 @@ -<script src="../dialog/jschannel.js"></script> -<script src="../dialog/crypto.js"></script> -<script src="../dialog/crypto-api.js"></script> +<head><title>non-interactive iframe</title> +<script src="/vepbundle"></script> +<script src="../dialog/resources/jschannel.js"></script> +<script src="../dialog/resources/storage.js"></script> <script src="../dialog/register_iframe.js"></script> +</head><body></body> diff --git a/browserid/static/dialog/register_iframe.js b/browserid/static/dialog/register_iframe.js index 42494702a9e5792f36c20e8630bfdf67a2f0bddb..8e8404598115a449bb4ab6ae6b1556c516dc208c 100644 --- a/browserid/static/dialog/register_iframe.js +++ b/browserid/static/dialog/register_iframe.js @@ -35,6 +35,11 @@ // this is the picker code! it runs in the identity provider's domain, and // fiddles the dom expressed by picker.html + +var jwk = require("./jwk"), + jwcert = require("./jwcert"), + vep = require("./vep"); + (function() { var chan = Channel.build( { @@ -42,35 +47,50 @@ origin: "*", scope: "mozid" }); + + // primary requests a keygen to certify + chan.bind("generateKey", function(trans, args) { + // keygen + var keypair = jwk.KeyPair.generate(vep.params.algorithm, 64); - function persistAddressAndKeyPair(email, keypair, issuer) - { - var emails = {}; - if (window.localStorage.emails) { - emails = JSON.parse(window.localStorage.emails); - } + // save it in a special place for now + storeTemporaryKeypair(keypair); + + // serialize and return + return keypair.publicKey.serialize(); + }); - emails[email] = { - created: new Date(), - pub: keypair.pub, - priv: keypair.priv - }; - if (issuer) { - emails[email].issuer = issuer; - } - window.localStorage.emails = JSON.stringify(emails); - } + // add the cert + chan.bind("registerVerifiedEmailCertificate", function(trans, args) { + var keypair = retrieveTemporaryKeypair(); - chan.bind("registerVerifiedEmail", function(trans, args) { - // This is a primary registration - the persisted - // identity does not have an issuer because it - // was directly asserted by the controlling domain. + // parse the cert + var raw_cert = args.cert; + var cert = new jwcert.JWCert(); + cert.parse(raw_cert); + var email = cert.principal.email; + var pk = cert.pk; - var keypair = CryptoStubs.genKeyPair(); - persistAddressAndKeyPair(args.email, keypair); - return keypair.pub; - }); + // check if the pk's match + if (!pk.equals(keypair.publicKey)) { + trans.error("bad cert"); + return; + } + + var new_email_obj= { + created: new Date(), + pub: keypair.publicKey.toSimpleObject(), + priv: keypair.secretKey.toSimpleObject(), + cert: raw_cert, + issuer: cert.issuer, + isPrimary: true + }; + + addEmail(email, new_email_obj); + }); + // reenable this once we're ready + /* function isSuperDomain(domain) { return true; } @@ -158,4 +178,5 @@ // if we get here, we've failed trans.error("X", "not a proper token-based call"); }); + */ })(); diff --git a/browserid/static/dialog/resources/browserid-identities.js b/browserid/static/dialog/resources/browserid-identities.js index 397a0e91a47d43d324ee68e047c2bb6e7747f46d..f33acef45da33db6098e2ea7abad44d5e758c681 100644 --- a/browserid/static/dialog/resources/browserid-identities.js +++ b/browserid/static/dialog/resources/browserid-identities.js @@ -108,7 +108,11 @@ var BrowserIDIdentities = (function() { var emails_to_remove = _.difference(client_emails, server_emails); // remove emails - _.each(emails_to_remove, function(email) {removeEmail(email);}); + _.each(emails_to_remove, function(email) { + // if it's not a primary + if (!issued_identities[email].isPrimary) + removeEmail(email); + }); // keygen for new emails // asynchronous @@ -266,10 +270,9 @@ var BrowserIDIdentities = (function() { * @param {function} [onFailure] - Called on error. */ syncIdentity: function(email, issuer, onSuccess, onFailure) { - // var keypair = CryptoStubs.genKeyPair(); + // FIXME use true key sizes //var keypair = jwk.KeyPair.generate(vep.params.algorithm, vep.params.keysize); var keypair = jwk.KeyPair.generate(vep.params.algorithm, 64); -// network.setKey(email, keypair, function() { network.certKey(email, keypair.publicKey, function(cert) { Identities.persistIdentity(email, keypair, cert, issuer, function() { if (onSuccess) { diff --git a/browserid/static/dialog/resources/storage.js b/browserid/static/dialog/resources/storage.js index 0a97ee8149984de75bbe5463ec270fa6bbb57944..c4784dae1538163c37dcb79695e719cb4aac4c7a 100644 --- a/browserid/static/dialog/resources/storage.js +++ b/browserid/static/dialog/resources/storage.js @@ -33,6 +33,8 @@ * * ***** END LICENSE BLOCK ***** */ +var jwk = require("./jwk"); + var getEmails = function() { try { var emails = JSON.parse(window.localStorage.emails); @@ -64,4 +66,23 @@ var removeEmail = function(email) { var clearEmails = function() { _storeEmails({}); +}; + +var storeTemporaryKeypair = function(keypair) { + window.localStorage.tempKeypair = JSON.stringify({ + publicKey: keypair.publicKey.toSimpleObject(), + secretKey: keypair.secretKey.toSimpleObject() + }); +}; + +var retrieveTemporaryKeypair = function() { + var raw_kp = JSON.parse(window.localStorage.tempKeypair); + if (raw_kp) { + var kp = new jwk.KeyPair(); + kp.publicKey = jwk.PublicKey.fromSimpleObject(raw_kp.publicKey); + kp.secretKey = jwk.SecretKey.fromSimpleObject(raw_kp.secretKey); + return kp; + } else { + return null; + } }; \ No newline at end of file diff --git a/browserid/static/include.js b/browserid/static/include.js index 5d076054711de0e0f196bfc3c487aaaf0b069f65..644f7a978eb57ddd5a9779cdd0cc7295ea484e5d 100644 --- a/browserid/static/include.js +++ b/browserid/static/include.js @@ -44,7 +44,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) { var ipServer = "https://browserid.org"; var isMobile = navigator.userAgent.indexOf('Fennec/') != -1; - + // local embedded copy of jschannel: http://github.com/mozilla/jschannel var Channel = (function() { // current transaction id, start out at a random *odd* number between 1 and a million @@ -52,7 +52,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) // channel instances. That means of all messages posted from a single javascript // evaluation context, we'll never have two with the same id. var s_curTranId = Math.floor(Math.random()*1000001); - + // no two bound channels in the same javascript evaluation context may have the same origin & scope. // futher if two bound channels have the same scope, they may not have *overlapping* origins // (either one or both support '*'). This restriction allows a single onMessage handler to efficiently @@ -412,7 +412,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) // what can we do? Also, here we'll ignore return values } } - } + }; // now register our bound channel for msg routing s_addBoundChan(cfg.origin, ((typeof cfg.scope === 'string') ? cfg.scope : ''), onMessage); @@ -421,7 +421,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) var scopeMethod = function(m) { if (typeof cfg.scope === 'string' && cfg.scope.length) m = [cfg.scope, m].join("::"); return m; - } + }; // a small wrapper around postmessage whose primary function is to handle the // case that clients start sending messages before the other end is "ready" @@ -444,7 +444,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) cfg.window.postMessage(JSON.stringify(msg), cfg.origin); } - } + }; var onReady = function(trans, type) { debug('ready msg received'); @@ -564,27 +564,30 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) var chan = undefined; - function _create_iframe(doc) { - var iframe = doc.createElement("iframe"); - iframe.style.display = "none"; - doc.body.appendChild(iframe); - iframe.src = ipServer + "/register_iframe"; - return iframe; + // this is for calls that are non-interactive + function _open_hidden_iframe(doc) { + var iframe = doc.createElement("iframe"); + // iframe.style.display = "none"; + doc.body.appendChild(iframe); + iframe.src = ipServer + "/register_iframe"; + return iframe; } - + function _open_relay_frame(doc) { - var iframe = doc.createElement("iframe"); - iframe.setAttribute('name', 'browserid_relay'); - iframe.setAttribute('src', ipServer + "/relay.html"); - iframe.style.display = "none"; - doc.body.appendChild(iframe); - return iframe; + var iframe = doc.createElement("iframe"); + iframe.setAttribute('name', 'browserid_relay'); + iframe.setAttribute('src', ipServer + "/relay"); + iframe.style.display = "none"; + doc.body.appendChild(iframe); + return iframe; } - + function _open_window() { - return window.open( - ipServer + "/sign_in#host=" + document.location.host, "_mozid_signin", - isMobile ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=520,height=350"); + // FIXME: need to pass the location in a more trustworthy fashion + // HOW? set up a direct reference to the open window + return window.open( + ipServer + "/sign_in#host=" + document.location.host, "_mozid_signin", + isMobile ? undefined : "menubar=0,location=0,resizable=0,scrollbars=0,status=0,dialog=1,width=520,height=350"); } navigator.id.getVerifiedEmail = function(callback) { @@ -595,7 +598,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) // clean up a previous channel that never was reaped if (chan) chan.destroy(); chan = Channel.build({window: iframe.contentWindow, origin: ipServer, scope: "mozid"}); - + function cleanup() { chan.destroy(); chan = undefined; @@ -620,6 +623,7 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) }); }; +/* // preauthorize a particular email // FIXME: lots of cut-and-paste code here, need to refactor // not refactoring now because experimenting and don't want to break existing code @@ -650,8 +654,11 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) } }); }; + */ // get a particular verified email + // FIXME: needs to ditched for now until fixed + /* navigator.id.getSpecificVerifiedEmail = function(email, token, onsuccess, onerror) { var doc = window.document; @@ -697,24 +704,27 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) } }); }; + */ - navigator.id.registerVerifiedEmail = function(email, onsuccess, onerror) { + function _noninteractiveCall(method, args, onsuccess, onerror) { var doc = window.document; - iframe = _create_iframe(doc); + iframe = _open_hidden_iframe(doc); + + // clean up channel if (chan) chan.destroy(); chan = Channel.build({window: iframe.contentWindow, origin: ipServer, scope: "mozid"}); - + function cleanup() { chan.destroy(); chan = undefined; doc.body.removeChild(iframe); } - + chan.call({ - method: "registerVerifiedEmail", - params: {email:email}, + method: method, + params: args, success: function(rv) { - console.log("registerVerifiedEmail channel returned: rv is " + rv); + console.log(method + " channel returned: rv is " + rv); if (onsuccess) { onsuccess(rv); } @@ -724,8 +734,28 @@ if (!navigator.id.getVerifiedEmail || navigator.id._getVerifiedEmailIsShimmed) if (onerror) onerror(code, msg); cleanup(); } - }); + }); + } + + // check if a valid cert exists for this verified email + // calls back with true or false + // FIXME: implement it for real, but + // be careful here because this needs to be limited + navigator.id.checkVerifiedEmail = function(email, onsuccess, onerror) { + onsuccess(false); }; + // generate a keypair + navigator.id.generateKey = function(onsuccess, onerror) { + _noninteractiveCall("generateKey", {}, + onsuccess, onerror); + }; + + navigator.id.registerVerifiedEmailCertificate = function(certificate, updateURL, onsuccess, onerror) { + _noninteractiveCall("registerVerifiedEmailCertificate", + {cert:certificate, updateURL: updateURL}, + onsuccess, onerror); + }; + navigator.id._getVerifiedEmailIsShimmed = true; }