|
| 1 | +import type { |
| 2 | + AnyAlgorithm, |
| 3 | + DeriveBitsAlgorithm, |
| 4 | + DigestAlgorithm, |
| 5 | + EncryptDecryptAlgorithm, |
| 6 | + EncryptDecryptParams, |
| 7 | + KeyPairAlgorithm, |
| 8 | + SecretKeyAlgorithm, |
| 9 | + SignVerifyAlgorithm, |
| 10 | + SubtleAlgorithm, |
| 11 | +} from './keys'; |
| 12 | + |
| 13 | +type SupportedAlgorithm<Type extends string> = { |
| 14 | + [key in Type]: string | null; |
| 15 | +}; |
| 16 | + |
| 17 | +type SupportedAlgorithms = { |
| 18 | + digest: SupportedAlgorithm<DigestAlgorithm>; |
| 19 | + generateKey: SupportedAlgorithm<KeyPairAlgorithm | SecretKeyAlgorithm>; |
| 20 | + sign: SupportedAlgorithm<SignVerifyAlgorithm>; |
| 21 | + verify: SupportedAlgorithm<SignVerifyAlgorithm>; |
| 22 | + importKey: SupportedAlgorithm< |
| 23 | + KeyPairAlgorithm | 'PBKDF2' | SecretKeyAlgorithm | 'HKDF' |
| 24 | + >; |
| 25 | + deriveBits: SupportedAlgorithm<DeriveBitsAlgorithm>; |
| 26 | + encrypt: SupportedAlgorithm<EncryptDecryptAlgorithm>; |
| 27 | + decrypt: SupportedAlgorithm<EncryptDecryptAlgorithm>; |
| 28 | + 'get key length': SupportedAlgorithm<SecretKeyAlgorithm | 'PBKDF2' | 'HKDF'>; |
| 29 | + wrapKey: SupportedAlgorithm<'AES-KW'>; |
| 30 | + unwrapKey: SupportedAlgorithm<'AES-KW'>; |
| 31 | +}; |
| 32 | + |
| 33 | +export type Operation = |
| 34 | + | 'digest' |
| 35 | + | 'generateKey' |
| 36 | + | 'sign' |
| 37 | + | 'verify' |
| 38 | + | 'importKey' |
| 39 | + | 'deriveBits' |
| 40 | + | 'encrypt' |
| 41 | + | 'decrypt' |
| 42 | + | 'get key length' |
| 43 | + | 'wrapKey' |
| 44 | + | 'unwrapKey'; |
| 45 | + |
| 46 | +const kSupportedAlgorithms: SupportedAlgorithms = { |
| 47 | + digest: { |
| 48 | + 'SHA-1': null, |
| 49 | + 'SHA-256': null, |
| 50 | + 'SHA-384': null, |
| 51 | + 'SHA-512': null, |
| 52 | + }, |
| 53 | + generateKey: { |
| 54 | + 'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams', |
| 55 | + 'RSA-PSS': 'RsaHashedKeyGenParams', |
| 56 | + 'RSA-OAEP': 'RsaHashedKeyGenParams', |
| 57 | + ECDSA: 'EcKeyGenParams', |
| 58 | + ECDH: 'EcKeyGenParams', |
| 59 | + 'AES-CTR': 'AesKeyGenParams', |
| 60 | + 'AES-CBC': 'AesKeyGenParams', |
| 61 | + 'AES-GCM': 'AesKeyGenParams', |
| 62 | + 'AES-KW': 'AesKeyGenParams', |
| 63 | + HMAC: 'HmacKeyGenParams', |
| 64 | + X25519: null, |
| 65 | + Ed25519: null, |
| 66 | + X448: null, |
| 67 | + Ed448: null, |
| 68 | + }, |
| 69 | + sign: { |
| 70 | + 'RSASSA-PKCS1-v1_5': null, |
| 71 | + 'RSA-PSS': 'RsaPssParams', |
| 72 | + ECDSA: 'EcdsaParams', |
| 73 | + HMAC: null, |
| 74 | + Ed25519: null, |
| 75 | + Ed448: 'Ed448Params', |
| 76 | + }, |
| 77 | + verify: { |
| 78 | + 'RSASSA-PKCS1-v1_5': null, |
| 79 | + 'RSA-PSS': 'RsaPssParams', |
| 80 | + ECDSA: 'EcdsaParams', |
| 81 | + HMAC: null, |
| 82 | + Ed25519: null, |
| 83 | + Ed448: 'Ed448Params', |
| 84 | + }, |
| 85 | + importKey: { |
| 86 | + 'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams', |
| 87 | + 'RSA-PSS': 'RsaHashedImportParams', |
| 88 | + 'RSA-OAEP': 'RsaHashedImportParams', |
| 89 | + ECDSA: 'EcKeyImportParams', |
| 90 | + ECDH: 'EcKeyImportParams', |
| 91 | + HMAC: 'HmacImportParams', |
| 92 | + HKDF: null, |
| 93 | + PBKDF2: null, |
| 94 | + 'AES-CTR': null, |
| 95 | + 'AES-CBC': null, |
| 96 | + 'AES-GCM': null, |
| 97 | + 'AES-KW': null, |
| 98 | + Ed25519: null, |
| 99 | + X25519: null, |
| 100 | + Ed448: null, |
| 101 | + X448: null, |
| 102 | + }, |
| 103 | + deriveBits: { |
| 104 | + HKDF: 'HkdfParams', |
| 105 | + PBKDF2: 'Pbkdf2Params', |
| 106 | + ECDH: 'EcdhKeyDeriveParams', |
| 107 | + X25519: 'EcdhKeyDeriveParams', |
| 108 | + X448: 'EcdhKeyDeriveParams', |
| 109 | + }, |
| 110 | + encrypt: { |
| 111 | + 'RSA-OAEP': 'RsaOaepParams', |
| 112 | + 'AES-CBC': 'AesCbcParams', |
| 113 | + 'AES-GCM': 'AesGcmParams', |
| 114 | + 'AES-CTR': 'AesCtrParams', |
| 115 | + }, |
| 116 | + decrypt: { |
| 117 | + 'RSA-OAEP': 'RsaOaepParams', |
| 118 | + 'AES-CBC': 'AesCbcParams', |
| 119 | + 'AES-GCM': 'AesGcmParams', |
| 120 | + 'AES-CTR': 'AesCtrParams', |
| 121 | + }, |
| 122 | + 'get key length': { |
| 123 | + 'AES-CBC': 'AesDerivedKeyParams', |
| 124 | + 'AES-CTR': 'AesDerivedKeyParams', |
| 125 | + 'AES-GCM': 'AesDerivedKeyParams', |
| 126 | + 'AES-KW': 'AesDerivedKeyParams', |
| 127 | + HMAC: 'HmacImportParams', |
| 128 | + HKDF: null, |
| 129 | + PBKDF2: null, |
| 130 | + }, |
| 131 | + wrapKey: { |
| 132 | + 'AES-KW': null, |
| 133 | + }, |
| 134 | + unwrapKey: { |
| 135 | + 'AES-KW': null, |
| 136 | + }, |
| 137 | +}; |
| 138 | + |
| 139 | +type AlgorithmDictionaries = { |
| 140 | + [key in string]: object; |
| 141 | +}; |
| 142 | + |
| 143 | +const simpleAlgorithmDictionaries: AlgorithmDictionaries = { |
| 144 | + AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' }, |
| 145 | + RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, |
| 146 | + EcKeyGenParams: {}, |
| 147 | + HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' }, |
| 148 | + RsaPssParams: {}, |
| 149 | + EcdsaParams: { hash: 'HashAlgorithmIdentifier' }, |
| 150 | + HmacImportParams: { hash: 'HashAlgorithmIdentifier' }, |
| 151 | + HkdfParams: { |
| 152 | + hash: 'HashAlgorithmIdentifier', |
| 153 | + salt: 'BufferSource', |
| 154 | + info: 'BufferSource', |
| 155 | + }, |
| 156 | + Ed448Params: { context: 'BufferSource' }, |
| 157 | + Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' }, |
| 158 | + RsaOaepParams: { label: 'BufferSource' }, |
| 159 | + RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' }, |
| 160 | + EcKeyImportParams: {}, |
| 161 | +}; |
| 162 | + |
| 163 | +// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm |
| 164 | +// adapted for Node.js from Deno's implementation |
| 165 | +// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195 |
| 166 | +export const normalizeAlgorithm = ( |
| 167 | + algorithm: SubtleAlgorithm | EncryptDecryptParams | AnyAlgorithm, |
| 168 | + op: Operation, |
| 169 | +): SubtleAlgorithm | EncryptDecryptParams => { |
| 170 | + if (typeof algorithm === 'string') { |
| 171 | + return normalizeAlgorithm({ name: algorithm }, op); |
| 172 | + } |
| 173 | + |
| 174 | + // 1. |
| 175 | + const registeredAlgorithms = kSupportedAlgorithms[op]; |
| 176 | + // 2. 3. |
| 177 | + // commented, because typescript takes care of this for us 🤞👀 |
| 178 | + // const initialAlg = webidl.converters.Algorithm(algorithm, { |
| 179 | + // prefix: 'Failed to normalize algorithm', |
| 180 | + // context: 'passed algorithm', |
| 181 | + // }); |
| 182 | + |
| 183 | + // 4. |
| 184 | + let algName = algorithm.name; |
| 185 | + if (algName === undefined) return { name: 'unknown' }; |
| 186 | + |
| 187 | + // 5. |
| 188 | + let desiredType: string | null | undefined; |
| 189 | + for (const key in registeredAlgorithms) { |
| 190 | + if (!Object.prototype.hasOwnProperty.call(registeredAlgorithms, key)) { |
| 191 | + continue; |
| 192 | + } |
| 193 | + if (key.toUpperCase() === algName.toUpperCase()) { |
| 194 | + algName = key as AnyAlgorithm; |
| 195 | + desiredType = ( |
| 196 | + registeredAlgorithms as Record<string, typeof desiredType> |
| 197 | + )[algName]; |
| 198 | + } |
| 199 | + } |
| 200 | + if (desiredType === undefined) |
| 201 | + throw new Error(`Unrecognized algorithm name: ${algName}`); |
| 202 | + |
| 203 | + // Fast path everything below if the registered dictionary is null |
| 204 | + if (desiredType === null) return { name: algName }; |
| 205 | + |
| 206 | + // 6. |
| 207 | + const normalizedAlgorithm = algorithm; |
| 208 | + // TODO: implement this? Maybe via typescript? |
| 209 | + // webidl.converters[desiredType](algorithm, { |
| 210 | + // prefix: 'Failed to normalize algorithm', |
| 211 | + // context: 'passed algorithm', |
| 212 | + // }); |
| 213 | + // 7. |
| 214 | + normalizedAlgorithm.name = algName; |
| 215 | + |
| 216 | + // 9. |
| 217 | + const dict = simpleAlgorithmDictionaries[desiredType]; |
| 218 | + // 10. |
| 219 | + const dictKeys = dict ? Object.keys(dict) : []; |
| 220 | + for (let i = 0; i < dictKeys.length; i++) { |
| 221 | + const member = dictKeys[i] || ''; |
| 222 | + if (!Object.prototype.hasOwnProperty.call(dict, member)) continue; |
| 223 | + // TODO: implement this? Maybe via typescript? |
| 224 | + // const idlType = dict[member]; |
| 225 | + // const idlValue = normalizedAlgorithm[member]; |
| 226 | + // 3. |
| 227 | + // if (idlType === 'BufferSource' && idlValue) { |
| 228 | + // const isView = ArrayBufferIsView(idlValue); |
| 229 | + // normalizedAlgorithm[member] = TypedArrayPrototypeSlice( |
| 230 | + // new Uint8Array( |
| 231 | + // isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue, |
| 232 | + // isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0, |
| 233 | + // isView |
| 234 | + // ? getDataViewOrTypedArrayByteLength(idlValue) |
| 235 | + // : ArrayBufferPrototypeGetByteLength(idlValue) |
| 236 | + // ) |
| 237 | + // ); |
| 238 | + // } else if (idlType === 'HashAlgorithmIdentifier') { |
| 239 | + // normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest'); |
| 240 | + // } else if (idlType === 'AlgorithmIdentifier') { |
| 241 | + // // This extension point is not used by any supported algorithm (yet?) |
| 242 | + // throw lazyDOMException('Not implemented.', 'NotSupportedError'); |
| 243 | + // } |
| 244 | + } |
| 245 | + |
| 246 | + return normalizedAlgorithm; |
| 247 | +}; |
0 commit comments