chromium/chrome/renderer/resources/extensions/platform_keys/subtle_crypto.js

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var utils = require('utils');
var internalAPI = getInternalApi('platformKeysInternal');
var keyModule = require('platformKeys.Key');
var getKeyIdentifier = keyModule.getKeyIdentifier;
var KeyUsage = keyModule.KeyUsage;

var normalizeAlgorithm =
    requireNative('platform_keys_natives').NormalizeAlgorithm;

// This error is thrown by the internal and public API's token functions and
// must be re-thrown by this custom binding. Keep this in sync with the C++ part
// of this API.
var errorInvalidToken = 'The token is not valid.';

// The following errors are specified in WebCrypto.
// TODO(pneubeck): These should be DOMExceptions.
function CreateNotSupportedError() {
  return new Error('The algorithm is not supported');
}

function CreateInvalidAccessError() {
  return new Error('The requested operation is not valid for the provided key');
}

function CreateSyntaxError() {
  return new Error('A required parameter was missing or out-of-range');
}

function CreateOperationError() {
  return new Error('The operation failed for an operation-specific reason');
}

// Catches an |internalErrorInvalidToken|. If so, forwards it to |reject| and
// returns true.
function catchInvalidTokenError(reject) {
  if (bindingUtil.hasLastError() &&
      bindingUtil.getLastErrorMessage() === errorInvalidToken) {
    var error = chrome.runtime.lastError;
    bindingUtil.clearLastError();
    reject(error);
    return true;
  }
  return false;
}

// Returns true if the |normalizedAlgorithm| returned by normalizeAlgorithm() is
// supported by platform keys subtle crypto internal API.
function isSupportedSignAlgorithm(normalizedAlgorithm) {
  if (normalizedAlgorithm.name === 'RSASSA-PKCS1-v1_5') {
    return true;
  }

  if (normalizedAlgorithm.name === 'ECDSA') {
    // Only SHA-256 algorithm is supported for ECDSA.
    return normalizedAlgorithm.hash.name === 'SHA-256';
  }

  return false;
}

/**
 * Implementation of WebCrypto.SubtleCrypto used in platformKeys and
 * enterprise.platformKeys.
 * @param {string} tokenId The id of the backing Token.
 * @param {boolean} softwareBacked Whether the key operations should be executed
 *     in software.
 * @constructor
 */
function SubtleCryptoImpl(tokenId, softwareBacked) {
  this.tokenId = tokenId;
  this.softwareBacked = softwareBacked;
}
$Object.setPrototypeOf(SubtleCryptoImpl.prototype, null);

SubtleCryptoImpl.prototype.sign = function(algorithm, key, dataView) {
  var subtleCrypto = this;
  return new Promise(function(resolve, reject) {
    if (key.type !== 'private' || key.usages.indexOf(KeyUsage.sign) === -1) {
      throw CreateInvalidAccessError();
    }

    var normalizedAlgorithmParameters = normalizeAlgorithm(algorithm, 'Sign');
    if (!normalizedAlgorithmParameters) {
      // TODO(pneubeck): It's not clear from the WebCrypto spec which error to
      // throw here.
      throw CreateSyntaxError();
    }

    if (normalizedAlgorithmParameters.name !== key.algorithm.name) {
      throw CreateInvalidAccessError();
    }

    if (!isSupportedSignAlgorithm(normalizedAlgorithmParameters)) {
      // Note: This deviates from WebCrypto.SubtleCrypto.
      throw CreateNotSupportedError();
    }

    var algorithmName = normalizedAlgorithmParameters.name;
    var hashAlgorithmName;
    if (algorithmName === 'RSASSA-PKCS1-v1_5') {
      // The hash algorithm when signing with RSASSA-PKCS1-v1_5 is specified at
      // key generation in RsaHashedKeyGenParameters. For more information about
      // RSA key generation parameters, please refer to:
      // https://www.w3.org/TR/WebCryptoAPI/#RsaHashedKeyGenParams-dictionary
      hashAlgorithmName = key.algorithm.hash.name;
    } else if (algorithmName === 'ECDSA') {
      // The hash algorithm when signing with ECDSA is specified in the signing
      // parameters. For more information about ECDSA parameters, please refer
      // to: https://www.w3.org/TR/WebCryptoAPI/#dfn-EcdsaParams
      hashAlgorithmName = normalizedAlgorithmParameters.hash.name;
    }
    // Create an ArrayBuffer that equals the dataView. Note that dataView.buffer
    // might contain more data than dataView.
    var data = dataView.buffer.slice(dataView.byteOffset,
                                     dataView.byteOffset + dataView.byteLength);
    internalAPI.sign(
        subtleCrypto.tokenId, getKeyIdentifier(key),
        normalizedAlgorithmParameters.name, hashAlgorithmName, data,
        function(signature) {
          if (catchInvalidTokenError(reject)) {
            return;
          }
          if (bindingUtil.hasLastError()) {
            bindingUtil.clearLastError();
            reject(CreateOperationError());
            return;
          }
          resolve(signature);
        });
  });
};

SubtleCryptoImpl.prototype.exportKey = function(format, key) {
  return new Promise(function(resolve, reject) {
    if (format === 'pkcs8') {
      // The 'pkcs8' format is intended for 'private' keys, which are always
      // non-extractable in this API. The 'raw' format is intended for 'secret'
      // keys and could also be handled with |InvalidAccessError|, but is
      // actually handled with |NotSupportedError| below, for legacy reasons.
      throw CreateInvalidAccessError();
    } else if (format === 'spki') {
      if (key.type !== 'public') {
        throw CreateInvalidAccessError();
      }
      resolve(getKeyIdentifier(key));
    } else {
      // All other exporting formats are unsupported.
      // TODO(pneubeck): It should be possible to export to format 'jwk'.
      throw CreateNotSupportedError();
    }
  });
};

function SubtleCrypto() {
  privates(SubtleCrypto).constructPrivate(this, arguments);
}
utils.expose(SubtleCrypto, SubtleCryptoImpl, {
  functions: [
    'sign',
    'exportKey',
  ],
});

// Required for sub-classing.
exports.$set('catchInvalidTokenError', catchInvalidTokenError);
exports.$set('SubtleCryptoImpl', SubtleCryptoImpl);
exports.$set('SubtleCrypto', SubtleCrypto);