1'use strict'; 2 3const { 4 ArrayPrototypeIncludes, 5 ObjectKeys, 6 SafeSet, 7} = primordials; 8 9const { 10 ECKeyExportJob, 11 KeyObjectHandle, 12 SignJob, 13 kCryptoJobAsync, 14 kKeyTypePrivate, 15 kSignJobModeSign, 16 kSignJobModeVerify, 17 kSigEncP1363, 18} = internalBinding('crypto'); 19 20const { 21 getUsagesUnion, 22 hasAnyNotIn, 23 jobPromise, 24 normalizeHashName, 25 validateKeyOps, 26 kHandle, 27 kKeyObject, 28 kNamedCurveAliases, 29} = require('internal/crypto/util'); 30 31const { 32 lazyDOMException, 33 promisify, 34} = require('internal/util'); 35 36const { 37 generateKeyPair: _generateKeyPair, 38} = require('internal/crypto/keygen'); 39 40const { 41 InternalCryptoKey, 42 PrivateKeyObject, 43 PublicKeyObject, 44 createPrivateKey, 45 createPublicKey, 46} = require('internal/crypto/keys'); 47 48const generateKeyPair = promisify(_generateKeyPair); 49 50function verifyAcceptableEcKeyUse(name, isPublic, usages) { 51 let checkSet; 52 switch (name) { 53 case 'ECDH': 54 checkSet = isPublic ? [] : ['deriveKey', 'deriveBits']; 55 break; 56 case 'ECDSA': 57 checkSet = isPublic ? ['verify'] : ['sign']; 58 break; 59 default: 60 throw lazyDOMException( 61 'The algorithm is not supported', 'NotSupportedError'); 62 } 63 if (hasAnyNotIn(usages, checkSet)) { 64 throw lazyDOMException( 65 `Unsupported key usage for a ${name} key`, 66 'SyntaxError'); 67 } 68} 69 70function createECPublicKeyRaw(namedCurve, keyData) { 71 const handle = new KeyObjectHandle(); 72 73 if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) { 74 throw lazyDOMException('Invalid keyData', 'DataError'); 75 } 76 77 return new PublicKeyObject(handle); 78} 79 80async function ecGenerateKey(algorithm, extractable, keyUsages) { 81 const { name, namedCurve } = algorithm; 82 83 if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) { 84 throw lazyDOMException( 85 'Unrecognized namedCurve', 86 'NotSupportedError'); 87 } 88 89 const usageSet = new SafeSet(keyUsages); 90 switch (name) { 91 case 'ECDSA': 92 if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { 93 throw lazyDOMException( 94 'Unsupported key usage for an ECDSA key', 95 'SyntaxError'); 96 } 97 break; 98 case 'ECDH': 99 if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) { 100 throw lazyDOMException( 101 'Unsupported key usage for an ECDH key', 102 'SyntaxError'); 103 } 104 // Fall through 105 } 106 107 const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => { 108 throw lazyDOMException( 109 'The operation failed for an operation-specific reason', 110 { name: 'OperationError', cause: err }); 111 }); 112 113 let publicUsages; 114 let privateUsages; 115 switch (name) { 116 case 'ECDSA': 117 publicUsages = getUsagesUnion(usageSet, 'verify'); 118 privateUsages = getUsagesUnion(usageSet, 'sign'); 119 break; 120 case 'ECDH': 121 publicUsages = []; 122 privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); 123 break; 124 } 125 126 const keyAlgorithm = { name, namedCurve }; 127 128 const publicKey = 129 new InternalCryptoKey( 130 keypair.publicKey, 131 keyAlgorithm, 132 publicUsages, 133 true); 134 135 const privateKey = 136 new InternalCryptoKey( 137 keypair.privateKey, 138 keyAlgorithm, 139 privateUsages, 140 extractable); 141 142 return { publicKey, privateKey }; 143} 144 145function ecExportKey(key, format) { 146 return jobPromise(() => new ECKeyExportJob( 147 kCryptoJobAsync, 148 format, 149 key[kKeyObject][kHandle])); 150} 151 152async function ecImportKey( 153 format, 154 keyData, 155 algorithm, 156 extractable, 157 keyUsages) { 158 159 const { name, namedCurve } = algorithm; 160 161 if (!ArrayPrototypeIncludes(ObjectKeys(kNamedCurveAliases), namedCurve)) { 162 throw lazyDOMException( 163 'Unrecognized namedCurve', 164 'NotSupportedError'); 165 } 166 167 let keyObject; 168 const usagesSet = new SafeSet(keyUsages); 169 switch (format) { 170 case 'spki': { 171 verifyAcceptableEcKeyUse(name, true, usagesSet); 172 try { 173 keyObject = createPublicKey({ 174 key: keyData, 175 format: 'der', 176 type: 'spki', 177 }); 178 } catch (err) { 179 throw lazyDOMException( 180 'Invalid keyData', { name: 'DataError', cause: err }); 181 } 182 break; 183 } 184 case 'pkcs8': { 185 verifyAcceptableEcKeyUse(name, false, usagesSet); 186 try { 187 keyObject = createPrivateKey({ 188 key: keyData, 189 format: 'der', 190 type: 'pkcs8', 191 }); 192 } catch (err) { 193 throw lazyDOMException( 194 'Invalid keyData', { name: 'DataError', cause: err }); 195 } 196 break; 197 } 198 case 'jwk': { 199 if (!keyData.kty) 200 throw lazyDOMException('Invalid keyData', 'DataError'); 201 if (keyData.kty !== 'EC') 202 throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); 203 if (keyData.crv !== namedCurve) 204 throw lazyDOMException( 205 'JWK "crv" does not match the requested algorithm', 206 'DataError'); 207 208 verifyAcceptableEcKeyUse( 209 name, 210 keyData.d === undefined, 211 usagesSet); 212 213 if (usagesSet.size > 0 && keyData.use !== undefined) { 214 const checkUse = name === 'ECDH' ? 'enc' : 'sig'; 215 if (keyData.use !== checkUse) 216 throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); 217 } 218 219 validateKeyOps(keyData.key_ops, usagesSet); 220 221 if (keyData.ext !== undefined && 222 keyData.ext === false && 223 extractable === true) { 224 throw lazyDOMException( 225 'JWK "ext" Parameter and extractable mismatch', 226 'DataError'); 227 } 228 229 if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) { 230 let algNamedCurve; 231 switch (keyData.alg) { 232 case 'ES256': algNamedCurve = 'P-256'; break; 233 case 'ES384': algNamedCurve = 'P-384'; break; 234 case 'ES512': algNamedCurve = 'P-521'; break; 235 } 236 if (algNamedCurve !== namedCurve) 237 throw lazyDOMException( 238 'JWK "alg" does not match the requested algorithm', 239 'DataError'); 240 } 241 242 const handle = new KeyObjectHandle(); 243 const type = handle.initJwk(keyData, namedCurve); 244 if (type === undefined) 245 throw lazyDOMException('Invalid JWK', 'DataError'); 246 keyObject = type === kKeyTypePrivate ? 247 new PrivateKeyObject(handle) : 248 new PublicKeyObject(handle); 249 break; 250 } 251 case 'raw': { 252 verifyAcceptableEcKeyUse(name, true, usagesSet); 253 keyObject = createECPublicKeyRaw(namedCurve, keyData); 254 break; 255 } 256 } 257 258 switch (algorithm.name) { 259 case 'ECDSA': 260 // Fall through 261 case 'ECDH': 262 if (keyObject.asymmetricKeyType !== 'ec') 263 throw lazyDOMException('Invalid key type', 'DataError'); 264 break; 265 } 266 267 if (!keyObject[kHandle].checkEcKeyData()) { 268 throw lazyDOMException('Invalid keyData', 'DataError'); 269 } 270 271 const { 272 namedCurve: checkNamedCurve, 273 } = keyObject[kHandle].keyDetail({}); 274 if (kNamedCurveAliases[namedCurve] !== checkNamedCurve) 275 throw lazyDOMException('Named curve mismatch', 'DataError'); 276 277 return new InternalCryptoKey( 278 keyObject, 279 { name, namedCurve }, 280 keyUsages, 281 extractable); 282} 283 284function ecdsaSignVerify(key, data, { name, hash }, signature) { 285 const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; 286 const type = mode === kSignJobModeSign ? 'private' : 'public'; 287 288 if (key.type !== type) 289 throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); 290 291 const hashname = normalizeHashName(hash.name); 292 293 return jobPromise(() => new SignJob( 294 kCryptoJobAsync, 295 mode, 296 key[kKeyObject][kHandle], 297 undefined, 298 undefined, 299 undefined, 300 data, 301 hashname, 302 undefined, // Salt length, not used with ECDSA 303 undefined, // PSS Padding, not used with ECDSA 304 kSigEncP1363, 305 signature)); 306} 307 308module.exports = { 309 ecExportKey, 310 ecImportKey, 311 ecGenerateKey, 312 ecdsaSignVerify, 313}; 314