Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 this.EXPORTED_SYMBOLS = [
8 "BulkKeyBundle",
9 "SyncKeyBundle"
10 ];
12 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
14 Cu.import("resource://services-sync/constants.js");
15 Cu.import("resource://gre/modules/Log.jsm");
16 Cu.import("resource://services-sync/util.js");
18 /**
19 * Represents a pair of keys.
20 *
21 * Each key stored in a key bundle is 256 bits. One key is used for symmetric
22 * encryption. The other is used for HMAC.
23 *
24 * A KeyBundle by itself is just an anonymous pair of keys. Other types
25 * deriving from this one add semantics, such as associated collections or
26 * generating a key bundle via HKDF from another key.
27 */
28 function KeyBundle() {
29 this._encrypt = null;
30 this._encryptB64 = null;
31 this._hmac = null;
32 this._hmacB64 = null;
33 this._hmacObj = null;
34 this._sha256HMACHasher = null;
35 }
36 KeyBundle.prototype = {
37 _encrypt: null,
38 _encryptB64: null,
39 _hmac: null,
40 _hmacB64: null,
41 _hmacObj: null,
42 _sha256HMACHasher: null,
44 equals: function equals(bundle) {
45 return bundle &&
46 (bundle.hmacKey == this.hmacKey) &&
47 (bundle.encryptionKey == this.encryptionKey);
48 },
50 /*
51 * Accessors for the two keys.
52 */
53 get encryptionKey() {
54 return this._encrypt;
55 },
57 set encryptionKey(value) {
58 if (!value || typeof value != "string") {
59 throw new Error("Encryption key can only be set to string values.");
60 }
62 if (value.length < 16) {
63 throw new Error("Encryption key must be at least 128 bits long.");
64 }
66 this._encrypt = value;
67 this._encryptB64 = btoa(value);
68 },
70 get encryptionKeyB64() {
71 return this._encryptB64;
72 },
74 get hmacKey() {
75 return this._hmac;
76 },
78 set hmacKey(value) {
79 if (!value || typeof value != "string") {
80 throw new Error("HMAC key can only be set to string values.");
81 }
83 if (value.length < 16) {
84 throw new Error("HMAC key must be at least 128 bits long.");
85 }
87 this._hmac = value;
88 this._hmacB64 = btoa(value);
89 this._hmacObj = value ? Utils.makeHMACKey(value) : null;
90 this._sha256HMACHasher = value ? Utils.makeHMACHasher(
91 Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null;
92 },
94 get hmacKeyB64() {
95 return this._hmacB64;
96 },
98 get hmacKeyObject() {
99 return this._hmacObj;
100 },
102 get sha256HMACHasher() {
103 return this._sha256HMACHasher;
104 },
106 /**
107 * Populate this key pair with 2 new, randomly generated keys.
108 */
109 generateRandom: function generateRandom() {
110 let generatedHMAC = Svc.Crypto.generateRandomKey();
111 let generatedEncr = Svc.Crypto.generateRandomKey();
112 this.keyPairB64 = [generatedEncr, generatedHMAC];
113 },
115 };
117 /**
118 * Represents a KeyBundle associated with a collection.
119 *
120 * This is just a KeyBundle with a collection attached.
121 */
122 this.BulkKeyBundle = function BulkKeyBundle(collection) {
123 let log = Log.repository.getLogger("Sync.BulkKeyBundle");
124 log.info("BulkKeyBundle being created for " + collection);
125 KeyBundle.call(this);
127 this._collection = collection;
128 }
130 BulkKeyBundle.prototype = {
131 __proto__: KeyBundle.prototype,
133 get collection() {
134 return this._collection;
135 },
137 /**
138 * Obtain the key pair in this key bundle.
139 *
140 * The returned keys are represented as raw byte strings.
141 */
142 get keyPair() {
143 return [this.encryptionKey, this.hmacKey];
144 },
146 set keyPair(value) {
147 if (!Array.isArray(value) || value.length != 2) {
148 throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys.");
149 }
151 this.encryptionKey = value[0];
152 this.hmacKey = value[1];
153 },
155 get keyPairB64() {
156 return [this.encryptionKeyB64, this.hmacKeyB64];
157 },
159 set keyPairB64(value) {
160 if (!Array.isArray(value) || value.length != 2) {
161 throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " +
162 "keys.");
163 }
165 this.encryptionKey = Utils.safeAtoB(value[0]);
166 this.hmacKey = Utils.safeAtoB(value[1]);
167 },
168 };
170 /**
171 * Represents a key pair derived from a Sync Key via HKDF.
172 *
173 * Instances of this type should be considered immutable. You create an
174 * instance by specifying the username and 26 character "friendly" Base32
175 * encoded Sync Key. The Sync Key is derived at instance creation time.
176 *
177 * If the username or Sync Key is invalid, an Error will be thrown.
178 */
179 this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) {
180 let log = Log.repository.getLogger("Sync.SyncKeyBundle");
181 log.info("SyncKeyBundle being created.");
182 KeyBundle.call(this);
184 this.generateFromKey(username, syncKey);
185 }
186 SyncKeyBundle.prototype = {
187 __proto__: KeyBundle.prototype,
189 /*
190 * If we've got a string, hash it into keys and store them.
191 */
192 generateFromKey: function generateFromKey(username, syncKey) {
193 if (!username || (typeof username != "string")) {
194 throw new Error("Sync Key cannot be generated from non-string username.");
195 }
197 if (!syncKey || (typeof syncKey != "string")) {
198 throw new Error("Sync Key cannot be generated from non-string key.");
199 }
201 if (!Utils.isPassphrase(syncKey)) {
202 throw new Error("Provided key is not a passphrase, cannot derive Sync " +
203 "Key Bundle.");
204 }
206 // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key.
207 let prk = Utils.decodeKeyBase32(syncKey);
208 let info = HMAC_INPUT + username;
209 let okm = Utils.hkdfExpand(prk, info, 32 * 2);
210 this.encryptionKey = okm.slice(0, 32);
211 this.hmacKey = okm.slice(32, 64);
212 },
213 };