1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5var utils = require('utils');
6var internalAPI = require('enterprise.platformKeys.internalAPI');
7var intersect = require('enterprise.platformKeys.utils').intersect;
8var KeyPair = require('enterprise.platformKeys.KeyPair').KeyPair;
9var keyModule = require('enterprise.platformKeys.Key');
10var getSpki = keyModule.getSpki;
11var KeyUsage = keyModule.KeyUsage;
12
13var normalizeAlgorithm =
14    requireNative('enterprise_platform_keys_natives').NormalizeAlgorithm;
15
16// This error is thrown by the internal and public API's token functions and
17// must be rethrown by this custom binding. Keep this in sync with the C++ part
18// of this API.
19var errorInvalidToken = "The token is not valid.";
20
21// The following errors are specified in WebCrypto.
22// TODO(pneubeck): These should be DOMExceptions.
23function CreateNotSupportedError() {
24  return new Error('The algorithm is not supported');
25}
26
27function CreateInvalidAccessError() {
28  return new Error('The requested operation is not valid for the provided key');
29}
30
31function CreateDataError() {
32  return new Error('Data provided to an operation does not meet requirements');
33}
34
35function CreateSyntaxError() {
36  return new Error('A required parameter was missing or out-of-range');
37}
38
39function CreateOperationError() {
40  return new Error('The operation failed for an operation-specific reason');
41}
42
43// Catches an |internalErrorInvalidToken|. If so, forwards it to |reject| and
44// returns true.
45function catchInvalidTokenError(reject) {
46  if (chrome.runtime.lastError &&
47      chrome.runtime.lastError.message == errorInvalidToken) {
48    reject(chrome.runtime.lastError);
49    return true;
50  }
51  return false;
52}
53
54// Returns true if |array| is a BigInteger describing the standard public
55// exponent 65537. In particular, it ignores leading zeros as required by the
56// BigInteger definition in WebCrypto.
57function equalsStandardPublicExponent(array) {
58  var expected = [0x01, 0x00, 0x01];
59  if (array.length < expected.length)
60    return false;
61  for (var i = 0; i < array.length; i++) {
62    var expectedDigit = 0;
63    if (i < expected.length) {
64      // |expected| is symmetric, endianness doesn't matter.
65      expectedDigit = expected[i];
66    }
67    if (array[array.length - 1 - i] !== expectedDigit)
68      return false;
69  }
70  return true;
71}
72
73/**
74 * Implementation of WebCrypto.SubtleCrypto used in enterprise.platformKeys.
75 * @param {string} tokenId The id of the backing Token.
76 * @constructor
77 */
78var SubtleCryptoImpl = function(tokenId) {
79  this.tokenId = tokenId;
80};
81
82SubtleCryptoImpl.prototype.generateKey =
83    function(algorithm, extractable, keyUsages) {
84  var subtleCrypto = this;
85  return new Promise(function(resolve, reject) {
86    // TODO(pneubeck): Apply the algorithm normalization of the WebCrypto
87    // implementation.
88
89    if (extractable) {
90      // Note: This deviates from WebCrypto.SubtleCrypto.
91      throw CreateNotSupportedError();
92    }
93    if (intersect(keyUsages, [KeyUsage.sign, KeyUsage.verify]).length !=
94        keyUsages.length) {
95      throw CreateDataError();
96    }
97    var normalizedAlgorithmParameters =
98        normalizeAlgorithm(algorithm, 'GenerateKey');
99    if (!normalizedAlgorithmParameters) {
100      // TODO(pneubeck): It's not clear from the WebCrypto spec which error to
101      // throw here.
102      throw CreateSyntaxError();
103    }
104
105    // normalizeAlgorithm returns an array, but publicExponent should be a
106    // Uint8Array.
107    normalizedAlgorithmParameters.publicExponent =
108        new Uint8Array(normalizedAlgorithmParameters.publicExponent);
109
110    if (normalizedAlgorithmParameters.name !== 'RSASSA-PKCS1-v1_5' ||
111        !equalsStandardPublicExponent(
112            normalizedAlgorithmParameters.publicExponent)) {
113      // Note: This deviates from WebCrypto.SubtleCrypto.
114      throw CreateNotSupportedError();
115    }
116
117    internalAPI.generateKey(subtleCrypto.tokenId,
118                            normalizedAlgorithmParameters.modulusLength,
119                            function(spki) {
120      if (catchInvalidTokenError(reject))
121        return;
122      if (chrome.runtime.lastError) {
123        reject(CreateOperationError());
124        return;
125      }
126      resolve(new KeyPair(spki, normalizedAlgorithmParameters, keyUsages));
127    });
128  });
129};
130
131SubtleCryptoImpl.prototype.sign = function(algorithm, key, dataView) {
132  var subtleCrypto = this;
133  return new Promise(function(resolve, reject) {
134    if (key.type != 'private' || key.usages.indexOf(KeyUsage.sign) == -1)
135      throw CreateInvalidAccessError();
136
137    var normalizedAlgorithmParameters =
138        normalizeAlgorithm(algorithm, 'Sign');
139    if (!normalizedAlgorithmParameters) {
140      // TODO(pneubeck): It's not clear from the WebCrypto spec which error to
141      // throw here.
142      throw CreateSyntaxError();
143    }
144
145    // Create an ArrayBuffer that equals the dataView. Note that dataView.buffer
146    // might contain more data than dataView.
147    var data = dataView.buffer.slice(dataView.byteOffset,
148                                     dataView.byteOffset + dataView.byteLength);
149    internalAPI.sign(subtleCrypto.tokenId,
150                     getSpki(key),
151                     key.algorithm.hash.name,
152                     data,
153                     function(signature) {
154      if (catchInvalidTokenError(reject))
155        return;
156      if (chrome.runtime.lastError) {
157        reject(CreateOperationError());
158        return;
159      }
160      resolve(signature);
161    });
162  });
163};
164
165SubtleCryptoImpl.prototype.exportKey = function(format, key) {
166  return new Promise(function(resolve, reject) {
167    if (format == 'pkcs8') {
168      // Either key.type is not 'private' or the key is not extractable. In both
169      // cases the error is the same.
170      throw CreateInvalidAccessError();
171    } else if (format == 'spki') {
172      if (key.type != 'public')
173        throw CreateInvalidAccessError();
174      resolve(getSpki(key));
175    } else {
176      // TODO(pneubeck): It should be possible to export to format 'jwk'.
177      throw CreateNotSupportedError();
178    }
179  });
180};
181
182exports.SubtleCrypto =
183    utils.expose('SubtleCrypto',
184                 SubtleCryptoImpl,
185                 {functions:['generateKey', 'sign', 'exportKey']});
186