forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			243 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | 
						|
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/* global Components */
 | 
						|
 | 
						|
var loop = loop || {};
 | 
						|
var inChrome = typeof Components != "undefined" && "utils" in Components;
 | 
						|
 | 
						|
(function(rootObject) {
 | 
						|
  "use strict";
 | 
						|
 | 
						|
  var sharedUtils;
 | 
						|
  if (inChrome) {
 | 
						|
    this.EXPORTED_SYMBOLS = ["LoopCrypto"];
 | 
						|
    var Cu = Components.utils;
 | 
						|
    Cu.importGlobalProperties(["crypto"]);
 | 
						|
    rootObject = {
 | 
						|
      crypto: crypto
 | 
						|
    };
 | 
						|
    sharedUtils = Cu.import("resource:///modules/loop/utils.js", {}).utils;
 | 
						|
  } else {
 | 
						|
    sharedUtils = this.shared.utils;
 | 
						|
  }
 | 
						|
 | 
						|
  var ALGORITHM = "AES-GCM";
 | 
						|
  var KEY_LENGTH = 128;
 | 
						|
  // We use JSON web key formats for the generated keys.
 | 
						|
  // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
 | 
						|
  var KEY_FORMAT = "jwk";
 | 
						|
  // This is the JSON web key type from the generateKey algorithm.
 | 
						|
  var KEY_TYPE = "oct";
 | 
						|
  var ENCRYPT_TAG_LENGTH = 128;
 | 
						|
  var INITIALIZATION_VECTOR_LENGTH = 12;
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets a new root object.  This is useful for testing crypto not supported as
 | 
						|
   * it allows us to fake crypto not being present.
 | 
						|
   * In beforeEach(), loop.crypto.setRootObject is used to
 | 
						|
   * substitute a fake window, and in afterEach(), the real window object is
 | 
						|
   * replaced.
 | 
						|
   *
 | 
						|
   * @param {Object}
 | 
						|
   */
 | 
						|
  function setRootObject(obj) {
 | 
						|
    rootObject = obj;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Determines if Web Crypto is supported by this browser.
 | 
						|
   *
 | 
						|
   * @return {Boolean} True if Web Crypto is supported
 | 
						|
   */
 | 
						|
  function isSupported() {
 | 
						|
    return "crypto" in rootObject;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Generates a random key using the Web Crypto libraries.
 | 
						|
   *
 | 
						|
   * @return {Promise} A promise which is rejected on failure, or resolved
 | 
						|
   *                   with a string that is in the JSON web key format.
 | 
						|
   */
 | 
						|
  function generateKey() {
 | 
						|
    if (!isSupported()) {
 | 
						|
      throw new Error("Web Crypto is not supported");
 | 
						|
    }
 | 
						|
 | 
						|
    return new Promise(function(resolve, reject) {
 | 
						|
      // First get a crypto key.
 | 
						|
      rootObject.crypto.subtle.generateKey({name: ALGORITHM, length: KEY_LENGTH },
 | 
						|
        // `true` means that the key can be extracted from the CryptoKey object.
 | 
						|
        true,
 | 
						|
        // Usages for the key.
 | 
						|
        ["encrypt", "decrypt"]
 | 
						|
      ).then(function(cryptoKey) {
 | 
						|
        // Now extract the key in the JSON web key format.
 | 
						|
        return rootObject.crypto.subtle.exportKey(KEY_FORMAT, cryptoKey);
 | 
						|
      }).then(function(exportedKey) {
 | 
						|
        // Lastly resolve the promise with the new key.
 | 
						|
        resolve(exportedKey.k);
 | 
						|
      }).catch(function(error) {
 | 
						|
        reject(error);
 | 
						|
      });
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Encrypts an object using the specified key.
 | 
						|
   *
 | 
						|
   * @param {String} key      The key to use for encryption. This should have
 | 
						|
   *                          been generated by generateKey.
 | 
						|
   * @param {String} data     The string to be encrypted.
 | 
						|
   *
 | 
						|
   * @return {Promise} A promise which is rejected on failure, or resolved
 | 
						|
   *                   with a string that is the encrypted context.
 | 
						|
   */
 | 
						|
  function encryptBytes(key, data) {
 | 
						|
    if (!isSupported()) {
 | 
						|
      throw new Error("Web Crypto is not supported");
 | 
						|
    }
 | 
						|
 | 
						|
    var iv = new Uint8Array(INITIALIZATION_VECTOR_LENGTH);
 | 
						|
 | 
						|
    return new Promise(function(resolve, reject) {
 | 
						|
      // First import the key to a format we can use.
 | 
						|
      rootObject.crypto.subtle.importKey(KEY_FORMAT,
 | 
						|
        {k: key, kty: KEY_TYPE},
 | 
						|
        ALGORITHM,
 | 
						|
        // If the key is extractable.
 | 
						|
        true,
 | 
						|
        // What we're using it for.
 | 
						|
        ["encrypt"]
 | 
						|
      ).then(function(cryptoKey) {
 | 
						|
        // Now we've got the cryptoKey, we can do the actual encryption.
 | 
						|
 | 
						|
        // First get the data into the format we need.
 | 
						|
        var dataBuffer = sharedUtils.strToUint8Array(data);
 | 
						|
 | 
						|
        // It is critically important to change the IV any time the
 | 
						|
        // encrypted information is updated.
 | 
						|
        rootObject.crypto.getRandomValues(iv);
 | 
						|
 | 
						|
        return rootObject.crypto.subtle.encrypt({
 | 
						|
            name: ALGORITHM,
 | 
						|
            iv: iv,
 | 
						|
            tagLength: ENCRYPT_TAG_LENGTH
 | 
						|
          }, cryptoKey,
 | 
						|
          dataBuffer);
 | 
						|
      }).then(function(cipherText) {
 | 
						|
        // Join the initialization vector and context for returning.
 | 
						|
        var joinedData = _mergeIVandCipherText(iv, new DataView(cipherText));
 | 
						|
 | 
						|
        // Now convert to a string and base-64 encode.
 | 
						|
        var encryptedData = sharedUtils.btoa(joinedData);
 | 
						|
 | 
						|
        resolve(encryptedData);
 | 
						|
      }).catch(function(error) {
 | 
						|
        reject(error);
 | 
						|
      });
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Decrypts an object using the specified key.
 | 
						|
   *
 | 
						|
   * @param {String} key           The key to use for encryption. This should have
 | 
						|
   *                               been generated by generateKey.
 | 
						|
   * @param {String} encryptedData The encrypted context.
 | 
						|
   * @return {Promise} A promise which is rejected on failure, or resolved
 | 
						|
   *                   with a string that is the decrypted context.
 | 
						|
   */
 | 
						|
  function decryptBytes(key, encryptedData) {
 | 
						|
    if (!isSupported()) {
 | 
						|
      throw new Error("Web Crypto is not supported");
 | 
						|
    }
 | 
						|
 | 
						|
    return new Promise(function(resolve, reject) {
 | 
						|
      // First import the key to a format we can use.
 | 
						|
      rootObject.crypto.subtle.importKey(KEY_FORMAT,
 | 
						|
        {k: key, kty: KEY_TYPE},
 | 
						|
        ALGORITHM,
 | 
						|
        // If the key is extractable.
 | 
						|
        true,
 | 
						|
        // What we're using it for.
 | 
						|
        ["decrypt"]
 | 
						|
      ).then(function(cryptoKey) {
 | 
						|
        // Now we've got the key, start the decryption.
 | 
						|
        var splitData = _splitIVandCipherText(encryptedData);
 | 
						|
 | 
						|
        return rootObject.crypto.subtle.decrypt({
 | 
						|
          name: ALGORITHM,
 | 
						|
          iv: splitData.iv,
 | 
						|
          tagLength: ENCRYPT_TAG_LENGTH
 | 
						|
        }, cryptoKey, splitData.cipherText);
 | 
						|
      }).then(function(plainText) {
 | 
						|
        // Now we just turn it back into a string and then an object.
 | 
						|
        resolve(sharedUtils.Uint8ArrayToStr(new Uint8Array(plainText)));
 | 
						|
      }).catch(function(error) {
 | 
						|
        reject(error);
 | 
						|
      });
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Appends the cipher text to the end of the initialization vector and
 | 
						|
   * returns the result.
 | 
						|
   *
 | 
						|
   * @param {Uint8Array} ivArray The array of initialization vector values.
 | 
						|
   * @param {DataView} cipherTextDataView The cipherText in data view format.
 | 
						|
   * @return {Uint8Array} An array of the IV and cipherText.
 | 
						|
   */
 | 
						|
  function _mergeIVandCipherText(ivArray, cipherTextDataView) {
 | 
						|
    // First we translate the data view to an array so we can get
 | 
						|
    // the length.
 | 
						|
    var cipherText = new Uint8Array(cipherTextDataView.buffer);
 | 
						|
    var cipherTextLength = cipherText.length;
 | 
						|
 | 
						|
    var joinedContext = new Uint8Array(INITIALIZATION_VECTOR_LENGTH + cipherTextLength);
 | 
						|
 | 
						|
    var i;
 | 
						|
    for (i = 0; i < INITIALIZATION_VECTOR_LENGTH; i++) {
 | 
						|
      joinedContext[i] = ivArray[i];
 | 
						|
    }
 | 
						|
 | 
						|
    for (i = 0; i < cipherTextLength; i++) {
 | 
						|
      joinedContext[i + INITIALIZATION_VECTOR_LENGTH] = cipherText[i];
 | 
						|
    }
 | 
						|
 | 
						|
    return joinedContext;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Takes the IV from the start of the passed in array and separates
 | 
						|
   * out the cipher text.
 | 
						|
   *
 | 
						|
   * @param {String} encryptedData Encrypted data in base64 format.
 | 
						|
   * @return {Object} An object consisting of two items: iv and cipherText,
 | 
						|
   *                  both are Uint8Arrays.
 | 
						|
   */
 | 
						|
  function _splitIVandCipherText(encryptedData) {
 | 
						|
    // Convert into byte arrays.
 | 
						|
    var encryptedDataArray = sharedUtils.atob(encryptedData);
 | 
						|
 | 
						|
    // Now split out the initialization vector and the cipherText.
 | 
						|
    var iv = encryptedDataArray.slice(0, INITIALIZATION_VECTOR_LENGTH);
 | 
						|
    var cipherText = encryptedDataArray.slice(INITIALIZATION_VECTOR_LENGTH,
 | 
						|
                                              encryptedDataArray.length);
 | 
						|
 | 
						|
    return {
 | 
						|
      iv: iv,
 | 
						|
      cipherText: cipherText
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  this[inChrome ? "LoopCrypto" : "crypto"] = {
 | 
						|
    decryptBytes: decryptBytes,
 | 
						|
    encryptBytes: encryptBytes,
 | 
						|
    generateKey: generateKey,
 | 
						|
    isSupported: isSupported,
 | 
						|
    setRootObject: setRootObject
 | 
						|
  };
 | 
						|
}).call(inChrome ? this : loop, this);
 |