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