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: this.EXPORTED_SYMBOLS = [ michael@0: "BulkKeyBundle", michael@0: "SyncKeyBundle" michael@0: ]; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://services-sync/constants.js"); michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: michael@0: /** michael@0: * Represents a pair of keys. michael@0: * michael@0: * Each key stored in a key bundle is 256 bits. One key is used for symmetric michael@0: * encryption. The other is used for HMAC. michael@0: * michael@0: * A KeyBundle by itself is just an anonymous pair of keys. Other types michael@0: * deriving from this one add semantics, such as associated collections or michael@0: * generating a key bundle via HKDF from another key. michael@0: */ michael@0: function KeyBundle() { michael@0: this._encrypt = null; michael@0: this._encryptB64 = null; michael@0: this._hmac = null; michael@0: this._hmacB64 = null; michael@0: this._hmacObj = null; michael@0: this._sha256HMACHasher = null; michael@0: } michael@0: KeyBundle.prototype = { michael@0: _encrypt: null, michael@0: _encryptB64: null, michael@0: _hmac: null, michael@0: _hmacB64: null, michael@0: _hmacObj: null, michael@0: _sha256HMACHasher: null, michael@0: michael@0: equals: function equals(bundle) { michael@0: return bundle && michael@0: (bundle.hmacKey == this.hmacKey) && michael@0: (bundle.encryptionKey == this.encryptionKey); michael@0: }, michael@0: michael@0: /* michael@0: * Accessors for the two keys. michael@0: */ michael@0: get encryptionKey() { michael@0: return this._encrypt; michael@0: }, michael@0: michael@0: set encryptionKey(value) { michael@0: if (!value || typeof value != "string") { michael@0: throw new Error("Encryption key can only be set to string values."); michael@0: } michael@0: michael@0: if (value.length < 16) { michael@0: throw new Error("Encryption key must be at least 128 bits long."); michael@0: } michael@0: michael@0: this._encrypt = value; michael@0: this._encryptB64 = btoa(value); michael@0: }, michael@0: michael@0: get encryptionKeyB64() { michael@0: return this._encryptB64; michael@0: }, michael@0: michael@0: get hmacKey() { michael@0: return this._hmac; michael@0: }, michael@0: michael@0: set hmacKey(value) { michael@0: if (!value || typeof value != "string") { michael@0: throw new Error("HMAC key can only be set to string values."); michael@0: } michael@0: michael@0: if (value.length < 16) { michael@0: throw new Error("HMAC key must be at least 128 bits long."); michael@0: } michael@0: michael@0: this._hmac = value; michael@0: this._hmacB64 = btoa(value); michael@0: this._hmacObj = value ? Utils.makeHMACKey(value) : null; michael@0: this._sha256HMACHasher = value ? Utils.makeHMACHasher( michael@0: Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null; michael@0: }, michael@0: michael@0: get hmacKeyB64() { michael@0: return this._hmacB64; michael@0: }, michael@0: michael@0: get hmacKeyObject() { michael@0: return this._hmacObj; michael@0: }, michael@0: michael@0: get sha256HMACHasher() { michael@0: return this._sha256HMACHasher; michael@0: }, michael@0: michael@0: /** michael@0: * Populate this key pair with 2 new, randomly generated keys. michael@0: */ michael@0: generateRandom: function generateRandom() { michael@0: let generatedHMAC = Svc.Crypto.generateRandomKey(); michael@0: let generatedEncr = Svc.Crypto.generateRandomKey(); michael@0: this.keyPairB64 = [generatedEncr, generatedHMAC]; michael@0: }, michael@0: michael@0: }; michael@0: michael@0: /** michael@0: * Represents a KeyBundle associated with a collection. michael@0: * michael@0: * This is just a KeyBundle with a collection attached. michael@0: */ michael@0: this.BulkKeyBundle = function BulkKeyBundle(collection) { michael@0: let log = Log.repository.getLogger("Sync.BulkKeyBundle"); michael@0: log.info("BulkKeyBundle being created for " + collection); michael@0: KeyBundle.call(this); michael@0: michael@0: this._collection = collection; michael@0: } michael@0: michael@0: BulkKeyBundle.prototype = { michael@0: __proto__: KeyBundle.prototype, michael@0: michael@0: get collection() { michael@0: return this._collection; michael@0: }, michael@0: michael@0: /** michael@0: * Obtain the key pair in this key bundle. michael@0: * michael@0: * The returned keys are represented as raw byte strings. michael@0: */ michael@0: get keyPair() { michael@0: return [this.encryptionKey, this.hmacKey]; michael@0: }, michael@0: michael@0: set keyPair(value) { michael@0: if (!Array.isArray(value) || value.length != 2) { michael@0: throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys."); michael@0: } michael@0: michael@0: this.encryptionKey = value[0]; michael@0: this.hmacKey = value[1]; michael@0: }, michael@0: michael@0: get keyPairB64() { michael@0: return [this.encryptionKeyB64, this.hmacKeyB64]; michael@0: }, michael@0: michael@0: set keyPairB64(value) { michael@0: if (!Array.isArray(value) || value.length != 2) { michael@0: throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " + michael@0: "keys."); michael@0: } michael@0: michael@0: this.encryptionKey = Utils.safeAtoB(value[0]); michael@0: this.hmacKey = Utils.safeAtoB(value[1]); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Represents a key pair derived from a Sync Key via HKDF. michael@0: * michael@0: * Instances of this type should be considered immutable. You create an michael@0: * instance by specifying the username and 26 character "friendly" Base32 michael@0: * encoded Sync Key. The Sync Key is derived at instance creation time. michael@0: * michael@0: * If the username or Sync Key is invalid, an Error will be thrown. michael@0: */ michael@0: this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) { michael@0: let log = Log.repository.getLogger("Sync.SyncKeyBundle"); michael@0: log.info("SyncKeyBundle being created."); michael@0: KeyBundle.call(this); michael@0: michael@0: this.generateFromKey(username, syncKey); michael@0: } michael@0: SyncKeyBundle.prototype = { michael@0: __proto__: KeyBundle.prototype, michael@0: michael@0: /* michael@0: * If we've got a string, hash it into keys and store them. michael@0: */ michael@0: generateFromKey: function generateFromKey(username, syncKey) { michael@0: if (!username || (typeof username != "string")) { michael@0: throw new Error("Sync Key cannot be generated from non-string username."); michael@0: } michael@0: michael@0: if (!syncKey || (typeof syncKey != "string")) { michael@0: throw new Error("Sync Key cannot be generated from non-string key."); michael@0: } michael@0: michael@0: if (!Utils.isPassphrase(syncKey)) { michael@0: throw new Error("Provided key is not a passphrase, cannot derive Sync " + michael@0: "Key Bundle."); michael@0: } michael@0: michael@0: // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key. michael@0: let prk = Utils.decodeKeyBase32(syncKey); michael@0: let info = HMAC_INPUT + username; michael@0: let okm = Utils.hkdfExpand(prk, info, 32 * 2); michael@0: this.encryptionKey = okm.slice(0, 32); michael@0: this.hmacKey = okm.slice(32, 64); michael@0: }, michael@0: }; michael@0: