michael@0: /* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/identity/LogUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, michael@0: "IdentityCryptoService", michael@0: "@mozilla.org/identity/crypto-service;1", michael@0: "nsIIdentityCryptoService"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["jwcrypto"]; michael@0: michael@0: const ALGORITHMS = { RS256: "RS256", DS160: "DS160" }; michael@0: const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime michael@0: michael@0: function log(...aMessageArgs) { michael@0: Logger.log.apply(Logger, ["jwcrypto"].concat(aMessageArgs)); michael@0: } michael@0: michael@0: function generateKeyPair(aAlgorithmName, aCallback) { michael@0: log("Generate key pair; alg =", aAlgorithmName); michael@0: michael@0: IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) { michael@0: if (!Components.isSuccessCode(rv)) { michael@0: return aCallback("key generation failed"); michael@0: } michael@0: michael@0: var publicKey; michael@0: michael@0: switch (aKeyPair.keyType) { michael@0: case ALGORITHMS.RS256: michael@0: publicKey = { michael@0: algorithm: "RS", michael@0: exponent: aKeyPair.hexRSAPublicKeyExponent, michael@0: modulus: aKeyPair.hexRSAPublicKeyModulus michael@0: }; michael@0: break; michael@0: michael@0: case ALGORITHMS.DS160: michael@0: publicKey = { michael@0: algorithm: "DS", michael@0: y: aKeyPair.hexDSAPublicValue, michael@0: p: aKeyPair.hexDSAPrime, michael@0: q: aKeyPair.hexDSASubPrime, michael@0: g: aKeyPair.hexDSAGenerator michael@0: }; michael@0: break; michael@0: michael@0: default: michael@0: return aCallback("unknown key type"); michael@0: } michael@0: michael@0: let keyWrapper = { michael@0: serializedPublicKey: JSON.stringify(publicKey), michael@0: _kp: aKeyPair michael@0: }; michael@0: michael@0: return aCallback(null, keyWrapper); michael@0: }); michael@0: } michael@0: michael@0: function sign(aPayload, aKeypair, aCallback) { michael@0: aKeypair._kp.sign(aPayload, function(rv, signature) { michael@0: if (!Components.isSuccessCode(rv)) { michael@0: log("ERROR: signer.sign failed"); michael@0: return aCallback("Sign failed"); michael@0: } michael@0: log("signer.sign: success"); michael@0: return aCallback(null, signature); michael@0: }); michael@0: } michael@0: michael@0: function jwcryptoClass() michael@0: { michael@0: } michael@0: michael@0: jwcryptoClass.prototype = { michael@0: /* michael@0: * Determine the expiration of the assertion. Returns expiry date michael@0: * in milliseconds as integer. michael@0: * michael@0: * @param localtimeOffsetMsec (optional) michael@0: * The number of milliseconds that must be added to the local clock michael@0: * for it to agree with the server. For example, if the local clock michael@0: * if two minutes fast, localtimeOffsetMsec would be -120000 michael@0: * michael@0: * @param now (options) michael@0: * Current date in milliseconds. Useful for mocking clock michael@0: * skew in testing. michael@0: */ michael@0: getExpiration: function(duration=DURATION_MS, localtimeOffsetMsec=0, now=Date.now()) { michael@0: return now + localtimeOffsetMsec + duration; michael@0: }, michael@0: michael@0: isCertValid: function(aCert, aCallback) { michael@0: // XXX check expiration, bug 769850 michael@0: aCallback(true); michael@0: }, michael@0: michael@0: generateKeyPair: function(aAlgorithmName, aCallback) { michael@0: log("generating"); michael@0: generateKeyPair(aAlgorithmName, aCallback); michael@0: }, michael@0: michael@0: /* michael@0: * Generate an assertion and return it through the provided callback. michael@0: * michael@0: * @param aCert michael@0: * Identity certificate michael@0: * michael@0: * @param aKeyPair michael@0: * KeyPair object michael@0: * michael@0: * @param aAudience michael@0: * Audience of the assertion michael@0: * michael@0: * @param aOptions (optional) michael@0: * Can include: michael@0: * { michael@0: * localtimeOffsetMsec: , michael@0: * now: michael@0: * duration: michael@0: * } michael@0: * michael@0: * localtimeOffsetMsec is the number of milliseconds that need to be michael@0: * added to the local clock time to make it concur with the server. michael@0: * For example, if the local clock is two minutes fast, the offset in michael@0: * milliseconds would be -120000. michael@0: * michael@0: * @param aCallback michael@0: * Function to invoke with resulting assertion. Assertion michael@0: * will be string or null on failure. michael@0: */ michael@0: generateAssertion: function(aCert, aKeyPair, aAudience, aOptions, aCallback) { michael@0: if (typeof aOptions == "function") { michael@0: aCallback = aOptions; michael@0: aOptions = { }; michael@0: } michael@0: michael@0: // for now, we hack the algorithm name michael@0: // XXX bug 769851 michael@0: var header = {"alg": "DS128"}; michael@0: var headerBytes = IdentityCryptoService.base64UrlEncode( michael@0: JSON.stringify(header)); michael@0: michael@0: var payload = { michael@0: exp: this.getExpiration( michael@0: aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now), michael@0: aud: aAudience michael@0: }; michael@0: var payloadBytes = IdentityCryptoService.base64UrlEncode( michael@0: JSON.stringify(payload)); michael@0: michael@0: log("payload bytes", payload, payloadBytes); michael@0: sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) { michael@0: if (err) michael@0: return aCallback(err); michael@0: michael@0: var signedAssertion = headerBytes + "." + payloadBytes + "." + signature; michael@0: return aCallback(null, aCert + "~" + signedAssertion); michael@0: }); michael@0: } michael@0: michael@0: }; michael@0: michael@0: this.jwcrypto = new jwcryptoClass(); michael@0: this.jwcrypto.ALGORITHMS = ALGORITHMS;