michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict" michael@0: michael@0: Cu.import('resource://gre/modules/identity/LogUtils.jsm'); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "IDService", michael@0: "resource://gre/modules/identity/Identity.jsm", michael@0: "IdentityService"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", michael@0: "resource://gre/modules/identity/jwcrypto.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, michael@0: "CryptoService", michael@0: "@mozilla.org/identity/crypto-service;1", michael@0: "nsIIdentityCryptoService"); michael@0: michael@0: const RP_ORIGIN = "http://123done.org"; michael@0: const INTERNAL_ORIGIN = "browserid://"; michael@0: michael@0: const SECOND_MS = 1000; michael@0: const MINUTE_MS = SECOND_MS * 60; michael@0: const HOUR_MS = MINUTE_MS * 60; michael@0: michael@0: function test_sanity() { michael@0: do_test_pending(); michael@0: michael@0: jwcrypto.generateKeyPair("DS160", function(err, kp) { michael@0: do_check_null(err); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: } michael@0: michael@0: function test_generate() { michael@0: do_test_pending(); michael@0: jwcrypto.generateKeyPair("DS160", function(err, kp) { michael@0: do_check_null(err); michael@0: do_check_neq(kp, null); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: } michael@0: michael@0: function test_get_assertion() { michael@0: do_test_pending(); michael@0: michael@0: jwcrypto.generateKeyPair( michael@0: "DS160", michael@0: function(err, kp) { michael@0: jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, (err, backedAssertion) => { michael@0: do_check_null(err); michael@0: michael@0: do_check_eq(backedAssertion.split("~").length, 2); michael@0: do_check_eq(backedAssertion.split(".").length, 3); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: function test_rsa() { michael@0: do_test_pending(); michael@0: function checkRSA(err, kpo) { michael@0: do_check_neq(kpo, undefined); michael@0: log(kpo.serializedPublicKey); michael@0: let pk = JSON.parse(kpo.serializedPublicKey); michael@0: do_check_eq(pk.algorithm, "RS"); michael@0: /* TODO michael@0: do_check_neq(kpo.sign, null); michael@0: do_check_eq(typeof kpo.sign, "function"); michael@0: do_check_neq(kpo.userID, null); michael@0: do_check_neq(kpo.url, null); michael@0: do_check_eq(kpo.url, INTERNAL_ORIGIN); michael@0: do_check_neq(kpo.exponent, null); michael@0: do_check_neq(kpo.modulus, null); michael@0: michael@0: // TODO: should sign be async? michael@0: let sig = kpo.sign("This is a message to sign"); michael@0: michael@0: do_check_neq(sig, null); michael@0: do_check_eq(typeof sig, "string"); michael@0: do_check_true(sig.length > 1); michael@0: */ michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }; michael@0: michael@0: jwcrypto.generateKeyPair("RS256", checkRSA); michael@0: } michael@0: michael@0: function test_dsa() { michael@0: do_test_pending(); michael@0: function checkDSA(err, kpo) { michael@0: do_check_neq(kpo, undefined); michael@0: log(kpo.serializedPublicKey); michael@0: let pk = JSON.parse(kpo.serializedPublicKey); michael@0: do_check_eq(pk.algorithm, "DS"); michael@0: /* TODO michael@0: do_check_neq(kpo.sign, null); michael@0: do_check_eq(typeof kpo.sign, "function"); michael@0: do_check_neq(kpo.userID, null); michael@0: do_check_neq(kpo.url, null); michael@0: do_check_eq(kpo.url, INTERNAL_ORIGIN); michael@0: do_check_neq(kpo.generator, null); michael@0: do_check_neq(kpo.prime, null); michael@0: do_check_neq(kpo.subPrime, null); michael@0: do_check_neq(kpo.publicValue, null); michael@0: michael@0: let sig = kpo.sign("This is a message to sign"); michael@0: michael@0: do_check_neq(sig, null); michael@0: do_check_eq(typeof sig, "string"); michael@0: do_check_true(sig.length > 1); michael@0: */ michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: }; michael@0: michael@0: jwcrypto.generateKeyPair("DS160", checkDSA); michael@0: } michael@0: michael@0: function test_get_assertion_with_offset() { michael@0: do_test_pending(); michael@0: michael@0: michael@0: // Use an arbitrary date in the past to ensure we don't accidentally pass michael@0: // this test with current dates, missing offsets, etc. michael@0: let serverMsec = Date.parse("Tue Oct 31 2000 00:00:00 GMT-0800"); michael@0: michael@0: // local clock skew michael@0: // clock is 12 hours fast; -12 hours offset must be applied michael@0: let localtimeOffsetMsec = -1 * 12 * HOUR_MS; michael@0: let localMsec = serverMsec - localtimeOffsetMsec; michael@0: michael@0: jwcrypto.generateKeyPair( michael@0: "DS160", michael@0: function(err, kp) { michael@0: jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, michael@0: { duration: MINUTE_MS, michael@0: localtimeOffsetMsec: localtimeOffsetMsec, michael@0: now: localMsec}, michael@0: function(err, backedAssertion) { michael@0: do_check_null(err); michael@0: michael@0: // properly formed michael@0: let cert; michael@0: let assertion; michael@0: [cert, assertion] = backedAssertion.split("~"); michael@0: michael@0: do_check_eq(cert, "fake-cert"); michael@0: do_check_eq(assertion.split(".").length, 3); michael@0: michael@0: let components = extractComponents(assertion); michael@0: michael@0: // Expiry is within two minutes, corrected for skew michael@0: let exp = parseInt(components.payload.exp, 10); michael@0: do_check_true(exp - serverMsec === MINUTE_MS); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: } michael@0: ); michael@0: } michael@0: ); michael@0: } michael@0: michael@0: function test_assertion_lifetime() { michael@0: do_test_pending(); michael@0: michael@0: jwcrypto.generateKeyPair( michael@0: "DS160", michael@0: function(err, kp) { michael@0: jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, michael@0: {duration: MINUTE_MS}, michael@0: function(err, backedAssertion) { michael@0: do_check_null(err); michael@0: michael@0: // properly formed michael@0: let cert; michael@0: let assertion; michael@0: [cert, assertion] = backedAssertion.split("~"); michael@0: michael@0: do_check_eq(cert, "fake-cert"); michael@0: do_check_eq(assertion.split(".").length, 3); michael@0: michael@0: let components = extractComponents(assertion); michael@0: michael@0: // Expiry is within one minute, as we specified above michael@0: let exp = parseInt(components.payload.exp, 10); michael@0: do_check_true(Math.abs(Date.now() - exp) > 50 * SECOND_MS); michael@0: do_check_true(Math.abs(Date.now() - exp) <= MINUTE_MS); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: } michael@0: ); michael@0: } michael@0: ); michael@0: } michael@0: michael@0: function test_audience_encoding_bug972582() { michael@0: let audience = "i-like-pie.com"; michael@0: michael@0: jwcrypto.generateKeyPair( michael@0: "DS160", michael@0: function(err, kp) { michael@0: do_check_null(err); michael@0: jwcrypto.generateAssertion("fake-cert", kp, audience, michael@0: function(err, backedAssertion) { michael@0: do_check_null(err); michael@0: michael@0: let [cert, assertion] = backedAssertion.split("~"); michael@0: let components = extractComponents(assertion); michael@0: do_check_eq(components.payload.aud, audience); michael@0: michael@0: do_test_finished(); michael@0: run_next_test(); michael@0: } michael@0: ); michael@0: } michael@0: ); michael@0: } michael@0: michael@0: // End of tests michael@0: // Helper function follow michael@0: michael@0: function extractComponents(signedObject) { michael@0: if (typeof(signedObject) != 'string') { michael@0: throw new Error("malformed signature " + typeof(signedObject)); michael@0: } michael@0: michael@0: let parts = signedObject.split("."); michael@0: if (parts.length != 3) { michael@0: throw new Error("signed object must have three parts, this one has " + parts.length); michael@0: } michael@0: michael@0: let headerSegment = parts[0]; michael@0: let payloadSegment = parts[1]; michael@0: let cryptoSegment = parts[2]; michael@0: michael@0: let header = JSON.parse(base64UrlDecode(headerSegment)); michael@0: let payload = JSON.parse(base64UrlDecode(payloadSegment)); michael@0: michael@0: // Ensure well-formed header michael@0: do_check_eq(Object.keys(header).length, 1); michael@0: do_check_true(!!header.alg); michael@0: michael@0: // Ensure well-formed payload michael@0: for (let field of ["exp", "aud"]) { michael@0: do_check_true(!!payload[field]); michael@0: } michael@0: michael@0: return {header: header, michael@0: payload: payload, michael@0: headerSegment: headerSegment, michael@0: payloadSegment: payloadSegment, michael@0: cryptoSegment: cryptoSegment}; michael@0: }; michael@0: michael@0: let TESTS = [ michael@0: test_sanity, michael@0: test_generate, michael@0: test_get_assertion, michael@0: test_get_assertion_with_offset, michael@0: test_assertion_lifetime, michael@0: test_audience_encoding_bug972582, michael@0: ]; michael@0: michael@0: TESTS = TESTS.concat([test_rsa, test_dsa]); michael@0: michael@0: TESTS.forEach(add_test); michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: }