201 lines, 108 LOC, 96 covered (88%)
11 | 1 | /* ***** BEGIN LICENSE BLOCK ***** |
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
|
3 | * |
|
4 | * The contents of this file are subject to the Mozilla Public License Version |
|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
|
6 | * the License. You may obtain a copy of the License at |
|
7 | * http://www.mozilla.org/MPL/ |
|
8 | * |
|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
|
11 | * for the specific language governing rights and limitations under the |
|
12 | * License. |
|
13 | * |
|
14 | * The Original Code is Weave. |
|
15 | * |
|
16 | * The Initial Developer of the Original Code is Mozilla. |
|
17 | * Portions created by the Initial Developer are Copyright (C) 2008 |
|
18 | * the Initial Developer. All Rights Reserved. |
|
19 | * |
|
20 | * Contributor(s): |
|
21 | * Dan Mills <thunder@mozilla.com> |
|
22 | * |
|
23 | * Alternatively, the contents of this file may be used under the terms of |
|
24 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
|
25 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
|
26 | * in which case the provisions of the GPL or the LGPL are applicable instead |
|
27 | * of those above. If you wish to allow use of your version of this file only |
|
28 | * under the terms of either the GPL or the LGPL, and not to allow others to |
|
29 | * use your version of this file under the terms of the MPL, indicate your |
|
30 | * decision by deleting the provisions above and replace them with the notice |
|
31 | * and other provisions required by the GPL or the LGPL. If you do not delete |
|
32 | * the provisions above, a recipient may use your version of this file under |
|
33 | * the terms of any one of the MPL, the GPL or the LGPL. |
|
34 | * |
|
35 | * ***** END LICENSE BLOCK ***** */ |
|
36 | ||
143 | 37 | const EXPORTED_SYMBOLS = ['CryptoWrapper', 'CryptoMeta', 'CryptoMetas']; |
38 | ||
44 | 39 | const Cc = Components.classes; |
44 | 40 | const Ci = Components.interfaces; |
44 | 41 | const Cr = Components.results; |
44 | 42 | const Cu = Components.utils; |
43 | ||
55 | 44 | Cu.import("resource://weave/identity.js"); |
55 | 45 | Cu.import("resource://weave/util.js"); |
55 | 46 | Cu.import("resource://weave/base_records/wbo.js"); |
55 | 47 | Cu.import("resource://weave/base_records/keys.js"); |
48 | ||
3675 | 49 | function CryptoWrapper(uri) { |
14612 | 50 | this.cleartext = {}; |
21918 | 51 | WBORecord.call(this, uri); |
10959 | 52 | this.encryption = ""; |
14612 | 53 | this.ciphertext = null; |
54 | } |
|
22 | 55 | CryptoWrapper.prototype = { |
33 | 56 | __proto__: WBORecord.prototype, |
22 | 57 | _logName: "Record.CryptoWrapper", |
58 | ||
2373 | 59 | encrypt: function CryptoWrapper_encrypt(passphrase) { |
9404 | 60 | let pubkey = PubKeys.getDefaultKey(); |
11755 | 61 | let privkey = PrivKeys.get(pubkey.privateKeyUri); |
62 | ||
11755 | 63 | let meta = CryptoMetas.get(this.encryption); |
14106 | 64 | let symkey = meta.getKey(privkey, passphrase); |
65 | ||
14106 | 66 | this.IV = Svc.Crypto.generateRandomIV(); |
18808 | 67 | this.ciphertext = Svc.Crypto.encrypt(JSON.stringify(this.cleartext), |
9404 | 68 | symkey, this.IV); |
16457 | 69 | this.hmac = Utils.sha256HMAC(this.ciphertext, symkey.hmacKey); |
9404 | 70 | this.cleartext = null; |
71 | }, |
|
72 | ||
1278 | 73 | decrypt: function CryptoWrapper_decrypt(passphrase) { |
5024 | 74 | let pubkey = PubKeys.getDefaultKey(); |
6280 | 75 | let privkey = PrivKeys.get(pubkey.privateKeyUri); |
76 | ||
6280 | 77 | let meta = CryptoMetas.get(this.encryption); |
7536 | 78 | let symkey = meta.getKey(privkey, passphrase); |
79 | ||
80 | // Authenticate the encrypted blob with the expected HMAC |
|
8792 | 81 | if (Utils.sha256HMAC(this.ciphertext, symkey.hmacKey) != this.hmac) |
4 | 82 | throw "Server attack?! SHA256 HMAC mismatch: " + this.hmac; |
83 | ||
10040 | 84 | this.cleartext = JSON.parse(Svc.Crypto.decrypt(this.ciphertext, symkey, |
5020 | 85 | this.IV)); |
3765 | 86 | this.ciphertext = null; |
87 | ||
88 | // Verify that the encrypted id matches the requested record's id |
|
6275 | 89 | if (this.cleartext.id != this.id) |
8 | 90 | throw "Server attack?! Id mismatch: " + [this.cleartext.id, this.id]; |
91 | ||
2508 | 92 | return this.cleartext; |
93 | }, |
|
94 | ||
22 | 95 | toString: function CryptoWrap_toString() "{ " + [ |
96 | "id: " + this.id, |
|
97 | "index: " + this.sortindex, |
|
98 | "modified: " + this.modified, |
|
99 | "payload: " + (this.deleted ? "DELETED" : JSON.stringify(this.cleartext)) |
|
100 | ].join("\n ") + " }", |
|
101 | ||
102 | // The custom setter below masks the parent's getter, so explicitly call it :( |
|
77772 | 103 | get id() WBORecord.prototype.__lookupGetter__("id").call(this), |
104 | ||
105 | // Keep both plaintext and encrypted versions of the id to verify integrity |
|
4807 | 106 | set id(val) { |
47520 | 107 | WBORecord.prototype.__lookupSetter__("id").call(this, val); |
19008 | 108 | return this.cleartext.id = val; |
109 | }, |
|
110 | }; |
|
111 | ||
165 | 112 | Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "encryption", "IV", |
55 | 113 | "hmac"]); |
77 | 114 | Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted"); |
115 | ||
41 | 116 | function CryptoMeta(uri) { |
114 | 117 | WBORecord.call(this, uri); |
95 | 118 | this.keyring = {}; |
119 | } |
|
22 | 120 | CryptoMeta.prototype = { |
33 | 121 | __proto__: WBORecord.prototype, |
22 | 122 | _logName: "Record.CryptoMeta", |
123 | ||
33 | 124 | getKey: function CryptoMeta_getKey(privkey, passphrase) { |
125 | // get the uri to our public key |
|
44 | 126 | let pubkeyUri = privkey.publicKeyUri.spec; |
127 | ||
128 | // each hash key is a relative uri, resolve those and match against ours |
|
22 | 129 | let wrapped_key; |
66 | 130 | for (let relUri in this.keyring) { |
77 | 131 | if (pubkeyUri == this.baseUri.resolve(relUri)) |
110 | 132 | wrapped_key = this.keyring[relUri]; |
133 | } |
|
33 | 134 | if (!wrapped_key) |
135 | throw "keyring doesn't contain a key for " + pubkeyUri; |
|
136 | ||
137 | // Make sure the wrapped key hasn't been tampered with |
|
66 | 138 | let localHMAC = Utils.sha256HMAC(wrapped_key.wrapped, this.hmacKey); |
33 | 139 | if (localHMAC != wrapped_key.hmac) |
8 | 140 | throw "Server attack?! SHA256 HMAC key fail: " + wrapped_key.hmac; |
141 | ||
142 | // Decrypt the symmetric key and make it a String object to add properties |
|
9 | 143 | let unwrappedKey = new String( |
27 | 144 | Svc.Crypto.unwrapSymmetricKey( |
9 | 145 | wrapped_key.wrapped, |
9 | 146 | privkey.keyData, |
9 | 147 | passphrase.password, |
9 | 148 | privkey.salt, |
36 | 149 | privkey.iv |
150 | ) |
|
151 | ); |
|
152 | ||
63 | 153 | unwrappedKey.hmacKey = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, |
27 | 154 | unwrappedKey); |
155 | ||
156 | // Cache the result after the first get and just return it |
|
10881 | 157 | return (this.getKey = function() unwrappedKey)(); |
158 | }, |
|
159 | ||
22 | 160 | addKey: function CryptoMeta_addKey(new_pubkey, privkey, passphrase) { |
161 | let symkey = this.getKey(privkey, passphrase); |
|
162 | this.addUnwrappedKey(new_pubkey, symkey); |
|
163 | }, |
|
164 | ||
34 | 165 | addUnwrappedKey: function CryptoMeta_addUnwrappedKey(new_pubkey, symkey) { |
166 | // get the new public key |
|
48 | 167 | if (typeof new_pubkey == "string") |
168 | new_pubkey = PubKeys.get(new_pubkey); |
|
169 | ||
170 | // each hash key is a relative uri, resolve those and |
|
171 | // if we find the one we're about to add, remove it |
|
48 | 172 | for (let relUri in this.keyring) { |
173 | if (pubkeyUri == this.uri.resolve(relUri)) |
|
48 | 174 | delete this.keyring[relUri]; |
175 | } |
|
176 | ||
177 | // Wrap the symmetric key and generate a HMAC for it |
|
84 | 178 | let wrapped = Svc.Crypto.wrapSymmetricKey(symkey, new_pubkey.keyData); |
60 | 179 | this.keyring[new_pubkey.uri.spec] = { |
24 | 180 | wrapped: wrapped, |
96 | 181 | hmac: Utils.sha256HMAC(wrapped, this.hmacKey) |
12 | 182 | }; |
183 | }, |
|
184 | ||
78 | 185 | get hmacKey() { |
138 | 186 | let passphrase = ID.get("WeaveCryptoID").password; |
207 | 187 | return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, passphrase); |
188 | } |
|
189 | }; |
|
190 | ||
77 | 191 | Utils.deferGetSet(CryptoMeta, "payload", "keyring"); |
192 | ||
77 | 193 | Utils.lazy(this, 'CryptoMetas', CryptoRecordManager); |
194 | ||
33 | 195 | function CryptoRecordManager() { |
66 | 196 | RecordManager.call(this); |
197 | } |
|
22 | 198 | CryptoRecordManager.prototype = { |
33 | 199 | __proto__: RecordManager.prototype, |
55 | 200 | _recordType: CryptoMeta |
11 | 201 | }; |