-
-
Notifications
You must be signed in to change notification settings - Fork 318
/
decrypt.ts
138 lines (121 loc) · 3.63 KB
/
decrypt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import { createDecipheriv, KeyObject } from 'node:crypto'
import type { CipherGCMTypes } from 'node:crypto'
import type { DecryptFunction } from '../interfaces.d'
import checkIvLength from '../../lib/check_iv_length.js'
import checkCekLength from './check_cek_length.js'
import { concat } from '../../lib/buffer_utils.js'
import { JOSENotSupported, JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js'
import timingSafeEqual from './timing_safe_equal.js'
import cbcTag from './cbc_tag.js'
import { isCryptoKey } from './webcrypto.js'
import { checkEncCryptoKey } from '../../lib/crypto_key.js'
import isKeyObject from './is_key_object.js'
import invalidKeyInput from '../../lib/invalid_key_input.js'
import supported from './ciphers.js'
import { types } from './is_key_like.js'
function cbcDecrypt(
enc: string,
cek: KeyObject | Uint8Array,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
aad: Uint8Array,
) {
const keySize = parseInt(enc.slice(1, 4), 10)
if (isKeyObject(cek)) {
cek = cek.export()
}
const encKey = cek.subarray(keySize >> 3)
const macKey = cek.subarray(0, keySize >> 3)
const macSize = parseInt(enc.slice(-3), 10)
const algorithm = `aes-${keySize}-cbc`
if (!supported(algorithm)) {
throw new JOSENotSupported(`alg ${enc} is not supported by your javascript runtime`)
}
const expectedTag = cbcTag(aad, iv, ciphertext, macSize, macKey, keySize)
let macCheckPassed!: boolean
try {
macCheckPassed = timingSafeEqual(tag, expectedTag)
} catch {
//
}
if (!macCheckPassed) {
throw new JWEDecryptionFailed()
}
let plaintext!: Uint8Array
try {
const decipher = createDecipheriv(algorithm, encKey, iv)
plaintext = concat(decipher.update(ciphertext), decipher.final())
} catch {
//
}
if (!plaintext) {
throw new JWEDecryptionFailed()
}
return plaintext
}
function gcmDecrypt(
enc: string,
cek: KeyObject | Uint8Array,
ciphertext: Uint8Array,
iv: Uint8Array,
tag: Uint8Array,
aad: Uint8Array,
) {
const keySize = parseInt(enc.slice(1, 4), 10)
const algorithm = <CipherGCMTypes>`aes-${keySize}-gcm`
if (!supported(algorithm)) {
throw new JOSENotSupported(`alg ${enc} is not supported by your javascript runtime`)
}
try {
const decipher = createDecipheriv(algorithm, cek, iv, { authTagLength: 16 })
decipher.setAuthTag(tag)
if (aad.byteLength) {
decipher.setAAD(aad, { plaintextLength: ciphertext.length })
}
const plaintext = decipher.update(ciphertext)
decipher.final()
return plaintext
} catch {
throw new JWEDecryptionFailed()
}
}
const decrypt: DecryptFunction = (
enc: string,
cek: unknown,
ciphertext: Uint8Array,
iv: Uint8Array | undefined,
tag: Uint8Array | undefined,
aad: Uint8Array,
) => {
let key: KeyObject | Uint8Array
if (isCryptoKey(cek)) {
checkEncCryptoKey(cek, enc, 'decrypt')
key = KeyObject.from(cek)
} else if (cek instanceof Uint8Array || isKeyObject(cek)) {
key = cek
} else {
throw new TypeError(invalidKeyInput(cek, ...types, 'Uint8Array'))
}
if (!iv) {
throw new JWEInvalid('JWE Initialization Vector missing')
}
if (!tag) {
throw new JWEInvalid('JWE Authentication Tag missing')
}
checkCekLength(enc, key)
checkIvLength(enc, iv)
switch (enc) {
case 'A128CBC-HS256':
case 'A192CBC-HS384':
case 'A256CBC-HS512':
return cbcDecrypt(enc, key, ciphertext, iv, tag, aad)
case 'A128GCM':
case 'A192GCM':
case 'A256GCM':
return gcmDecrypt(enc, key, ciphertext, iv, tag, aad)
default:
throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm')
}
}
export default decrypt