Skip to content

Commit fcc662c

Browse files
committed
crypto: add AES-OCB Web Cryptography algorithm
1 parent 589ef79 commit fcc662c

17 files changed

+437
-23
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3041,6 +3041,9 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
30413041
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
30423042
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);
30433043
const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap);
3044+
const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb);
3045+
const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb);
3046+
const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb);
30443047
const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305);
30453048

30463049
bool Cipher::isGcmMode() const {
@@ -3243,6 +3246,11 @@ bool CipherCtxPointer::isGcmMode() const {
32433246
return getMode() == EVP_CIPH_GCM_MODE;
32443247
}
32453248

3249+
bool CipherCtxPointer::isOcbMode() const {
3250+
if (!ctx_) return false;
3251+
return getMode() == EVP_CIPH_OCB_MODE;
3252+
}
3253+
32463254
bool CipherCtxPointer::isCcmMode() const {
32473255
if (!ctx_) return false;
32483256
return getMode() == EVP_CIPH_CCM_MODE;

deps/ncrypto/ncrypto.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ class Cipher final {
373373
static const Cipher AES_128_KW;
374374
static const Cipher AES_192_KW;
375375
static const Cipher AES_256_KW;
376+
static const Cipher AES_128_OCB;
377+
static const Cipher AES_192_OCB;
378+
static const Cipher AES_256_OCB;
376379
static const Cipher CHACHA20_POLY1305;
377380

378381
struct CipherParams {
@@ -738,6 +741,7 @@ class CipherCtxPointer final {
738741
int getNid() const;
739742

740743
bool isGcmMode() const;
744+
bool isOcbMode() const;
741745
bool isCcmMode() const;
742746
bool isWrapMode() const;
743747
bool isChaCha20Poly1305() const;

doc/api/webcrypto.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
<!-- YAML
44
changes:
5+
- version: REPLACEME
6+
pr-url: https://github.com/nodejs/node/pull/59539
7+
description: AES-OCB algorithm is now supported.
58
- version: REPLACEME
69
pr-url: https://github.com/nodejs/node/pull/59569
710
description: ML-KEM algorithms are now supported.
@@ -104,6 +107,7 @@ WICG proposal:
104107

105108
Algorithms:
106109

110+
* `'AES-OCB'`[^openssl30]
107111
* `'ChaCha20-Poly1305'`
108112
* `'cSHAKE128'`
109113
* `'cSHAKE256'`
@@ -825,6 +829,9 @@ The algorithms currently supported include:
825829
<!-- YAML
826830
added: v15.0.0
827831
changes:
832+
- version: REPLACEME
833+
pr-url: https://github.com/nodejs/node/pull/59539
834+
description: AES-OCB algorithm is now supported.
828835
- version: REPLACEME
829836
pr-url: https://github.com/nodejs/node/pull/59365
830837
description: ChaCha20-Poly1305 algorithm is now supported.
@@ -845,6 +852,7 @@ The algorithms currently supported include:
845852
* `'AES-CBC'`
846853
* `'AES-CTR'`
847854
* `'AES-GCM'`
855+
* `'AES-OCB'`[^modern-algos]
848856
* `'ChaCha20-Poly1305'`[^modern-algos]
849857
* `'RSA-OAEP'`
850858
@@ -1015,6 +1023,9 @@ The algorithms currently supported include:
10151023
<!-- YAML
10161024
added: v15.0.0
10171025
changes:
1026+
- version: REPLACEME
1027+
pr-url: https://github.com/nodejs/node/pull/59539
1028+
description: AES-OCB algorithm is now supported.
10181029
- version: REPLACEME
10191030
pr-url: https://github.com/nodejs/node/pull/59365
10201031
description: ChaCha20-Poly1305 algorithm is now supported.
@@ -1035,6 +1046,7 @@ The algorithms currently supported include:
10351046
* `'AES-CBC'`
10361047
* `'AES-CTR'`
10371048
* `'AES-GCM'`
1049+
* `'AES-OCB'`[^modern-algos]
10381050
* `'ChaCha20-Poly1305'`[^modern-algos]
10391051
* `'RSA-OAEP'`
10401052
@@ -1086,6 +1098,7 @@ specification.
10861098
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
10871099
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
10881100
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
1101+
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
10891102
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
10901103
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
10911104
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
@@ -1171,6 +1184,7 @@ The {CryptoKey} (secret key) generating algorithms supported include:
11711184
* `'AES-CTR'`
11721185
* `'AES-GCM'`
11731186
* `'AES-KW'`
1187+
* `'AES-OCB'`[^modern-algos]
11741188
* `'ChaCha20-Poly1305'`[^modern-algos]
11751189
* `'HMAC'`
11761190
@@ -1228,6 +1242,7 @@ The algorithms currently supported include:
12281242
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
12291243
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
12301244
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
1245+
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
12311246
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
12321247
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
12331248
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
@@ -1294,6 +1309,9 @@ The algorithms currently supported include:
12941309
<!-- YAML
12951310
added: v15.0.0
12961311
changes:
1312+
- version: REPLACEME
1313+
pr-url: https://github.com/nodejs/node/pull/59539
1314+
description: AES-OCB algorithm is now supported.
12971315
- version: REPLACEME
12981316
pr-url: https://github.com/nodejs/node/pull/59365
12991317
description: ChaCha20-Poly1305 algorithm is now supported.
@@ -1330,6 +1348,7 @@ The wrapping algorithms currently supported include:
13301348
* `'AES-CTR'`
13311349
* `'AES-GCM'`
13321350
* `'AES-KW'`
1351+
* `'AES-OCB'`[^modern-algos]
13331352
* `'ChaCha20-Poly1305'`[^modern-algos]
13341353
* `'RSA-OAEP'`
13351354
@@ -1339,6 +1358,7 @@ The unwrapped key algorithms supported include:
13391358
* `'AES-CTR'`
13401359
* `'AES-GCM'`
13411360
* `'AES-KW'`
1361+
* `'AES-OCB'`[^modern-algos]
13421362
* `'ChaCha20-Poly1305'`[^modern-algos]
13431363
* `'ECDH'`
13441364
* `'ECDSA'`
@@ -1404,6 +1424,9 @@ The algorithms currently supported include:
14041424
<!-- YAML
14051425
added: v15.0.0
14061426
changes:
1427+
- version: REPLACEME
1428+
pr-url: https://github.com/nodejs/node/pull/59539
1429+
description: AES-OCB algorithm is now supported.
14071430
- version: REPLACEME
14081431
pr-url: https://github.com/nodejs/node/pull/59365
14091432
description: ChaCha20-Poly1305 algorithm is now supported.
@@ -1436,6 +1459,7 @@ The wrapping algorithms currently supported include:
14361459
* `'AES-CTR'`
14371460
* `'AES-GCM'`
14381461
* `'AES-KW'`
1462+
* `'AES-OCB'`[^modern-algos]
14391463
* `'ChaCha20-Poly1305'`[^modern-algos]
14401464
* `'RSA-OAEP'`
14411465
@@ -1493,7 +1517,7 @@ given key.
14931517
added: v15.0.0
14941518
-->
14951519
1496-
* Type: {string} Must be `'AES-GCM'` or `'ChaCha20-Poly1305'`.
1520+
* Type: {string} Must be `'AES-GCM'`, `'AES-OCB'`, or `'ChaCha20-Poly1305'`.
14971521
14981522
#### `aeadParams.tagLength`
14991523
@@ -1515,8 +1539,7 @@ added: v15.0.0
15151539
added: v15.0.0
15161540
-->
15171541
1518-
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
1519-
`'AES-KW'`
1542+
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, `'AES-OCB'`, or `'AES-KW'`
15201543
15211544
#### `aesDerivedKeyParams.length`
15221545
@@ -2392,6 +2415,8 @@ The length (in bytes) of the random salt to use.
23922415
23932416
[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][]
23942417
2418+
[^openssl30]: Requires OpenSSL >= 3.0
2419+
23952420
[^openssl35]: Requires OpenSSL >= 3.5
23962421
23972422
[JSON Web Key]: https://tools.ietf.org/html/rfc7517

lib/internal/crypto/aes.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ const {
1818
kKeyVariantAES_CBC_128,
1919
kKeyVariantAES_GCM_128,
2020
kKeyVariantAES_KW_128,
21+
kKeyVariantAES_OCB_128,
2122
kKeyVariantAES_CTR_192,
2223
kKeyVariantAES_CBC_192,
2324
kKeyVariantAES_GCM_192,
2425
kKeyVariantAES_KW_192,
26+
kKeyVariantAES_OCB_192,
2527
kKeyVariantAES_CTR_256,
2628
kKeyVariantAES_CBC_256,
2729
kKeyVariantAES_GCM_256,
2830
kKeyVariantAES_KW_256,
31+
kKeyVariantAES_OCB_256,
2932
kWebCryptoCipherDecrypt,
3033
kWebCryptoCipherEncrypt,
3134
} = internalBinding('crypto');
@@ -62,6 +65,7 @@ function getAlgorithmName(name, length) {
6265
case 'AES-CTR': return `A${length}CTR`;
6366
case 'AES-GCM': return `A${length}GCM`;
6467
case 'AES-KW': return `A${length}KW`;
68+
case 'AES-OCB': return `A${length}OCB`;
6569
}
6670
}
6771

@@ -100,6 +104,13 @@ function getVariant(name, length) {
100104
case 256: return kKeyVariantAES_KW_256;
101105
}
102106
break;
107+
case 'AES-OCB':
108+
switch (length) {
109+
case 128: return kKeyVariantAES_OCB_128;
110+
case 192: return kKeyVariantAES_OCB_192;
111+
case 256: return kKeyVariantAES_OCB_256;
112+
}
113+
break;
103114
}
104115
}
105116

@@ -173,11 +184,49 @@ function asyncAesGcmCipher(mode, key, data, algorithm) {
173184
algorithm.additionalData));
174185
}
175186

