@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lindorm/aes",
3
- "version": "0.7.0",
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.0",
45
- "@lindorm/errors": "^0.2.0",
46
- "@lindorm/is": "^0.2.0",
47
- "@lindorm/kryptos": "^0.8.0",
48
- "@lindorm/types": "^0.6.0",
49
- "@lindorm/utils": "^0.8.0"
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": "a2b0a53295aebda806b4057f34707e8583570265"
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,2 +0,0 @@
1
- export * from "./buffer-utils.js";
2
- export * from "./jwe-adapter.js";
@@ -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
- });
package/vitest.config.mjs DELETED
@@ -1,6 +0,0 @@
1
- import { createVitestConfig } from "../../vitest.config.base.mjs";
2
-
3
- const config = createVitestConfig();
4
- config.test.include = ["src/**/*.test.ts", "__tests__/**/*.test.ts"];
5
-
6
- export default config;