1'use strict'; 2 3const common = require('../common'); 4 5if (!common.hasCrypto) 6 common.skip('missing crypto'); 7 8const assert = require('assert'); 9const { subtle } = require('crypto').webcrypto; 10 11const kWrappingData = { 12 'RSA-OAEP': { 13 generate: { 14 modulusLength: 4096, 15 publicExponent: new Uint8Array([1, 0, 1]), 16 hash: 'SHA-256', 17 }, 18 wrap: { label: new Uint8Array(8) }, 19 pair: true 20 }, 21 'AES-CTR': { 22 generate: { length: 128 }, 23 wrap: { counter: new Uint8Array(16), length: 64 }, 24 pair: false 25 }, 26 'AES-CBC': { 27 generate: { length: 128 }, 28 wrap: { iv: new Uint8Array(16) }, 29 pair: false 30 }, 31 'AES-GCM': { 32 generate: { length: 128 }, 33 wrap: { 34 iv: new Uint8Array(16), 35 additionalData: new Uint8Array(16), 36 tagLength: 64 37 }, 38 pair: false 39 }, 40 'AES-KW': { 41 generate: { length: 128 }, 42 wrap: { }, 43 pair: false 44 } 45}; 46 47function generateWrappingKeys() { 48 return Promise.all(Object.keys(kWrappingData).map(async (name) => { 49 const keys = await subtle.generateKey( 50 { name, ...kWrappingData[name].generate }, 51 true, 52 ['wrapKey', 'unwrapKey']); 53 if (kWrappingData[name].pair) { 54 kWrappingData[name].wrappingKey = keys.publicKey; 55 kWrappingData[name].unwrappingKey = keys.privateKey; 56 } else { 57 kWrappingData[name].wrappingKey = keys; 58 kWrappingData[name].unwrappingKey = keys; 59 } 60 })); 61} 62 63async function generateKeysToWrap() { 64 const parameters = [ 65 { 66 algorithm: { 67 name: 'RSASSA-PKCS1-v1_5', 68 modulusLength: 1024, 69 publicExponent: new Uint8Array([1, 0, 1]), 70 hash: 'SHA-256' 71 }, 72 privateUsages: ['sign'], 73 publicUsages: ['verify'], 74 pair: true, 75 }, 76 { 77 algorithm: { 78 name: 'RSA-PSS', 79 modulusLength: 1024, 80 publicExponent: new Uint8Array([1, 0, 1]), 81 hash: 'SHA-256' 82 }, 83 privateUsages: ['sign'], 84 publicUsages: ['verify'], 85 pair: true, 86 }, 87 { 88 algorithm: { 89 name: 'RSA-OAEP', 90 modulusLength: 1024, 91 publicExponent: new Uint8Array([1, 0, 1]), 92 hash: 'SHA-256' 93 }, 94 privateUsages: ['decrypt'], 95 publicUsages: ['encrypt'], 96 pair: true, 97 }, 98 { 99 algorithm: { 100 name: 'ECDSA', 101 namedCurve: 'P-384' 102 }, 103 privateUsages: ['sign'], 104 publicUsages: ['verify'], 105 pair: true, 106 }, 107 { 108 algorithm: { 109 name: 'ECDH', 110 namedCurve: 'P-384' 111 }, 112 privateUsages: ['deriveBits'], 113 publicUsages: [], 114 pair: true, 115 }, 116 { 117 algorithm: { 118 name: 'Ed25519', 119 }, 120 privateUsages: ['sign'], 121 publicUsages: ['verify'], 122 pair: true, 123 }, 124 { 125 algorithm: { 126 name: 'Ed448', 127 }, 128 privateUsages: ['sign'], 129 publicUsages: ['verify'], 130 pair: true, 131 }, 132 { 133 algorithm: { 134 name: 'X25519', 135 }, 136 privateUsages: ['deriveBits'], 137 publicUsages: [], 138 pair: true, 139 }, 140 { 141 algorithm: { 142 name: 'X448', 143 }, 144 privateUsages: ['deriveBits'], 145 publicUsages: [], 146 pair: true, 147 }, 148 { 149 algorithm: { 150 name: 'AES-CTR', 151 length: 128 152 }, 153 usages: ['encrypt', 'decrypt'], 154 pair: false, 155 }, 156 { 157 algorithm: { 158 name: 'AES-CBC', 159 length: 128 160 }, 161 usages: ['encrypt', 'decrypt'], 162 pair: false, 163 }, 164 { 165 algorithm: { 166 name: 'AES-GCM', length: 128 167 }, 168 usages: ['encrypt', 'decrypt'], 169 pair: false, 170 }, 171 { 172 algorithm: { 173 name: 'AES-KW', 174 length: 128 175 }, 176 usages: ['wrapKey', 'unwrapKey'], 177 pair: false, 178 }, 179 { 180 algorithm: { 181 name: 'HMAC', 182 length: 128, 183 hash: 'SHA-256' 184 }, 185 usages: ['sign', 'verify'], 186 pair: false, 187 }, 188 ]; 189 190 const allkeys = await Promise.all(parameters.map(async (params) => { 191 const usages = 'usages' in params ? 192 params.usages : 193 params.publicUsages.concat(params.privateUsages); 194 195 const keys = await subtle.generateKey(params.algorithm, true, usages); 196 197 if (params.pair) { 198 return [ 199 { 200 algorithm: params.algorithm, 201 usages: params.publicUsages, 202 key: keys.publicKey, 203 }, 204 { 205 algorithm: params.algorithm, 206 usages: params.privateUsages, 207 key: keys.privateKey, 208 }, 209 ]; 210 } 211 212 return [{ 213 algorithm: params.algorithm, 214 usages: params.usages, 215 key: keys, 216 }]; 217 })); 218 219 return allkeys.flat(); 220} 221 222function getFormats(key) { 223 switch (key.key.type) { 224 case 'secret': return ['raw', 'jwk']; 225 case 'public': return ['spki', 'jwk']; 226 case 'private': return ['pkcs8', 'jwk']; 227 } 228} 229 230// If the wrapping algorithm is AES-KW, the exported key 231// material length must be a multiple of 8. 232// If the wrapping algorithm is RSA-OAEP, the exported key 233// material maximum length is a factor of the modulusLength 234// 235// As per the NOTE in step 13 https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey 236// we're padding AES-KW wrapped JWK to make sure it is always a multiple of 8 bytes 237// in length 238async function wrappingIsPossible(name, exported) { 239 if ('byteLength' in exported) { 240 switch (name) { 241 case 'AES-KW': 242 return exported.byteLength % 8 === 0; 243 case 'RSA-OAEP': 244 return exported.byteLength <= 446; 245 } 246 } else if ('kty' in exported && name === 'RSA-OAEP') { 247 return JSON.stringify(exported).length <= 478; 248 } 249 return true; 250} 251 252async function testWrap(wrappingKey, unwrappingKey, key, wrap, format) { 253 const exported = await subtle.exportKey(format, key.key); 254 if (!(await wrappingIsPossible(wrappingKey.algorithm.name, exported))) 255 return; 256 257 const wrapped = 258 await subtle.wrapKey( 259 format, 260 key.key, 261 wrappingKey, 262 { name: wrappingKey.algorithm.name, ...wrap }); 263 const unwrapped = 264 await subtle.unwrapKey( 265 format, 266 wrapped, 267 unwrappingKey, 268 { name: wrappingKey.algorithm.name, ...wrap }, 269 key.algorithm, 270 true, 271 key.usages); 272 assert(unwrapped.extractable); 273 274 const exportedAgain = await subtle.exportKey(format, unwrapped); 275 assert.deepStrictEqual(exported, exportedAgain); 276} 277 278function testWrapping(name, keys) { 279 const variations = []; 280 281 const { 282 wrappingKey, 283 unwrappingKey, 284 wrap 285 } = kWrappingData[name]; 286 287 keys.forEach((key) => { 288 getFormats(key).forEach((format) => { 289 variations.push(testWrap(wrappingKey, unwrappingKey, key, wrap, format)); 290 }); 291 }); 292 293 return variations; 294} 295 296(async function() { 297 await generateWrappingKeys(); 298 const keys = await generateKeysToWrap(); 299 const variations = []; 300 Object.keys(kWrappingData).forEach((name) => { 301 variations.push(...testWrapping(name, keys)); 302 }); 303 await Promise.all(variations); 304})().then(common.mustCall()); 305