diff --git a/bin/verifier b/bin/verifier
index 1d9f48e6097486e866f80dd2e31ce9d84aeb725c..4499256930558eed528919976ddd7a576ada1498 100755
--- a/bin/verifier
+++ b/bin/verifier
@@ -88,7 +88,7 @@ app.post('/verify', function(req, resp, next) {
     } catch (e) {
       return resp.json({ status: "failure", reason: "Content-Type expected to be one of: " + want_ct.join(", ") }, 415);
     }
-    return resp.json({ status: "failure", reason: "need assertion and audience" }, 400);
+    return resp.json({ status: "failure", reason: "need assertion and audience"}, 400);
   }
 
   var startTime = new Date();
diff --git a/lib/keysigner/ca.js b/lib/keysigner/ca.js
index bc7be67f663cd32d6f7ce11b245021477a0f73cf..5e95fef85edcc18555b9e8774651e7e553c4eefe 100644
--- a/lib/keysigner/ca.js
+++ b/lib/keysigner/ca.js
@@ -4,15 +4,18 @@
 
 // certificate authority
 
-var jwcert = require('jwcrypto/jwcert'),
-    jwk = require('jwcrypto/jwk'),
-    jws = require('jwcrypto/jws'),
+var jwcrypto = require('jwcrypto'),
+    cert = jwcrypto.cert,
     path = require("path"),
     fs = require("fs"),
     secrets = require('../secrets.js'),
     logger = require('../logging.js').logger,
     urlparse = require('urlparse');
 
