services/crypto/modules/utils.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/crypto/modules/utils.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,577 @@
     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 +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["CryptoUtils"];
    1.11 +
    1.12 +Cu.import("resource://services-common/observers.js");
    1.13 +Cu.import("resource://services-common/utils.js");
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +
    1.16 +this.CryptoUtils = {
    1.17 +  xor: function xor(a, b) {
    1.18 +    let bytes = [];
    1.19 +
    1.20 +    if (a.length != b.length) {
    1.21 +      throw new Error("can't xor unequal length strings: "+a.length+" vs "+b.length);
    1.22 +    }
    1.23 +
    1.24 +    for (let i = 0; i < a.length; i++) {
    1.25 +      bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
    1.26 +    }
    1.27 +
    1.28 +    return String.fromCharCode.apply(String, bytes);
    1.29 +  },
    1.30 +
    1.31 +  /**
    1.32 +   * Generate a string of random bytes.
    1.33 +   */
    1.34 +  generateRandomBytes: function generateRandomBytes(length) {
    1.35 +    let rng = Cc["@mozilla.org/security/random-generator;1"]
    1.36 +                .createInstance(Ci.nsIRandomGenerator);
    1.37 +    let bytes = rng.generateRandomBytes(length);
    1.38 +    return CommonUtils.byteArrayToString(bytes);
    1.39 +  },
    1.40 +
    1.41 +  /**
    1.42 +   * UTF8-encode a message and hash it with the given hasher. Returns a
    1.43 +   * string containing bytes. The hasher is reset if it's an HMAC hasher.
    1.44 +   */
    1.45 +  digestUTF8: function digestUTF8(message, hasher) {
    1.46 +    let data = this._utf8Converter.convertToByteArray(message, {});
    1.47 +    hasher.update(data, data.length);
    1.48 +    let result = hasher.finish(false);
    1.49 +    if (hasher instanceof Ci.nsICryptoHMAC) {
    1.50 +      hasher.reset();
    1.51 +    }
    1.52 +    return result;
    1.53 +  },
    1.54 +
    1.55 +  /**
    1.56 +   * Treat the given message as a bytes string and hash it with the given
    1.57 +   * hasher. Returns a string containing bytes. The hasher is reset if it's
    1.58 +   * an HMAC hasher.
    1.59 +   */
    1.60 +  digestBytes: function digestBytes(message, hasher) {
    1.61 +    // No UTF-8 encoding for you, sunshine.
    1.62 +    let bytes = [b.charCodeAt() for each (b in message)];
    1.63 +    hasher.update(bytes, bytes.length);
    1.64 +    let result = hasher.finish(false);
    1.65 +    if (hasher instanceof Ci.nsICryptoHMAC) {
    1.66 +      hasher.reset();
    1.67 +    }
    1.68 +    return result;
    1.69 +  },
    1.70 +
    1.71 +  /**
    1.72 +   * Encode the message into UTF-8 and feed the resulting bytes into the
    1.73 +   * given hasher. Does not return a hash. This can be called multiple times
    1.74 +   * with a single hasher, but eventually you must extract the result
    1.75 +   * yourself.
    1.76 +   */
    1.77 +  updateUTF8: function(message, hasher) {
    1.78 +    let bytes = this._utf8Converter.convertToByteArray(message, {});
    1.79 +    hasher.update(bytes, bytes.length);
    1.80 +  },
    1.81 +
    1.82 +  /**
    1.83 +   * UTF-8 encode a message and perform a SHA-1 over it.
    1.84 +   *
    1.85 +   * @param message
    1.86 +   *        (string) Buffer to perform operation on. Should be a JS string.
    1.87 +   *                 It is possible to pass in a string representing an array
    1.88 +   *                 of bytes. But, you probably don't want to UTF-8 encode
    1.89 +   *                 such data and thus should not be using this function.
    1.90 +   *
    1.91 +   * @return string
    1.92 +   *         Raw bytes constituting SHA-1 hash. Value is a JS string. Each
    1.93 +   *         character is the byte value for that offset. Returned string
    1.94 +   *         always has .length == 20.
    1.95 +   */
    1.96 +  UTF8AndSHA1: function UTF8AndSHA1(message) {
    1.97 +    let hasher = Cc["@mozilla.org/security/hash;1"]
    1.98 +                 .createInstance(Ci.nsICryptoHash);
    1.99 +    hasher.init(hasher.SHA1);
   1.100 +
   1.101 +    return CryptoUtils.digestUTF8(message, hasher);
   1.102 +  },
   1.103 +
   1.104 +  sha1: function sha1(message) {
   1.105 +    return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
   1.106 +  },
   1.107 +
   1.108 +  sha1Base32: function sha1Base32(message) {
   1.109 +    return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
   1.110 +  },
   1.111 +
   1.112 +  /**
   1.113 +   * Produce an HMAC key object from a key string.
   1.114 +   */
   1.115 +  makeHMACKey: function makeHMACKey(str) {
   1.116 +    return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
   1.117 +  },
   1.118 +
   1.119 +  /**
   1.120 +   * Produce an HMAC hasher and initialize it with the given HMAC key.
   1.121 +   */
   1.122 +  makeHMACHasher: function makeHMACHasher(type, key) {
   1.123 +    let hasher = Cc["@mozilla.org/security/hmac;1"]
   1.124 +                   .createInstance(Ci.nsICryptoHMAC);
   1.125 +    hasher.init(type, key);
   1.126 +    return hasher;
   1.127 +  },
   1.128 +
   1.129 +  /**
   1.130 +   * HMAC-based Key Derivation (RFC 5869).
   1.131 +   */
   1.132 +  hkdf: function hkdf(ikm, xts, info, len) {
   1.133 +    const BLOCKSIZE = 256 / 8;
   1.134 +    if (typeof xts === undefined)
   1.135 +      xts = String.fromCharCode(0, 0, 0, 0,  0, 0, 0, 0,
   1.136 +                                0, 0, 0, 0,  0, 0, 0, 0,
   1.137 +                                0, 0, 0, 0,  0, 0, 0, 0,
   1.138 +                                0, 0, 0, 0,  0, 0, 0, 0);
   1.139 +    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
   1.140 +                                       CryptoUtils.makeHMACKey(xts));
   1.141 +    let prk = CryptoUtils.digestBytes(ikm, h);
   1.142 +    return CryptoUtils.hkdfExpand(prk, info, len);
   1.143 +  },
   1.144 +
   1.145 +  /**
   1.146 +   * HMAC-based Key Derivation Step 2 according to RFC 5869.
   1.147 +   */
   1.148 +  hkdfExpand: function hkdfExpand(prk, info, len) {
   1.149 +    const BLOCKSIZE = 256 / 8;
   1.150 +    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
   1.151 +                                       CryptoUtils.makeHMACKey(prk));
   1.152 +    let T = "";
   1.153 +    let Tn = "";
   1.154 +    let iterations = Math.ceil(len/BLOCKSIZE);
   1.155 +    for (let i = 0; i < iterations; i++) {
   1.156 +      Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
   1.157 +      T += Tn;
   1.158 +    }
   1.159 +    return T.slice(0, len);
   1.160 +  },
   1.161 +
   1.162 +  /**
   1.163 +   * PBKDF2 implementation in Javascript.
   1.164 +   *
   1.165 +   * The arguments to this function correspond to items in
   1.166 +   * PKCS #5, v2.0 pp. 9-10
   1.167 +   *
   1.168 +   * P: the passphrase, an octet string:              e.g., "secret phrase"
   1.169 +   * S: the salt, an octet string:                    e.g., "DNXPzPpiwn"
   1.170 +   * c: the number of iterations, a positive integer: e.g., 4096
   1.171 +   * dkLen: the length in octets of the destination
   1.172 +   *        key, a positive integer:                  e.g., 16
   1.173 +   * hmacAlg: The algorithm to use for hmac
   1.174 +   * hmacLen: The hmac length
   1.175 +   *
   1.176 +   * The default value of 20 for hmacLen is appropriate for SHA1.  For SHA256,
   1.177 +   * hmacLen should be 32.
   1.178 +   *
   1.179 +   * The output is an octet string of length dkLen, which you
   1.180 +   * can encode as you wish.
   1.181 +   */
   1.182 +  pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen,
   1.183 +                       hmacAlg=Ci.nsICryptoHMAC.SHA1, hmacLen=20) {
   1.184 +
   1.185 +    // We don't have a default in the algo itself, as NSS does.
   1.186 +    // Use the constant.
   1.187 +    if (!dkLen) {
   1.188 +      dkLen = SYNC_KEY_DECODED_LENGTH;
   1.189 +    }
   1.190 +
   1.191 +    function F(S, c, i, h) {
   1.192 +
   1.193 +      function XOR(a, b, isA) {
   1.194 +        if (a.length != b.length) {
   1.195 +          return false;
   1.196 +        }
   1.197 +
   1.198 +        let val = [];
   1.199 +        for (let i = 0; i < a.length; i++) {
   1.200 +          if (isA) {
   1.201 +            val[i] = a[i] ^ b[i];
   1.202 +          } else {
   1.203 +            val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
   1.204 +          }
   1.205 +        }
   1.206 +
   1.207 +        return val;
   1.208 +      }
   1.209 +
   1.210 +      let ret;
   1.211 +      let U = [];
   1.212 +
   1.213 +      /* Encode i into 4 octets: _INT */
   1.214 +      let I = [];
   1.215 +      I[0] = String.fromCharCode((i >> 24) & 0xff);
   1.216 +      I[1] = String.fromCharCode((i >> 16) & 0xff);
   1.217 +      I[2] = String.fromCharCode((i >> 8) & 0xff);
   1.218 +      I[3] = String.fromCharCode(i & 0xff);
   1.219 +
   1.220 +      U[0] = CryptoUtils.digestBytes(S + I.join(''), h);
   1.221 +      for (let j = 1; j < c; j++) {
   1.222 +        U[j] = CryptoUtils.digestBytes(U[j - 1], h);
   1.223 +      }
   1.224 +
   1.225 +      ret = U[0];
   1.226 +      for (let j = 1; j < c; j++) {
   1.227 +        ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
   1.228 +      }
   1.229 +
   1.230 +      return ret;
   1.231 +    }
   1.232 +
   1.233 +    let l = Math.ceil(dkLen / hmacLen);
   1.234 +    let r = dkLen - ((l - 1) * hmacLen);
   1.235 +
   1.236 +    // Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
   1.237 +    let h = CryptoUtils.makeHMACHasher(hmacAlg,
   1.238 +                                       CryptoUtils.makeHMACKey(P));
   1.239 +
   1.240 +    let T = [];
   1.241 +    for (let i = 0; i < l;) {
   1.242 +      T[i] = F(S, c, ++i, h);
   1.243 +    }
   1.244 +
   1.245 +    let ret = "";
   1.246 +    for (let i = 0; i < l-1;) {
   1.247 +      ret += T[i++];
   1.248 +    }
   1.249 +    ret += T[l - 1].substr(0, r);
   1.250 +
   1.251 +    return ret;
   1.252 +  },
   1.253 +
   1.254 +  deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase,
   1.255 +                                                            salt,
   1.256 +                                                            keyLength,
   1.257 +                                                            forceJS) {
   1.258 +    if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
   1.259 +      return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
   1.260 +    }
   1.261 +    else {
   1.262 +      // Fall back to JS implementation.
   1.263 +      // 4096 is hardcoded in WeaveCrypto, so do so here.
   1.264 +      return CryptoUtils.pbkdf2Generate(passphrase, atob(salt), 4096,
   1.265 +                                        keyLength);
   1.266 +    }
   1.267 +  },
   1.268 +
   1.269 +  /**
   1.270 +   * Compute the HTTP MAC SHA-1 for an HTTP request.
   1.271 +   *
   1.272 +   * @param  identifier
   1.273 +   *         (string) MAC Key Identifier.
   1.274 +   * @param  key
   1.275 +   *         (string) MAC Key.
   1.276 +   * @param  method
   1.277 +   *         (string) HTTP request method.
   1.278 +   * @param  URI
   1.279 +   *         (nsIURI) HTTP request URI.
   1.280 +   * @param  extra
   1.281 +   *         (object) Optional extra parameters. Valid keys are:
   1.282 +   *           nonce_bytes - How many bytes the nonce should be. This defaults
   1.283 +   *             to 8. Note that this many bytes are Base64 encoded, so the
   1.284 +   *             string length of the nonce will be longer than this value.
   1.285 +   *           ts - Timestamp to use. Should only be defined for testing.
   1.286 +   *           nonce - String nonce. Should only be defined for testing as this
   1.287 +   *             function will generate a cryptographically secure random one
   1.288 +   *             if not defined.
   1.289 +   *           ext - Extra string to be included in MAC. Per the HTTP MAC spec,
   1.290 +   *             the format is undefined and thus application specific.
   1.291 +   * @returns
   1.292 +   *         (object) Contains results of operation and input arguments (for
   1.293 +   *           symmetry). The object has the following keys:
   1.294 +   *
   1.295 +   *           identifier - (string) MAC Key Identifier (from arguments).
   1.296 +   *           key - (string) MAC Key (from arguments).
   1.297 +   *           method - (string) HTTP request method (from arguments).
   1.298 +   *           hostname - (string) HTTP hostname used (derived from arguments).
   1.299 +   *           port - (string) HTTP port number used (derived from arguments).
   1.300 +   *           mac - (string) Raw HMAC digest bytes.
   1.301 +   *           getHeader - (function) Call to obtain the string Authorization
   1.302 +   *             header value for this invocation.
   1.303 +   *           nonce - (string) Nonce value used.
   1.304 +   *           ts - (number) Integer seconds since Unix epoch that was used.
   1.305 +   */
   1.306 +  computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
   1.307 +                                                  uri, extra) {
   1.308 +    let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
   1.309 +    let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
   1.310 +
   1.311 +    // We are allowed to use more than the Base64 alphabet if we want.
   1.312 +    let nonce = (extra && extra.nonce)
   1.313 +                ? extra.nonce
   1.314 +                : btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
   1.315 +
   1.316 +    let host = uri.asciiHost;
   1.317 +    let port;
   1.318 +    let usedMethod = method.toUpperCase();
   1.319 +
   1.320 +    if (uri.port != -1) {
   1.321 +      port = uri.port;
   1.322 +    } else if (uri.scheme == "http") {
   1.323 +      port = "80";
   1.324 +    } else if (uri.scheme == "https") {
   1.325 +      port = "443";
   1.326 +    } else {
   1.327 +      throw new Error("Unsupported URI scheme: " + uri.scheme);
   1.328 +    }
   1.329 +
   1.330 +    let ext = (extra && extra.ext) ? extra.ext : "";
   1.331 +
   1.332 +    let requestString = ts.toString(10) + "\n" +
   1.333 +                        nonce           + "\n" +
   1.334 +                        usedMethod      + "\n" +
   1.335 +                        uri.path        + "\n" +
   1.336 +                        host            + "\n" +
   1.337 +                        port            + "\n" +
   1.338 +                        ext             + "\n";
   1.339 +
   1.340 +    let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
   1.341 +                                            CryptoUtils.makeHMACKey(key));
   1.342 +    let mac = CryptoUtils.digestBytes(requestString, hasher);
   1.343 +
   1.344 +    function getHeader() {
   1.345 +      return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
   1.346 +                                              this.nonce, this.mac, this.ext);
   1.347 +    }
   1.348 +
   1.349 +    return {
   1.350 +      identifier: identifier,
   1.351 +      key:        key,
   1.352 +      method:     usedMethod,
   1.353 +      hostname:   host,
   1.354 +      port:       port,
   1.355 +      mac:        mac,
   1.356 +      nonce:      nonce,
   1.357 +      ts:         ts,
   1.358 +      ext:        ext,
   1.359 +      getHeader:  getHeader
   1.360 +    };
   1.361 +  },
   1.362 +
   1.363 +
   1.364 +  /**
   1.365 +   * Obtain the HTTP MAC Authorization header value from fields.
   1.366 +   *
   1.367 +   * @param  identifier
   1.368 +   *         (string) MAC key identifier.
   1.369 +   * @param  ts
   1.370 +   *         (number) Integer seconds since Unix epoch.
   1.371 +   * @param  nonce
   1.372 +   *         (string) Nonce value.
   1.373 +   * @param  mac
   1.374 +   *         (string) Computed HMAC digest (raw bytes).
   1.375 +   * @param  ext
   1.376 +   *         (optional) (string) Extra string content.
   1.377 +   * @returns
   1.378 +   *         (string) Value to put in Authorization header.
   1.379 +   */
   1.380 +  getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
   1.381 +                                                      mac, ext) {
   1.382 +    let header ='MAC id="' + identifier + '", ' +
   1.383 +                'ts="'     + ts         + '", ' +
   1.384 +                'nonce="'  + nonce      + '", ' +
   1.385 +                'mac="'    + btoa(mac)  + '"';
   1.386 +
   1.387 +    if (!ext) {
   1.388 +      return header;
   1.389 +    }
   1.390 +
   1.391 +    return header += ', ext="' + ext +'"';
   1.392 +  },
   1.393 +
   1.394 +  /**
   1.395 +   * Given an HTTP header value, strip out any attributes.
   1.396 +   */
   1.397 +
   1.398 +  stripHeaderAttributes: function(value) {
   1.399 +    let value = value || "";
   1.400 +    let i = value.indexOf(";");
   1.401 +    return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
   1.402 +  },
   1.403 +
   1.404 +  /**
   1.405 +   * Compute the HAWK client values (mostly the header) for an HTTP request.
   1.406 +   *
   1.407 +   * @param  URI
   1.408 +   *         (nsIURI) HTTP request URI.
   1.409 +   * @param  method
   1.410 +   *         (string) HTTP request method.
   1.411 +   * @param  options
   1.412 +   *         (object) extra parameters (all but "credentials" are optional):
   1.413 +   *           credentials - (object, mandatory) HAWK credentials object.
   1.414 +   *             All three keys are required:
   1.415 +   *             id - (string) key identifier
   1.416 +   *             key - (string) raw key bytes
   1.417 +   *             algorithm - (string) which hash to use: "sha1" or "sha256"
   1.418 +   *           ext - (string) application-specific data, included in MAC
   1.419 +   *           localtimeOffsetMsec - (number) local clock offset (vs server)
   1.420 +   *           payload - (string) payload to include in hash, containing the
   1.421 +   *                     HTTP request body. If not provided, the HAWK hash
   1.422 +   *                     will not cover the request body, and the server
   1.423 +   *                     should not check it either. This will be UTF-8
   1.424 +   *                     encoded into bytes before hashing. This function
   1.425 +   *                     cannot handle arbitrary binary data, sorry (the
   1.426 +   *                     UTF-8 encoding process will corrupt any codepoints
   1.427 +   *                     between U+0080 and U+00FF). Callers must be careful
   1.428 +   *                     to use an HTTP client function which encodes the
   1.429 +   *                     payload exactly the same way, otherwise the hash
   1.430 +   *                     will not match.
   1.431 +   *           contentType - (string) payload Content-Type. This is included
   1.432 +   *                         (without any attributes like "charset=") in the
   1.433 +   *                         HAWK hash. It does *not* affect interpretation
   1.434 +   *                         of the "payload" property.
   1.435 +   *           hash - (base64 string) pre-calculated payload hash. If
   1.436 +   *                  provided, "payload" is ignored.
   1.437 +   *           ts - (number) pre-calculated timestamp, secs since epoch
   1.438 +   *           now - (number) current time, ms-since-epoch, for tests
   1.439 +   *           nonce - (string) pre-calculated nonce. Should only be defined
   1.440 +   *                   for testing as this function will generate a
   1.441 +   *                   cryptographically secure random one if not defined.
   1.442 +   * @returns
   1.443 +   *         (object) Contains results of operation. The object has the
   1.444 +   *         following keys:
   1.445 +   *           field - (string) HAWK header, to use in Authorization: header
   1.446 +   *           artifacts - (object) other generated values:
   1.447 +   *             ts - (number) timestamp, in seconds since epoch
   1.448 +   *             nonce - (string)
   1.449 +   *             method - (string)
   1.450 +   *             resource - (string) path plus querystring
   1.451 +   *             host - (string)
   1.452 +   *             port - (number)
   1.453 +   *             hash - (string) payload hash (base64)
   1.454 +   *             ext - (string) app-specific data
   1.455 +   *             MAC - (string) request MAC (base64)
   1.456 +   */
   1.457 +  computeHAWK: function(uri, method, options) {
   1.458 +    let credentials = options.credentials;
   1.459 +    let ts = options.ts || Math.floor(((options.now || Date.now()) +
   1.460 +                                       (options.localtimeOffsetMsec || 0))
   1.461 +                                      / 1000);
   1.462 +
   1.463 +    let hash_algo, hmac_algo;
   1.464 +    if (credentials.algorithm == "sha1") {
   1.465 +      hash_algo = Ci.nsICryptoHash.SHA1;
   1.466 +      hmac_algo = Ci.nsICryptoHMAC.SHA1;
   1.467 +    } else if (credentials.algorithm == "sha256") {
   1.468 +      hash_algo = Ci.nsICryptoHash.SHA256;
   1.469 +      hmac_algo = Ci.nsICryptoHMAC.SHA256;
   1.470 +    } else {
   1.471 +      throw new Error("Unsupported algorithm: " + credentials.algorithm);
   1.472 +    }
   1.473 +
   1.474 +    let port;
   1.475 +    if (uri.port != -1) {
   1.476 +      port = uri.port;
   1.477 +    } else if (uri.scheme == "http") {
   1.478 +      port = 80;
   1.479 +    } else if (uri.scheme == "https") {
   1.480 +      port = 443;
   1.481 +    } else {
   1.482 +      throw new Error("Unsupported URI scheme: " + uri.scheme);
   1.483 +    }
   1.484 +
   1.485 +    let artifacts = {
   1.486 +      ts: ts,
   1.487 +      nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
   1.488 +      method: method.toUpperCase(),
   1.489 +      resource: uri.path, // This includes both path and search/queryarg.
   1.490 +      host: uri.asciiHost.toLowerCase(), // This includes punycoding.
   1.491 +      port: port.toString(10),
   1.492 +      hash: options.hash,
   1.493 +      ext: options.ext,
   1.494 +    };
   1.495 +
   1.496 +    let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
   1.497 +
   1.498 +    if (!artifacts.hash && options.hasOwnProperty("payload")
   1.499 +        && options.payload) {
   1.500 +      let hasher = Cc["@mozilla.org/security/hash;1"]
   1.501 +                     .createInstance(Ci.nsICryptoHash);
   1.502 +      hasher.init(hash_algo);
   1.503 +      CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
   1.504 +      CryptoUtils.updateUTF8(contentType+"\n", hasher);
   1.505 +      CryptoUtils.updateUTF8(options.payload, hasher);
   1.506 +      CryptoUtils.updateUTF8("\n", hasher);
   1.507 +      let hash = hasher.finish(false);
   1.508 +      // HAWK specifies this .hash to use +/ (not _-) and include the
   1.509 +      // trailing "==" padding.
   1.510 +      let hash_b64 = btoa(hash);
   1.511 +      artifacts.hash = hash_b64;
   1.512 +    }
   1.513 +
   1.514 +    let requestString = ("hawk.1.header"        + "\n" +
   1.515 +                         artifacts.ts.toString(10) + "\n" +
   1.516 +                         artifacts.nonce        + "\n" +
   1.517 +                         artifacts.method       + "\n" +
   1.518 +                         artifacts.resource     + "\n" +
   1.519 +                         artifacts.host         + "\n" +
   1.520 +                         artifacts.port         + "\n" +
   1.521 +                         (artifacts.hash || "") + "\n");
   1.522 +    if (artifacts.ext) {
   1.523 +      requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
   1.524 +    }
   1.525 +    requestString += "\n";
   1.526 +
   1.527 +    let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
   1.528 +                                            CryptoUtils.makeHMACKey(credentials.key));
   1.529 +    artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
   1.530 +    // The output MAC uses "+" and "/", and padded== .
   1.531 +
   1.532 +    function escape(attribute) {
   1.533 +      // This is used for "x=y" attributes inside HTTP headers.
   1.534 +      return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
   1.535 +    }
   1.536 +    let header = ('Hawk id="' + credentials.id + '", ' +
   1.537 +                  'ts="' + artifacts.ts + '", ' +
   1.538 +                  'nonce="' + artifacts.nonce + '", ' +
   1.539 +                  (artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
   1.540 +                  (artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
   1.541 +                  'mac="' + artifacts.mac + '"');
   1.542 +    return {
   1.543 +      artifacts: artifacts,
   1.544 +      field: header,
   1.545 +    };
   1.546 +  },
   1.547 +
   1.548 +};
   1.549 +
   1.550 +XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
   1.551 +  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   1.552 +                    .createInstance(Ci.nsIScriptableUnicodeConverter);
   1.553 +  converter.charset = "UTF-8";
   1.554 +
   1.555 +  return converter;
   1.556 +});
   1.557 +
   1.558 +let Svc = {};
   1.559 +
   1.560 +XPCOMUtils.defineLazyServiceGetter(Svc,
   1.561 +                                   "KeyFactory",
   1.562 +                                   "@mozilla.org/security/keyobjectfactory;1",
   1.563 +                                   "nsIKeyObjectFactory");
   1.564 +
   1.565 +Svc.__defineGetter__("Crypto", function() {
   1.566 +  let ns = {};
   1.567 +  Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
   1.568 +
   1.569 +  let wc = new ns.WeaveCrypto();
   1.570 +  delete Svc.Crypto;
   1.571 +  return Svc.Crypto = wc;
   1.572 +});
   1.573 +
   1.574 +Observers.add("xpcom-shutdown", function unloadServices() {
   1.575 +  Observers.remove("xpcom-shutdown", unloadServices);
   1.576 +
   1.577 +  for (let k in Svc) {
   1.578 +    delete Svc[k];
   1.579 +  }
   1.580 +});

mercurial