1'use strict'; 2 3const { 4 SafeSet, 5} = primordials; 6 7const { Buffer } = require('buffer'); 8 9const { 10 ECKeyExportJob, 11 KeyObjectHandle, 12 SignJob, 13 kCryptoJobAsync, 14 kKeyTypePrivate, 15 kKeyTypePublic, 16 kSignJobModeSign, 17 kSignJobModeVerify, 18} = internalBinding('crypto'); 19 20const { 21 getUsagesUnion, 22 hasAnyNotIn, 23 jobPromise, 24 validateKeyOps, 25 kHandle, 26 kKeyObject, 27} = require('internal/crypto/util'); 28 29const { 30 emitExperimentalWarning, 31 lazyDOMException, 32 promisify, 33} = require('internal/util'); 34 35const { 36 generateKeyPair: _generateKeyPair, 37} = require('internal/crypto/keygen'); 38 39const { 40 InternalCryptoKey, 41 PrivateKeyObject, 42 PublicKeyObject, 43 createPrivateKey, 44 createPublicKey, 45} = require('internal/crypto/keys'); 46 47const generateKeyPair = promisify(_generateKeyPair); 48 49function verifyAcceptableCfrgKeyUse(name, isPublic, usages) { 50 let checkSet; 51 switch (name) { 52 case 'X25519': 53 // Fall through 54 case 'X448': 55 checkSet = isPublic ? [] : ['deriveKey', 'deriveBits']; 56 break; 57 case 'Ed25519': 58 // Fall through 59 case 'Ed448': 60 checkSet = isPublic ? ['verify'] : ['sign']; 61 break; 62 default: 63 throw lazyDOMException( 64 'The algorithm is not supported', 'NotSupportedError'); 65 } 66 if (hasAnyNotIn(usages, checkSet)) { 67 throw lazyDOMException( 68 `Unsupported key usage for a ${name} key`, 69 'SyntaxError'); 70 } 71} 72 73function createCFRGRawKey(name, keyData, isPublic) { 74 const handle = new KeyObjectHandle(); 75 76 switch (name) { 77 case 'Ed25519': 78 case 'X25519': 79 if (keyData.byteLength !== 32) { 80 throw lazyDOMException( 81 `${name} raw keys must be exactly 32-bytes`, 'DataError'); 82 } 83 break; 84 case 'Ed448': 85 if (keyData.byteLength !== 57) { 86 throw lazyDOMException( 87 `${name} raw keys must be exactly 57-bytes`, 'DataError'); 88 } 89 break; 90 case 'X448': 91 if (keyData.byteLength !== 56) { 92 throw lazyDOMException( 93 `${name} raw keys must be exactly 56-bytes`, 'DataError'); 94 } 95 break; 96 } 97 98 const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate; 99 if (!handle.initEDRaw(name, keyData, keyType)) { 100 throw lazyDOMException('Invalid keyData', 'DataError'); 101 } 102 103 return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle); 104} 105 106async function cfrgGenerateKey(algorithm, extractable, keyUsages) { 107 const { name } = algorithm; 108 emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); 109 110 const usageSet = new SafeSet(keyUsages); 111 switch (name) { 112 case 'Ed25519': 113 // Fall through 114 case 'Ed448': 115 if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { 116 throw lazyDOMException( 117 `Unsupported key usage for an ${name} key`, 118 'SyntaxError'); 119 } 120 break; 121 case 'X25519': 122 // Fall through 123 case 'X448': 124 if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) { 125 throw lazyDOMException( 126 `Unsupported key usage for an ${name} key`, 127 'SyntaxError'); 128 } 129 break; 130 } 131 let genKeyType; 132 switch (name) { 133 case 'Ed25519': 134 genKeyType = 'ed25519'; 135 break; 136 case 'Ed448': 137 genKeyType = 'ed448'; 138 break; 139 case 'X25519': 140 genKeyType = 'x25519'; 141 break; 142 case 'X448': 143 genKeyType = 'x448'; 144 break; 145 } 146 147 const keyPair = await generateKeyPair(genKeyType).catch((err) => { 148 throw lazyDOMException( 149 'The operation failed for an operation-specific reason', 150 { name: 'OperationError', cause: err }); 151 }); 152 153 let publicUsages; 154 let privateUsages; 155 switch (name) { 156 case 'Ed25519': 157 // Fall through 158 case 'Ed448': 159 publicUsages = getUsagesUnion(usageSet, 'verify'); 160 privateUsages = getUsagesUnion(usageSet, 'sign'); 161 break; 162 case 'X25519': 163 // Fall through 164 case 'X448': 165 publicUsages = []; 166 privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits'); 167 break; 168 } 169 170 const keyAlgorithm = { name }; 171 172 const publicKey = 173 new InternalCryptoKey( 174 keyPair.publicKey, 175 keyAlgorithm, 176 publicUsages, 177 true); 178 179 const privateKey = 180 new InternalCryptoKey( 181 keyPair.privateKey, 182 keyAlgorithm, 183 privateUsages, 184 extractable); 185 186 return { privateKey, publicKey }; 187} 188 189function cfrgExportKey(key, format) { 190 emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`); 191 return jobPromise(() => new ECKeyExportJob( 192 kCryptoJobAsync, 193 format, 194 key[kKeyObject][kHandle])); 195} 196 197async function cfrgImportKey( 198 format, 199 keyData, 200 algorithm, 201 extractable, 202 keyUsages) { 203 204 const { name } = algorithm; 205 emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); 206 let keyObject; 207 const usagesSet = new SafeSet(keyUsages); 208 switch (format) { 209 case 'spki': { 210 verifyAcceptableCfrgKeyUse(name, true, usagesSet); 211 try { 212 keyObject = createPublicKey({ 213 key: keyData, 214 format: 'der', 215 type: 'spki', 216 }); 217 } catch (err) { 218 throw lazyDOMException( 219 'Invalid keyData', { name: 'DataError', cause: err }); 220 } 221 break; 222 } 223 case 'pkcs8': { 224 verifyAcceptableCfrgKeyUse(name, false, usagesSet); 225 try { 226 keyObject = createPrivateKey({ 227 key: keyData, 228 format: 'der', 229 type: 'pkcs8', 230 }); 231 } catch (err) { 232 throw lazyDOMException( 233 'Invalid keyData', { name: 'DataError', cause: err }); 234 } 235 break; 236 } 237 case 'jwk': { 238 if (!keyData.kty) 239 throw lazyDOMException('Invalid keyData', 'DataError'); 240 if (keyData.kty !== 'OKP') 241 throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError'); 242 if (keyData.crv !== name) 243 throw lazyDOMException( 244 'JWK "crv" Parameter and algorithm name mismatch', 'DataError'); 245 const isPublic = keyData.d === undefined; 246 247 if (usagesSet.size > 0 && keyData.use !== undefined) { 248 let checkUse; 249 switch (name) { 250 case 'Ed25519': 251 // Fall through 252 case 'Ed448': 253 checkUse = 'sig'; 254 break; 255 case 'X25519': 256 // Fall through 257 case 'X448': 258 checkUse = 'enc'; 259 break; 260 } 261 if (keyData.use !== checkUse) 262 throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError'); 263 } 264 265 validateKeyOps(keyData.key_ops, usagesSet); 266 267 if (keyData.ext !== undefined && 268 keyData.ext === false && 269 extractable === true) { 270 throw lazyDOMException( 271 'JWK "ext" Parameter and extractable mismatch', 272 'DataError'); 273 } 274 275 if (keyData.alg !== undefined) { 276 if ( 277 (name === 'Ed25519' || name === 'Ed448') && 278 keyData.alg !== 'EdDSA' 279 ) { 280 throw lazyDOMException( 281 'JWK "alg" does not match the requested algorithm', 282 'DataError'); 283 } 284 } 285 286 if (!isPublic && typeof keyData.x !== 'string') { 287 throw lazyDOMException('Invalid JWK', 'DataError'); 288 } 289 290 verifyAcceptableCfrgKeyUse( 291 name, 292 isPublic, 293 usagesSet); 294 295 const publicKeyObject = createCFRGRawKey( 296 name, 297 Buffer.from(keyData.x, 'base64'), 298 true); 299 300 if (isPublic) { 301 keyObject = publicKeyObject; 302 } else { 303 keyObject = createCFRGRawKey( 304 name, 305 Buffer.from(keyData.d, 'base64'), 306 false); 307 308 if (!createPublicKey(keyObject).equals(publicKeyObject)) { 309 throw lazyDOMException('Invalid JWK', 'DataError'); 310 } 311 } 312 break; 313 } 314 case 'raw': { 315 verifyAcceptableCfrgKeyUse(name, true, usagesSet); 316 keyObject = createCFRGRawKey(name, keyData, true); 317 break; 318 } 319 } 320 321 if (keyObject.asymmetricKeyType !== name.toLowerCase()) { 322 throw lazyDOMException('Invalid key type', 'DataError'); 323 } 324 325 return new InternalCryptoKey( 326 keyObject, 327 { name }, 328 keyUsages, 329 extractable); 330} 331 332function eddsaSignVerify(key, data, { name, context }, signature) { 333 emitExperimentalWarning(`The ${name} Web Crypto API algorithm`); 334 const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify; 335 const type = mode === kSignJobModeSign ? 'private' : 'public'; 336 337 if (key.type !== type) 338 throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError'); 339 340 if (name === 'Ed448' && context?.byteLength) { 341 throw lazyDOMException( 342 'Non zero-length context is not yet supported.', 'NotSupportedError'); 343 } 344 345 return jobPromise(() => new SignJob( 346 kCryptoJobAsync, 347 mode, 348 key[kKeyObject][kHandle], 349 undefined, 350 undefined, 351 undefined, 352 data, 353 undefined, 354 undefined, 355 undefined, 356 undefined, 357 signature)); 358} 359 360module.exports = { 361 cfrgExportKey, 362 cfrgImportKey, 363 cfrgGenerateKey, 364 eddsaSignVerify, 365}; 366