+// load up the right algorithms
+require("jwcrypto/lib/algs/rs");
+require("jwcrypto/lib/algs/ds");
+
 try {
   const secret_key = secrets.loadSecretKey();
   const public_key = secrets.loadPublicKey();
@@ -22,37 +25,58 @@ try {
 }
 
 function parsePublicKey(serializedPK) {
-  return jwk.PublicKey.deserialize(serializedPK);
-}
-
-function parseCert(serializedCert) {
-  var cert = new jwcert.JWCert();
-  cert.parse(serializedCert);
-  return cert;
+  return jwcrypto.loadPublicKey(serializedPK);
 }
 
-function certify(hostname, email, publicKey, expiration) {
+function certify(hostname, email, publicKey, expiration, cb) {
   if (expiration == null)
-    throw "expiration cannot be null";
-  return new jwcert.JWCert(hostname, expiration, new Date(), publicKey, {email: email}).sign(secret_key);
+    return cb("expiration cannot be null");
+
+  cert.sign(publicKey, {email: email},
+            {issuer: hostname, issuedAt: new Date(), expiresAt: expiration},
+            null,
+            secret_key, cb);
 }
 
+// hostname is issuer
+// certChain is an array of raw certs
+// the cb is called with the last public key and principal
 function verifyChain(hostname, certChain, cb) {
-  // raw certs
-  return jwcert.JWCert.verifyChain(
+  return cert.verifyChain(
     certChain, new Date(),
     function(issuer, next) {
       // for now we only do browserid.org issued keys
       if (issuer != hostname)
-        return next(null);
+        return next("only verifying " + hostname + "-issued keys");
+
+      next(null, exports.PUBLIC_KEY);
+    }, function(err, certParamsArray) {
+      if (err) return cb(err);
+
+      var lastParams = certParamsArray[certParamsArray.length - 1];
+      cb(null, lastParams.certParams['public-key'], lastParams.certParams.principal, certParamsArray);
+    });
+}
+
+function verifyBundle(hostname, bundle, cb) {
+  return cert.verifyBundle(
+    bundle, new Date(),
+    function(issuer, next) {
+      // for now we only do browserid.org issued keys
+      if (issuer != hostname)
+        return next("only verifying " + hostname + "-issued keys");
+
+      next(null, exports.PUBLIC_KEY);
+    }, function(err, certParamsArray, payload, assertionParams) {
+      if (err) return cb(err);
 
-      next(exports.PUBLIC_KEY);
-    }, cb);
+      cb(null, certParamsArray, payload, assertionParams);
+    });  
 }
 
 // exports, not the key stuff
 exports.certify = certify;
 exports.verifyChain = verifyChain;
+exports.verifyBundle = verifyBundle;
 exports.parsePublicKey = parsePublicKey;
-exports.parseCert = parseCert;
 exports.PUBLIC_KEY = public_key;
diff --git a/lib/keysigner/keysigner-compute.js b/lib/keysigner/keysigner-compute.js
index 5a64adb0c92e6e8172a70b4ff51946097ad4a7f4..39e761756dd554998fc83a238f8c0c0454033e88 100644
--- a/lib/keysigner/keysigner-compute.js
+++ b/lib/keysigner/keysigner-compute.js
@@ -14,8 +14,12 @@ process.on('message', function(m) {
     // we certify it for a day for now
     var expiration = new Date();
     expiration.setTime(new Date().valueOf() + m.validityPeriod);
-    var cert = ca.certify(m.hostname, m.email, pk, expiration);
-    process.send({"success": cert});
+    ca.certify(m.hostname, m.email, pk, expiration, function(err, cert) {
+      if (err)
+        return process.send({"error": err});
+        
+      process.send({"success": cert});
+    });
   } catch(e) {
     process.send({"error": e ? e.toString() : "unknown"});
   }
diff --git a/lib/primary.js b/lib/primary.js
index 55861ece63a6614d02107bf4b4927705d3031e83..060d199cca3fb1a6c327d9b9aba3a0ad197bc5c0 100644
--- a/lib/primary.js
+++ b/lib/primary.js
@@ -11,12 +11,13 @@ https = require('https'),
 http = require('http'),
 logger = require('./logging.js').logger,
 urlparse = require('urlparse'),
-jwk = require('jwcrypto/jwk'),
-jwcert = require("jwcrypto/jwcert"),
-vep = require("jwcrypto/vep"),
-jwt = require("jwcrypto/jwt"),
+jwcrypto = require("jwcrypto"),
 config = require("./configuration.js");
 
+// alg
+require("jwcrypto/lib/algs/rs");
+require("jwcrypto/lib/algs/ds");
+
 const WELL_KNOWN_URL = "/.well-known/browserid";
 
 // Protect from stack overflows and network DDOS attacks
@@ -88,7 +89,7 @@ function parseWellKnownBody(body, domain, delegates, cb) {
 
   // parse the public key
   return cb(null, {
-    publicKey: jwk.PublicKey.fromSimpleObject(v['public-key']),
+    publicKey: jwcrypto.loadPublicKeyFromObject(v['public-key']),
     urls: urls
   });
 }
@@ -232,40 +233,32 @@ exports.verifyAssertion = function(assertion, cb) {
     return process.nextTick(function() { cb("primary support disabled") });
   }
 
-  try {
-    var bundle = vep.unbundleCertsAndAssertion(assertion);
-  } catch(e) {
-    return process.nextTick(function() { cb("malformed assertion: " + e); });
-  }
-  jwcert.JWCert.verifyChain(
-    bundle.certificates,
-    new Date(), function(issuer, next) {
-      // issuer cannot be the browserid
-      if (issuer === HOSTNAME) {
-        cb("cannot authenticate to browserid with a certificate issued by it.");
-      } else {
-
-        exports.getPublicKey(issuer, function(err, pubKey) {
-          if (err) return cb(err);
-          next(pubKey);
-        });
-      }
-    }, function(pk, principal) {
-      try {
-        var tok = new jwt.JWT();
-        tok.parse(bundle.assertion);
-
-        // audience must be browserid itself
-        var want = urlparse(config.get('public_url')).originOnly();
-        var got = urlparse(tok.audience).originOnly();
+  var getRoot = function(issuer, next) {
+    // issuer cannot be the browserid
+    if (issuer === HOSTNAME) {
+      next("cannot authenticate to browserid with a certificate issued by it.");
+    } else {
+      exports.getPublicKey(issuer, function(err, pubKey) {
+        if (err) return next(err);
+        next(null, pubKey);
+      });
+    }
+  };
+  
+  // verify the assertion bundle
+  var now = new Date();
+  jwcrypto.cert.verifyBundle(assertion, now, getRoot, function(err, certParamsArray, payload, assertionParams) {
+    if (err) return cb(err);
+    
+    // audience must be browserid itself
+    var want = urlparse(config.get('public_url')).originOnly();
+    var got = urlparse(assertionParams.audience).originOnly();
+
+    if (want.toString() !== got.toString()) {
+      return cb("can't log in with an assertion for '" + got.toString() + "'");
+    }
 
-        if (want.toString() !== got.toString()) {
-          return cb("can't log in with an assertion for '" + got.toString() + "'");
-        }
-        if (!tok.verify(pk)) throw "verification failure";
-        cb(null, principal.email);
-      } catch(e) {
-        cb("can't verify assertion: " + e.toString());
-      }
-    }, cb);
+    // all is well, get the principal from the last cert
+    cb(null, certParamsArray[certParamsArray.length-1].certParams.principal.email);
+  });  
 };
diff --git a/lib/secrets.js b/lib/secrets.js
index 4b854c3436d02d0238fb84823851dad088d367ba..eb674481e89d6b0487284c8c9597d6e4027e3988 100644
--- a/lib/secrets.js
+++ b/lib/secrets.js
@@ -5,8 +5,7 @@
 const
 path = require('path'),
 fs = require('fs'),
-jwk = require('jwcrypto/jwk'),
-jwt = require('jwcrypto/jwt'),
+jwcrypto = require('jwcrypto'),
 Buffer = require('buffer').Buffer,
 crypto = require('crypto');
 
@@ -88,7 +87,7 @@ exports.loadSecretKey = function(name, dir) {
   }
 
   // parse it
-  return jwk.SecretKey.deserialize(secret);
+  return jwcrypto.loadSecretKey(secret);
 }
 
 function readAndParseCert(name, dir) {
@@ -107,9 +106,8 @@ function readAndParseCert(name, dir) {
   // parse it
   // it should be a JSON structure with alg and serialized key
   // {alg: <ALG>, value: <SERIALIZED_KEY>}
-  var tok = new jwt.JWT();
-  tok.parse(cert);
-  return JSON.parse(new Buffer(tok.payloadSegment, 'base64').toString());
+  var payloadSegment = jwcrypto.extractComponents(cert).payloadSegment;
+  return JSON.parse(new Buffer(payloadSegment, 'base64').toString());
 }
 
 exports.publicKeyCreationDate = function(name, dir) {
@@ -117,5 +115,5 @@ exports.publicKeyCreationDate = function(name, dir) {
 };
 
 exports.loadPublicKey = function(name, dir) {
-  return jwk.PublicKey.deserialize(JSON.stringify(readAndParseCert(name, dir)['public-key']));
+  return jwcrypto.loadPublicKey(JSON.stringify(readAndParseCert(name, dir)['public-key']));
 };
diff --git a/lib/static_resources.js b/lib/static_resources.js
index 1bd4a35f5d3b5f1ff58547c758a79f6a089c32fa..456f5ce695f4539c95737bcf07bfbcd191af2bc8 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -19,7 +19,7 @@ var common_js = [
   '/lib/jquery-1.7.1.min.js',
   '/lib/winchan.js',
   '/lib/underscore-min.js',
-  '/lib/vepbundle.js',
+  '/lib/bidbundle-min.js',  
   '/lib/ejs.js',
   '/lib/micrajax.js',
   '/shared/javascript-extensions.js',
@@ -117,7 +117,7 @@ exports.resources = {
     '/lib/jschannel.js',
     '/lib/winchan.js',
     '/lib/underscore-min.js',
-    '/lib/vepbundle.js',
+    '/lib/bidbundle-min.js',
     '/lib/hub.js',
     '/lib/micrajax.js',
     '/shared/javascript-extensions.js',
diff --git a/lib/verifier/certassertion.js b/lib/verifier/certassertion.js
index b437fe4f9abbc5ecbcb38b66ae71e21557ceda50..42816d69830de1f4fc12937d7b83957c4c192a50 100644
--- a/lib/verifier/certassertion.js
+++ b/lib/verifier/certassertion.js
@@ -6,16 +6,16 @@ const
 http = require("http"),
 https = require("https"),
 url = require("url"),
-jwk = require("jwcrypto/jwk"),
-jwt = require("jwcrypto/jwt"),
-jwcert = require("jwcrypto/jwcert"),
-vep = require("jwcrypto/vep"),
+jwcrypto = require("jwcrypto"),
 config = require("../configuration.js"),
 logger = require("../logging.js").logger,
 secrets = require('../secrets.js'),
 primary = require('../primary.js'),
 urlparse = require('urlparse');
 
+require("jwcrypto/lib/algs/ds");
+require("jwcrypto/lib/algs/rs");
+
 try {
   const publicKey = secrets.loadPublicKey();
   if (typeof publicKey !== 'object') throw "secrets.loadPublicKey() returns non-object, load failure";
@@ -89,23 +89,17 @@ function compareAudiences(want, got) {
 // audience is a web origin, e.g. https://foo.com or http://foo.org:81
 function verify(assertion, audience, successCB, errorCB) {
   // assertion is bundle
-  try {
-    var bundle = vep.unbundleCertsAndAssertion(assertion);
-  } catch(e) {
-    return errorCB("malformed assertion");
-  }
-
   var ultimateIssuer;
 
-  jwcert.JWCert.verifyChain(
-    bundle.certificates,
+  jwcrypto.cert.verifyBundle(
+    assertion,
     new Date(), function(issuer, next) {
       // update issuer with each issuer in the chain, so the
       // returned issuer will be the last cert in the chain
       ultimateIssuer = issuer;
 
       // allow other retrievers for testing
-      if (issuer === HOSTNAME) return next(publicKey);
+      if (issuer === HOSTNAME) return next(null, publicKey);
       else if (config.get('disable_primary_support')) {
         return errorCB("this verifier doesn't respect certs issued from domains other than: " +
                        HOSTNAME);
@@ -120,35 +114,33 @@ function verify(assertion, audience, successCB, errorCB) {
       // let's go fetch the public key for this host
       primary.getPublicKey(issuer, function(err, pubKey) {
         if (err) return errorCB(err);
-        next(pubKey);
+        next(null, pubKey);
       });
-    }, function(pk, principal) {
-      var tok = new jwt.JWT();
-      tok.parse(bundle.assertion);
-
+    }, function(err, certParamsArray, payload, assertionParams) {
+      if (err) return errorCB(err);
+        
       // audience must match!
-      var err = compareAudiences(tok.audience, audience)
+      var err = compareAudiences(assertionParams.audience, audience)
       if (err) {
         logger.debug("verification failure, audience mismatch: '"
-                     + tok.audience + "' != '" + audience + "': " + err);
+                     + assertionParams.audience + "' != '" + audience + "': " + err);
         return errorCB("audience mismatch: " + err);
       }
 
+      // principal is in the last cert
+      var principal = certParamsArray[certParamsArray.length - 1].certParams.principal;
+      
       // verify that the issuer is the same as the email domain
       // NOTE: for "delegation of authority" support we'll need to make this check
       // more sophisticated
       var domainFromEmail = principal.email.replace(/^.*@/, '');
       if (ultimateIssuer != HOSTNAME && ultimateIssuer !== domainFromEmail)
       {
-        return errorCB("issuer issue '" + ultimateIssuer + "' may not speak for emails from '"
+        return errorCB("issuer '" + ultimateIssuer + "' may not speak for emails from '"
                        + domainFromEmail + "'");
       }
 
-      if (tok.verify(pk)) {
-        successCB(principal.email, tok.audience, tok.expires, ultimateIssuer);
-      } else {
-        errorCB("verification failure");
-      }
+      successCB(principal.email, assertionParams.audience, assertionParams.expiresAt, ultimateIssuer);
     }, errorCB);
 };
 
diff --git a/package.json b/package.json
index e75ed621df3f3b3e85b3a9424434bfd53677ba22..4da780272d92c3946e403909f1d319763c90285c 100644
--- a/package.json
+++ b/package.json
@@ -17,8 +17,8 @@
         "etagify": "0.0.2",
         "express": "2.5.0",
         "iconv": "1.1.3",
-        "jwcrypto": "0.1.1",
         "mustache": "0.3.1-dev",
+        "jwcrypto": "https://github.com/mozilla/jwcrypto/tarball/43ef3846813882cb",
         "mysql": "0.9.5",
         "node-gettext": "0.1.1",
         "node-statsd": "https://github.com/downloads/lloyd/node-statsd/0509f85.tgz",
diff --git a/resources/static/lib/bidbundle-min.js b/resources/static/lib/bidbundle-min.js
new file mode 120000
index 0000000000000000000000000000000000000000..1e002cab82753cef375b961d7d590f706edb00a7
--- /dev/null
+++ b/resources/static/lib/bidbundle-min.js
@@ -0,0 +1 @@
+../../../node_modules/jwcrypto/bidbundle-min.js
\ No newline at end of file
diff --git a/resources/static/lib/vepbundle.js b/resources/static/lib/vepbundle.js
deleted file mode 120000
index 99cb32376ee635d267c21a3ede971c60fff554f1..0000000000000000000000000000000000000000
--- a/resources/static/lib/vepbundle.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../node_modules/jwcrypto/vepbundle.js
\ No newline at end of file
diff --git a/resources/static/shared/browserid.js b/resources/static/shared/browserid.js
index 34481fb5c7a0a74fff17cce319daf5134d49d546..5f662387697380a5bbeb9639d4d2260842fa1be3 100644
--- a/resources/static/shared/browserid.js
+++ b/resources/static/shared/browserid.js
@@ -9,7 +9,11 @@
 
   // Define some constants.
   _.extend(window.BrowserID, {
-    // always use 1024 DSA keys - see issue #1293
+    // always use 1024/160 DSA keys - see issue #1293
+    // this used to be called keysize 128, but that made
+    // no sense since no component of this is 128 bits
+    // so making this 160 as per DSA 1024/160
+    // EXCEPT, for backwards compatibility this is still 128 for now
     KEY_LENGTH: 128
   });
 }());
diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js
index 45f422fbe7aa32b59fddc09e9cdd636171efd798..b56ef5684c9e539ba1b9ef0069fe15f51b4df948 100644
--- a/resources/static/shared/network.js
+++ b/resources/static/shared/network.js
@@ -47,7 +47,8 @@ BrowserID.Network = (function() {
     // seed the PRNG
     // FIXME: properly abstract this out, probably by exposing a jwcrypto
     // interface for randomness
-    require("./libs/all").sjcl.random.addEntropy(result.random_seed);
+    // require("./libs/all").sjcl.random.addEntropy(result.random_seed);
+    // FIXME: this wasn't doing anything for real, so commenting this out for now
   }
 
   function withContext(cb, onFailure) {
diff --git a/resources/static/shared/provisioning.js b/resources/static/shared/provisioning.js
index 4eba6b1561caf66f590a4b528f72de6335344439..c0fc73410f5bcc6b137a50965ee3efd6106c96c3 100644
--- a/resources/static/shared/provisioning.js
+++ b/resources/static/shared/provisioning.js
@@ -6,7 +6,7 @@
 BrowserID.Provisioning = (function() {
   "use strict";
 
-  var jwk = require("./jwk");
+  var jwcrypto = require("./lib/jwcrypto");
 
   var Provisioning = function(args, successCB, failureCB) {
     function tearDown() {
@@ -69,8 +69,10 @@ BrowserID.Provisioning = (function() {
     });
 
     chan.bind('genKeyPair', function(trans, s) {
-      keypair = jwk.KeyPair.generate("DS", BrowserID.KEY_LENGTH);
-      return keypair.publicKey.toSimpleObject();
+      trans.delayReturn(true);
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: BrowserID.KEY_LENGTH}, function(err, keypair) {
+        trans.complete(keypair.publicKey.toSimpleObject());
+      });
     });
 
     chan.bind('raiseProvisioningFailure', function(trans, s) {
diff --git a/resources/static/shared/storage.js b/resources/static/shared/storage.js
index 2233ea73c84121624db6fe6cc10b204f0b0ccb87..0447082ac25d516a7d4fd8a0c6398ac66e23deaa 100644
--- a/resources/static/shared/storage.js
+++ b/resources/static/shared/storage.js
@@ -5,7 +5,7 @@
 BrowserID.Storage = (function() {
   "use strict";
 
-  var jwk,
+  var jwcrypto,
       ONE_DAY_IN_MS = (1000 * 60 * 60 * 24),
       storage;
 
@@ -26,8 +26,8 @@ BrowserID.Storage = (function() {
   }
 
   function prepareDeps() {
-    if (!jwk) {
-      jwk = require("./jwk");
+    if (!jwcrypto) {
+      jwcrypto = require("./jwcrypto");
     }
   }
 
@@ -125,9 +125,10 @@ BrowserID.Storage = (function() {
     storage.tempKeypair = null;
     if (raw_kp) {
       prepareDeps();
-      var kp = new jwk.KeyPair();
-      kp.publicKey = jwk.PublicKey.fromSimpleObject(raw_kp.publicKey);
-      kp.secretKey = jwk.SecretKey.fromSimpleObject(raw_kp.secretKey);
+
+      var kp = {};
+      kp.publicKey = jwcrypto.loadPublicKeyFromObject(raw_kp.publicKey);
+      kp.secretKey = jwcrypto.loadSecretKeyFromObject(raw_kp.secretKey);
       return kp;
     } else {
       return null;
diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js
index fb95b25567fa639e3bee1fe83d68ea4cd919600f..5bf0599972680e19aa29b35da6bd99503feaa3fa 100644
--- a/resources/static/shared/user.js
+++ b/resources/static/shared/user.js
@@ -7,7 +7,7 @@
 BrowserID.User = (function() {
   "use strict";
 
-  var jwk, jwt, vep, jwcert, origin,
+  var jwcrypto, origin,
       bid = BrowserID,
       network = bid.Network,
       storage = bid.Storage,
@@ -19,11 +19,8 @@ BrowserID.User = (function() {
       registrationComplete = false;
 
   function prepareDeps() {
-    if (!jwk) {
-      jwk= require("./jwk");
-      jwt = require("./jwt");
-      vep = require("./vep");
-      jwcert = require("./jwcert");
+    if (!jwcrypto) {
+      jwcrypto= require("./lib/jwcrypto");
     }
   }
 
@@ -36,14 +33,14 @@ BrowserID.User = (function() {
         // if the certificate expires in less that 5 minutes from now.
         function isExpired(cert) {
           // if it expires in less than 2 minutes, it's too old to use.
-          var diff = cert.expires.valueOf() - serverTime.valueOf();
+          var diff = cert.payload.exp.valueOf() - serverTime.valueOf();
           if (diff < (60 * 2 * 1000)) {
             return true;
           }
 
           // or if it was issued before the last time the domain key
           // was updated, it's invalid
-          if (!cert.issued_at || cert.issued_at < creationTime) {
+          if (!cert.payload.iat || cert.payload.iat < creationTime) {
             return true;
           }
 
@@ -55,7 +52,7 @@ BrowserID.User = (function() {
         prepareDeps();
         _(emails).each(function(email_obj, email_address) {
           try {
-            email_obj.pub = jwk.PublicKey.fromSimpleObject(email_obj.pub);
+            email_obj.pub = jwcrypto.loadPublicKeyFromObject(email_obj.pub);
           } catch (x) {
             storage.invalidateEmail(email_address);
             return;
@@ -67,8 +64,7 @@ BrowserID.User = (function() {
           } else {
             try {
               // parse the cert
-              var cert = new jwcert.JWCert();
-              cert.parse(emails[email_address].cert);
+              var cert = jwcrypto.extractComponents(emails[email_address].cert);
 
               // check if this certificate is still valid.
               if (isExpired(cert)) {
@@ -917,10 +913,9 @@ BrowserID.User = (function() {
      */
     syncEmailKeypair: function(email, onComplete, onFailure) {
       prepareDeps();
-      var keypair = jwk.KeyPair.generate("DS", bid.KEY_LENGTH);
-      setTimeout(function() {
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: bid.KEY_LENGTH}, function(err, keypair) {
         certifyEmailKeypair(email, keypair, onComplete, onFailure);
-      }, 0);
+      });
     },
 
 
@@ -946,22 +941,20 @@ BrowserID.User = (function() {
 
         function createAssertion(idInfo) {
           network.serverTime(function(serverTime) {
-            var sk = jwk.SecretKey.fromSimpleObject(idInfo.priv);
-
-            // yield!
-            setTimeout(function() {
-              // assertions are valid for 2 minutes
-              var expirationMS = serverTime.getTime() + (2 * 60 * 1000);
-              var expirationDate = new Date(expirationMS);
-              var tok = new jwt.JWT(null, expirationDate, audience);
-
-              // yield!
-              setTimeout(function() {
-                assertion = vep.bundleCertsAndAssertion([idInfo.cert], tok.sign(sk), true);
+            var sk = jwcrypto.loadSecretKeyFromObject(idInfo.priv);
+
+            // assertions are valid for 2 minutes
+            var expirationMS = serverTime.getTime() + (2 * 60 * 1000);
+            var expirationDate = new Date(expirationMS);
+            
+            jwcrypto.assertion.sign(
+              {}, {audience: audience, expiresAt: expirationDate},
+              sk,
+              function(err, signedAssertion) {
+                assertion = jwcrypto.cert.bundle([idInfo.cert], signedAssertion);
                 storage.site.set(audience, "email", email);
                 complete(assertion);
-              }, 0);
-            }, 0);
+              });
           }, onFailure);
         }
 
diff --git a/resources/static/test/cases/shared/user.js b/resources/static/test/cases/shared/user.js
index 455736004a20fc4b675c16c70b135ba6d6b5fc01..c44fe0cd719b806924b091a621d1f2a0e0af5cfa 100644
--- a/resources/static/test/cases/shared/user.js
+++ b/resources/static/test/cases/shared/user.js
@@ -3,10 +3,7 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-var jwk = require("./jwk");
-var jwt = require("./jwt");
-var jwcert = require("./jwcert");
-var vep = require("./vep");
+var jwcrypto = require("./lib/jwcrypto");
 
 (function() {
   var bid = BrowserID,
@@ -33,20 +30,18 @@ var vep = require("./vep");
     // Decode the assertion to a bundle.
     // var bundle = JSON.parse(window.atob(assertion));
     // WOW, ^^ was assuming a specific format, let's fix that
-    var bundle = vep.unbundleCertsAndAssertion(assertion);
+    var bundle = jwcrypto.cert.unbundle(assertion);
 
     // Make sure both parts of the bundle exist
-    ok(bundle.certificates && bundle.certificates.length, "we have an array like object for the certificates");
-    equal(typeof bundle.assertion, "string");
+    ok(bundle.certs && bundle.certs.length, "we have an array like object for the certificates");
+    equal(typeof bundle.signedAssertion, "string");
 
     // Decode the assertion itself
-    var tok = new jwt.JWT();
-    tok.parse(bundle.assertion);
-
+    var components = jwcrypto.extractComponents(bundle.signedAssertion);
 
     // Check for parts of the assertion
-    equal(tok.audience, testOrigin, "correct audience");
-    var expires = tok.expires.getTime();
+    equal(components.payload.aud, testOrigin, "correct audience");
+    var expires = parseInt(components.payload.exp);
     ok(typeof expires === "number" && !isNaN(expires), "expiration date is valid");
 
     // this should be based on server time, not local time.
@@ -58,9 +53,9 @@ var vep = require("./vep");
       var diff = Math.abs(expires - nowPlus2Mins);
       ok(diff < 5000, "expiration date must be within 5 seconds of 2 minutes from now: " + diff);
 
-      equal(typeof tok.cryptoSegment, "string", "cryptoSegment exists");
-      equal(typeof tok.headerSegment, "string", "headerSegment exists");
-      equal(typeof tok.payloadSegment, "string", "payloadSegment exists");
+      equal(typeof components.cryptoSegment, "string", "cryptoSegment exists");
+      equal(typeof components.headerSegment, "string", "headerSegment exists");
+      equal(typeof components.payloadSegment, "string", "payloadSegment exists");
 
       if(cb) cb();
     });
@@ -155,15 +150,15 @@ var vep = require("./vep");
 
   asyncTest("createPrimaryUser with primary, user verified with primary - expect 'primary.verified'", function() {
     xhr.useResult("primary");
-    provisioning.setStatus(provisioning.AUTHENTICATED);
-
-    lib.createPrimaryUser({email: "unregistered@testuser.com"}, function(status) {
-      equal(status, "primary.verified", "primary user is already verified, correct status");
-      network.checkAuth(function(authenticated) {
-        equal(authenticated, "assertion", "after provisioning user, user should be automatically authenticated to BrowserID");
-        start();
-      });
-    }, testHelpers.unexpectedXHRFailure);
+    provisioning.setStatus(provisioning.AUTHENTICATED, function() {
+      lib.createPrimaryUser({email: "unregistered@testuser.com"}, function(status) {
+        equal(status, "primary.verified", "primary user is already verified, correct status");
+        network.checkAuth(function(authenticated) {
+          equal(authenticated, "assertion", "after provisioning user, user should be automatically authenticated to BrowserID");
+          start();
+        });
+      }, testHelpers.unexpectedXHRFailure);
+    });
   });
 
   asyncTest("createPrimaryUser with primary, user must authenticate with primary - expect 'primary.verify'", function() {
diff --git a/resources/static/test/mocks/provisioning.js b/resources/static/test/mocks/provisioning.js
index 1b3dc6b48f25c3b558f5c90e725ef5c58f5172a4..cdee24b1306b946483456f8fc2fad4e6ca16de3a 100644
--- a/resources/static/test/mocks/provisioning.js
+++ b/resources/static/test/mocks/provisioning.js
@@ -11,7 +11,7 @@ BrowserID.Mocks.Provisioning = (function() {
       // this cert is meaningless, but it has the right format
       cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik",
       failure,
-      jwk = require("./jwk"),
+      jwcrypto = require("./lib/jwcrypto"),
       status;
 
   function Provisioning(info, onsuccess, onfailure) {
@@ -21,20 +21,25 @@ BrowserID.Mocks.Provisioning = (function() {
     else onfailure(failure);
   }
 
-  Provisioning.setStatus = function(newStatus) {
+  Provisioning.setStatus = function(newStatus, cb) {
     failure = null;
 
+    status = newStatus;
+
     if(newStatus === Provisioning.NOT_AUTHENTICATED) {
       failure = {
         code: "primaryError",
         msg: "user is not authenticated as target user"
       };
+      if (cb) cb();
     }
     else if(newStatus === Provisioning.AUTHENTICATED) {
-      keypair = keypair || jwk.KeyPair.generate("DS", 256);
+      if (!keypair)
+        jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, function(err, kp) {
+          keypair = kp;
+          if (cb) cb();
+        });
     }
-
-    status = newStatus;
   };
 
   Provisioning.NOT_AUTHENTICATED = "not_authenticated";
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index cde5603356951b9e18ed8894031b9178c5bbf88f..31a0236d8349d220175a77df873877c3e6b37734 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -69,7 +69,7 @@
     <script src="/i18n/en_US/client.json"></script>
     <script src="/shared/javascript-extensions.js"></script>
     <script src="/shared/gettext.js"></script>
-    <script src="/lib/vepbundle.js"></script>
+    <script src="/lib/bidbundle-min.js"></script>
     <script src="http://testmob.org/include.js"></script>
     <script src="/shared/browserid.js"></script>
     <script src="/lib/dom-jquery.js"></script>
diff --git a/scripts/serve_example_primary.js b/scripts/serve_example_primary.js
index 0a5a4617bc34859dceeb2c753293ff9c47266a2a..e7198487ff2202c6158e6f43f0f92b6423239428 100755
--- a/scripts/serve_example_primary.js
+++ b/scripts/serve_example_primary.js
@@ -12,8 +12,10 @@ urlparse = require('urlparse'),
 postprocess = require('postprocess'),
 querystring = require('querystring'),
 sessions = require('connect-cookie-session'),
-jwk = require('jwcrypto/jwk'),
-jwcert = require('jwcrypto/jwcert');
+jwcrypto = require("jwcrypto");
+
+// alg
+require("jwcrypto/lib/algs/rs");
 
 var exampleServer = express.createServer();
 
@@ -75,9 +77,9 @@ exampleServer.get("/api/logout", function (req, res) {
   return res.json(null);
 });
 
-var _privKey = jwk.SecretKey.fromSimpleObject(
-  JSON.parse(require('fs').readFileSync(
-    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
+var _privKey = jwcrypto.loadSecretKey(
+  require('fs').readFileSync(
+    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey')));
 
 exampleServer.post("/api/cert_key", function (req, res) {
   var user = req.session.user;
@@ -85,10 +87,11 @@ exampleServer.post("/api/cert_key", function (req, res) {
   var domain = process.env['SHIMMED_DOMAIN'];
 
   var expiration = new Date();
-  var pubkey = jwk.PublicKey.fromSimpleObject(req.body.pubkey);
+  var pubkey = jwcrypto.loadPublicKeyFromObject(req.body.pubkey);
   expiration.setTime(new Date().valueOf() + req.body.duration * 1000);
-  var cert = new jwcert.JWCert(domain, expiration, new Date(),
-                               pubkey, {email: user + "@" + domain}).sign(_privKey);
+  jwcrypto.cert.sign(pubkey, {email: user + "@" + domain}, {issuer: domain, expiresAt: expiration, issuedAt: new Date()}, _privkey, function(err, cert) {
+    res.json({ cert: cert });
+  });
 
   res.json({ cert: cert });
 });
diff --git a/tests/add-email-with-assertion-test.js b/tests/add-email-with-assertion-test.js
index 19a58f0ae405b8c595101759dea3e47c14d5997e..e6e0688ea47dfd29fd26e39bc96bed566d3622fc 100755
--- a/tests/add-email-with-assertion-test.js
+++ b/tests/add-email-with-assertion-test.js
@@ -13,16 +13,17 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 db = require('../lib/db.js'),
 config = require('../lib/configuration.js'),
-jwk = require('jwcrypto/jwk.js'),
-jwt = require('jwcrypto/jwt.js'),
-vep = require('jwcrypto/vep.js'),
-jwcert = require('jwcrypto/jwcert.js'),
+jwcrypto = require('jwcrypto'),
 http = require('http'),
 querystring = require('querystring'),
 path = require("path");
 
 var suite = vows.describe('auth-with-assertion');
 
+// algs
+require("jwcrypto/lib/algs/ds");
+require("jwcrypto/lib/algs/rs");
+
 // disable vows (often flakey?) async error behavior
 suite.options.error = false;
 
@@ -43,9 +44,9 @@ var g_keypair, g_cert;
 suite.addBatch({
   "generating a keypair": {
     topic: function() {
-      return jwk.KeyPair.generate("DS", 256)
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, this.callback);
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isObject(r);
       assert.isObject(r.publicKey);
       assert.isObject(r.secretKey);
@@ -56,9 +57,8 @@ suite.addBatch({
 
 // for this trick we'll need the "secret" key of our built in
 // primary
-var g_privKey = jwk.SecretKey.fromSimpleObject(
-  JSON.parse(require('fs').readFileSync(
-    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
+var g_privKey = jwcrypto.loadSecretKey(require('fs').readFileSync(
+    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey')));
 
 
 suite.addBatch({
@@ -68,13 +68,12 @@ suite.addBatch({
 
       var expiration = new Date();
       expiration.setTime(new Date().valueOf() + 60 * 60 * 1000);
-      g_cert = new jwcert.JWCert(TEST_DOMAIN, expiration, new Date(),
-                                 g_keypair.publicKey, {email: TEST_EMAIL}).sign(g_privKey);
-      return g_cert;
+      jwcrypto.cert.sign(g_keypair.publicKey, {email: TEST_EMAIL}, {issuer: TEST_DOMAIN, expiresAt: expiration, issuedAt: new Date()}, null, g_privKey, this.callback);
     },
-    "works swimmingly": function(cert, err) {
+    "works swimmingly": function(err, cert) {
       assert.isString(cert);
       assert.lengthOf(cert.split('.'), 3);
+      g_cert = cert;
     }
   }
 });
@@ -83,11 +82,21 @@ suite.addBatch({
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
+      var self = this;
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([g_cert], tok.sign(g_keypair.secretKey));
+      jwcrypto.assertion.sign(
+        {},
+        {audience: TEST_ORIGIN,
+         issuer: TEST_DOMAIN,
+         expiresAt: expirationDate},
+        g_keypair.secretKey,
+        function(err, signedAssertion) {
+          self.callback(err, jwcrypto.cert.bundle([g_cert], signedAssertion));
+        });
+      // var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
+      //return vep.bundleCertsAndAssertion([g_cert], tok.sign(g_keypair.secretKey));
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isString(r);
       g_assertion = r;
     }
diff --git a/tests/auth-with-assertion-test.js b/tests/auth-with-assertion-test.js
index eda9f1768df9931dbebd5c39817f1ef2fbd997ed..94a0e9ff1592254241627f123add37974b0077e0 100755
--- a/tests/auth-with-assertion-test.js
+++ b/tests/auth-with-assertion-test.js
@@ -36,17 +36,28 @@ var primaryUser = new primary({
   domain: TEST_DOMAIN
 });
 
+suite.addBatch({
+  "set things up": {
+    topic: function() {
+      primaryUser.setup(this.callback);
+    },
+    "works": function() {
+      // nothing to do here
+    }
+  }
+});
+
 // now let's generate an assertion using this user
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(TEST_ORIGIN);
+      primaryUser.getAssertion(TEST_ORIGIN, this.callback);
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion succeeds": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: true
diff --git a/tests/ca-test.js b/tests/ca-test.js
index ecce1e0897dc7e4c7103bde217444a26d2a4ade7..43e1ccefc0aeac5ade5e2ae31a8424f8c6871e84 100755
--- a/tests/ca-test.js
+++ b/tests/ca-test.js
@@ -12,45 +12,62 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 email = require('../lib/email.js'),
 ca = require('../lib/keysigner/ca.js'),
-jwcert = require('jwcrypto/jwcert'),
-jwk = require('jwcrypto/jwk'),
-jws = require('jwcrypto/jws');
+jwcrypto = require('jwcrypto'),
+cert = jwcrypto.cert,
+assertion = jwcrypto.assertion;
+
+// algorithms
+require("jwcrypto/lib/algs/rs");
 
 var suite = vows.describe('ca');
 
 // disable vows (often flakey?) async error behavior
 suite.options.error = false;
 
-// generate a public key
-var kp = jwk.KeyPair.generate("RS",64);
-
 var email_addr = "foo@foo.com";
+var issuer = "127.0.0.1";
+
+var kp = null;
 
 // certify a key
 suite.addBatch({
-  "certify a public key": {
+  "generate a keypair": {
     topic: function() {
-      var expiration = new Date();
-      expiration.setTime(new Date().valueOf() + 5000);
-      return ca.certify('127.0.0.1', email_addr, kp.publicKey, expiration);
+      // generate a public key
+      jwcrypto.generateKeypair({algorithm: "RS", keysize: 64}, this.callback);
     },
-    "parses" : function(cert_raw, err) {
-      var cert = ca.parseCert(cert_raw);
-      assert.notEqual(cert, null);
+    "got a keypair": function(err, keypair) {
+      assert.isNull(err);
+      assert.isObject(keypair);
+      kp = keypair;
     },
-    "verifies": function(cert_raw, err) {
-      // FIXME we might want to turn this into a true async test
-      // rather than one that is assumed to be synchronous although
-      // it has an async structure
-      ca.verifyChain('127.0.0.1', [cert_raw], function(pk) {
-        assert.isTrue(kp.publicKey.equals(pk));
-      });
+    "certify a public key": {
+      topic: function() {
+        var expiration = new Date();
+        expiration.setTime(new Date().valueOf() + 5000);
+        ca.certify(issuer, email_addr, kp.publicKey, expiration, this.callback);
+      },
+      "does not error out": function(err, cert_raw) {
+        assert.isNull(err);
+        assert.isNotNull(cert_raw);
+      },
+      "looks ok" : function(err, cert_raw) {
+        assert.equal(cert_raw.split(".").length, 3);
+      },
+      "upon verification": {
+        topic: function(err, cert_raw) {
+          ca.verifyChain(issuer, [cert_raw], this.callback);
+        },
+        "verifies": function(err, pk, principal) {
+          assert.isNull(err);
+          assert.isTrue(kp.publicKey.equals(pk));
+          assert.equal(principal.email, email_addr);
+        }
+      }
     }
-  },
-  "certify a chain of keys": {
   }
 });
-
+               
 // run or export the suite.
 if (process.argv[1] === __filename) suite.run();
 else suite.export(module);
diff --git a/tests/cert-emails-test.js b/tests/cert-emails-test.js
index a8f746f568d1fd78242098618fe9c393984a77e2..dab8fa36775d5adcbbc4708da14832e565def5f8 100755
--- a/tests/cert-emails-test.js
+++ b/tests/cert-emails-test.js
@@ -12,8 +12,7 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 email = require('../lib/email.js'),
 ca = require('../lib/keysigner/ca.js'),
-jwk = require('jwcrypto/jwk'),
-jwt = require('jwcrypto/jwt');
+jwcrypto = require("jwcrypto");
 
 var suite = vows.describe('cert-emails');
 
@@ -70,90 +69,118 @@ var cert_key_url = "/wsapi/cert_key";
 
 // generate a keypair, we'll use this to sign assertions, as if
 // this keypair is stored in the browser localStorage
-var kp = jwk.KeyPair.generate("RS",64);
+var kp;
 
 suite.addBatch({
-  "check the public key": {
-    topic: wsapi.get("/pk"),
-    "returns a 200": function(err, r) {
-      assert.strictEqual(r.code, 200);
+  "generate a keypair": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm: "RS", keysize: 64}, this.callback);
     },
-    "returns the right public key": function(err, r) {
-      var pk = jwk.PublicKey.deserialize(r.body);
-      assert.ok(pk);
-    }
-  },
-  "cert key with no parameters": {
-    topic: wsapi.post(cert_key_url, {}),
-    "fails with HTTP 400" : function(err, r) {
-      assert.strictEqual(r.code, 400);
-    }
-  },
-  "cert key invoked with just an email": {
-    topic: wsapi.post(cert_key_url, { email: 'syncer@somehost.com' }),
-    "returns a 400" : function(err, r) {
-      assert.strictEqual(r.code, 400);
-    }
-  },
-  "cert key invoked with proper argument": {
-    topic: wsapi.post(cert_key_url, {
-      email: 'syncer@somehost.com',
-      pubkey: kp.publicKey.serialize(),
-      ephemeral: false
-    }),
-    "returns a response with a proper content-type" : function(err, r) {
-      assert.strictEqual(r.code, 200);
+    "works": function(err, keypair) {
+      assert.isNull(err);
+      assert.isObject(keypair);
+      kp = keypair;
+    },
+    "check the public key": {
+      topic: function() {
+        wsapi.get("/pk").call(this);
+      },
+      "returns a 200": function(err, r) {
+        assert.strictEqual(r.code, 200);
+      },
+      "returns the right public key": function(err, r) {
+        var pk = jwcrypto.loadPublicKey(r.body);
+        assert.ok(pk);
+      }
     },
-    "returns a proper cert": function(err, r) {
-      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
-        assert.isTrue(kp.publicKey.equals(pk));
-      });
+    "cert key with no parameters": {
+      topic: function() {
+        wsapi.post(cert_key_url, {}).call(this);
+      },
+      "fails with HTTP 400" : function(err, r) {
+        assert.strictEqual(r.code, 400);
+      }
+    },
+    "cert key invoked with just an email": {
+      topic: function() {
+        wsapi.post(cert_key_url, { email: 'syncer@somehost.com' }).call(this);
+      },
+      "returns a 400" : function(err, r) {
+        assert.strictEqual(r.code, 400);
+      }
     },
-    "generate an assertion": {
-      topic: function(err, r) {
-        var serializedCert = r.body.toString();
-        var expiration = new Date(new Date().getTime() + (2 * 60 * 1000));
-        var assertion = new jwt.JWT(null, expiration, "rp.com");
-        var full_assertion = {
-          certificates: [serializedCert],
-          assertion: assertion.sign(kp.secretKey)
-        };
-
-        return full_assertion;
+    "cert key invoked with proper argument": {
+      topic: function() {
+        wsapi.post(cert_key_url, {
+          email: 'syncer@somehost.com',
+          pubkey: kp.publicKey.serialize(),
+          ephemeral: false
+        }).call(this);
+      },
+      "returns a response with a proper content-type" : function(err, r) {
+        assert.strictEqual(r.code, 200);
       },
-      "full assertion looks good": function(full_assertion) {
-        assert.equal(full_assertion.certificates[0].split(".").length, 3);
-        assert.equal(full_assertion.assertion.split(".").length, 3);
+      "returns a proper cert": {
+        topic: function(err, r) {
+          ca.verifyChain('127.0.0.1', [r.body], this.callback);
+        },
+        "that verifies": function(err, pk, principal) {
+          assert.isNull(err);
+          assert.equal(principal.email, 'syncer@somehost.com');
+          assert.equal(kp.publicKey.serialize(), pk.serialize());
+        }
       },
-      "assertion verifies": {
-        topic: function(full_assertion) {
-          var cb = this.callback;
-          // extract public key at the tail of the chain
-          ca.verifyChain('127.0.0.1', full_assertion.certificates, function(pk) {
-            if (!pk)
-              cb(false);
-
-            var assertion = new jwt.JWT();
-            assertion.parse(full_assertion.assertion);
-            cb(assertion.verify(pk));
+      "generate an assertion": {
+        topic: function(err, r) {
+          var serializedCert = r.body.toString();
+          var expiration = new Date(new Date().getTime() + (2 * 60 * 1000));
+
+          var self = this;
+          jwcrypto.assertion.sign({}, {issuer: "127.0.0.1", expiresAt: expiration, issuedAt: new Date()}, kp.secretKey, function(err, signedObject) {
+            if (err) return self.callback(err);
+            
+            self.callback(null, {
+              certificates: [serializedCert],
+              assertion: signedObject
+            });
           });
         },
-        "verifies": function(result, err) {
-          assert.isTrue(result);
+        "full bundle looks good": function(err, certs_and_assertion) {
+          assert.isNull(err);
+          assert.equal(certs_and_assertion.certificates[0].split(".").length, 3);
+          assert.equal(certs_and_assertion.assertion.split(".").length, 3);
+        },
+        "assertion verifies": {
+          topic: function(err, certs_and_assertion) {
+            // bundle and verify
+            var bundle = jwcrypto.cert.bundle(certs_and_assertion.certificates, certs_and_assertion.assertion);
+            
+            var cb = this.callback;
+            // extract public key at the tail of the chain
+            ca.verifyBundle('127.0.0.1', bundle, this.callback);
+          },
+          "verifies": function(err, certParamsArray, payload, assertionParams) {
+            assert.isNull(err);
+            assert.isArray(certParamsArray);
+            assert.isObject(payload);
+            assert.isObject(assertionParams);
+          }
         }
       }
+    },
+    "cert key invoked proper arguments but incorrect email address": {
+      topic: function() {
+        wsapi.post(cert_key_url, {
+          email: 'syncer2@somehost.com',
+          pubkey: kp.publicKey.serialize(),
+          ephemeral: false
+        }).call(this);
+      },
+      "returns a response with a proper error content-type" : function(err, r) {
+        assert.strictEqual(r.code, 400);
+      }
     }
   },
-  "cert key invoked proper arguments but incorrect email address": {
-    topic: wsapi.post(cert_key_url, {
-      email: 'syncer2@somehost.com',
-      pubkey: kp.publicKey.serialize(),
-      ephemeral: false
-    }),
-    "returns a response with a proper error content-type" : function(err, r) {
-      assert.strictEqual(r.code, 400);
-    }
-  }
 });
 
 start_stop.addShutdownBatches(suite);
diff --git a/tests/conformance-test.js b/tests/conformance-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..205cb628990567b2092ca932885bc68dfb26fbf8
--- /dev/null
+++ b/tests/conformance-test.js
@@ -0,0 +1,299 @@
+#!/usr/bin/env node
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const
+vows = require('vows'),
+assert = require('assert'),
+path = require('path'),
+jwcrypto = require('jwcrypto');
+
+require("jwcrypto/lib/algs/rs");
+require("jwcrypto/lib/algs/ds");
+
+var suite = vows.describe('Conformance Tests');
+
+var domainKeypair;
+var userKeypair;
+
+suite.addBatch({
+  "generate a keypair": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm: "RS", keysize: 256}, this.callback);
+    },
+    "works" : function(err, kp) {
+      assert.isNull(err);
+      domainKeypair = kp;
+    }
+  }
+});
+
+suite.addBatch({
+  "generate a keypair": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: 128}, this.callback);
+    },
+    "works" : function(err, kp) {
+      assert.isNull(err);
+      userKeypair = kp;
+    }
+  }
+});
+
+/*
+ * some functions to do b64url encoding/decoding
+ */
+function base64urlencode(arg) {
+  var s = new Buffer(arg).toString('base64'); // window.btoa(arg);
+  s = s.split('=')[0]; // Remove any trailing '='s
+  s = s.replace(/\+/g, '-'); // 62nd char of encoding
+  s = s.replace(/\//g, '_'); // 63rd char of encoding
+  // TODO optimize this; we can do much better
+  return s;
+}
+
+function base64urldecode(arg) {
+  var s = arg;
+  s = s.replace(/-/g, '+'); // 62nd char of encoding
+  s = s.replace(/_/g, '/'); // 63rd char of encoding
+  switch (s.length % 4) // Pad with trailing '='s
+  {
+  case 0: break; // No pad chars in this case
+  case 2: s += "=="; break; // Two pad chars
+  case 3: s += "="; break; // One pad char
+  default: throw new InputException("Illegal base64url string!");
+  }
+  return new Buffer(s,'base64').toString('ascii'); // window.atob(s); // Standard base64 decoder
+}
+
+var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz";
+function int2char(n) { return BI_RM.charAt(n); }
+
+var b64urlmap="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+function b64urltohex(s) {
+  var ret = "";
+  var i;
+  var k = 0; // b64 state, 0-3
+  var slop;
+  for(i = 0; i < s.length; ++i) {
+    var v = b64urlmap.indexOf(s.charAt(i));
+    if(v < 0) continue;
+    if(k == 0) {
+      ret += int2char(v >> 2);
+      slop = v & 3;
+      k = 1;
+    }
+    else if(k == 1) {
+      ret += int2char((slop << 2) | (v >> 4));
+      slop = v & 0xf;
+      k = 2;
+    }
+    else if(k == 2) {
+      ret += int2char(slop);
+      ret += int2char(v >> 2);
+      slop = v & 3;
+      k = 3;
+    }
+    else {
+      ret += int2char((slop << 2) | (v >> 4));
+      ret += int2char(v & 0xf);
+      k = 0;
+    }
+  }
+  if(k == 1)
+    ret += int2char(slop << 2);
+
+  // initial 0? only one for now
+  if (ret[0] == '0')
+    return ret.substring(1);
+  else
+    return ret;
+}
+
+// this function (jwcrypto.extractComponents) is copied here so that
+// a change in the library doesn't mess up these conformance tests.
+function extractComponents(signedObject) {
+  if (typeof(signedObject) != 'string')
+    throw "malformed signature " + typeof(signedObject);
+  
+  var parts = signedObject.split(".");
+  if (parts.length != 3) {
+    throw "signed object must have three parts, this one has " + parts.length;
+  }    
+  
+  var headerSegment = parts[0];
+  var payloadSegment = parts[1];
+  var cryptoSegment = parts[2];  
+
+  // we verify based on the actual string
+  // FIXME: we should validate that the header contains only proper fields
+  var header = JSON.parse(base64urldecode(headerSegment));
+  var payload = JSON.parse(base64urldecode(payloadSegment));
+  var signature = b64urltohex(cryptoSegment);
+
+  return {header: header,
+          payload: payload,
+          signature: signature,
+          headerSegment: headerSegment,
+          payloadSegment: payloadSegment,
+          cryptoSegment: cryptoSegment};
+};
+
+const AUDIENCE = "http://foobar.com";
+const ISSUER = "issuer.com";
+const EMAIL = "john@example.com";
+
+var now = new Date();
+var in_a_minute = new Date(new Date().valueOf() + 60000);
+
+suite.addBatch({
+  "sign an assertion": {
+    topic: function() {
+      jwcrypto.assertion.sign({}, {audience: AUDIENCE, expiresAt: in_a_minute},
+                              userKeypair.secretKey, this.callback);
+    },
+    "works" : function(err, signedObject) {
+      assert.isNull(err);
+    },
+    "has three part": function(err, signedObject) {
+      assert.equal(signedObject.split(".").length, 3);
+    },
+    "and then parsed": {
+      topic: function(signedObject) {
+        return extractComponents(signedObject);
+      },
+      "has proper header": function(components) {
+        assert.isObject(components.header);
+        assert.equal(components.header.alg, 'DS128');
+        assert.equal(Object.keys(components.header).length, 1);
+      },
+      "has proper payload": function(components) {
+        assert.isObject(components.payload);
+        assert.equal(components.payload.exp, in_a_minute.valueOf());
+        assert.equal(components.payload.aud, AUDIENCE);
+
+        // nothing else
+        assert.equal(Object.keys(components.payload).length, 2);
+      },
+      "has proper signature": function(components) {
+        assert.isString(components.signature);
+
+        // 160 bits for r and s, 320 bits together, 80 hex chars
+        // but because of encoding, leading 0s may have gotten removed
+        // likelihood of X zeros, 1/(2^(4X))
+        // let's allow for up to 5 zeros.
+        assert.ok(components.signature.length <= 80);
+        assert.ok(components.signature.length > 75);        
+      }
+    }
+  }
+});
+
+suite.addBatch({
+  "sign a cert": {
+    topic: function() {
+      jwcrypto.cert.sign(userKeypair.publicKey, {email: EMAIL},
+                         {issuedAt: now, issuer: ISSUER, expiresAt: in_a_minute},
+                         {},
+                         domainKeypair.secretKey, this.callback);
+    },
+    "works" : function(err, signedObject) {
+      assert.isNull(err);
+    },
+    "has three parts": function(err, signedObject) {
+      assert.equal(signedObject.split(".").length, 3);
+    },
+    "and then parsed": {
+      topic: function(signedObject) {
+        return extractComponents(signedObject);
+      },
+      "has proper header": function(components) {
+        assert.isObject(components.header);
+        assert.equal(components.header.alg, 'RS256');
+        assert.equal(Object.keys(components.header).length, 1);
+      },
+      "has proper payload": function(components) {
+        assert.isObject(components.payload);
+        assert.equal(components.payload.iss, ISSUER);
+        assert.equal(components.payload.exp, in_a_minute.valueOf());
+        assert.equal(components.payload.iat, now.valueOf());
+
+        assert.isObject(components.payload.principal);
+        assert.equal(components.payload.principal.email, EMAIL);
+        assert.equal(Object.keys(components.payload.principal).length, 1);
+
+        assert.equal(JSON.stringify(components.payload['public-key']), userKeypair.publicKey.serialize());
+
+        // nothing else
+        assert.equal(Object.keys(components.payload).length, 5);
+      },
+      "has proper signature": function(components) {
+        assert.isString(components.signature);
+
+        // 2048 bits = 512 hex chars, but could be less. Though very unlikely
+        // to be less than 32 bits less :)
+        assert.ok(480 < components.signature.length);
+        assert.ok(components.signature.length <= 512);
+      }
+    }
+  }
+});
+
+/*
+ * and the vectors
+ */
+
+var VECTORS = [
+  {
+    assertion: "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiIxMjcuMC4wLjEiLCJleHAiOjEzMzU1NjI2OTg3NjgsImlhdCI6MTMzNTU1OTA5ODc2OCwicHVibGljLWtleSI6eyJhbGdvcml0aG0iOiJEUyIsInkiOiIyN2Y2OTgzMWIzNzdlMmY1NzRiZGE5Njg1YWJmNTM5OTY1ZDAyNDI2Mjg0ZDZmYzViOWVkMjA0MzJmN2U5Yjg1YTFjMjJiMTQ2M2I0NmQwMzljMTIzOWJkZWI2NDc1ZDZjMDM0MWJlZmRiYzBjYjJmMjQ4MTUzYjRjMzFkZDMxNWFjZjFkZmY0ZWUwYmY2NGY4OTUyN2VlMTlmNTkxNTM3NWFjZTNkNTZjMWQ1NDUzY2FjNmRkMTE4NzU3NTI3MmRhYjBlZGQzMGYxYjRlOTI2Yzg3YTNlNGFjYWY2NmY5MmZlZDFhMDRhYjI3Y2NjNDkxM2FmZTI0ZGRjZjNmZTk4IiwicCI6ImZmNjAwNDgzZGI2YWJmYzViNDVlYWI3ODU5NGIzNTMzZDU1MGQ5ZjFiZjJhOTkyYTdhOGRhYTZkYzM0ZjgwNDVhZDRlNmUwYzQyOWQzMzRlZWVhYWVmZDdlMjNkNDgxMGJlMDBlNGNjMTQ5MmNiYTMyNWJhODFmZjJkNWE1YjMwNWE4ZDE3ZWIzYmY0YTA2YTM0OWQzOTJlMDBkMzI5NzQ0YTUxNzkzODAzNDRlODJhMThjNDc5MzM0MzhmODkxZTIyYWVlZjgxMmQ2OWM4Zjc1ZTMyNmNiNzBlYTAwMGMzZjc3NmRmZGJkNjA0NjM4YzJlZjcxN2ZjMjZkMDJlMTciLCJxIjoiZTIxZTA0ZjkxMWQxZWQ3OTkxMDA4ZWNhYWIzYmY3NzU5ODQzMDljMyIsImciOiJjNTJhNGEwZmYzYjdlNjFmZGYxODY3Y2U4NDEzODM2OWE2MTU0ZjRhZmE5Mjk2NmUzYzgyN2UyNWNmYTZjZjUwOGI5MGU1ZGU0MTllMTMzN2UwN2EyZTllMmEzY2Q1ZGVhNzA0ZDE3NWY4ZWJmNmFmMzk3ZDY5ZTExMGI5NmFmYjE3YzdhMDMyNTkzMjllNDgyOWIwZDAzYmJjNzg5NmIxNWI0YWRlNTNlMTMwODU4Y2MzNGQ5NjI2OWFhODkwNDFmNDA5MTM2YzcyNDJhMzg4OTVjOWQ1YmNjYWQ0ZjM4OWFmMWQ3YTRiZDEzOThiZDA3MmRmZmE4OTYyMzMzOTdhIn0sInByaW5jaXBhbCI6eyJlbWFpbCI6ImJlbkBhZGlkYS5uZXQifX0.MklRRWfQweUwYR2crhFU2EuLyUOZlpY4zJgg9LSWDF1MQIGJtNZAclB_tU4sNWfWyrHBa6ICXGfT9mMbkWwPIZC714clAkCMAQXiL2FhuzZSHlnYRO0_BFLO0LqtxIbwdGAQ0WvmaS5lPCgwHdoJbIHPVupebT1C-nUUu21pBoFI_8sPjzINwGBlE6K6WQQy0KbF2m0VDZY5EAYa4mh4o84xiABCoYZYSEeA9FIzmYRJEVrqYHjQeVucZdqkDDCTEK49nVIR4hi8Mm1EItYDn__HDydZORotzfOHuLmB9xyVgBX_tcKJ9mND7MQJVeOumhDAx9QyXtRUhPhKUTDNgA~eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzMzU1NTk0MTU3MzMsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MTAwMDEifQ.BBoFaSGq0UAYDi9vdbsoBegeJ7FHVDxzODiV8MD8pF0emOPp1i_Uzg",
+    root: {"algorithm":"RS","n":"13717766671510433111303151806101127171813773557424962001210686599690717644398501153133960329815327700526221729490916021955004415636643109524427762578738613915853895591332921269523141755077814022043323454871557827878869765578483437974192481801184235473918125161566266295979176194039841474030846700306142580608077665527626562098429368267997746767380874004089196208403356658867000112308693077043530239627194850786092251128137244380236693014852428390414510793421293487373711079360003639159681004539188014924495483277607084448583613608953997565952445532663265804891482606228128383798830560843667395414521699843061983900619","e":"65537"}
+  }
+];
+
+var assertion = VECTORS[0].assertion;
+var pk = jwcrypto.loadPublicKeyFromObject(VECTORS[0].root);
+var now = new Date();
+
+// times
+var timeOfCert = 1335562698768;
+var timeOfAssertion = 1335559415733;
+
+// a bit before both cert and assertion
+var timeThatShouldWork = new Date(Math.min(timeOfCert, timeOfAssertion) - 1000);
+
+suite.addBatch(
+  {
+    "verifying a test-vector assertion that is expired" : {
+      topic: function() {
+        jwcrypto.cert.verifyBundle(
+          assertion, now, function(issuer, next) {
+            process.nextTick(function() {next(null, pk);});
+          },
+          this.callback);
+      },
+      "fails appropriately": function(err, certParamsArray, payload, assertionParams) {
+        assert.equal(err, "assertion has expired");
+      }
+    }    
+});
+
+suite.addBatch(
+  {
+    "verifying a test-vector assertion with appropriate verif time" : {
+      topic: function() {
+        jwcrypto.cert.verifyBundle(
+          assertion, timeThatShouldWork, function(issuer, next) {
+            process.nextTick(function() {next(null, pk);});
+          },
+          this.callback);
+      },
+      "succeed": function(err, certParamsArray, payload, assertionParams) {
+        assert.isNull(err);
+      }
+    }    
+});
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/tests/cookie-session-security-test.js b/tests/cookie-session-security-test.js
index dd3466090546e782684210debf352d2e15e024e4..b45d4d6a6588636497f9a2da2603b5f0e0b4d0e8 100755
--- a/tests/cookie-session-security-test.js
+++ b/tests/cookie-session-security-test.js
@@ -10,10 +10,7 @@ const assert = require('assert'),
 vows = require('vows'),
 start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
-email = require('../lib/email.js'),
-jwcert = require('jwcrypto/jwcert'),
-jwk = require('jwcrypto/jwk'),
-jws = require('jwcrypto/jws');
+email = require('../lib/email.js');
 
 var suite = vows.describe('cookie-session-security');
 
diff --git a/tests/lib/primary.js b/tests/lib/primary.js
index e9825a0c90c71ffd577a774a29bdf9f614f8771e..1ff1b4d779c9d3cf8a3cc417aeca27f55c984b17 100644
--- a/tests/lib/primary.js
+++ b/tests/lib/primary.js
@@ -3,32 +3,53 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const
-jwk = require('jwcrypto/jwk.js'),
-jwt = require('jwcrypto/jwt.js'),
-vep = require('jwcrypto/vep.js'),
-jwcert = require('jwcrypto/jwcert.js'),
+jwcrypto = require('jwcrypto'),
 path = require("path");
 
-// the private secret of our built-in primary
-const g_privKey = jwk.SecretKey.fromSimpleObject(
-  JSON.parse(require('fs').readFileSync(
-    path.join(__dirname, '..', '..', 'example', 'primary', 'sample.privatekey'))));
+require("jwcrypto/lib/algs/rs");
+require("jwcrypto/lib/algs/ds");
 
+// the private secret of our built-in primary
+const g_privKey = jwcrypto.loadSecretKey(
+  require('fs').readFileSync(
+    path.join(__dirname, '..', '..', 'example', 'primary', 'sample.privatekey')));
 
 function User(options) {
+  this.options = options;
+}
+
+User.prototype.setup = function(cb) {
+  var self = this;
+
   // upon allocation of a user, we'll gen a keypair and get a signed cert
-  this._keyPair = jwk.KeyPair.generate("DS", 256);
-  var expiration = new Date();
-  expiration.setTime(new Date().valueOf() + 60 * 60 * 1000);
-  this._cert = new jwcert.JWCert(
-    options.domain, expiration, new Date(),
-    this._keyPair.publicKey, {email: options.email}).sign(g_privKey);
+  jwcrypto.generateKeypair({algorithm:"DS", keysize:256}, function(err, kp) {
+    if (err) return cb(err);
+    
+    self._keyPair = kp;
+    
+    var expiration = new Date();
+    expiration.setTime(new Date().valueOf() + 60 * 60 * 1000);
+    
+    jwcrypto.cert.sign(self._keyPair.publicKey, {email: self.options.email},
+                       {expiresAt: expiration, issuer: self.options.domain, issuedAt: new Date()},
+                       {}, g_privKey, function(err, signedCert) {
+                         if (err) return cb(err);
+                         self._cert = signedCert;
+                         
+                         cb(null);
+                       });
+  });
 }
 
-User.prototype.getAssertion = function(origin) {
+User.prototype.getAssertion = function(origin, cb) {
+  var self = this;
   var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-  var tok = new jwt.JWT(null, expirationDate, origin);
-  return vep.bundleCertsAndAssertion([this._cert], tok.sign(this._keyPair.secretKey));
-}
+  jwcrypto.assertion.sign({}, {audience: origin, issuer: "127.0.0.1", expiresAt: expirationDate},
+                         this._keyPair.secretKey, function(err, signedObject) {
+                           if (err) return cb(err);
+
+                           cb(null, jwcrypto.cert.bundle([self._cert], signedObject));
+                         });
+};
 
 module.exports = User;
\ No newline at end of file
diff --git a/tests/primary-then-secondary-test.js b/tests/primary-then-secondary-test.js
index c09e023b04c68520d03c5229878611cbed93d32c..2112b3e2a674b6f5f16992d55996c2a30b7e36fa 100755
--- a/tests/primary-then-secondary-test.js
+++ b/tests/primary-then-secondary-test.js
@@ -38,17 +38,28 @@ var primaryUser = new primary({
   domain: TEST_DOMAIN
 });
 
+suite.addBatch({
+  "set things up": {
+    topic: function() {
+      primaryUser.setup(this.callback);
+    },
+    "works": function() {
+      // nothing to do here
+    }
+  }
+});
+
 // now let's generate an assertion using this user
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(TEST_ORIGIN);
+      primaryUser.getAssertion(TEST_ORIGIN, this.callback);
     },
-    "succeeds": function(r) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion succeeds": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: true
diff --git a/tests/session-duration-test.js b/tests/session-duration-test.js
index 232ad6320a6f7979a6f5f5d39f800eb377229a9a..ae63d111655951967ce5dbdcf9870095ca25a63d 100755
--- a/tests/session-duration-test.js
+++ b/tests/session-duration-test.js
@@ -4,6 +4,7 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+
 require('./lib/test_env.js');
 
 const assert =
@@ -16,9 +17,7 @@ config = require('../lib/configuration.js'),
 bcrypt = require('bcrypt'),
 primary = require('./lib/primary.js'),
 ca = require('../lib/keysigner/ca.js'),
-jwk = require('jwcrypto/jwk'),
-jwt = require('jwcrypto/jwt'),
-jws = require('jwcrypto/jws');
+jwcrypto = require('jwcrypto');
 
 var suite = vows.describe('session-context');
 
@@ -40,16 +39,29 @@ var primaryUser = new primary({
   domain: PRIMARY_DOMAIN
 });
 
+suite.addBatch({
+  "setup user": {
+    topic: function() {
+      primaryUser.setup(this.callback);
+    },
+    "works": function(err) {
+      assert.isNull(err);
+      assert.isObject(primaryUser._keyPair);
+      assert.isString(primaryUser._cert);
+    }
+  }
+});
+
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(PRIMARY_ORIGIN);
+      primaryUser.getAssertion(PRIMARY_ORIGIN, this.callback);
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion with ephemeral = true": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: true
@@ -70,13 +82,13 @@ suite.addBatch({
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(PRIMARY_ORIGIN);
+      primaryUser.getAssertion(PRIMARY_ORIGIN, this.callback);
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion with ephemeral = false": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: false
@@ -176,61 +188,79 @@ suite.addBatch({
 
 // finally, let's verify that ephemeral is properly handled when certifying keys for a user
 
-var kp = jwk.KeyPair.generate("RS", 64);
+var kp = null;
 
 assert.within = function(got, expected, margin) {
   assert.ok(got + margin > expected);
   assert.ok(got - margin < expected);
 }
 
+suite.addBatch({
+  "generate keypair": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm:"RS", keysize: 64}, this.callback);
+    },
+    "works": function(err, keypair) {
+      assert.isNull(err);
+      kp = keypair;
+    }
+  }
+});
+
 suite.addBatch({
   "cert_key invoked with ephemeral = false": {
-    topic: wsapi.post('/wsapi/cert_key', {
-      email: TEST_EMAIL,
-      pubkey: kp.publicKey.serialize(),
-      ephemeral: false
-    }),
+    topic: function() {
+      wsapi.post('/wsapi/cert_key', {
+        email: TEST_EMAIL,
+        pubkey: kp.publicKey.serialize(),
+        ephemeral: false
+      }).call(this);
+    },
     "returns a response with a proper content-type" : function(err, r) {
       assert.strictEqual(r.code, 200);
     },
-    "returns a valid cert": function(err, r) {
-      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
+    "upon validation": {
+      topic: function(err, r) {
+        ca.verifyChain('127.0.0.1', [r.body], this.callback);
+      },
+      "works": function(err, pk, principal, certParamsArray) {
         assert.isTrue(kp.publicKey.equals(pk));
-      });
-    },
-    "has the correct expiration": function(err, r) {
-      var cert = new jws.JWS();
-      cert.parse(r.body);
-      var pl = JSON.parse(cert.payload);
-      assert.within(pl.exp - pl.iat,
-                    config.get('certificate_validity_ms'),
-                    200);
+      },
+      "has the correct expiration": function(err, pk, principal, certParamsArray) {
+        var params = certParamsArray[certParamsArray.length - 1].assertionParams;
+        assert.within(params.expiresAt - params.issuedAt,
+                      config.get('certificate_validity_ms'),
+                      200);
+      }      
     }
   }
 });
 
 suite.addBatch({
   "cert_key invoked with ephemeral = true": {
-    topic: wsapi.post('/wsapi/cert_key', {
-      email: TEST_EMAIL,
-      pubkey: kp.publicKey.serialize(),
-      ephemeral: true
-    }),
+    topic: function() {
+      wsapi.post('/wsapi/cert_key', {
+        email: TEST_EMAIL,
+        pubkey: kp.publicKey.serialize(),
+        ephemeral: true
+      }).call(this);
+    },
     "returns a response with a proper content-type" : function(err, r) {
       assert.strictEqual(r.code, 200);
     },
-    "returns a valid cert": function(err, r) {
-      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
+    "upon validation": {
+      topic: function(err, r) {
+        ca.verifyChain('127.0.0.1', [r.body], this.callback);
+      },
+      "works": function(err, pk, principal, certParamsArray) {
         assert.isTrue(kp.publicKey.equals(pk));
-      });
-    },
-    "has the correct expiration": function(err, r) {
-      var cert = new jws.JWS();
-      cert.parse(r.body);
-      var pl = JSON.parse(cert.payload);
-      assert.within(pl.exp - pl.iat,
-                    config.get('ephemeral_session_duration_ms'),
-                    200);
+      },
+      "has the correct expiration": function(err, pk, principal, certParamsArray) {
+        var params = certParamsArray[certParamsArray.length - 1].assertionParams;
+        assert.within(params.expiresAt - params.issuedAt,
+                      config.get('ephemeral_session_duration_ms'),
+                      200);
+      }
     }
   }
 });
diff --git a/tests/stalled-mysql-test.js b/tests/stalled-mysql-test.js
index e75fbad678cf03961d3a2a32bafe626768e4b1df..ceeb498e95224f90c8e5d945e85967e1b46bdcdc 100755
--- a/tests/stalled-mysql-test.js
+++ b/tests/stalled-mysql-test.js
@@ -14,14 +14,14 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 temp = require('temp'),
 fs = require('fs'),
-jwk = require('jwcrypto/jwk.js'),
-jwt = require('jwcrypto/jwt.js'),
-vep = require('jwcrypto/vep.js'),
-jwcert = require('jwcrypto/jwcert.js'),
+jwcrypto = require('jwcrypto'),
 path = require('path');
 
 var suite = vows.describe('forgotten-email');
 
+require("jwcrypto/lib/algs/ds");
+require("jwcrypto/lib/algs/rs");
+
 // disable vows (often flakey?) async error behavior
 suite.options.error = false;
 
@@ -305,9 +305,9 @@ var g_keypair, g_cert, g_assertion;
 suite.addBatch({
   "generating a keypair": {
     topic: function() {
-      return jwk.KeyPair.generate("DS", 256)
+      jwcrypto.generateKeypair({algorithm: "DS", keysize:256}, this.callback);
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
       assert.isObject(r);
       assert.isObject(r.publicKey);
       assert.isObject(r.secretKey);
@@ -316,9 +316,9 @@ suite.addBatch({
   }
 });
 
-var g_privKey = jwk.SecretKey.fromSimpleObject(
-  JSON.parse(require('fs').readFileSync(
-    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
+var g_privKey = jwcrypto.loadSecretKey(
+  require('fs').readFileSync(
+    path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey')));
 
 
 suite.addBatch({
@@ -328,11 +328,12 @@ suite.addBatch({
 
       var expiration = new Date();
       expiration.setTime(new Date().valueOf() + 60 * 60 * 1000);
-      g_cert = new jwcert.JWCert(TEST_DOMAIN, expiration, new Date(),
-                                 g_keypair.publicKey, {email: TEST_EMAIL}).sign(g_privKey);
-      return g_cert;
+      jwcrypto.cert.sign(g_keypair.publicKey, {email: TEST_EMAIL},
+                        {expiresAt: expiration, issuedAt: new Date(), issuer: TEST_DOMAIN},
+                        g_privKey, this.callback);
     },
-    "works swimmingly": function(cert, err) {
+    "works swimmingly": function(err, cert) {
+      g_cert = cert;
       assert.isString(cert);
       assert.lengthOf(cert.split('.'), 3);
     }
@@ -342,11 +343,16 @@ suite.addBatch({
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
+      var self = this;
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([g_cert], tok.sign(g_keypair.secretKey));
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                              g_keypair.secretKey, function(err, assertion) {
+                                self.callback(err,
+                                              err ? undefined : jwcrypto.cert.bundle([g_cert], assertion));
+                              });
     },
-    "succeeds": function(r, err) {
+    "succeeds": function(err, r) {
+      assert.isNull(err);
       assert.isString(r);
       g_assertion = r;
     }
diff --git a/tests/two-level-auth-test.js b/tests/two-level-auth-test.js
index 0353d89acc9c5b91cfdbddcfabffeb55b161292a..dc635485964432534be00ffbcf6f4ffa284a6a81 100755
--- a/tests/two-level-auth-test.js
+++ b/tests/two-level-auth-test.js
@@ -29,17 +29,28 @@ var primaryUser = new primary({
   domain: TEST_DOMAIN
 });
 
+suite.addBatch({
+  "set things up": {
+    topic: function() {
+      primaryUser.setup(this.callback);
+    },
+    "works": function() {
+      // nothing to do here
+    }
+  }
+});
+
 // now let's generate an assertion using this user
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(TEST_ORIGIN);
+      primaryUser.getAssertion(TEST_ORIGIN, this.callback);
     },
-    "succeeds": function(r) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: true
diff --git a/tests/verifier-test.js b/tests/verifier-test.js
index efa098a102eec4110daff9b8dc582218e1e2eb6d..c3f708acc268dd61e9c7b05de0d7f702a76fc6f2 100755
--- a/tests/verifier-test.js
+++ b/tests/verifier-test.js
@@ -13,16 +13,16 @@ start_stop = require('./lib/start-stop.js'),
 wsapi = require('./lib/wsapi.js'),
 db = require('../lib/db.js'),
 config = require('../lib/configuration.js'),
-jwk = require('jwcrypto/jwk.js'),
-jwt = require('jwcrypto/jwt.js'),
-vep = require('jwcrypto/vep.js'),
-jwcert = require('jwcrypto/jwcert.js'),
+jwcrypto = require('jwcrypto'),
 http = require('http'),
 querystring = require('querystring'),
 path = require('path');
 
 var suite = vows.describe('verifier');
 
+require("jwcrypto/lib/algs/rs");
+require("jwcrypto/lib/algs/ds");
+
 // disable vows (often flakey?) async error behavior
 suite.options.error = false;
 
@@ -81,9 +81,10 @@ var g_keypair, g_cert;
 suite.addBatch({
   "generating a keypair": {
     topic: function() {
-      return jwk.KeyPair.generate("DS", 256)
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, this.callback);
     },
-    "succeeds": function(r) {
+    "succeeds": function(err, r) {
+      assert.isNull(err);
       assert.isObject(r);
       assert.isObject(r.publicKey);
       assert.isObject(r.secretKey);
@@ -116,16 +117,21 @@ function make_basic_tests(new_style) {
   var tests = {
     topic: function() {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([g_cert],
-                                         tok.sign(g_keypair.secretKey),
-                                         new_style);
-    },
-    "succeeds": function(r) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
+    },
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and verifying that assertion by specifying domain as audience": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: TEST_DOMAIN,
           assertion: assertion
@@ -144,7 +150,7 @@ function make_basic_tests(new_style) {
       }
     },
     "and verifying that assertion by specifying origin as audience": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
           assertion: assertion
@@ -163,7 +169,7 @@ function make_basic_tests(new_style) {
       }
     },
     "but specifying the wrong audience": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: "notfakesite.com",
           assertion: assertion
@@ -176,7 +182,7 @@ function make_basic_tests(new_style) {
       }
     },
     "but specifying the wrong port": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: "http://fakesite.com:8888",
           assertion: assertion
@@ -189,7 +195,7 @@ function make_basic_tests(new_style) {
       }
     },
     "but specifying the wrong scheme": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: "https://fakesite.com:8080",
           assertion: assertion
@@ -202,7 +208,7 @@ function make_basic_tests(new_style) {
       }
     },
     "and providing just a domain and port": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: "fakesite.com:8080",
           assertion: assertion
@@ -221,7 +227,7 @@ function make_basic_tests(new_style) {
       }
     },
     "but providing just a domain and the wrong port": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: "fakesite.com:8888",
           assertion: assertion
@@ -234,7 +240,7 @@ function make_basic_tests(new_style) {
       }
     },
     "leaving off the audience": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           assertion: assertion
         }).call(this);
@@ -246,7 +252,7 @@ function make_basic_tests(new_style) {
       }
     },
     "leaving off the assertion": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN
         }).call(this);
@@ -275,16 +281,21 @@ function make_post_format_tests(new_style) {
   var tests = {
     topic: function() {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([g_cert],
-                                         tok.sign(g_keypair.secretKey),
-                                         new_style);
-    },
-    "succeeds": function(r) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
+    },
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "posting assertion and audience as get parameters in a post request": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         var cb = this.callback;
         var postArgs = { assertion: assertion, audience: TEST_ORIGIN };
         http.request({
@@ -318,7 +329,7 @@ function make_post_format_tests(new_style) {
       }
     },
     "posting assertion in body and audience as get parameter in a post request": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         var cb = this.callback;
         var postArgs = querystring.stringify({ assertion: assertion });
         var getArgs = querystring.stringify({ audience: TEST_ORIGIN });
@@ -355,7 +366,7 @@ function make_post_format_tests(new_style) {
       }
     },
     "posting audience in body and asssertion as get parameter in a post request": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         var cb = this.callback;
         var getArgs = querystring.stringify({ assertion: assertion });
         var postArgs = querystring.stringify({ audience: TEST_ORIGIN });
@@ -406,17 +417,21 @@ function make_post_format_2_tests(new_style) {
   var tests = {
     topic: function() {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion(
-        [g_cert],
-        tok.sign(g_keypair.secretKey),
-        new_style);
-    },
-    "succeeds": function(r, err) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
+    },
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and submitting without proper Content-Type headers": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         var cb = this.callback;
         var postArgs = querystring.stringify({ assertion: assertion, audience: TEST_ORIGIN });
         var req = http.request({
@@ -443,7 +458,7 @@ function make_post_format_2_tests(new_style) {
       }
     },
     "while submitting as application/json": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         var cb = this.callback;
         var postArgs = JSON.stringify({ assertion: assertion, audience: TEST_ORIGIN });
         var req = http.request({
@@ -487,30 +502,77 @@ function make_post_format_2_tests(new_style) {
 suite.addBatch(make_post_format_2_tests(false));
 suite.addBatch(make_post_format_2_tests(true));
 
+var fakeDomainKeypair, newClientKeypair;
+
+// let's reuse the keys, cause we don't need new ones.
+suite.addBatch({
+  "set up fake domain key": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm: "RS", keysize: 64}, this.callback);
+    },
+    "works": function(err, kp) {
+      assert.isNull(err);
+      assert.isObject(kp);
+      fakeDomainKeypair = kp;
+    }
+  }
+});
+
+suite.addBatch({
+  "set up user key": {
+    topic: function() {
+      jwcrypto.generateKeypair({algorithm: "DS", keysize: 256}, this.callback);
+    },
+    "works": function(err, kp) {
+      assert.isNull(err);
+      assert.isObject(kp);
+      newClientKeypair = kp;
+    }
+  }
+});
+
+var fakeCert;
+suite.addBatch({
+  "certify the user key": {
+    topic: function() {
+      var expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
+      jwcrypto.cert.sign(newClientKeypair.publicKey, {email: TEST_EMAIL},
+                         {issuedAt: new Date(), issuer: "127.0.0.1",
+                          expiresAt: expiration},
+                         {}, fakeDomainKeypair.secretKey, this.callback);
+    },
+    "works": function(err, cert) {
+      assert.isNull(err);
+      assert.isString(cert);
+      fakeCert = cert;
+    }
+  }
+});
+
+
+
 // now verify that a incorrectly signed assertion yields a good error message
 function make_incorrect_assertion_tests(new_style) {
   var title = "generating an assertion from a bogus cert with " + (new_style? "new style" : "old style");
   var tests = {
     topic: function() {
-      var fakeDomainKeypair = jwk.KeyPair.generate("RS", 64);
-      var newClientKeypair = jwk.KeyPair.generate("DS", 256);
-      expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
-      var cert = new jwcert.JWCert("127.0.0.1", expiration, new Date(), newClientKeypair.publicKey,
-                                   {email: TEST_EMAIL}).sign(fakeDomainKeypair.secretKey);
-
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion(
-        [cert],
-        tok.sign(newClientKeypair.secretKey),
-        new_style);
-    },
-    "yields a good looking assertion": function (r, err) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             newClientKeypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([fakeCert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
+    },
+    "yields a good looking assertion": function (err, r) {
       assert.isString(r);
       assert.equal(r.length > 0, true);
     },
     "will cause the verifier": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
           assertion: assertion
@@ -545,20 +607,23 @@ suite.addBatch({
     "fails with a nice error": function(err, r) {
       var resp = JSON.parse(r.body);
       assert.strictEqual(resp.status, 'failure');
-      assert.strictEqual(resp.reason, 'malformed assertion');
+      // the error message here will be that there are no certs
+      assert.strictEqual(resp.reason, 'no certificates provided');
     }
   },
   "using an integer as an assertion (which is bogus)": {
     topic: function()  {
       wsapi.post('/verify', {
         audience: TEST_ORIGIN,
-         assertion: 777
+        assertion: 777
       }).call(this);
     },
     "fails with a nice error": function(err, r) {
       var resp = JSON.parse(r.body);
       assert.strictEqual(resp.status, 'failure');
-      assert.strictEqual(resp.reason, 'malformed assertion');
+      // this error message has to do with the full (backed) assertion not looking
+      // like a bundle of certs and assertion
+      assert.strictEqual(resp.reason, 'malformed backed assertion');
     }
   }
 });
@@ -568,17 +633,22 @@ function make_crazy_assertion_tests(new_style) {
   var tests = {
     topic: function()  {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion(
-        [g_cert],
-        tok.sign(g_keypair.secretKey),
-        new_style);
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
     },
     "and removing the last two chars from it": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         // we used to chop off one char, but because of
         // robustness in base64-decoding, that still worked 25%
         // of the time. No need to build this knowledge in here.
+        // also, chopping off 2 characters gives varying error messages
         assertion = assertion.substr(0, assertion.length - 2);
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
@@ -588,16 +658,14 @@ function make_crazy_assertion_tests(new_style) {
       "fails with a nice error": function(err, r) {
         var resp = JSON.parse(r.body);
         assert.strictEqual(resp.status, 'failure');
-        // with new assertion format, the error is different
-        if (new_style) {
-          assert.strictEqual(resp.reason, 'verification failure');
-        } else {
-          assert.strictEqual(resp.reason, 'malformed assertion');
-        }
+
+        // so depending on the assertion, this is going to be invalid or malformed
+        // so we're not testing the error message for now
+        // assert.strictEqual(resp.reason, 'invalid signature');
       }
     },
     "and removing the first char from it": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         assertion = assertion.substr(1);
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
@@ -606,18 +674,14 @@ function make_crazy_assertion_tests(new_style) {
       },
       "fails with a nice error": function(err, r) {
         // XXX this test is failing because there's an exception thrown
-        // that's revealing too much info about the malformed assertion
+        // that's revealing too much info about the malformed signature
         var resp = JSON.parse(r.body);
         assert.strictEqual(resp.status, 'failure');
-        if (new_style) {
-          assert.strictEqual(resp.reason, 'SyntaxError: Unexpected token Ș');
-        } else {
-          assert.strictEqual(resp.reason, 'malformed assertion');
-        }
+        assert.strictEqual(resp.reason, 'malformed signature');
       }
     },
     "and appending gunk to it": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         assertion += "gunk";
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
@@ -627,11 +691,7 @@ function make_crazy_assertion_tests(new_style) {
       "fails with a nice error": function(err, r) {
         var resp = JSON.parse(r.body);
         assert.strictEqual(resp.status, 'failure');
-        if (new_style) {
-          assert.strictEqual(resp.reason, 'verification failure');
-        } else {
-          assert.strictEqual(resp.reason, 'malformed assertion');
-        }
+        assert.strictEqual(resp.reason, 'malformed signature');
       }
     }
   };
@@ -651,92 +711,120 @@ suite.addBatch({
   "An assertion that expired a millisecond ago": {
     topic: function()  {
       var expirationDate = new Date(new Date().getTime() - 10);
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      var assertion = vep.bundleCertsAndAssertion(
-        [g_cert],
-        tok.sign(g_keypair.secretKey),
-        true);
-      wsapi.post('/verify', {
-        audience: TEST_ORIGIN,
-        assertion: assertion
-      }).call(this);
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert],
+                                                            assertion,
+                                                            true); // XXX IGNORED
+                               
+                               wsapi.post('/verify', {
+                                 audience: TEST_ORIGIN,
+                                 assertion: b
+                               }).call(self);
+                             });
     },
     "fails with a nice error": function(err, r) {
       var resp = JSON.parse(r.body);
       assert.strictEqual(resp.status, 'failure');
-      // XXX: the verifier should return a clearer error message
-      assert.strictEqual(resp.reason, 'verification failure');
+      assert.strictEqual(resp.reason, 'assertion has expired');
     }
   },
   "An assertion with a bundled bogus certificate": {
     topic: function()  {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      var assertion = vep.bundleCertsAndAssertion(
-        [g_cert, "bogus cert"],
-        tok.sign(g_keypair.secretKey),
-        true);
-      wsapi.post('/verify', {
-        audience: TEST_ORIGIN,
-        assertion: assertion
-      }).call(this);
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([g_cert, "bogus cert"],
+                                                            assertion,
+                                                            true); // XXX IGNORED
+                               
+                               wsapi.post('/verify', {
+                                 audience: TEST_ORIGIN,
+                                 assertion: b
+                               }).call(self);
+                             });
     },
     "fails with a nice error": function(err, r) {
       var resp = JSON.parse(r.body);
       assert.strictEqual(resp.status, 'failure');
-      // XXX: this error should be clearer
-      assert.strictEqual(resp.reason, 'Malformed JSON web signature: Must have three parts');
+      assert.strictEqual(resp.reason, 'malformed signature');
     }
   },
   "An assertion with a no certificate": {
     topic: function()  {
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-
-      // XXX this call throws an exception if it's new style
-      // how to test this?
-      var assertion = vep.bundleCertsAndAssertion(
-        [],
-        tok.sign(g_keypair.secretKey),
-        false);
-      wsapi.post('/verify', {
-        audience: TEST_ORIGIN,
-        assertion: assertion
-      }).call(this);
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             g_keypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+
+                               // a bundle with no certs is no longer possible,
+                               // so we submit just the assertion
+                               
+                               wsapi.post('/verify', {
+                                 audience: TEST_ORIGIN,
+                                 assertion: assertion
+                               }).call(self);
+                             });
     },
     "fails with a nice error": function(err, r) {
       var resp = JSON.parse(r.body);
       assert.strictEqual(resp.status, 'failure');
-      // XXX: this error should be clearer
-      assert.strictEqual(resp.reason, "TypeError: Cannot read property 'issuer' of undefined");
+      // an error that indicates no certs
+      assert.strictEqual(resp.reason, "no certificates provided");
     }
   }
 });
 
+var otherIssuerCert;
+
+suite.addBatch({
+  "certify the user key for other issuer": {
+    topic: function() {
+      var expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
+      jwcrypto.cert.sign(newClientKeypair.publicKey, {email: TEST_EMAIL},
+                         {issuedAt: new Date(), issuer: "no.such.domain",
+                          expiresAt: expiration},
+                         {}, fakeDomainKeypair.secretKey, this.callback);
+    },
+    "works": function(err, cert) {
+      assert.isNull(err);
+      assert.isString(cert);
+      otherIssuerCert = cert;
+    }
+  }
+});
+
+
 // now verify that assertions from a primary who does not have browserid support
 // will fail to verify
 function make_other_issuer_tests(new_style) {
   var title = "generating an assertion from a cert signed by some other domain with " + (new_style ? "new style" : "old style");
   var tests = {
     topic: function() {
-      var fakeDomainKeypair = jwk.KeyPair.generate("RS", 64);
-      var newClientKeypair = jwk.KeyPair.generate("DS", 256);
-      expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
-      var cert = new jwcert.JWCert("no.such.domain", expiration, new Date(), newClientKeypair.publicKey,
-                                   {email: TEST_EMAIL}).sign(fakeDomainKeypair.secretKey);
-
+      // keys are already generated
+      // otherIssuerCert is already generated
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([cert],
-                                         tok.sign(newClientKeypair.secretKey),
-                                         new_style);
-    },
-    "yields a good looking assertion": function (r) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             newClientKeypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([otherIssuerCert],
+                                                            assertion,
+                                                            new_style); // XXX IGNORED
+                               self.callback(null, b);
+                             });
+    },
+    "yields a good looking assertion": function (err, r) {
       assert.isString(r);
       assert.equal(r.length > 0, true);
     },
     "will cause the verifier": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
           assertion: assertion
@@ -758,30 +846,52 @@ function make_other_issuer_tests(new_style) {
 suite.addBatch(make_other_issuer_tests(false));
 suite.addBatch(make_other_issuer_tests(true));
 
+// prepare a cert with example.domain primary
+var primaryCert;
+suite.addBatch({
+  "certify the user key by example.domain for wrong email address": {
+    topic: function() {
+      var secretKey = jwcrypto.loadSecretKey(
+        require('fs').readFileSync(
+          path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey')));
+
+      var expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
+      jwcrypto.cert.sign(newClientKeypair.publicKey, {email: TEST_EMAIL},
+                         {issuedAt: new Date(), issuer: "example.domain",
+                          expiresAt: expiration},
+                         {}, secretKey, this.callback);
+    },
+    "works": function(err, cert) {
+      assert.isNull(err);
+      assert.isString(cert);
+      primaryCert = cert;
+    }
+  }
+});
+
 // now verify that assertions from a primary who does have browserid support
 // but has no authority to speak for an email address will fail
 suite.addBatch({
   "generating an assertion from a cert signed by a real (simulated) primary": {
     topic: function() {
-      var secretKey = jwk.SecretKey.fromSimpleObject(
-        JSON.parse(require('fs').readFileSync(
-          path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
-
-      var newClientKeypair = jwk.KeyPair.generate("DS", 256);
-      expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
-      var cert = new jwcert.JWCert("example.domain", expiration, new Date(), newClientKeypair.publicKey,
-                                   {email: TEST_EMAIL}).sign(secretKey);
-
+      // newClientKeypair already generated, reusing
+      // primaryCert already generated
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([cert], tok.sign(newClientKeypair.secretKey));
-    },
-    "yields a good looking assertion": function (r) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             newClientKeypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([primaryCert],
+                                                            assertion);
+                               self.callback(null, b);
+                             });
+    },
+    "yields a good looking assertion": function (err, r) {
       assert.isString(r);
       assert.equal(r.length > 0, true);
     },
     "will cause the verifier": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
           assertion: assertion
@@ -790,42 +900,62 @@ suite.addBatch({
       "to return a clear error message": function (err, r) {
         var resp = JSON.parse(r.body);
         assert.strictEqual(resp.status, 'failure');
-        assert.strictEqual(resp.reason, "issuer issue 'example.domain' may not speak for emails from 'somedomain.com'");
+        assert.strictEqual(resp.reason, "issuer 'example.domain' may not speak for emails from 'somedomain.com'");
       }
     }
   }
 });
 
+suite.addBatch({
+  "certify the user key by example.domain for right email address": {
+    topic: function() {
+      var secretKey = jwcrypto.loadSecretKey(
+        require('fs').readFileSync(
+          path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey')));
+
+      var expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
+      jwcrypto.cert.sign(newClientKeypair.publicKey, {email: "foo@example.domain"},
+                         {issuedAt: new Date(), issuer: "example.domain",
+                          expiresAt: expiration},
+                         {}, secretKey, this.callback);
+    },
+    "works": function(err, cert) {
+      assert.isNull(err);
+      assert.isString(cert);
+      primaryCert = cert;
+    }
+  }
+});
+
 // now verify that assertions from a primary who does have browserid support
 // and may speak for an email address will succeed
 suite.addBatch({
   "generating an assertion from a cert signed by a real (simulated) primary": {
     topic: function() {
-      var secretKey = jwk.SecretKey.fromSimpleObject(
-        JSON.parse(require('fs').readFileSync(
-          path.join(__dirname, '..', 'example', 'primary', 'sample.privatekey'))));
-
-      var newClientKeypair = jwk.KeyPair.generate("DS", 256);
-      expiration = new Date(new Date().getTime() + (1000 * 60 * 60 * 6));
-      var cert = new jwcert.JWCert("example.domain", expiration, new Date(), newClientKeypair.publicKey,
-                                   {email: "foo@example.domain"}).sign(secretKey);
-
+      // primaryCert generated
+      // newClientKeypair generated
       var expirationDate = new Date(new Date().getTime() + (2 * 60 * 1000));
-      var tok = new jwt.JWT(null, expirationDate, TEST_ORIGIN);
-      return vep.bundleCertsAndAssertion([cert], tok.sign(newClientKeypair.secretKey));
-    },
-    "yields a good looking assertion": function (r) {
+      var self = this;
+      jwcrypto.assertion.sign({}, {audience: TEST_ORIGIN, expiresAt: expirationDate},
+                             newClientKeypair.secretKey, function(err, assertion) {
+                               if (err) return self.callback(err);
+                               var b = jwcrypto.cert.bundle([primaryCert],
+                                                            assertion);
+                               self.callback(null, b);
+                             });
+    },
+    "yields a good looking assertion": function (err, r) {
       assert.isString(r);
       assert.equal(r.length > 0, true);
     },
     "will cause the verifier": {
-      topic: function(assertion) {
+      topic: function(err, assertion) {
         wsapi.post('/verify', {
           audience: TEST_ORIGIN,
           assertion: assertion
         }).call(this);
       },
-      "to return a clear error message": function (err, r) {
+      "to succeed": function (err, r) {
         var resp = JSON.parse(r.body);
         assert.strictEqual(resp.status, 'okay');
         assert.strictEqual(resp.issuer, "example.domain");
diff --git a/tests/verify-in-different-browser-test.js b/tests/verify-in-different-browser-test.js
index 6ebd2fe15cbf02556009865a404aa998d831ea7b..b5a5a0fbb5f5cf6463088a36bba301a3b29e67f1 100755
--- a/tests/verify-in-different-browser-test.js
+++ b/tests/verify-in-different-browser-test.js
@@ -39,18 +39,29 @@ var primaryUser = new primary({
   domain: TEST_DOMAIN
 });
 
+suite.addBatch({
+  "set things up": {
+    topic: function() {
+      primaryUser.setup(this.callback);
+    },
+    "works": function() {
+      // nothing to do here
+    }
+  }
+});
+
 // first we'll create an account without a password by using
 // a primary address.
 suite.addBatch({
   "generating an assertion": {
     topic: function() {
-      return primaryUser.getAssertion(TEST_ORIGIN);
+      return primaryUser.getAssertion(TEST_ORIGIN, this.callback);
     },
-    "succeeds": function(r) {
+    "succeeds": function(err, r) {
       assert.isString(r);
     },
     "and logging in with the assertion": {
-      topic: function(assertion)  {
+      topic: function(err, assertion)  {
         wsapi.post('/wsapi/auth_with_assertion', {
           assertion: assertion,
           ephemeral: true