services/crypto/modules/utils.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
michael@0 6
michael@0 7 this.EXPORTED_SYMBOLS = ["CryptoUtils"];
michael@0 8
michael@0 9 Cu.import("resource://services-common/observers.js");
michael@0 10 Cu.import("resource://services-common/utils.js");
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12
michael@0 13 this.CryptoUtils = {
michael@0 14 xor: function xor(a, b) {
michael@0 15 let bytes = [];
michael@0 16
michael@0 17 if (a.length != b.length) {
michael@0 18 throw new Error("can't xor unequal length strings: "+a.length+" vs "+b.length);
michael@0 19 }
michael@0 20
michael@0 21 for (let i = 0; i < a.length; i++) {
michael@0 22 bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
michael@0 23 }
michael@0 24
michael@0 25 return String.fromCharCode.apply(String, bytes);
michael@0 26 },
michael@0 27
michael@0 28 /**
michael@0 29 * Generate a string of random bytes.
michael@0 30 */
michael@0 31 generateRandomBytes: function generateRandomBytes(length) {
michael@0 32 let rng = Cc["@mozilla.org/security/random-generator;1"]
michael@0 33 .createInstance(Ci.nsIRandomGenerator);
michael@0 34 let bytes = rng.generateRandomBytes(length);
michael@0 35 return CommonUtils.byteArrayToString(bytes);
michael@0 36 },
michael@0 37
michael@0 38 /**
michael@0 39 * UTF8-encode a message and hash it with the given hasher. Returns a
michael@0 40 * string containing bytes. The hasher is reset if it's an HMAC hasher.
michael@0 41 */
michael@0 42 digestUTF8: function digestUTF8(message, hasher) {
michael@0 43 let data = this._utf8Converter.convertToByteArray(message, {});
michael@0 44 hasher.update(data, data.length);
michael@0 45 let result = hasher.finish(false);
michael@0 46 if (hasher instanceof Ci.nsICryptoHMAC) {
michael@0 47 hasher.reset();
michael@0 48 }
michael@0 49 return result;
michael@0 50 },
michael@0 51
michael@0 52 /**
michael@0 53 * Treat the given message as a bytes string and hash it with the given
michael@0 54 * hasher. Returns a string containing bytes. The hasher is reset if it's
michael@0 55 * an HMAC hasher.
michael@0 56 */
michael@0 57 digestBytes: function digestBytes(message, hasher) {
michael@0 58 // No UTF-8 encoding for you, sunshine.
michael@0 59 let bytes = [b.charCodeAt() for each (b in message)];
michael@0 60 hasher.update(bytes, bytes.length);
michael@0 61 let result = hasher.finish(false);
michael@0 62 if (hasher instanceof Ci.nsICryptoHMAC) {
michael@0 63 hasher.reset();
michael@0 64 }
michael@0 65 return result;
michael@0 66 },
michael@0 67
michael@0 68 /**
michael@0 69 * Encode the message into UTF-8 and feed the resulting bytes into the
michael@0 70 * given hasher. Does not return a hash. This can be called multiple times
michael@0 71 * with a single hasher, but eventually you must extract the result
michael@0 72 * yourself.
michael@0 73 */
michael@0 74 updateUTF8: function(message, hasher) {
michael@0 75 let bytes = this._utf8Converter.convertToByteArray(message, {});
michael@0 76 hasher.update(bytes, bytes.length);
michael@0 77 },
michael@0 78
michael@0 79 /**
michael@0 80 * UTF-8 encode a message and perform a SHA-1 over it.
michael@0 81 *
michael@0 82 * @param message
michael@0 83 * (string) Buffer to perform operation on. Should be a JS string.
michael@0 84 * It is possible to pass in a string representing an array
michael@0 85 * of bytes. But, you probably don't want to UTF-8 encode
michael@0 86 * such data and thus should not be using this function.
michael@0 87 *
michael@0 88 * @return string
michael@0 89 * Raw bytes constituting SHA-1 hash. Value is a JS string. Each
michael@0 90 * character is the byte value for that offset. Returned string
michael@0 91 * always has .length == 20.
michael@0 92 */
michael@0 93 UTF8AndSHA1: function UTF8AndSHA1(message) {
michael@0 94 let hasher = Cc["@mozilla.org/security/hash;1"]
michael@0 95 .createInstance(Ci.nsICryptoHash);
michael@0 96 hasher.init(hasher.SHA1);
michael@0 97
michael@0 98 return CryptoUtils.digestUTF8(message, hasher);
michael@0 99 },
michael@0 100
michael@0 101 sha1: function sha1(message) {
michael@0 102 return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
michael@0 103 },
michael@0 104
michael@0 105 sha1Base32: function sha1Base32(message) {
michael@0 106 return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
michael@0 107 },
michael@0 108
michael@0 109 /**
michael@0 110 * Produce an HMAC key object from a key string.
michael@0 111 */
michael@0 112 makeHMACKey: function makeHMACKey(str) {
michael@0 113 return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
michael@0 114 },
michael@0 115
michael@0 116 /**
michael@0 117 * Produce an HMAC hasher and initialize it with the given HMAC key.
michael@0 118 */
michael@0 119 makeHMACHasher: function makeHMACHasher(type, key) {
michael@0 120 let hasher = Cc["@mozilla.org/security/hmac;1"]
michael@0 121 .createInstance(Ci.nsICryptoHMAC);
michael@0 122 hasher.init(type, key);
michael@0 123 return hasher;
michael@0 124 },
michael@0 125
michael@0 126 /**
michael@0 127 * HMAC-based Key Derivation (RFC 5869).
michael@0 128 */
michael@0 129 hkdf: function hkdf(ikm, xts, info, len) {
michael@0 130 const BLOCKSIZE = 256 / 8;
michael@0 131 if (typeof xts === undefined)
michael@0 132 xts = String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0,
michael@0 133 0, 0, 0, 0, 0, 0, 0, 0,
michael@0 134 0, 0, 0, 0, 0, 0, 0, 0,
michael@0 135 0, 0, 0, 0, 0, 0, 0, 0);
michael@0 136 let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
michael@0 137 CryptoUtils.makeHMACKey(xts));
michael@0 138 let prk = CryptoUtils.digestBytes(ikm, h);
michael@0 139 return CryptoUtils.hkdfExpand(prk, info, len);
michael@0 140 },
michael@0 141
michael@0 142 /**
michael@0 143 * HMAC-based Key Derivation Step 2 according to RFC 5869.
michael@0 144 */
michael@0 145 hkdfExpand: function hkdfExpand(prk, info, len) {
michael@0 146 const BLOCKSIZE = 256 / 8;
michael@0 147 let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
michael@0 148 CryptoUtils.makeHMACKey(prk));
michael@0 149 let T = "";
michael@0 150 let Tn = "";
michael@0 151 let iterations = Math.ceil(len/BLOCKSIZE);
michael@0 152 for (let i = 0; i < iterations; i++) {
michael@0 153 Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
michael@0 154 T += Tn;
michael@0 155 }
michael@0 156 return T.slice(0, len);
michael@0 157 },
michael@0 158
michael@0 159 /**
michael@0 160 * PBKDF2 implementation in Javascript.
michael@0 161 *
michael@0 162 * The arguments to this function correspond to items in
michael@0 163 * PKCS #5, v2.0 pp. 9-10
michael@0 164 *
michael@0 165 * P: the passphrase, an octet string: e.g., "secret phrase"
michael@0 166 * S: the salt, an octet string: e.g., "DNXPzPpiwn"
michael@0 167 * c: the number of iterations, a positive integer: e.g., 4096
michael@0 168 * dkLen: the length in octets of the destination
michael@0 169 * key, a positive integer: e.g., 16
michael@0 170 * hmacAlg: The algorithm to use for hmac
michael@0 171 * hmacLen: The hmac length
michael@0 172 *
michael@0 173 * The default value of 20 for hmacLen is appropriate for SHA1. For SHA256,
michael@0 174 * hmacLen should be 32.
michael@0 175 *
michael@0 176 * The output is an octet string of length dkLen, which you
michael@0 177 * can encode as you wish.
michael@0 178 */
michael@0 179 pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen,
michael@0 180 hmacAlg=Ci.nsICryptoHMAC.SHA1, hmacLen=20) {
michael@0 181
michael@0 182 // We don't have a default in the algo itself, as NSS does.
michael@0 183 // Use the constant.
michael@0 184 if (!dkLen) {
michael@0 185 dkLen = SYNC_KEY_DECODED_LENGTH;
michael@0 186 }
michael@0 187
michael@0 188 function F(S, c, i, h) {
michael@0 189
michael@0 190 function XOR(a, b, isA) {
michael@0 191 if (a.length != b.length) {
michael@0 192 return false;
michael@0 193 }
michael@0 194
michael@0 195 let val = [];
michael@0 196 for (let i = 0; i < a.length; i++) {
michael@0 197 if (isA) {
michael@0 198 val[i] = a[i] ^ b[i];
michael@0 199 } else {
michael@0 200 val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
michael@0 201 }
michael@0 202 }
michael@0 203
michael@0 204 return val;
michael@0 205 }
michael@0 206
michael@0 207 let ret;
michael@0 208 let U = [];
michael@0 209
michael@0 210 /* Encode i into 4 octets: _INT */
michael@0 211 let I = [];
michael@0 212 I[0] = String.fromCharCode((i >> 24) & 0xff);
michael@0 213 I[1] = String.fromCharCode((i >> 16) & 0xff);
michael@0 214 I[2] = String.fromCharCode((i >> 8) & 0xff);
michael@0 215 I[3] = String.fromCharCode(i & 0xff);
michael@0 216
michael@0 217 U[0] = CryptoUtils.digestBytes(S + I.join(''), h);
michael@0 218 for (let j = 1; j < c; j++) {
michael@0 219 U[j] = CryptoUtils.digestBytes(U[j - 1], h);
michael@0 220 }
michael@0 221
michael@0 222 ret = U[0];
michael@0 223 for (let j = 1; j < c; j++) {
michael@0 224 ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
michael@0 225 }
michael@0 226
michael@0 227 return ret;
michael@0 228 }
michael@0 229
michael@0 230 let l = Math.ceil(dkLen / hmacLen);
michael@0 231 let r = dkLen - ((l - 1) * hmacLen);
michael@0 232
michael@0 233 // Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
michael@0 234 let h = CryptoUtils.makeHMACHasher(hmacAlg,
michael@0 235 CryptoUtils.makeHMACKey(P));
michael@0 236
michael@0 237 let T = [];
michael@0 238 for (let i = 0; i < l;) {
michael@0 239 T[i] = F(S, c, ++i, h);
michael@0 240 }
michael@0 241
michael@0 242 let ret = "";
michael@0 243 for (let i = 0; i < l-1;) {
michael@0 244 ret += T[i++];
michael@0 245 }
michael@0 246 ret += T[l - 1].substr(0, r);
michael@0 247
michael@0 248 return ret;
michael@0 249 },
michael@0 250
michael@0 251 deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase,
michael@0 252 salt,
michael@0 253 keyLength,
michael@0 254 forceJS) {
michael@0 255 if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
michael@0 256 return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
michael@0 257 }
michael@0 258 else {
michael@0 259 // Fall back to JS implementation.
michael@0 260 // 4096 is hardcoded in WeaveCrypto, so do so here.
michael@0 261 return CryptoUtils.pbkdf2Generate(passphrase, atob(salt), 4096,
michael@0 262 keyLength);
michael@0 263 }
michael@0 264 },
michael@0 265
michael@0 266 /**
michael@0 267 * Compute the HTTP MAC SHA-1 for an HTTP request.
michael@0 268 *
michael@0 269 * @param identifier
michael@0 270 * (string) MAC Key Identifier.
michael@0 271 * @param key
michael@0 272 * (string) MAC Key.
michael@0 273 * @param method
michael@0 274 * (string) HTTP request method.
michael@0 275 * @param URI
michael@0 276 * (nsIURI) HTTP request URI.
michael@0 277 * @param extra
michael@0 278 * (object) Optional extra parameters. Valid keys are:
michael@0 279 * nonce_bytes - How many bytes the nonce should be. This defaults
michael@0 280 * to 8. Note that this many bytes are Base64 encoded, so the
michael@0 281 * string length of the nonce will be longer than this value.
michael@0 282 * ts - Timestamp to use. Should only be defined for testing.
michael@0 283 * nonce - String nonce. Should only be defined for testing as this
michael@0 284 * function will generate a cryptographically secure random one
michael@0 285 * if not defined.
michael@0 286 * ext - Extra string to be included in MAC. Per the HTTP MAC spec,
michael@0 287 * the format is undefined and thus application specific.
michael@0 288 * @returns
michael@0 289 * (object) Contains results of operation and input arguments (for
michael@0 290 * symmetry). The object has the following keys:
michael@0 291 *
michael@0 292 * identifier - (string) MAC Key Identifier (from arguments).
michael@0 293 * key - (string) MAC Key (from arguments).
michael@0 294 * method - (string) HTTP request method (from arguments).
michael@0 295 * hostname - (string) HTTP hostname used (derived from arguments).
michael@0 296 * port - (string) HTTP port number used (derived from arguments).
michael@0 297 * mac - (string) Raw HMAC digest bytes.
michael@0 298 * getHeader - (function) Call to obtain the string Authorization
michael@0 299 * header value for this invocation.
michael@0 300 * nonce - (string) Nonce value used.
michael@0 301 * ts - (number) Integer seconds since Unix epoch that was used.
michael@0 302 */
michael@0 303 computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
michael@0 304 uri, extra) {
michael@0 305 let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
michael@0 306 let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
michael@0 307
michael@0 308 // We are allowed to use more than the Base64 alphabet if we want.
michael@0 309 let nonce = (extra && extra.nonce)
michael@0 310 ? extra.nonce
michael@0 311 : btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
michael@0 312
michael@0 313 let host = uri.asciiHost;
michael@0 314 let port;
michael@0 315 let usedMethod = method.toUpperCase();
michael@0 316
michael@0 317 if (uri.port != -1) {
michael@0 318 port = uri.port;
michael@0 319 } else if (uri.scheme == "http") {
michael@0 320 port = "80";
michael@0 321 } else if (uri.scheme == "https") {
michael@0 322 port = "443";
michael@0 323 } else {
michael@0 324 throw new Error("Unsupported URI scheme: " + uri.scheme);
michael@0 325 }
michael@0 326
michael@0 327 let ext = (extra && extra.ext) ? extra.ext : "";
michael@0 328
michael@0 329 let requestString = ts.toString(10) + "\n" +
michael@0 330 nonce + "\n" +
michael@0 331 usedMethod + "\n" +
michael@0 332 uri.path + "\n" +
michael@0 333 host + "\n" +
michael@0 334 port + "\n" +
michael@0 335 ext + "\n";
michael@0 336
michael@0 337 let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
michael@0 338 CryptoUtils.makeHMACKey(key));
michael@0 339 let mac = CryptoUtils.digestBytes(requestString, hasher);
michael@0 340
michael@0 341 function getHeader() {
michael@0 342 return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
michael@0 343 this.nonce, this.mac, this.ext);
michael@0 344 }
michael@0 345
michael@0 346 return {
michael@0 347 identifier: identifier,
michael@0 348 key: key,
michael@0 349 method: usedMethod,
michael@0 350 hostname: host,
michael@0 351 port: port,
michael@0 352 mac: mac,
michael@0 353 nonce: nonce,
michael@0 354 ts: ts,
michael@0 355 ext: ext,
michael@0 356 getHeader: getHeader
michael@0 357 };
michael@0 358 },
michael@0 359
michael@0 360
michael@0 361 /**
michael@0 362 * Obtain the HTTP MAC Authorization header value from fields.
michael@0 363 *
michael@0 364 * @param identifier
michael@0 365 * (string) MAC key identifier.
michael@0 366 * @param ts
michael@0 367 * (number) Integer seconds since Unix epoch.
michael@0 368 * @param nonce
michael@0 369 * (string) Nonce value.
michael@0 370 * @param mac
michael@0 371 * (string) Computed HMAC digest (raw bytes).
michael@0 372 * @param ext
michael@0 373 * (optional) (string) Extra string content.
michael@0 374 * @returns
michael@0 375 * (string) Value to put in Authorization header.
michael@0 376 */
michael@0 377 getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
michael@0 378 mac, ext) {
michael@0 379 let header ='MAC id="' + identifier + '", ' +
michael@0 380 'ts="' + ts + '", ' +
michael@0 381 'nonce="' + nonce + '", ' +
michael@0 382 'mac="' + btoa(mac) + '"';
michael@0 383
michael@0 384 if (!ext) {
michael@0 385 return header;
michael@0 386 }
michael@0 387
michael@0 388 return header += ', ext="' + ext +'"';
michael@0 389 },
michael@0 390
michael@0 391 /**
michael@0 392 * Given an HTTP header value, strip out any attributes.
michael@0 393 */
michael@0 394
michael@0 395 stripHeaderAttributes: function(value) {
michael@0 396 let value = value || "";
michael@0 397 let i = value.indexOf(";");
michael@0 398 return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
michael@0 399 },
michael@0 400
michael@0 401 /**
michael@0 402 * Compute the HAWK client values (mostly the header) for an HTTP request.
michael@0 403 *
michael@0 404 * @param URI
michael@0 405 * (nsIURI) HTTP request URI.
michael@0 406 * @param method
michael@0 407 * (string) HTTP request method.
michael@0 408 * @param options
michael@0 409 * (object) extra parameters (all but "credentials" are optional):
michael@0 410 * credentials - (object, mandatory) HAWK credentials object.
michael@0 411 * All three keys are required:
michael@0 412 * id - (string) key identifier
michael@0 413 * key - (string) raw key bytes
michael@0 414 * algorithm - (string) which hash to use: "sha1" or "sha256"
michael@0 415 * ext - (string) application-specific data, included in MAC
michael@0 416 * localtimeOffsetMsec - (number) local clock offset (vs server)
michael@0 417 * payload - (string) payload to include in hash, containing the
michael@0 418 * HTTP request body. If not provided, the HAWK hash
michael@0 419 * will not cover the request body, and the server
michael@0 420 * should not check it either. This will be UTF-8
michael@0 421 * encoded into bytes before hashing. This function
michael@0 422 * cannot handle arbitrary binary data, sorry (the
michael@0 423 * UTF-8 encoding process will corrupt any codepoints
michael@0 424 * between U+0080 and U+00FF). Callers must be careful
michael@0 425 * to use an HTTP client function which encodes the
michael@0 426 * payload exactly the same way, otherwise the hash
michael@0 427 * will not match.
michael@0 428 * contentType - (string) payload Content-Type. This is included
michael@0 429 * (without any attributes like "charset=") in the
michael@0 430 * HAWK hash. It does *not* affect interpretation
michael@0 431 * of the "payload" property.
michael@0 432 * hash - (base64 string) pre-calculated payload hash. If
michael@0 433 * provided, "payload" is ignored.
michael@0 434 * ts - (number) pre-calculated timestamp, secs since epoch
michael@0 435 * now - (number) current time, ms-since-epoch, for tests
michael@0 436 * nonce - (string) pre-calculated nonce. Should only be defined
michael@0 437 * for testing as this function will generate a
michael@0 438 * cryptographically secure random one if not defined.
michael@0 439 * @returns
michael@0 440 * (object) Contains results of operation. The object has the
michael@0 441 * following keys:
michael@0 442 * field - (string) HAWK header, to use in Authorization: header
michael@0 443 * artifacts - (object) other generated values:
michael@0 444 * ts - (number) timestamp, in seconds since epoch
michael@0 445 * nonce - (string)
michael@0 446 * method - (string)
michael@0 447 * resource - (string) path plus querystring
michael@0 448 * host - (string)
michael@0 449 * port - (number)
michael@0 450 * hash - (string) payload hash (base64)
michael@0 451 * ext - (string) app-specific data
michael@0 452 * MAC - (string) request MAC (base64)
michael@0 453 */
michael@0 454 computeHAWK: function(uri, method, options) {
michael@0 455 let credentials = options.credentials;
michael@0 456 let ts = options.ts || Math.floor(((options.now || Date.now()) +
michael@0 457 (options.localtimeOffsetMsec || 0))
michael@0 458 / 1000);
michael@0 459
michael@0 460 let hash_algo, hmac_algo;
michael@0 461 if (credentials.algorithm == "sha1") {
michael@0 462 hash_algo = Ci.nsICryptoHash.SHA1;
michael@0 463 hmac_algo = Ci.nsICryptoHMAC.SHA1;
michael@0 464 } else if (credentials.algorithm == "sha256") {
michael@0 465 hash_algo = Ci.nsICryptoHash.SHA256;
michael@0 466 hmac_algo = Ci.nsICryptoHMAC.SHA256;
michael@0 467 } else {
michael@0 468 throw new Error("Unsupported algorithm: " + credentials.algorithm);
michael@0 469 }
michael@0 470
michael@0 471 let port;
michael@0 472 if (uri.port != -1) {
michael@0 473 port = uri.port;
michael@0 474 } else if (uri.scheme == "http") {
michael@0 475 port = 80;
michael@0 476 } else if (uri.scheme == "https") {
michael@0 477 port = 443;
michael@0 478 } else {
michael@0 479 throw new Error("Unsupported URI scheme: " + uri.scheme);
michael@0 480 }
michael@0 481
michael@0 482 let artifacts = {
michael@0 483 ts: ts,
michael@0 484 nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
michael@0 485 method: method.toUpperCase(),
michael@0 486 resource: uri.path, // This includes both path and search/queryarg.
michael@0 487 host: uri.asciiHost.toLowerCase(), // This includes punycoding.
michael@0 488 port: port.toString(10),
michael@0 489 hash: options.hash,
michael@0 490 ext: options.ext,
michael@0 491 };
michael@0 492
michael@0 493 let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
michael@0 494
michael@0 495 if (!artifacts.hash && options.hasOwnProperty("payload")
michael@0 496 && options.payload) {
michael@0 497 let hasher = Cc["@mozilla.org/security/hash;1"]
michael@0 498 .createInstance(Ci.nsICryptoHash);
michael@0 499 hasher.init(hash_algo);
michael@0 500 CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
michael@0 501 CryptoUtils.updateUTF8(contentType+"\n", hasher);
michael@0 502 CryptoUtils.updateUTF8(options.payload, hasher);
michael@0 503 CryptoUtils.updateUTF8("\n", hasher);
michael@0 504 let hash = hasher.finish(false);
michael@0 505 // HAWK specifies this .hash to use +/ (not _-) and include the
michael@0 506 // trailing "==" padding.
michael@0 507 let hash_b64 = btoa(hash);
michael@0 508 artifacts.hash = hash_b64;
michael@0 509 }
michael@0 510
michael@0 511 let requestString = ("hawk.1.header" + "\n" +
michael@0 512 artifacts.ts.toString(10) + "\n" +
michael@0 513 artifacts.nonce + "\n" +
michael@0 514 artifacts.method + "\n" +
michael@0 515 artifacts.resource + "\n" +
michael@0 516 artifacts.host + "\n" +
michael@0 517 artifacts.port + "\n" +
michael@0 518 (artifacts.hash || "") + "\n");
michael@0 519 if (artifacts.ext) {
michael@0 520 requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
michael@0 521 }
michael@0 522 requestString += "\n";
michael@0 523
michael@0 524 let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
michael@0 525 CryptoUtils.makeHMACKey(credentials.key));
michael@0 526 artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
michael@0 527 // The output MAC uses "+" and "/", and padded== .
michael@0 528
michael@0 529 function escape(attribute) {
michael@0 530 // This is used for "x=y" attributes inside HTTP headers.
michael@0 531 return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
michael@0 532 }
michael@0 533 let header = ('Hawk id="' + credentials.id + '", ' +
michael@0 534 'ts="' + artifacts.ts + '", ' +
michael@0 535 'nonce="' + artifacts.nonce + '", ' +
michael@0 536 (artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
michael@0 537 (artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
michael@0 538 'mac="' + artifacts.mac + '"');
michael@0 539 return {
michael@0 540 artifacts: artifacts,
michael@0 541 field: header,
michael@0 542 };
michael@0 543 },
michael@0 544
michael@0 545 };
michael@0 546
michael@0 547 XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
michael@0 548 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 549 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 550 converter.charset = "UTF-8";
michael@0 551
michael@0 552 return converter;
michael@0 553 });
michael@0 554
michael@0 555 let Svc = {};
michael@0 556
michael@0 557 XPCOMUtils.defineLazyServiceGetter(Svc,
michael@0 558 "KeyFactory",
michael@0 559 "@mozilla.org/security/keyobjectfactory;1",
michael@0 560 "nsIKeyObjectFactory");
michael@0 561
michael@0 562 Svc.__defineGetter__("Crypto", function() {
michael@0 563 let ns = {};
michael@0 564 Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
michael@0 565
michael@0 566 let wc = new ns.WeaveCrypto();
michael@0 567 delete Svc.Crypto;
michael@0 568 return Svc.Crypto = wc;
michael@0 569 });
michael@0 570
michael@0 571 Observers.add("xpcom-shutdown", function unloadServices() {
michael@0 572 Observers.remove("xpcom-shutdown", unloadServices);
michael@0 573
michael@0 574 for (let k in Svc) {
michael@0 575 delete Svc[k];
michael@0 576 }
michael@0 577 });

mercurial