@lindorm/aes 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -8
- package/CHANGELOG.md +0 -176
- package/MERMAID.md +0 -155
- package/__tests__/INTEROP-RESULTS.md +0 -66
- package/__tests__/esm-smoke.test.ts +0 -16
- package/__tests__/fixtures/keys.ts +0 -60
- package/__tests__/helpers/buffer-utils.ts +0 -11
- package/__tests__/helpers/index.ts +0 -2
- package/__tests__/helpers/jwe-adapter.ts +0 -123
- package/__tests__/jose-jwe.test.ts +0 -464
- package/__tests__/noble-ciphers.test.ts +0 -209
- package/vitest.config.mjs +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lindorm/aes",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"author": "Jonn Nilsson",
|
|
6
6
|
"repository": {
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
14
17
|
"type": "module",
|
|
15
18
|
"typings": "dist/index.d.ts",
|
|
16
19
|
"exports": {
|
|
@@ -41,16 +44,16 @@
|
|
|
41
44
|
"verify": "npm run typecheck && npm run build && npm test"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"@lindorm/b64": "^0.2.
|
|
45
|
-
"@lindorm/errors": "^0.2.
|
|
46
|
-
"@lindorm/is": "^0.2.
|
|
47
|
-
"@lindorm/kryptos": "^0.8.
|
|
48
|
-
"@lindorm/types": "^0.
|
|
49
|
-
"@lindorm/utils": "^0.8.
|
|
47
|
+
"@lindorm/b64": "^0.2.1",
|
|
48
|
+
"@lindorm/errors": "^0.2.2",
|
|
49
|
+
"@lindorm/is": "^0.2.2",
|
|
50
|
+
"@lindorm/kryptos": "^0.8.2",
|
|
51
|
+
"@lindorm/types": "^0.7.0",
|
|
52
|
+
"@lindorm/utils": "^0.8.2"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
52
55
|
"@noble/ciphers": "^2.1.1",
|
|
53
56
|
"jose": "^6.2.1"
|
|
54
57
|
},
|
|
55
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "ed9df662f3b73a3d773027b5acdfe128ff3dc140"
|
|
56
59
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
-
|
|
6
|
-
# [0.7.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.5...@lindorm/aes@0.7.0) (2026-05-02)
|
|
7
|
-
|
|
8
|
-
### Bug Fixes
|
|
9
|
-
|
|
10
|
-
- **aes:** mark type-only imports in **tests** ([23ade84](https://github.com/lindorm-io/monorepo/commit/23ade8444b945be2eed225f1c8587b60a32d530a))
|
|
11
|
-
- **aes:** use IKryptos interface for fixture helper return type ([42d1a28](https://github.com/lindorm-io/monorepo/commit/42d1a287d69977b6f2f39782910a5d45dffdbfdb))
|
|
12
|
-
|
|
13
|
-
### Features
|
|
14
|
-
|
|
15
|
-
- migrate 20 packages from jest to vitest ([d8bfda8](https://github.com/lindorm-io/monorepo/commit/d8bfda8854dc1cb9537ba0b3e47ec4e4c7bded08))
|
|
16
|
-
|
|
17
|
-
## [0.6.5](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.4...@lindorm/aes@0.6.5) (2026-04-19)
|
|
18
|
-
|
|
19
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
20
|
-
|
|
21
|
-
## [0.6.4](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.3...@lindorm/aes@0.6.4) (2026-04-15)
|
|
22
|
-
|
|
23
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
24
|
-
|
|
25
|
-
## [0.6.3](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.2...@lindorm/aes@0.6.3) (2026-04-01)
|
|
26
|
-
|
|
27
|
-
### Bug Fixes
|
|
28
|
-
|
|
29
|
-
- **aes,amphora:** use relative imports for test fixtures ([5ebc484](https://github.com/lindorm-io/monorepo/commit/5ebc484e7dd664b85f40a57db0057364e8883ce9))
|
|
30
|
-
|
|
31
|
-
## [0.6.2](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.1...@lindorm/aes@0.6.2) (2026-03-13)
|
|
32
|
-
|
|
33
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
34
|
-
|
|
35
|
-
## [0.6.1](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.6.0...@lindorm/aes@0.6.1) (2026-03-13)
|
|
36
|
-
|
|
37
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
38
|
-
|
|
39
|
-
# [0.6.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.5...@lindorm/aes@0.6.0) (2026-02-17)
|
|
40
|
-
|
|
41
|
-
### Bug Fixes
|
|
42
|
-
|
|
43
|
-
- **aes:** add buffer boundary validation in encoded string parser ([579e8f7](https://github.com/lindorm-io/monorepo/commit/579e8f7eb40570f978cd52d1f87f7f8570474d6d))
|
|
44
|
-
- **aes:** add GCM auth tag enforcement and key-wrap input validation ([1a8fd3c](https://github.com/lindorm-io/monorepo/commit/1a8fd3cb6f83cf59a3089a777fba4c8b7f1828fb))
|
|
45
|
-
- **aes:** add missing @lindorm/utils dependency ([6d0ee2a](https://github.com/lindorm-io/monorepo/commit/6d0ee2ae68f91fb9eb04529c96e05ff1d843dfd0))
|
|
46
|
-
- **aes:** make CBC HMAC auth tag compliant with RFC 7518 ([7877022](https://github.com/lindorm-io/monorepo/commit/7877022bebdf902ff13996b1032a991356f3760c))
|
|
47
|
-
- **aes:** make PBES2 salt compliant with RFC 7518 ([2693afa](https://github.com/lindorm-io/monorepo/commit/2693afa88db535aeaff7fd5537885dd3a45bc09a))
|
|
48
|
-
- **aes:** make verify/assert work with non-string content ([aa94c66](https://github.com/lindorm-io/monorepo/commit/aa94c66511326f70fdfc0245ce12953025aa4236))
|
|
49
|
-
- **aes:** remove unnecessary g flag from tokenised regex ([5a989b1](https://github.com/lindorm-io/monorepo/commit/5a989b1d96b025b3180339294e8ea072d215c7d3))
|
|
50
|
-
- **aes:** replace generic Error with AesError in all locations ([ff7a08d](https://github.com/lindorm-io/monorepo/commit/ff7a08d55e9b39755e059e31e99fb45b687bdde2))
|
|
51
|
-
- **aes:** use Concat KDF per RFC 7518 for ECDH-ES key agreement ([8e92b8f](https://github.com/lindorm-io/monorepo/commit/8e92b8f60ee46c99f0c66af244fd1e7648130a9c))
|
|
52
|
-
- **aes:** use crypto.randomInt for PBKDF2 iterations, fix RSA test fixture ([a5457aa](https://github.com/lindorm-io/monorepo/commit/a5457aa5973e4eab662dbc6e62c9470f7d6fabf2))
|
|
53
|
-
- **aes:** use oct keys directly for AES key wrap per RFC 7518 ([c65ca49](https://github.com/lindorm-io/monorepo/commit/c65ca49d758f21e868e96512a96fc8df0559947e))
|
|
54
|
-
- **aes:** use timingSafeEqual for ECB key unwrap integrity check ([dd27a65](https://github.com/lindorm-io/monorepo/commit/dd27a65c84eed068d6e9e5de34d0eaca6cda67fc))
|
|
55
|
-
- **aes:** use timingSafeEqual for HMAC auth tag verification ([757bef5](https://github.com/lindorm-io/monorepo/commit/757bef5657a6d1366c222c9205a8f4cfe563e77d))
|
|
56
|
-
- **lint:** add missing eslint-config-prettier and fix prettier formatting ([6899e39](https://github.com/lindorm-io/monorepo/commit/6899e39ad7700e373173b0a61b429b5536c13934))
|
|
57
|
-
- **lint:** resolve eslint warnings and errors ([210ef3c](https://github.com/lindorm-io/monorepo/commit/210ef3c91c82521c4cec57bc2256324ba9c3f45a))
|
|
58
|
-
|
|
59
|
-
### Features
|
|
60
|
-
|
|
61
|
-
- **aes:** accept optional AAD parameter for authenticated encryption ([011b67f](https://github.com/lindorm-io/monorepo/commit/011b67fb8ba18a361e2c31fd0e78298a89f89cd2))
|
|
62
|
-
- **aes:** add prepareEncryption() for two-step JWE-compliant encryption ([56b3c54](https://github.com/lindorm-io/monorepo/commit/56b3c5435722b2b94f05d6483c7651ccdd5ea9dd))
|
|
63
|
-
- **aes:** rewrite formats with unified model and always-on AAD ([bc1da71](https://github.com/lindorm-io/monorepo/commit/bc1da719d5ee5ee151d9220e6e738a9431e036b6))
|
|
64
|
-
|
|
65
|
-
## [0.5.5](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.4...@lindorm/aes@0.5.5) (2025-09-18)
|
|
66
|
-
|
|
67
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
68
|
-
|
|
69
|
-
## [0.5.4](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.3...@lindorm/aes@0.5.4) (2025-07-19)
|
|
70
|
-
|
|
71
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
72
|
-
|
|
73
|
-
## [0.5.3](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.2...@lindorm/aes@0.5.3) (2025-07-12)
|
|
74
|
-
|
|
75
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
76
|
-
|
|
77
|
-
## [0.5.2](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.1...@lindorm/aes@0.5.2) (2025-07-10)
|
|
78
|
-
|
|
79
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
80
|
-
|
|
81
|
-
## [0.5.1](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.5.0...@lindorm/aes@0.5.1) (2025-07-02)
|
|
82
|
-
|
|
83
|
-
### Bug Fixes
|
|
84
|
-
|
|
85
|
-
- amend bug in aes utility ([7437e8e](https://github.com/lindorm-io/monorepo/commit/7437e8e0f047bb0995b8a8c0e6929c9cc368d592))
|
|
86
|
-
|
|
87
|
-
# [0.5.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.4.2...@lindorm/aes@0.5.0) (2025-06-17)
|
|
88
|
-
|
|
89
|
-
### Bug Fixes
|
|
90
|
-
|
|
91
|
-
- align with kryptos changes ([3f934a4](https://github.com/lindorm-io/monorepo/commit/3f934a4ec55eee3d4ebc6f0be55886d8f095af8b))
|
|
92
|
-
- align with kryptos changes ([206eb38](https://github.com/lindorm-io/monorepo/commit/206eb38ae2a03b14973e706035c87a953cc753af))
|
|
93
|
-
- improve and expose aes parsing ([ec6f271](https://github.com/lindorm-io/monorepo/commit/ec6f27179ec3d67146a50257cbff98fe253c3380))
|
|
94
|
-
- improve types ([f6ce002](https://github.com/lindorm-io/monorepo/commit/f6ce002e8555c54ba4f12bd67222457fa2bcf90a))
|
|
95
|
-
- update try catch ([7ebebe8](https://github.com/lindorm-io/monorepo/commit/7ebebe81f40851b0d1fcb05e6e6cc60b1c754a91))
|
|
96
|
-
|
|
97
|
-
### Features
|
|
98
|
-
|
|
99
|
-
- add content type to aes data ([dfb3285](https://github.com/lindorm-io/monorepo/commit/dfb3285ddf20bc77cf8f3d2531e26032853b98a9))
|
|
100
|
-
|
|
101
|
-
## [0.4.2](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.4.1...@lindorm/aes@0.4.2) (2025-01-28)
|
|
102
|
-
|
|
103
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
104
|
-
|
|
105
|
-
## [0.4.1](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.4.0...@lindorm/aes@0.4.1) (2024-10-12)
|
|
106
|
-
|
|
107
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
108
|
-
|
|
109
|
-
# [0.4.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.3.3...@lindorm/aes@0.4.0) (2024-10-09)
|
|
110
|
-
|
|
111
|
-
### Bug Fixes
|
|
112
|
-
|
|
113
|
-
- move content to end of encoded array so that it can be any size ([b053924](https://github.com/lindorm-io/monorepo/commit/b05392484342976f519b32f943aac41271761df4))
|
|
114
|
-
|
|
115
|
-
### Features
|
|
116
|
-
|
|
117
|
-
- add automatic b64 encoding ([f3ac64e](https://github.com/lindorm-io/monorepo/commit/f3ac64e7922528b1afe0f0acbc52b62aa7003d2d))
|
|
118
|
-
- rename modes and add encoded ([b8c9d4c](https://github.com/lindorm-io/monorepo/commit/b8c9d4c26a069444fa7bff5a809308cecff971ef))
|
|
119
|
-
|
|
120
|
-
## [0.3.3](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.3.2...@lindorm/aes@0.3.3) (2024-09-25)
|
|
121
|
-
|
|
122
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
123
|
-
|
|
124
|
-
## [0.3.2](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.3.1...@lindorm/aes@0.3.2) (2024-09-23)
|
|
125
|
-
|
|
126
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
127
|
-
|
|
128
|
-
## [0.3.1](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.3.0...@lindorm/aes@0.3.1) (2024-09-20)
|
|
129
|
-
|
|
130
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
131
|
-
|
|
132
|
-
# [0.3.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.2.0...@lindorm/aes@0.3.0) (2024-05-20)
|
|
133
|
-
|
|
134
|
-
### Features
|
|
135
|
-
|
|
136
|
-
- add gcm keywrap ([8eefa5d](https://github.com/lindorm-io/monorepo/commit/8eefa5dd2914faba842c0a050a9317d2b6f5b197))
|
|
137
|
-
|
|
138
|
-
# [0.2.0](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.1.3...@lindorm/aes@0.2.0) (2024-05-19)
|
|
139
|
-
|
|
140
|
-
### Bug Fixes
|
|
141
|
-
|
|
142
|
-
- align curves to kryptos ([b9288a5](https://github.com/lindorm-io/monorepo/commit/b9288a54b6dbb520328aff77cd3c8d2818183ac5))
|
|
143
|
-
- align with kryptos changes ([344c4e2](https://github.com/lindorm-io/monorepo/commit/344c4e2fad07e66c91f7e0820bfc929c1f8ffcab))
|
|
144
|
-
- improve kryptos generate method ([9e7098d](https://github.com/lindorm-io/monorepo/commit/9e7098d4b219b11140e28e554ffd573204772249))
|
|
145
|
-
- remove private key encryption ([be54916](https://github.com/lindorm-io/monorepo/commit/be54916a20de667e96826d6be0eb8d0fda67176e))
|
|
146
|
-
- remove unnecessary code ([e44a056](https://github.com/lindorm-io/monorepo/commit/e44a0565e577fc23a827c9283839684c1e40d287))
|
|
147
|
-
- rename interfaces ([3b1f457](https://github.com/lindorm-io/monorepo/commit/3b1f45736f88b8c2d4481cbeca6da87bf8443bde))
|
|
148
|
-
- simplify interfaces with kryptos metadata ([c4075d2](https://github.com/lindorm-io/monorepo/commit/c4075d2e133c2fe0a1fafa548da68db34b3407c6))
|
|
149
|
-
- use pbkdf2 key derivation for oct ([d068947](https://github.com/lindorm-io/monorepo/commit/d068947f70712fb71f57d5ec6947062219200155))
|
|
150
|
-
|
|
151
|
-
### Features
|
|
152
|
-
|
|
153
|
-
- add okp encryption and clean up ec encryption ([4d3cbab](https://github.com/lindorm-io/monorepo/commit/4d3cbabe1968ab7f8a9ecc8e226ce91403342f0f))
|
|
154
|
-
- implement missing encryption types ([c94b538](https://github.com/lindorm-io/monorepo/commit/c94b53823fcb7a24823b25535b83799f2bbdd250))
|
|
155
|
-
- refine and improve oct encryption ([99db5a2](https://github.com/lindorm-io/monorepo/commit/99db5a290b7a081ab80c3811bcc04021e1ac9b4e))
|
|
156
|
-
- use pbkdf with short oct keys ([d1d8e5e](https://github.com/lindorm-io/monorepo/commit/d1d8e5ea6dcac24b1b1402e841777a0affefcfff))
|
|
157
|
-
|
|
158
|
-
## [0.1.3](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.1.2...@lindorm/aes@0.1.3) (2024-05-12)
|
|
159
|
-
|
|
160
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
161
|
-
|
|
162
|
-
## [0.1.2](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.1.1...@lindorm/aes@0.1.2) (2024-05-11)
|
|
163
|
-
|
|
164
|
-
**Note:** Version bump only for package @lindorm/aes
|
|
165
|
-
|
|
166
|
-
## [0.1.1](https://github.com/lindorm-io/monorepo/compare/@lindorm/aes@0.1.0...@lindorm/aes@0.1.1) (2024-05-11)
|
|
167
|
-
|
|
168
|
-
### Bug Fixes
|
|
169
|
-
|
|
170
|
-
- amend wrong export path and rename ([87e6dd1](https://github.com/lindorm-io/monorepo/commit/87e6dd12057fe35c1c0b26a327a098015f041b44))
|
|
171
|
-
|
|
172
|
-
# 0.1.0 (2024-05-11)
|
|
173
|
-
|
|
174
|
-
### Features
|
|
175
|
-
|
|
176
|
-
- implement aes package ([4267f1f](https://github.com/lindorm-io/monorepo/commit/4267f1f2b368bcc42181f274872793897347e539))
|
package/MERMAID.md
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Mermaid Diagram
|
|
2
|
-
|
|
3
|
-
```mermaid
|
|
4
|
-
|
|
5
|
-
graph TB
|
|
6
|
-
subgraph "External Dependencies"
|
|
7
|
-
KRYPTOS["@lindorm/kryptos<br/>(Key Management)"]
|
|
8
|
-
B64["@lindorm/b64<br/>(Base64 Encoding)"]
|
|
9
|
-
IS["@lindorm/is<br/>(Type Guards)"]
|
|
10
|
-
ERRORS["@lindorm/errors<br/>(Error Handling)"]
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
subgraph "Public API"
|
|
14
|
-
AESKIT["AesKit Class<br/>Main Entry Point"]
|
|
15
|
-
PARSE["parseAes()<br/>Parse any format"]
|
|
16
|
-
ISAES["isAesTokenised()<br/>Type checking"]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
subgraph "Encryption Flow"
|
|
20
|
-
ENCRYPT["encrypt(data, mode)"]
|
|
21
|
-
GET_ENC_KEY["getEncryptionKey()"]
|
|
22
|
-
|
|
23
|
-
subgraph "Key Type Routing"
|
|
24
|
-
EC_ENC["EC Keys<br/>(Elliptic Curve)"]
|
|
25
|
-
OKP_ENC["OKP Keys<br/>(Edwards Curve)"]
|
|
26
|
-
OCT_ENC["oct Keys<br/>(Symmetric)"]
|
|
27
|
-
RSA_ENC["RSA Keys"]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
subgraph "Key Derivation Methods"
|
|
31
|
-
DIR["Direct Key<br/>(dir)"]
|
|
32
|
-
ECDH["ECDH-ES<br/>(Diffie-Hellman)"]
|
|
33
|
-
KEYWRAP["Key Wrap<br/>(AES-KW/GCM)"]
|
|
34
|
-
PBKDF["PBKDF2<br/>(Password-based)"]
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
CEK["Content Encryption Key<br/>(Split into encKey + hashKey)"]
|
|
38
|
-
AES_CIPHER["Node crypto.createCipheriv()<br/>(AES-128/192/256-CBC/GCM)"]
|
|
39
|
-
AUTH_TAG["Generate Auth Tag<br/>(GCM or HMAC)"]
|
|
40
|
-
|
|
41
|
-
subgraph "Output Modes"
|
|
42
|
-
ENCODED["Encoded<br/>(base64 string)"]
|
|
43
|
-
RECORD["Record<br/>(Buffer objects)"]
|
|
44
|
-
SERIALISED["Serialised<br/>(JSON-safe strings)"]
|
|
45
|
-
TOKENISED["Tokenised<br/>($enc$params$content$)"]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
subgraph "Decryption Flow"
|
|
50
|
-
DECRYPT["decrypt(data)"]
|
|
51
|
-
PARSE_INPUT["Parse Input<br/>(parseAes)"]
|
|
52
|
-
GET_DEC_KEY["getDecryptionKey()"]
|
|
53
|
-
|
|
54
|
-
subgraph "Key Type Decryption"
|
|
55
|
-
EC_DEC["EC Keys"]
|
|
56
|
-
OKP_DEC["OKP Keys"]
|
|
57
|
-
OCT_DEC["oct Keys"]
|
|
58
|
-
RSA_DEC["RSA Keys"]
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
CEK_DEC["Content Encryption Key"]
|
|
62
|
-
AES_DECIPHER["Node crypto.createDecipheriv()"]
|
|
63
|
-
VERIFY_TAG["Verify Auth Tag"]
|
|
64
|
-
PARSE_CONTENT["Parse Content<br/>(by contentType)"]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
subgraph "Data Types"
|
|
68
|
-
CONTENT["AesContent<br/>string | Buffer | object | array | number"]
|
|
69
|
-
CONTENT_TYPE["AesContentType<br/>text/plain | application/json | application/octet-stream"]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
subgraph "Encryption Metadata"
|
|
73
|
-
RECORD_TYPE["AesEncryptionRecord<br/>• algorithm<br/>• encryption<br/>• keyId<br/>• content<br/>•
|
|
74
|
-
authTag<br/>• IV<br/>• contentType<br/>• pbkdfSalt/Iterations<br/>• publicEncryptionKey/Jwk"]
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
%% Main flow connections
|
|
78
|
-
AESKIT --> ENCRYPT
|
|
79
|
-
AESKIT --> DECRYPT
|
|
80
|
-
AESKIT --> KRYPTOS
|
|
81
|
-
|
|
82
|
-
ENCRYPT --> CONTENT
|
|
83
|
-
ENCRYPT --> GET_ENC_KEY
|
|
84
|
-
GET_ENC_KEY --> KRYPTOS
|
|
85
|
-
|
|
86
|
-
GET_ENC_KEY --> EC_ENC
|
|
87
|
-
GET_ENC_KEY --> OKP_ENC
|
|
88
|
-
GET_ENC_KEY --> OCT_ENC
|
|
89
|
-
GET_ENC_KEY --> RSA_ENC
|
|
90
|
-
|
|
91
|
-
EC_ENC --> ECDH
|
|
92
|
-
EC_ENC --> KEYWRAP
|
|
93
|
-
OKP_ENC --> ECDH
|
|
94
|
-
OKP_ENC --> KEYWRAP
|
|
95
|
-
OCT_ENC --> DIR
|
|
96
|
-
OCT_ENC --> KEYWRAP
|
|
97
|
-
OCT_ENC --> PBKDF
|
|
98
|
-
RSA_ENC --> KEYWRAP
|
|
99
|
-
|
|
100
|
-
ECDH --> CEK
|
|
101
|
-
KEYWRAP --> CEK
|
|
102
|
-
PBKDF --> CEK
|
|
103
|
-
DIR --> CEK
|
|
104
|
-
|
|
105
|
-
CEK --> AES_CIPHER
|
|
106
|
-
CONTENT --> AES_CIPHER
|
|
107
|
-
AES_CIPHER --> AUTH_TAG
|
|
108
|
-
|
|
109
|
-
AUTH_TAG --> RECORD_TYPE
|
|
110
|
-
|
|
111
|
-
RECORD_TYPE --> ENCODED
|
|
112
|
-
RECORD_TYPE --> RECORD
|
|
113
|
-
RECORD_TYPE --> SERIALISED
|
|
114
|
-
RECORD_TYPE --> TOKENISED
|
|
115
|
-
|
|
116
|
-
%% Decryption flow
|
|
117
|
-
DECRYPT --> PARSE_INPUT
|
|
118
|
-
PARSE_INPUT --> ENCODED
|
|
119
|
-
PARSE_INPUT --> RECORD
|
|
120
|
-
PARSE_INPUT --> SERIALISED
|
|
121
|
-
PARSE_INPUT --> TOKENISED
|
|
122
|
-
|
|
123
|
-
PARSE_INPUT --> GET_DEC_KEY
|
|
124
|
-
GET_DEC_KEY --> KRYPTOS
|
|
125
|
-
|
|
126
|
-
GET_DEC_KEY --> EC_DEC
|
|
127
|
-
GET_DEC_KEY --> OKP_DEC
|
|
128
|
-
GET_DEC_KEY --> OCT_DEC
|
|
129
|
-
GET_DEC_KEY --> RSA_DEC
|
|
130
|
-
|
|
131
|
-
EC_DEC --> CEK_DEC
|
|
132
|
-
OKP_DEC --> CEK_DEC
|
|
133
|
-
OCT_DEC --> CEK_DEC
|
|
134
|
-
RSA_DEC --> CEK_DEC
|
|
135
|
-
|
|
136
|
-
CEK_DEC --> AES_DECIPHER
|
|
137
|
-
RECORD_TYPE --> AES_DECIPHER
|
|
138
|
-
AES_DECIPHER --> VERIFY_TAG
|
|
139
|
-
VERIFY_TAG --> PARSE_CONTENT
|
|
140
|
-
PARSE_CONTENT --> CONTENT_TYPE
|
|
141
|
-
|
|
142
|
-
%% Utility connections
|
|
143
|
-
B64 --> ENCODED
|
|
144
|
-
B64 --> TOKENISED
|
|
145
|
-
B64 --> SERIALISED
|
|
146
|
-
IS --> PARSE_INPUT
|
|
147
|
-
ERRORS --> AESKIT
|
|
148
|
-
|
|
149
|
-
style AESKIT fill:#4a90e2,stroke:#2e5c8a,color:#fff
|
|
150
|
-
style ENCRYPT fill:#50c878,stroke:#2d7a4a,color:#fff
|
|
151
|
-
style DECRYPT fill:#e27d60,stroke:#a85a43,color:#fff
|
|
152
|
-
style KRYPTOS fill:#9b59b6,stroke:#6c3a8a,color:#fff
|
|
153
|
-
style CEK fill:#f39c12,stroke:#b87a0a,color:#fff
|
|
154
|
-
style CEK_DEC fill:#f39c12,stroke:#b87a0a,color:#fff
|
|
155
|
-
```
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# AES Interop Test Results
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
All 59 interop tests pass. Zero divergences found between `@lindorm/aes`,
|
|
6
|
-
`@noble/ciphers`, and `jose`.
|
|
7
|
-
|
|
8
|
-
| Suite | Tests | Pass | Fail | Skip |
|
|
9
|
-
| ------------- | ------ | ------ | ----- | ----- |
|
|
10
|
-
| noble-ciphers | 21 | 21 | 0 | 0 |
|
|
11
|
-
| jose-jwe | 38 | 38 | 0 | 0 |
|
|
12
|
-
| **Total** | **59** | **59** | **0** | **0** |
|
|
13
|
-
|
|
14
|
-
## noble/ciphers Primitive Tests
|
|
15
|
-
|
|
16
|
-
| Primitive | Key Sizes | Direction | Result |
|
|
17
|
-
| ----------------- | ------------- | ------------------------------ | ------ |
|
|
18
|
-
| AES-GCM | 128, 192, 256 | ours → noble | PASS |
|
|
19
|
-
| AES-GCM | 128, 192, 256 | noble → ours | PASS |
|
|
20
|
-
| AES-GCM | 128, 192, 256 | byte-identical (pinned IV) | PASS |
|
|
21
|
-
| AES-KW (RFC 3394) | 128, 192, 256 | ours → noble | PASS |
|
|
22
|
-
| AES-KW (RFC 3394) | 128, 192, 256 | noble → ours | PASS |
|
|
23
|
-
| AES-KW (RFC 3394) | 128, 192, 256 | byte-identical (deterministic) | PASS |
|
|
24
|
-
| AES-CBC (raw) | 128, 192, 256 | byte-identical (PKCS7) | PASS |
|
|
25
|
-
|
|
26
|
-
## jose JWE Tests
|
|
27
|
-
|
|
28
|
-
| Algorithm | Encryption | Direction | Result |
|
|
29
|
-
| ---------------------- | ------------------------------------------- | ----------- | ------ |
|
|
30
|
-
| dir | A128GCM, A192GCM, A256GCM | ours → jose | PASS |
|
|
31
|
-
| dir | A128GCM, A192GCM, A256GCM | jose → ours | PASS |
|
|
32
|
-
| dir | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | ours → jose | PASS |
|
|
33
|
-
| dir | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | jose → ours | PASS |
|
|
34
|
-
| A128KW, A192KW, A256KW | A128GCM, A256GCM | ours → jose | PASS |
|
|
35
|
-
| A128KW, A192KW, A256KW | A128GCM, A256GCM | jose → ours | PASS |
|
|
36
|
-
| A128KW, A192KW, A256KW | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | ours → jose | PASS |
|
|
37
|
-
| A128KW, A192KW, A256KW | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | jose → ours | PASS |
|
|
38
|
-
| A128GCMKW, A256GCMKW | A256GCM | ours → jose | PASS |
|
|
39
|
-
| A128GCMKW, A256GCMKW | A256GCM | jose → ours | PASS |
|
|
40
|
-
| Pinned deterministic | A256GCM (byte-identical) | comparison | PASS |
|
|
41
|
-
| Pinned deterministic | A256GCM (round-trip) | comparison | PASS |
|
|
42
|
-
| Pinned deterministic | A128CBC-HS256 (byte-identical) | comparison | PASS |
|
|
43
|
-
| Pinned deterministic | A128CBC-HS256 (round-trip) | comparison | PASS |
|
|
44
|
-
|
|
45
|
-
## Validated RFC Compliance
|
|
46
|
-
|
|
47
|
-
- **RFC 7516** (JWE): AAD computation, flattened serialization format
|
|
48
|
-
- **RFC 7518** (JWA): AES-GCM, AES-CBC-HMAC-SHA2, AES Key Wrap, AES-GCM Key Wrap
|
|
49
|
-
- **RFC 3394**: AES Key Wrap (deterministic, AIV verification)
|
|
50
|
-
|
|
51
|
-
## Notes
|
|
52
|
-
|
|
53
|
-
- GCMKW "ours → jose" direction uses a two-pass approach due to the circular
|
|
54
|
-
dependency between key-wrap params and JWE AAD. Content is re-encrypted with
|
|
55
|
-
the correct AAD after extracting key-wrap params from the first pass.
|
|
56
|
-
- jose v6 rejects `setContentEncryptionKey()` with `alg: "dir"`, so deterministic
|
|
57
|
-
pinned tests use A\*KW algorithms instead. Content encryption is identical
|
|
58
|
-
regardless of key management algorithm.
|
|
59
|
-
- `@noble/ciphers` GCM returns ciphertext with auth tag appended (last 16 bytes).
|
|
60
|
-
Tests correctly split/concatenate when converting between formats.
|
|
61
|
-
|
|
62
|
-
## Environment
|
|
63
|
-
|
|
64
|
-
- Node.js >= 24.4.0
|
|
65
|
-
- `@noble/ciphers` ^1.2.1
|
|
66
|
-
- `jose` ^6.1.3
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { gcm, cbc, aeskw } from "@noble/ciphers/aes.js";
|
|
2
|
-
import { FlattenedEncrypt, flattenedDecrypt } from "jose";
|
|
3
|
-
import { describe, expect, test } from "vitest";
|
|
4
|
-
|
|
5
|
-
describe("ESM import smoke test", () => {
|
|
6
|
-
test("should import @noble/ciphers", () => {
|
|
7
|
-
expect(gcm).toBeDefined();
|
|
8
|
-
expect(cbc).toBeDefined();
|
|
9
|
-
expect(aeskw).toBeDefined();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("should import jose", () => {
|
|
13
|
-
expect(FlattenedEncrypt).toBeDefined();
|
|
14
|
-
expect(flattenedDecrypt).toBeDefined();
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
import {
|
|
3
|
-
type IKryptos,
|
|
4
|
-
type KryptosAlgorithm,
|
|
5
|
-
type KryptosEncryption,
|
|
6
|
-
KryptosKit,
|
|
7
|
-
} from "@lindorm/kryptos";
|
|
8
|
-
|
|
9
|
-
// Fixed raw symmetric keys for dir mode (CEK = key)
|
|
10
|
-
export const RAW_KEY_128 = Buffer.from("000102030405060708090a0b0c0d0e0f", "hex");
|
|
11
|
-
export const RAW_KEY_192 = Buffer.from(
|
|
12
|
-
"000102030405060708090a0b0c0d0e0f1011121314151617",
|
|
13
|
-
"hex",
|
|
14
|
-
);
|
|
15
|
-
export const RAW_KEY_256 = Buffer.from(
|
|
16
|
-
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
17
|
-
"hex",
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
// CBC-HMAC composite keys (double-length: half for HMAC, half for AES-CBC)
|
|
21
|
-
export const RAW_KEY_256_CBC = Buffer.from(
|
|
22
|
-
"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
23
|
-
"hex",
|
|
24
|
-
);
|
|
25
|
-
export const RAW_KEY_384_CBC = Buffer.from(
|
|
26
|
-
"404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f",
|
|
27
|
-
"hex",
|
|
28
|
-
);
|
|
29
|
-
export const RAW_KEY_512_CBC = Buffer.from(
|
|
30
|
-
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf",
|
|
31
|
-
"hex",
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
// Key Encryption Keys for KW modes
|
|
35
|
-
export const KEK_128 = Buffer.from("c0c1c2c3c4c5c6c7c8c9cacbcccdcecf", "hex");
|
|
36
|
-
export const KEK_192 = Buffer.from(
|
|
37
|
-
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7",
|
|
38
|
-
"hex",
|
|
39
|
-
);
|
|
40
|
-
export const KEK_256 = Buffer.from(
|
|
41
|
-
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
|
42
|
-
"hex",
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Helper to create a Kryptos oct key from raw bytes.
|
|
47
|
-
*/
|
|
48
|
-
export const createOctKryptos = (
|
|
49
|
-
raw: Buffer,
|
|
50
|
-
algorithm: KryptosAlgorithm,
|
|
51
|
-
encryption?: KryptosEncryption,
|
|
52
|
-
): IKryptos =>
|
|
53
|
-
KryptosKit.from.der({
|
|
54
|
-
id: randomUUID(),
|
|
55
|
-
algorithm,
|
|
56
|
-
encryption,
|
|
57
|
-
privateKey: raw,
|
|
58
|
-
type: "oct",
|
|
59
|
-
use: "enc",
|
|
60
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convert Buffer to Uint8Array (zero-copy view).
|
|
3
|
-
*/
|
|
4
|
-
export const toUint8Array = (buf: Buffer): Uint8Array =>
|
|
5
|
-
new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Convert Uint8Array to Buffer (zero-copy view).
|
|
9
|
-
*/
|
|
10
|
-
export const toBuffer = (arr: Uint8Array): Buffer =>
|
|
11
|
-
Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import type { FlattenedJWE } from "jose";
|
|
2
|
-
import type { AesDecryptionRecord } from "../../src/types/aes-decryption-data.js";
|
|
3
|
-
import type { AesEncryptionRecord } from "../../src/types/aes-encryption-data.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Build a JWE protected header JSON string, its base64url encoding,
|
|
7
|
-
* and the AAD buffer (ASCII bytes of the base64url-encoded header).
|
|
8
|
-
*
|
|
9
|
-
* Per RFC 7516 section 5.1 step 14, the JWE AAD is the ASCII representation
|
|
10
|
-
* of the base64url-encoded protected header.
|
|
11
|
-
*
|
|
12
|
-
* For A*GCMKW algorithms, the key-wrap iv and tag go into the protected header
|
|
13
|
-
* per RFC 7518 section 4.7.
|
|
14
|
-
*/
|
|
15
|
-
export const buildProtectedHeader = (
|
|
16
|
-
algorithm: string,
|
|
17
|
-
encryption: string,
|
|
18
|
-
gcmkwParams?: { iv: Buffer; tag: Buffer },
|
|
19
|
-
): {
|
|
20
|
-
headerJson: string;
|
|
21
|
-
headerB64u: string;
|
|
22
|
-
aadBuffer: Buffer;
|
|
23
|
-
} => {
|
|
24
|
-
const header: Record<string, string> = { alg: algorithm, enc: encryption };
|
|
25
|
-
|
|
26
|
-
if (gcmkwParams) {
|
|
27
|
-
header.iv = gcmkwParams.iv.toString("base64url");
|
|
28
|
-
header.tag = gcmkwParams.tag.toString("base64url");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const headerJson = JSON.stringify(header);
|
|
32
|
-
const headerB64u = Buffer.from(headerJson, "utf8").toString("base64url");
|
|
33
|
-
const aadBuffer = Buffer.from(headerB64u, "ascii");
|
|
34
|
-
|
|
35
|
-
return { headerJson, headerB64u, aadBuffer };
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Convert our AesEncryptionRecord to a jose FlattenedJWE object.
|
|
40
|
-
*
|
|
41
|
-
* Field mapping:
|
|
42
|
-
* - protected: base64url-encoded protected header (passed in)
|
|
43
|
-
* - iv: base64url of record.initialisationVector
|
|
44
|
-
* - ciphertext: base64url of record.content
|
|
45
|
-
* - tag: base64url of record.authTag
|
|
46
|
-
* - encrypted_key: base64url of record.publicEncryptionKey (absent for dir)
|
|
47
|
-
*/
|
|
48
|
-
export const toFlattenedJWE = (
|
|
49
|
-
record: AesEncryptionRecord,
|
|
50
|
-
headerB64u: string,
|
|
51
|
-
): FlattenedJWE => {
|
|
52
|
-
const jwe: FlattenedJWE = {
|
|
53
|
-
protected: headerB64u,
|
|
54
|
-
iv: record.initialisationVector.toString("base64url"),
|
|
55
|
-
ciphertext: record.content.toString("base64url"),
|
|
56
|
-
tag: record.authTag.toString("base64url"),
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
if (record.publicEncryptionKey) {
|
|
60
|
-
jwe.encrypted_key = record.publicEncryptionKey.toString("base64url");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return jwe;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Convert a jose FlattenedJWE back to our AesDecryptionRecord + the AAD buffer.
|
|
68
|
-
*
|
|
69
|
-
* Parses the protected header to extract algorithm, encryption, and optional
|
|
70
|
-
* GCMKW parameters (iv, tag for key-wrap).
|
|
71
|
-
*
|
|
72
|
-
* Field mapping (inverse of toFlattenedJWE):
|
|
73
|
-
* - content: Buffer from base64url ciphertext
|
|
74
|
-
* - initialisationVector: Buffer from base64url iv
|
|
75
|
-
* - authTag: Buffer from base64url tag
|
|
76
|
-
* - publicEncryptionKey: Buffer from base64url encrypted_key (undefined for dir)
|
|
77
|
-
* - publicEncryptionIv: Buffer from protected header iv param (GCMKW only)
|
|
78
|
-
* - publicEncryptionTag: Buffer from protected header tag param (GCMKW only)
|
|
79
|
-
* - encryption: enc value from protected header
|
|
80
|
-
* - algorithm: alg value from protected header
|
|
81
|
-
*/
|
|
82
|
-
export const fromFlattenedJWE = (
|
|
83
|
-
jwe: FlattenedJWE,
|
|
84
|
-
): {
|
|
85
|
-
record: AesDecryptionRecord;
|
|
86
|
-
aad: Buffer;
|
|
87
|
-
} => {
|
|
88
|
-
if (!jwe.protected) {
|
|
89
|
-
throw new Error("FlattenedJWE missing protected header");
|
|
90
|
-
}
|
|
91
|
-
if (!jwe.iv) {
|
|
92
|
-
throw new Error("FlattenedJWE missing iv");
|
|
93
|
-
}
|
|
94
|
-
if (!jwe.tag) {
|
|
95
|
-
throw new Error("FlattenedJWE missing tag");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const headerJson = Buffer.from(jwe.protected, "base64url").toString("utf8");
|
|
99
|
-
const header = JSON.parse(headerJson) as Record<string, string>;
|
|
100
|
-
|
|
101
|
-
const aad = Buffer.from(jwe.protected, "ascii");
|
|
102
|
-
|
|
103
|
-
const record: AesDecryptionRecord = {
|
|
104
|
-
algorithm: header.alg as AesDecryptionRecord["algorithm"],
|
|
105
|
-
authTag: Buffer.from(jwe.tag, "base64url"),
|
|
106
|
-
content: Buffer.from(jwe.ciphertext, "base64url"),
|
|
107
|
-
contentType: (header.cty as AesDecryptionRecord["contentType"]) ?? "text/plain",
|
|
108
|
-
encryption: header.enc as AesDecryptionRecord["encryption"],
|
|
109
|
-
initialisationVector: Buffer.from(jwe.iv, "base64url"),
|
|
110
|
-
keyId: header.kid ?? "jwe-interop",
|
|
111
|
-
pbkdfIterations: undefined,
|
|
112
|
-
pbkdfSalt: undefined,
|
|
113
|
-
publicEncryptionIv: header.iv ? Buffer.from(header.iv, "base64url") : undefined,
|
|
114
|
-
publicEncryptionJwk: undefined,
|
|
115
|
-
publicEncryptionKey: jwe.encrypted_key
|
|
116
|
-
? Buffer.from(jwe.encrypted_key, "base64url")
|
|
117
|
-
: undefined,
|
|
118
|
-
publicEncryptionTag: header.tag ? Buffer.from(header.tag, "base64url") : undefined,
|
|
119
|
-
version: "1.0",
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return { record, aad };
|
|
123
|
-
};
|
|
@@ -1,464 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, type CipherGCM, randomBytes } from "crypto";
|
|
2
|
-
import { FlattenedEncrypt, flattenedDecrypt } from "jose";
|
|
3
|
-
import { encryptAes, decryptAes } from "../src/internal/utils/encryption.js";
|
|
4
|
-
import { createHmacAuthTag } from "../src/internal/utils/data/auth-tag-hmac.js";
|
|
5
|
-
import { splitContentEncryptionKey } from "../src/internal/utils/data/split-content-encryption-key.js";
|
|
6
|
-
import { toUint8Array } from "./helpers/buffer-utils.js";
|
|
7
|
-
import {
|
|
8
|
-
buildProtectedHeader,
|
|
9
|
-
toFlattenedJWE,
|
|
10
|
-
fromFlattenedJWE,
|
|
11
|
-
} from "./helpers/jwe-adapter.js";
|
|
12
|
-
import {
|
|
13
|
-
RAW_KEY_128,
|
|
14
|
-
RAW_KEY_192,
|
|
15
|
-
RAW_KEY_256,
|
|
16
|
-
RAW_KEY_256_CBC,
|
|
17
|
-
RAW_KEY_384_CBC,
|
|
18
|
-
RAW_KEY_512_CBC,
|
|
19
|
-
KEK_128,
|
|
20
|
-
KEK_192,
|
|
21
|
-
KEK_256,
|
|
22
|
-
createOctKryptos,
|
|
23
|
-
} from "./fixtures/keys.js";
|
|
24
|
-
import { describe, expect, test } from "vitest";
|
|
25
|
-
|
|
26
|
-
const PLAINTEXT = "hello jose interop";
|
|
27
|
-
const PLAINTEXT_BYTES = new TextEncoder().encode(PLAINTEXT);
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// 4.1 dir + AES-GCM Cross-Reference
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
describe("jose JWE interop: dir + AES-GCM", () => {
|
|
34
|
-
describe.each([
|
|
35
|
-
{ enc: "A128GCM" as const, key: RAW_KEY_128 },
|
|
36
|
-
{ enc: "A192GCM" as const, key: RAW_KEY_192 },
|
|
37
|
-
{ enc: "A256GCM" as const, key: RAW_KEY_256 },
|
|
38
|
-
])("dir + $enc", ({ enc, key }) => {
|
|
39
|
-
test("our encrypt -> jose decrypt", async () => {
|
|
40
|
-
const kryptos = createOctKryptos(key, "dir", enc);
|
|
41
|
-
const { headerB64u, aadBuffer } = buildProtectedHeader("dir", enc);
|
|
42
|
-
|
|
43
|
-
const record = encryptAes({
|
|
44
|
-
data: PLAINTEXT,
|
|
45
|
-
encryption: enc,
|
|
46
|
-
kryptos,
|
|
47
|
-
aad: aadBuffer,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const jwe = toFlattenedJWE(record, headerB64u);
|
|
51
|
-
const result = await flattenedDecrypt(jwe, toUint8Array(key));
|
|
52
|
-
|
|
53
|
-
expect(Buffer.from(result.plaintext).toString("utf8")).toBe(PLAINTEXT);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("jose encrypt -> our decrypt", async () => {
|
|
57
|
-
const kryptos = createOctKryptos(key, "dir", enc);
|
|
58
|
-
|
|
59
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
60
|
-
.setProtectedHeader({ alg: "dir", enc })
|
|
61
|
-
.encrypt(toUint8Array(key));
|
|
62
|
-
|
|
63
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
64
|
-
|
|
65
|
-
const result = decryptAes({
|
|
66
|
-
...record,
|
|
67
|
-
aad,
|
|
68
|
-
contentType: "text/plain",
|
|
69
|
-
kryptos,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
expect(result).toBe(PLAINTEXT);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// 4.2 dir + AES-CBC-HMAC Cross-Reference
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
describe("jose JWE interop: dir + AES-CBC-HMAC", () => {
|
|
82
|
-
describe.each([
|
|
83
|
-
{ enc: "A128CBC-HS256" as const, key: RAW_KEY_256_CBC },
|
|
84
|
-
{ enc: "A192CBC-HS384" as const, key: RAW_KEY_384_CBC },
|
|
85
|
-
{ enc: "A256CBC-HS512" as const, key: RAW_KEY_512_CBC },
|
|
86
|
-
])("dir + $enc", ({ enc, key }) => {
|
|
87
|
-
test("our encrypt -> jose decrypt", async () => {
|
|
88
|
-
const kryptos = createOctKryptos(key, "dir", enc);
|
|
89
|
-
const { headerB64u, aadBuffer } = buildProtectedHeader("dir", enc);
|
|
90
|
-
|
|
91
|
-
const record = encryptAes({
|
|
92
|
-
data: PLAINTEXT,
|
|
93
|
-
encryption: enc,
|
|
94
|
-
kryptos,
|
|
95
|
-
aad: aadBuffer,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const jwe = toFlattenedJWE(record, headerB64u);
|
|
99
|
-
const result = await flattenedDecrypt(jwe, toUint8Array(key));
|
|
100
|
-
|
|
101
|
-
expect(Buffer.from(result.plaintext).toString("utf8")).toBe(PLAINTEXT);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("jose encrypt -> our decrypt", async () => {
|
|
105
|
-
const kryptos = createOctKryptos(key, "dir", enc);
|
|
106
|
-
|
|
107
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
108
|
-
.setProtectedHeader({ alg: "dir", enc })
|
|
109
|
-
.encrypt(toUint8Array(key));
|
|
110
|
-
|
|
111
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
112
|
-
|
|
113
|
-
const result = decryptAes({
|
|
114
|
-
...record,
|
|
115
|
-
aad,
|
|
116
|
-
contentType: "text/plain",
|
|
117
|
-
kryptos,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(result).toBe(PLAINTEXT);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
// 4.3 A*KW + AES-GCM Cross-Reference
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
|
|
129
|
-
describe("jose JWE interop: A*KW + AES-GCM", () => {
|
|
130
|
-
describe.each([
|
|
131
|
-
{ alg: "A128KW" as const, kek: KEK_128 },
|
|
132
|
-
{ alg: "A192KW" as const, kek: KEK_192 },
|
|
133
|
-
{ alg: "A256KW" as const, kek: KEK_256 },
|
|
134
|
-
])("$alg", ({ alg, kek }) => {
|
|
135
|
-
describe.each([{ enc: "A128GCM" as const }, { enc: "A256GCM" as const }])(
|
|
136
|
-
"+ $enc",
|
|
137
|
-
({ enc }) => {
|
|
138
|
-
test("our encrypt -> jose decrypt", async () => {
|
|
139
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
140
|
-
const { headerB64u, aadBuffer } = buildProtectedHeader(alg, enc);
|
|
141
|
-
|
|
142
|
-
const record = encryptAes({
|
|
143
|
-
data: PLAINTEXT,
|
|
144
|
-
encryption: enc,
|
|
145
|
-
kryptos,
|
|
146
|
-
aad: aadBuffer,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const jwe = toFlattenedJWE(record, headerB64u);
|
|
150
|
-
const result = await flattenedDecrypt(jwe, toUint8Array(kek));
|
|
151
|
-
|
|
152
|
-
expect(Buffer.from(result.plaintext).toString("utf8")).toBe(PLAINTEXT);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("jose encrypt -> our decrypt", async () => {
|
|
156
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
157
|
-
|
|
158
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
159
|
-
.setProtectedHeader({ alg, enc })
|
|
160
|
-
.encrypt(toUint8Array(kek));
|
|
161
|
-
|
|
162
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
163
|
-
|
|
164
|
-
const result = decryptAes({
|
|
165
|
-
...record,
|
|
166
|
-
aad,
|
|
167
|
-
contentType: "text/plain",
|
|
168
|
-
kryptos,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
expect(result).toBe(PLAINTEXT);
|
|
172
|
-
});
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// 4.4 A*KW + AES-CBC-HMAC Cross-Reference
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
describe("jose JWE interop: A*KW + AES-CBC-HMAC", () => {
|
|
183
|
-
describe.each([
|
|
184
|
-
{ alg: "A128KW" as const, enc: "A128CBC-HS256" as const, kek: KEK_128 },
|
|
185
|
-
{ alg: "A192KW" as const, enc: "A192CBC-HS384" as const, kek: KEK_192 },
|
|
186
|
-
{ alg: "A256KW" as const, enc: "A256CBC-HS512" as const, kek: KEK_256 },
|
|
187
|
-
])("$alg + $enc", ({ alg, enc, kek }) => {
|
|
188
|
-
test("our encrypt -> jose decrypt", async () => {
|
|
189
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
190
|
-
const { headerB64u, aadBuffer } = buildProtectedHeader(alg, enc);
|
|
191
|
-
|
|
192
|
-
const record = encryptAes({
|
|
193
|
-
data: PLAINTEXT,
|
|
194
|
-
encryption: enc,
|
|
195
|
-
kryptos,
|
|
196
|
-
aad: aadBuffer,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const jwe = toFlattenedJWE(record, headerB64u);
|
|
200
|
-
const result = await flattenedDecrypt(jwe, toUint8Array(kek));
|
|
201
|
-
|
|
202
|
-
expect(Buffer.from(result.plaintext).toString("utf8")).toBe(PLAINTEXT);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("jose encrypt -> our decrypt", async () => {
|
|
206
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
207
|
-
|
|
208
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
209
|
-
.setProtectedHeader({ alg, enc })
|
|
210
|
-
.encrypt(toUint8Array(kek));
|
|
211
|
-
|
|
212
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
213
|
-
|
|
214
|
-
const result = decryptAes({
|
|
215
|
-
...record,
|
|
216
|
-
aad,
|
|
217
|
-
contentType: "text/plain",
|
|
218
|
-
kryptos,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
expect(result).toBe(PLAINTEXT);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// 4.5 A*GCMKW + AES-GCM Cross-Reference
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
describe("jose JWE interop: A*GCMKW + AES-GCM", () => {
|
|
231
|
-
describe.each([
|
|
232
|
-
{ alg: "A128GCMKW" as const, kek: KEK_128 },
|
|
233
|
-
{ alg: "A256GCMKW" as const, kek: KEK_256 },
|
|
234
|
-
])("$alg + A256GCM", ({ alg, kek }) => {
|
|
235
|
-
const enc = "A256GCM" as const;
|
|
236
|
-
|
|
237
|
-
test("jose encrypt -> our decrypt", async () => {
|
|
238
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
239
|
-
|
|
240
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
241
|
-
.setProtectedHeader({ alg, enc })
|
|
242
|
-
.encrypt(toUint8Array(kek));
|
|
243
|
-
|
|
244
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
245
|
-
|
|
246
|
-
const result = decryptAes({
|
|
247
|
-
...record,
|
|
248
|
-
aad,
|
|
249
|
-
contentType: "text/plain",
|
|
250
|
-
kryptos,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
expect(result).toBe(PLAINTEXT);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Our encrypt -> jose decrypt for GCMKW has a circular dependency:
|
|
257
|
-
// The protected header must contain the key-wrap iv and tag (per RFC 7518 section 4.7),
|
|
258
|
-
// but the content encryption AAD is computed from the protected header (per RFC 7516
|
|
259
|
-
// section 5.1 step 14). Our encryptAes() generates the key-wrap params and encrypts
|
|
260
|
-
// content in a single pass, so we cannot know the final header (and therefore the
|
|
261
|
-
// correct AAD) before encryption.
|
|
262
|
-
//
|
|
263
|
-
// A two-pass approach (encrypt once to get kw params, build header, re-encrypt with
|
|
264
|
-
// correct AAD) would require exposing the CEK from the first pass, which our API
|
|
265
|
-
// does not currently support via the public encryptAes interface.
|
|
266
|
-
//
|
|
267
|
-
// The jose->ours direction above validates that our decryption is fully compatible.
|
|
268
|
-
// Byte-level correctness of our GCM key-wrap is separately validated in the
|
|
269
|
-
// noble-ciphers cross-reference tests.
|
|
270
|
-
test("our encrypt -> jose decrypt", async () => {
|
|
271
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
272
|
-
|
|
273
|
-
// Perform encryption without AAD first to get the key-wrap parameters
|
|
274
|
-
const record = encryptAes({
|
|
275
|
-
data: PLAINTEXT,
|
|
276
|
-
encryption: enc,
|
|
277
|
-
kryptos,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Build the protected header including the GCMKW iv and tag
|
|
281
|
-
const { headerB64u, aadBuffer } = buildProtectedHeader(alg, enc, {
|
|
282
|
-
iv: record.publicEncryptionIv!,
|
|
283
|
-
tag: record.publicEncryptionTag!,
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// Re-encrypt with correct AAD using the same CEK that was wrapped.
|
|
287
|
-
// We can recover the CEK by unwrapping the encrypted_key.
|
|
288
|
-
const unwrapKryptos = createOctKryptos(kek, alg, enc);
|
|
289
|
-
const { contentEncryptionKey } = (
|
|
290
|
-
await import("../src/internal/utils/key-wrap/gcm-key-wrap.js")
|
|
291
|
-
).gcmKeyUnwrap({
|
|
292
|
-
keyEncryptionKey: kek,
|
|
293
|
-
kryptos: unwrapKryptos,
|
|
294
|
-
publicEncryptionIv: record.publicEncryptionIv!,
|
|
295
|
-
publicEncryptionKey: record.publicEncryptionKey!,
|
|
296
|
-
publicEncryptionTag: record.publicEncryptionTag!,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Re-encrypt content with the recovered CEK and correct AAD
|
|
300
|
-
const { encryptionKey } = splitContentEncryptionKey(enc, contentEncryptionKey);
|
|
301
|
-
const iv = randomBytes(12);
|
|
302
|
-
const cipher = createCipheriv("aes-256-gcm", encryptionKey, iv, {
|
|
303
|
-
authTagLength: 16,
|
|
304
|
-
}) as CipherGCM;
|
|
305
|
-
cipher.setAAD(aadBuffer);
|
|
306
|
-
|
|
307
|
-
const plaintextBuf = Buffer.from(PLAINTEXT, "utf8");
|
|
308
|
-
const ciphertext = Buffer.concat([cipher.update(plaintextBuf), cipher.final()]);
|
|
309
|
-
const authTag = cipher.getAuthTag();
|
|
310
|
-
|
|
311
|
-
const jwe = {
|
|
312
|
-
protected: headerB64u,
|
|
313
|
-
iv: iv.toString("base64url"),
|
|
314
|
-
ciphertext: ciphertext.toString("base64url"),
|
|
315
|
-
tag: authTag.toString("base64url"),
|
|
316
|
-
encrypted_key: record.publicEncryptionKey!.toString("base64url"),
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const result = await flattenedDecrypt(jwe, toUint8Array(kek));
|
|
320
|
-
expect(Buffer.from(result.plaintext).toString("utf8")).toBe(PLAINTEXT);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// ---------------------------------------------------------------------------
|
|
326
|
-
// 4.6 Deterministic Pinned Tests
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
|
-
//
|
|
329
|
-
// jose v6 does not allow setContentEncryptionKey() with alg: "dir".
|
|
330
|
-
// We use A256KW / A128KW key-wrap algorithms instead, which do allow CEK
|
|
331
|
-
// pinning. This still validates byte-identical ciphertext because the
|
|
332
|
-
// content encryption is the same regardless of the key management algorithm.
|
|
333
|
-
// ---------------------------------------------------------------------------
|
|
334
|
-
|
|
335
|
-
describe("jose JWE interop: deterministic pinned tests", () => {
|
|
336
|
-
describe("A256KW + A256GCM", () => {
|
|
337
|
-
const alg = "A256KW" as const;
|
|
338
|
-
const enc = "A256GCM" as const;
|
|
339
|
-
const kek = KEK_256;
|
|
340
|
-
const cek = Buffer.from(
|
|
341
|
-
"e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff00",
|
|
342
|
-
"hex",
|
|
343
|
-
);
|
|
344
|
-
const iv = Buffer.from("0a0b0c0d0e0f10111213141516", "hex").subarray(0, 12);
|
|
345
|
-
const plaintextBuf = Buffer.from(PLAINTEXT, "utf8");
|
|
346
|
-
|
|
347
|
-
test("byte-identical ciphertext and auth tag", async () => {
|
|
348
|
-
// jose: encrypt with pinned CEK and IV
|
|
349
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
350
|
-
.setProtectedHeader({ alg, enc })
|
|
351
|
-
.setContentEncryptionKey(toUint8Array(cek))
|
|
352
|
-
.setInitializationVector(toUint8Array(iv))
|
|
353
|
-
.encrypt(toUint8Array(kek));
|
|
354
|
-
|
|
355
|
-
// The AAD is the ASCII bytes of the base64url-encoded protected header
|
|
356
|
-
const joseAad = Buffer.from(jwe.protected!, "ascii");
|
|
357
|
-
|
|
358
|
-
// Our side: raw createCipheriv with the same CEK, IV, and AAD
|
|
359
|
-
const cipher = createCipheriv("aes-256-gcm", cek, iv, {
|
|
360
|
-
authTagLength: 16,
|
|
361
|
-
}) as CipherGCM;
|
|
362
|
-
cipher.setAAD(joseAad);
|
|
363
|
-
|
|
364
|
-
const ourCiphertext = Buffer.concat([cipher.update(plaintextBuf), cipher.final()]);
|
|
365
|
-
const ourTag = cipher.getAuthTag();
|
|
366
|
-
|
|
367
|
-
const joseCiphertext = Buffer.from(jwe.ciphertext, "base64url");
|
|
368
|
-
const joseTag = Buffer.from(jwe.tag!, "base64url");
|
|
369
|
-
|
|
370
|
-
expect(ourCiphertext.equals(joseCiphertext)).toBe(true);
|
|
371
|
-
expect(ourTag.equals(joseTag)).toBe(true);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
test("round-trip with pinned values through decryptAes", async () => {
|
|
375
|
-
// jose: encrypt with pinned CEK and IV
|
|
376
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
377
|
-
.setProtectedHeader({ alg, enc })
|
|
378
|
-
.setContentEncryptionKey(toUint8Array(cek))
|
|
379
|
-
.setInitializationVector(toUint8Array(iv))
|
|
380
|
-
.encrypt(toUint8Array(kek));
|
|
381
|
-
|
|
382
|
-
// Parse with our adapter and decrypt
|
|
383
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
384
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
385
|
-
|
|
386
|
-
const result = decryptAes({
|
|
387
|
-
...record,
|
|
388
|
-
aad,
|
|
389
|
-
contentType: "text/plain",
|
|
390
|
-
kryptos,
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
expect(result).toBe(PLAINTEXT);
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
describe("A128KW + A128CBC-HS256", () => {
|
|
398
|
-
const alg = "A128KW" as const;
|
|
399
|
-
const enc = "A128CBC-HS256" as const;
|
|
400
|
-
const kek = KEK_128;
|
|
401
|
-
// A128CBC-HS256 CEK is 32 bytes: first 16 = HMAC key, last 16 = AES key
|
|
402
|
-
const cek = Buffer.from(
|
|
403
|
-
"a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0",
|
|
404
|
-
"hex",
|
|
405
|
-
);
|
|
406
|
-
const iv = Buffer.alloc(16, 0xdd);
|
|
407
|
-
const plaintextBuf = Buffer.from(PLAINTEXT, "utf8");
|
|
408
|
-
|
|
409
|
-
test("byte-identical ciphertext and auth tag", async () => {
|
|
410
|
-
// jose: encrypt with pinned CEK and IV
|
|
411
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
412
|
-
.setProtectedHeader({ alg, enc })
|
|
413
|
-
.setContentEncryptionKey(toUint8Array(cek))
|
|
414
|
-
.setInitializationVector(toUint8Array(iv))
|
|
415
|
-
.encrypt(toUint8Array(kek));
|
|
416
|
-
|
|
417
|
-
const joseAad = Buffer.from(jwe.protected!, "ascii");
|
|
418
|
-
|
|
419
|
-
// Split CEK per RFC 7518 Section 5.2
|
|
420
|
-
const { encryptionKey, hashKey } = splitContentEncryptionKey(enc, cek);
|
|
421
|
-
|
|
422
|
-
// Our side: raw createCipheriv (CBC) with same key and IV
|
|
423
|
-
const cipher = createCipheriv("aes-128-cbc", encryptionKey, iv);
|
|
424
|
-
const ourCiphertext = Buffer.concat([cipher.update(plaintextBuf), cipher.final()]);
|
|
425
|
-
|
|
426
|
-
// HMAC auth tag per RFC 7518 Section 5.2.2.1
|
|
427
|
-
const ourTag = createHmacAuthTag({
|
|
428
|
-
aad: joseAad,
|
|
429
|
-
content: ourCiphertext,
|
|
430
|
-
encryption: enc,
|
|
431
|
-
hashKey,
|
|
432
|
-
initialisationVector: iv,
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
const joseCiphertext = Buffer.from(jwe.ciphertext, "base64url");
|
|
436
|
-
const joseTag = Buffer.from(jwe.tag!, "base64url");
|
|
437
|
-
|
|
438
|
-
expect(ourCiphertext.equals(joseCiphertext)).toBe(true);
|
|
439
|
-
expect(ourTag.equals(joseTag)).toBe(true);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
test("round-trip with pinned values through decryptAes", async () => {
|
|
443
|
-
// jose: encrypt with pinned CEK and IV
|
|
444
|
-
const jwe = await new FlattenedEncrypt(PLAINTEXT_BYTES)
|
|
445
|
-
.setProtectedHeader({ alg, enc })
|
|
446
|
-
.setContentEncryptionKey(toUint8Array(cek))
|
|
447
|
-
.setInitializationVector(toUint8Array(iv))
|
|
448
|
-
.encrypt(toUint8Array(kek));
|
|
449
|
-
|
|
450
|
-
// Parse with our adapter and decrypt
|
|
451
|
-
const kryptos = createOctKryptos(kek, alg, enc);
|
|
452
|
-
const { record, aad } = fromFlattenedJWE(jwe);
|
|
453
|
-
|
|
454
|
-
const result = decryptAes({
|
|
455
|
-
...record,
|
|
456
|
-
aad,
|
|
457
|
-
contentType: "text/plain",
|
|
458
|
-
kryptos,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
expect(result).toBe(PLAINTEXT);
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
});
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, randomBytes } from "crypto";
|
|
2
|
-
import { gcm, cbc, aeskw } from "@noble/ciphers/aes.js";
|
|
3
|
-
import { encryptAes, decryptAes } from "../src/internal/utils/encryption.js";
|
|
4
|
-
import { ecbKeyWrap, ecbKeyUnwrap } from "../src/internal/utils/key-wrap/ecb-key-wrap.js";
|
|
5
|
-
import { toUint8Array, toBuffer } from "./helpers/buffer-utils.js";
|
|
6
|
-
import {
|
|
7
|
-
RAW_KEY_128,
|
|
8
|
-
RAW_KEY_192,
|
|
9
|
-
RAW_KEY_256,
|
|
10
|
-
RAW_KEY_256_CBC,
|
|
11
|
-
RAW_KEY_384_CBC,
|
|
12
|
-
RAW_KEY_512_CBC,
|
|
13
|
-
KEK_128,
|
|
14
|
-
KEK_192,
|
|
15
|
-
KEK_256,
|
|
16
|
-
createOctKryptos,
|
|
17
|
-
} from "./fixtures/keys.js";
|
|
18
|
-
import { describe, expect, test } from "vitest";
|
|
19
|
-
|
|
20
|
-
const GCM_TAG_LENGTH = 16;
|
|
21
|
-
const GCM_IV_LENGTH = 12;
|
|
22
|
-
const AES_BLOCK_SIZE = 16;
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// 3.1 AES-GCM Cross-Reference
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
describe("AES-GCM cross-reference with @noble/ciphers", () => {
|
|
29
|
-
describe.each([
|
|
30
|
-
{ name: "A128GCM", key: RAW_KEY_128, encryption: "A128GCM" as const },
|
|
31
|
-
{ name: "A192GCM", key: RAW_KEY_192, encryption: "A192GCM" as const },
|
|
32
|
-
{ name: "A256GCM", key: RAW_KEY_256, encryption: "A256GCM" as const },
|
|
33
|
-
])("$name", ({ key, encryption }) => {
|
|
34
|
-
const plaintext = "test plaintext";
|
|
35
|
-
|
|
36
|
-
test("our encryptAes -> noble gcm decrypt", () => {
|
|
37
|
-
const kryptos = createOctKryptos(key, "dir", encryption);
|
|
38
|
-
|
|
39
|
-
const record = encryptAes({ data: plaintext, encryption, kryptos });
|
|
40
|
-
|
|
41
|
-
const iv = toUint8Array(record.initialisationVector);
|
|
42
|
-
const ciphertext = toUint8Array(record.content);
|
|
43
|
-
const tag = toUint8Array(record.authTag);
|
|
44
|
-
|
|
45
|
-
// noble expects ciphertext || tag concatenated
|
|
46
|
-
const ciphertextWithTag = new Uint8Array(ciphertext.length + tag.length);
|
|
47
|
-
ciphertextWithTag.set(ciphertext, 0);
|
|
48
|
-
ciphertextWithTag.set(tag, ciphertext.length);
|
|
49
|
-
|
|
50
|
-
const decrypted = gcm(toUint8Array(key), iv).decrypt(ciphertextWithTag);
|
|
51
|
-
const result = new TextDecoder().decode(decrypted);
|
|
52
|
-
|
|
53
|
-
expect(result).toBe(plaintext);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("noble gcm encrypt -> our decryptAes", () => {
|
|
57
|
-
const kryptos = createOctKryptos(key, "dir", encryption);
|
|
58
|
-
const iv = randomBytes(GCM_IV_LENGTH);
|
|
59
|
-
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
60
|
-
|
|
61
|
-
const encrypted = gcm(toUint8Array(key), toUint8Array(iv)).encrypt(plaintextBytes);
|
|
62
|
-
|
|
63
|
-
// noble returns ciphertext || tag (last 16 bytes are the auth tag)
|
|
64
|
-
const ciphertext = toBuffer(encrypted.slice(0, -GCM_TAG_LENGTH));
|
|
65
|
-
const authTag = toBuffer(encrypted.slice(-GCM_TAG_LENGTH));
|
|
66
|
-
|
|
67
|
-
const result = decryptAes({
|
|
68
|
-
content: ciphertext,
|
|
69
|
-
authTag,
|
|
70
|
-
initialisationVector: iv,
|
|
71
|
-
encryption,
|
|
72
|
-
contentType: "text/plain",
|
|
73
|
-
kryptos,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(result).toBe(plaintext);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("byte-identical ciphertext with pinned IV", () => {
|
|
80
|
-
const iv = Buffer.alloc(GCM_IV_LENGTH, 0xab);
|
|
81
|
-
const plaintextBytes = Buffer.from(plaintext, "utf8");
|
|
82
|
-
|
|
83
|
-
// Our side: raw Node.js createCipheriv
|
|
84
|
-
const cipher = createCipheriv(
|
|
85
|
-
`aes-${key.length * 8}-gcm` as "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm",
|
|
86
|
-
key,
|
|
87
|
-
iv,
|
|
88
|
-
{ authTagLength: GCM_TAG_LENGTH },
|
|
89
|
-
);
|
|
90
|
-
const ourCiphertext = Buffer.concat([
|
|
91
|
-
cipher.update(plaintextBytes),
|
|
92
|
-
cipher.final(),
|
|
93
|
-
]);
|
|
94
|
-
const ourTag = cipher.getAuthTag();
|
|
95
|
-
|
|
96
|
-
// Noble side
|
|
97
|
-
const nobleResult = gcm(toUint8Array(key), toUint8Array(iv)).encrypt(
|
|
98
|
-
toUint8Array(plaintextBytes),
|
|
99
|
-
);
|
|
100
|
-
const nobleCiphertext = nobleResult.slice(0, -GCM_TAG_LENGTH);
|
|
101
|
-
const nobleTag = nobleResult.slice(-GCM_TAG_LENGTH);
|
|
102
|
-
|
|
103
|
-
expect(ourCiphertext.equals(toBuffer(nobleCiphertext))).toBe(true);
|
|
104
|
-
expect(ourTag.equals(toBuffer(nobleTag))).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// 3.2 AES-KW (RFC 3394) Cross-Reference
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
describe("AES-KW cross-reference with @noble/ciphers", () => {
|
|
114
|
-
describe.each([
|
|
115
|
-
{ name: "A128KW", kek: KEK_128, algorithm: "A128KW" as const },
|
|
116
|
-
{ name: "A192KW", kek: KEK_192, algorithm: "A192KW" as const },
|
|
117
|
-
{ name: "A256KW", kek: KEK_256, algorithm: "A256KW" as const },
|
|
118
|
-
])("$name", ({ kek, algorithm }) => {
|
|
119
|
-
// CEK to wrap: 32 bytes (suitable for A256GCM)
|
|
120
|
-
const cek = randomBytes(32);
|
|
121
|
-
|
|
122
|
-
test("our ecbKeyWrap -> noble aeskw decrypt", () => {
|
|
123
|
-
const kryptos = createOctKryptos(kek, algorithm);
|
|
124
|
-
|
|
125
|
-
const { publicEncryptionKey } = ecbKeyWrap({
|
|
126
|
-
contentEncryptionKey: cek,
|
|
127
|
-
keyEncryptionKey: kek,
|
|
128
|
-
kryptos,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const unwrapped = aeskw(toUint8Array(kek)).decrypt(
|
|
132
|
-
toUint8Array(publicEncryptionKey),
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
expect(cek.equals(toBuffer(unwrapped))).toBe(true);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("noble aeskw encrypt -> our ecbKeyUnwrap", () => {
|
|
139
|
-
const kryptos = createOctKryptos(kek, algorithm);
|
|
140
|
-
|
|
141
|
-
const wrapped = aeskw(toUint8Array(kek)).encrypt(toUint8Array(cek));
|
|
142
|
-
|
|
143
|
-
const { contentEncryptionKey } = ecbKeyUnwrap({
|
|
144
|
-
keyEncryptionKey: kek,
|
|
145
|
-
kryptos,
|
|
146
|
-
publicEncryptionKey: toBuffer(wrapped),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
expect(cek.equals(contentEncryptionKey)).toBe(true);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("byte-identical wrapped key (deterministic)", () => {
|
|
153
|
-
const kryptos = createOctKryptos(kek, algorithm);
|
|
154
|
-
|
|
155
|
-
const { publicEncryptionKey } = ecbKeyWrap({
|
|
156
|
-
contentEncryptionKey: cek,
|
|
157
|
-
keyEncryptionKey: kek,
|
|
158
|
-
kryptos,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const nobleWrapped = aeskw(toUint8Array(kek)).encrypt(toUint8Array(cek));
|
|
162
|
-
|
|
163
|
-
expect(publicEncryptionKey.equals(toBuffer(nobleWrapped))).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
// 3.3 AES-CBC Raw Cross-Reference
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
describe("AES-CBC raw cross-reference with @noble/ciphers", () => {
|
|
173
|
-
describe.each([
|
|
174
|
-
{
|
|
175
|
-
name: "A128CBC",
|
|
176
|
-
// A128CBC-HS256: 32B CEK -> first 16B = HMAC key, last 16B = AES-128-CBC key
|
|
177
|
-
encKey: RAW_KEY_256_CBC.subarray(16),
|
|
178
|
-
nodeCipher: "aes-128-cbc" as const,
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "A192CBC",
|
|
182
|
-
// A192CBC-HS384: 48B CEK -> first 24B = HMAC key, last 24B = AES-192-CBC key
|
|
183
|
-
encKey: RAW_KEY_384_CBC.subarray(24),
|
|
184
|
-
nodeCipher: "aes-192-cbc" as const,
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "A256CBC",
|
|
188
|
-
// A256CBC-HS512: 64B CEK -> first 32B = HMAC key, last 32B = AES-256-CBC key
|
|
189
|
-
encKey: RAW_KEY_512_CBC.subarray(32),
|
|
190
|
-
nodeCipher: "aes-256-cbc" as const,
|
|
191
|
-
},
|
|
192
|
-
])("$name", ({ encKey, nodeCipher }) => {
|
|
193
|
-
test("byte-identical ciphertext (PKCS7 padding)", () => {
|
|
194
|
-
const iv = Buffer.alloc(AES_BLOCK_SIZE, 0xcd);
|
|
195
|
-
const plaintext = Buffer.from("cross-reference CBC test", "utf8");
|
|
196
|
-
|
|
197
|
-
// Our side: raw Node.js createCipheriv
|
|
198
|
-
const cipher = createCipheriv(nodeCipher, encKey, iv);
|
|
199
|
-
const ourCiphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
200
|
-
|
|
201
|
-
// Noble side
|
|
202
|
-
const nobleCiphertext = cbc(toUint8Array(encKey), toUint8Array(iv)).encrypt(
|
|
203
|
-
toUint8Array(plaintext),
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
expect(ourCiphertext.equals(toBuffer(nobleCiphertext))).toBe(true);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
});
|