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;
 }