services/sync/modules/keys.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/sync/modules/keys.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,214 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = [
    1.11 +  "BulkKeyBundle",
    1.12 +  "SyncKeyBundle"
    1.13 +];
    1.14 +
    1.15 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.16 +
    1.17 +Cu.import("resource://services-sync/constants.js");
    1.18 +Cu.import("resource://gre/modules/Log.jsm");
    1.19 +Cu.import("resource://services-sync/util.js");
    1.20 +
    1.21 +/**
    1.22 + * Represents a pair of keys.
    1.23 + *
    1.24 + * Each key stored in a key bundle is 256 bits. One key is used for symmetric
    1.25 + * encryption. The other is used for HMAC.
    1.26 + *
    1.27 + * A KeyBundle by itself is just an anonymous pair of keys. Other types
    1.28 + * deriving from this one add semantics, such as associated collections or
    1.29 + * generating a key bundle via HKDF from another key.
    1.30 + */
    1.31 +function KeyBundle() {
    1.32 +  this._encrypt = null;
    1.33 +  this._encryptB64 = null;
    1.34 +  this._hmac = null;
    1.35 +  this._hmacB64 = null;
    1.36 +  this._hmacObj = null;
    1.37 +  this._sha256HMACHasher = null;
    1.38 +}
    1.39 +KeyBundle.prototype = {
    1.40 +  _encrypt: null,
    1.41 +  _encryptB64: null,
    1.42 +  _hmac: null,
    1.43 +  _hmacB64: null,
    1.44 +  _hmacObj: null,
    1.45 +  _sha256HMACHasher: null,
    1.46 +
    1.47 +  equals: function equals(bundle) {
    1.48 +    return bundle &&
    1.49 +           (bundle.hmacKey == this.hmacKey) &&
    1.50 +           (bundle.encryptionKey == this.encryptionKey);
    1.51 +  },
    1.52 +
    1.53 +  /*
    1.54 +   * Accessors for the two keys.
    1.55 +   */
    1.56 +  get encryptionKey() {
    1.57 +    return this._encrypt;
    1.58 +  },
    1.59 +
    1.60 +  set encryptionKey(value) {
    1.61 +    if (!value || typeof value != "string") {
    1.62 +      throw new Error("Encryption key can only be set to string values.");
    1.63 +    }
    1.64 +
    1.65 +    if (value.length < 16) {
    1.66 +      throw new Error("Encryption key must be at least 128 bits long.");
    1.67 +    }
    1.68 +
    1.69 +    this._encrypt = value;
    1.70 +    this._encryptB64 = btoa(value);
    1.71 +  },
    1.72 +
    1.73 +  get encryptionKeyB64() {
    1.74 +    return this._encryptB64;
    1.75 +  },
    1.76 +
    1.77 +  get hmacKey() {
    1.78 +    return this._hmac;
    1.79 +  },
    1.80 +
    1.81 +  set hmacKey(value) {
    1.82 +    if (!value || typeof value != "string") {
    1.83 +      throw new Error("HMAC key can only be set to string values.");
    1.84 +    }
    1.85 +
    1.86 +    if (value.length < 16) {
    1.87 +      throw new Error("HMAC key must be at least 128 bits long.");
    1.88 +    }
    1.89 +
    1.90 +    this._hmac = value;
    1.91 +    this._hmacB64 = btoa(value);
    1.92 +    this._hmacObj = value ? Utils.makeHMACKey(value) : null;
    1.93 +    this._sha256HMACHasher = value ? Utils.makeHMACHasher(
    1.94 +      Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null;
    1.95 +  },
    1.96 +
    1.97 +  get hmacKeyB64() {
    1.98 +    return this._hmacB64;
    1.99 +  },
   1.100 +
   1.101 +  get hmacKeyObject() {
   1.102 +    return this._hmacObj;
   1.103 +  },
   1.104 +
   1.105 +  get sha256HMACHasher() {
   1.106 +    return this._sha256HMACHasher;
   1.107 +  },
   1.108 +
   1.109 +  /**
   1.110 +   * Populate this key pair with 2 new, randomly generated keys.
   1.111 +   */
   1.112 +  generateRandom: function generateRandom() {
   1.113 +    let generatedHMAC = Svc.Crypto.generateRandomKey();
   1.114 +    let generatedEncr = Svc.Crypto.generateRandomKey();
   1.115 +    this.keyPairB64 = [generatedEncr, generatedHMAC];
   1.116 +  },
   1.117 +
   1.118 +};
   1.119 +
   1.120 +/**
   1.121 + * Represents a KeyBundle associated with a collection.
   1.122 + *
   1.123 + * This is just a KeyBundle with a collection attached.
   1.124 + */
   1.125 +this.BulkKeyBundle = function BulkKeyBundle(collection) {
   1.126 +  let log = Log.repository.getLogger("Sync.BulkKeyBundle");
   1.127 +  log.info("BulkKeyBundle being created for " + collection);
   1.128 +  KeyBundle.call(this);
   1.129 +
   1.130 +  this._collection = collection;
   1.131 +}
   1.132 +
   1.133 +BulkKeyBundle.prototype = {
   1.134 +  __proto__: KeyBundle.prototype,
   1.135 +
   1.136 +  get collection() {
   1.137 +    return this._collection;
   1.138 +  },
   1.139 +
   1.140 +  /**
   1.141 +   * Obtain the key pair in this key bundle.
   1.142 +   *
   1.143 +   * The returned keys are represented as raw byte strings.
   1.144 +   */
   1.145 +  get keyPair() {
   1.146 +    return [this.encryptionKey, this.hmacKey];
   1.147 +  },
   1.148 +
   1.149 +  set keyPair(value) {
   1.150 +    if (!Array.isArray(value) || value.length != 2) {
   1.151 +      throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys.");
   1.152 +    }
   1.153 +
   1.154 +    this.encryptionKey = value[0];
   1.155 +    this.hmacKey       = value[1];
   1.156 +  },
   1.157 +
   1.158 +  get keyPairB64() {
   1.159 +    return [this.encryptionKeyB64, this.hmacKeyB64];
   1.160 +  },
   1.161 +
   1.162 +  set keyPairB64(value) {
   1.163 +    if (!Array.isArray(value) || value.length != 2) {
   1.164 +      throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " +
   1.165 +                      "keys.");
   1.166 +    }
   1.167 +
   1.168 +    this.encryptionKey  = Utils.safeAtoB(value[0]);
   1.169 +    this.hmacKey        = Utils.safeAtoB(value[1]);
   1.170 +  },
   1.171 +};
   1.172 +
   1.173 +/**
   1.174 + * Represents a key pair derived from a Sync Key via HKDF.
   1.175 + *
   1.176 + * Instances of this type should be considered immutable. You create an
   1.177 + * instance by specifying the username and 26 character "friendly" Base32
   1.178 + * encoded Sync Key. The Sync Key is derived at instance creation time.
   1.179 + *
   1.180 + * If the username or Sync Key is invalid, an Error will be thrown.
   1.181 + */
   1.182 +this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) {
   1.183 +  let log = Log.repository.getLogger("Sync.SyncKeyBundle");
   1.184 +  log.info("SyncKeyBundle being created.");
   1.185 +  KeyBundle.call(this);
   1.186 +
   1.187 +  this.generateFromKey(username, syncKey);
   1.188 +}
   1.189 +SyncKeyBundle.prototype = {
   1.190 +  __proto__: KeyBundle.prototype,
   1.191 +
   1.192 +  /*
   1.193 +   * If we've got a string, hash it into keys and store them.
   1.194 +   */
   1.195 +  generateFromKey: function generateFromKey(username, syncKey) {
   1.196 +    if (!username || (typeof username != "string")) {
   1.197 +      throw new Error("Sync Key cannot be generated from non-string username.");
   1.198 +    }
   1.199 +
   1.200 +    if (!syncKey || (typeof syncKey != "string")) {
   1.201 +      throw new Error("Sync Key cannot be generated from non-string key.");
   1.202 +    }
   1.203 +
   1.204 +    if (!Utils.isPassphrase(syncKey)) {
   1.205 +      throw new Error("Provided key is not a passphrase, cannot derive Sync " +
   1.206 +                      "Key Bundle.");
   1.207 +    }
   1.208 +
   1.209 +    // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key.
   1.210 +    let prk = Utils.decodeKeyBase32(syncKey);
   1.211 +    let info = HMAC_INPUT + username;
   1.212 +    let okm = Utils.hkdfExpand(prk, info, 32 * 2);
   1.213 +    this.encryptionKey = okm.slice(0, 32);
   1.214 +    this.hmacKey = okm.slice(32, 64);
   1.215 +  },
   1.216 +};
   1.217 +

mercurial