base_records/crypto.js

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
};