187+
function asyncAesOcbCipher(mode, key, data, algorithm) {
188+
const { tagLength = 128 } = algorithm;
189+
190+
const tagByteLength = tagLength / 8;
191+
let tag;
192+
switch (mode) {
193+
case kWebCryptoCipherDecrypt: {
194+
const slice = ArrayBufferIsView(data) ?
195+
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
196+
tag = slice(data, -tagByteLength);
197+
198+
// Similar to GCM, OCB requires the tag to be present for decryption
199+
if (tagByteLength > tag.byteLength) {
200+
return PromiseReject(lazyDOMException(
201+
'The provided data is too small.',
202+
'OperationError'));
203+
}
204+
205+
data = slice(data, 0, -tagByteLength);
206+
break;
207+
}
208+
case kWebCryptoCipherEncrypt:
209+
tag = tagByteLength;
210+
break;
211+
}
212+
213+
return jobPromise(() => new AESCipherJob(
214+
kCryptoJobAsync,
215+
mode,
216+
key[kKeyObject][kHandle],
217+
data,
218+
getVariant('AES-OCB', key.algorithm.length),
219+
algorithm.iv,
220+
tag,
221+
algorithm.additionalData));
222+
}
223+
176224
function aesCipher(mode, key, data, algorithm) {
177225
switch (algorithm.name) {
178226
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
179227
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
180228
case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm);
229+
case 'AES-OCB': return asyncAesOcbCipher(mode, key, data, algorithm);
181230
case 'AES-KW': return asyncAesKwCipher(mode, key, data);
182231
}
183232
}
@@ -236,7 +285,11 @@ function aesImportKey(
236285
keyObject = keyData;
237286
break;
238287
}
288+
case 'raw-secret':
239289
case 'raw': {
290+
if (format === 'raw' && name === 'AES-OCB') {
291+
return undefined;
292+
}
240293
validateKeyLength(keyData.byteLength * 8);
241294
keyObject = createSecretKey(keyData);
242295
break;

lib/internal/crypto/keys.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ const {
199199
case 'AES-GCM':
200200
// Fall through
201201
case 'AES-KW':
202+
// Fall through
203+
case 'AES-OCB':
202204
result = require('internal/crypto/aes')
203205
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
204206
break;

lib/internal/crypto/util.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const {
3939
EVP_PKEY_ML_KEM_512,
4040
EVP_PKEY_ML_KEM_768,
4141
EVP_PKEY_ML_KEM_1024,
42+
kKeyVariantAES_OCB_128: hasAesOcbMode,
4243
} = internalBinding('crypto');
4344

4445
const { getOptionValue } = require('internal/options');
@@ -208,6 +209,14 @@ const kAlgorithmDefinitions = {
208209
'wrapKey': null,
209210
'unwrapKey': null,
210211
},
212+
'AES-OCB': {
213+
'generateKey': 'AesKeyGenParams',
214+
'exportKey': null,
215+
'importKey': null,
216+
'encrypt': 'AeadParams',
217+
'decrypt': 'AeadParams',
218+
'get key length': 'AesDerivedKeyParams',
219+
},
211220
'ChaCha20-Poly1305': {
212221
'generateKey': null,
213222
'exportKey': null,
@@ -350,6 +359,7 @@ const kAlgorithmDefinitions = {
350359
// Conditionally supported algorithms
351360
const conditionalAlgorithms = {
352361
'AES-KW': !process.features.openssl_is_boringssl,
362+
'AES-OCB': !!hasAesOcbMode,
353363
'ChaCha20-Poly1305': !process.features.openssl_is_boringssl ||
354364
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
355365
'cSHAKE128': !process.features.openssl_is_boringssl ||
@@ -374,6 +384,7 @@ const conditionalAlgorithms = {
374384

375385
// Experimental algorithms
376386
const experimentalAlgorithms = [
387+
'AES-OCB',
377388
'ChaCha20-Poly1305',
378389
'cSHAKE128',
379390
'cSHAKE256',

lib/internal/crypto/webcrypto.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ async function generateKey(
155155
// Fall through
156156
case 'AES-GCM':
157157
// Fall through
158+
case 'AES-OCB':
159+
// Fall through
158160
case 'AES-KW':
159161
resultType = 'CryptoKey';
160162
result = await require('internal/crypto/aes')
@@ -253,6 +255,7 @@ function getKeyLength({ name, length, hash }) {
253255
case 'AES-CTR':
254256
case 'AES-CBC':
255257
case 'AES-GCM':
258+
case 'AES-OCB':
256259
case 'AES-KW':
257260
if (length !== 128 && length !== 192 && length !== 256)
258261
throw lazyDOMException('Invalid key length', 'OperationError');
@@ -510,6 +513,8 @@ async function exportKeyRawSecret(key, format) {
510513
// Fall through
511514
case 'HMAC':
512515
return key[kKeyObject][kHandle].export().buffer;
516+
case 'AES-OCB':
517+
// Fall through
513518
case 'ChaCha20-Poly1305':
514519
if (format === 'raw-secret') {
515520
return key[kKeyObject][kHandle].export().buffer;
@@ -572,6 +577,8 @@ async function exportKeyJWK(key) {
572577
// Fall through
573578
case 'AES-GCM':
574579
// Fall through
580+
case 'AES-OCB':
581+
// Fall through
575582
case 'AES-KW':
576583
parameters.alg = require('internal/crypto/aes')
577584
.getAlgorithmName(key[kAlgorithm].name, key[kAlgorithm].length);
@@ -758,7 +765,11 @@ async function importKey(
758765
case 'AES-GCM':
759766
// Fall through
760767
case 'AES-KW':
761-
format = aliasKeyFormat(format);
768+
// Fall through
769+
case 'AES-OCB':
770+
if (algorithm.name !== 'AES-OCB') {
771+
format = aliasKeyFormat(format);
772+
}
762773
result = require('internal/crypto/aes')
763774
.aesImportKey(algorithm, format, keyData, extractable, keyUsages);
764775
break;
@@ -1060,6 +1071,8 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
10601071
case 'AES-CBC':
10611072
// Fall through
10621073
case 'AES-GCM':
1074+
// Fall through
1075+
case 'AES-OCB':
10631076
return require('internal/crypto/aes')
10641077
.aesCipher(mode, key, data, algorithm);
10651078
case 'ChaCha20-Poly1305':

0 commit comments

Comments
 (0)