Advertisement
Guest User

Untitled

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