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 +