Skip to content

Commit efe1d34

Browse files
committed
crypto: add stub implementations for Web Crypto API Key Encapsulation
1 parent a848faf commit efe1d34

File tree

6 files changed

+327
-13
lines changed

6 files changed

+327
-13
lines changed

doc/api/webcrypto.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,35 @@ Allows feature detection in Web Crypto API, which can be used to detect whether
639639
a given algorithm identifier (including any of its parameters) is supported for
640640
the given operation.
641641
642+
### `subtle.decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext)`
643+
644+
<!-- YAML
645+
added: REPLACEME
646+
-->
647+
648+
> Stability: 1.0 - Early development
649+
650+
* `decapsulationAlgorithm` {string|Algorithm}
651+
* `decapsulationKey` {CryptoKey}
652+
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
653+
* Returns: {Promise} Fulfills with {ArrayBuffer} upon success.
654+
655+
### `subtle.decapsulateKey(decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages)`
656+
657+
<!-- YAML
658+
added: REPLACEME
659+
-->
660+
661+
> Stability: 1.0 - Early development
662+
663+
* `decapsulationAlgorithm` {string|Algorithm}
664+
* `decapsulationKey` {CryptoKey}
665+
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
666+
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
667+
* `extractable` {boolean}
668+
* `usages` {string\[]} See [Key usages][].
669+
* Returns: {Promise} Fulfills with {CryptoKey} upon success.
670+
642671
### `subtle.decrypt(algorithm, key, data)`
643672
644673
<!-- YAML
@@ -773,6 +802,33 @@ If `algorithm` is provided as a {string}, it must be one of:
773802
If `algorithm` is provided as an {Object}, it must have a `name` property
774803
whose value is one of the above.
775804
805+
### `subtle.encapsulateBits(encapsulationAlgorithm, encapsulationKey)`
806+
807+
<!-- YAML
808+
added: REPLACEME
809+
-->
810+
811+
> Stability: 1.0 - Early development
812+
813+
* `encapsulationAlgorithm` {string|Algorithm}
814+
* `encapsulationKey` {CryptoKey}
815+
* Returns: {Promise} Fulfills with {EncapsulatedBits} upon success.
816+
817+
### `subtle.encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages)`
818+
819+
<!-- YAML
820+
added: REPLACEME
821+
-->
822+
823+
> Stability: 1.0 - Early development
824+
825+
* `encapsulationAlgorithm` {string|Algorithm}
826+
* `encapsulationKey` {CryptoKey}
827+
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
828+
* `extractable` {boolean}
829+
* `usages` {string\[]} See [Key usages][].
830+
* Returns: {Promise} Fulfills with {EncapsulatedKey} upon success.
831+
776832
### `subtle.encrypt(algorithm, key, data)`
777833
778834
<!-- YAML
@@ -1467,6 +1523,50 @@ the message.
14671523
The Node.js Web Crypto API implementation only supports zero-length context
14681524
which is equivalent to not providing context at all.
14691525
1526+
### Class: `EncapsulatedBits`
1527+
1528+
<!-- YAML
1529+
added: REPLACEME
1530+
-->
1531+
1532+
#### `encapsulatedBits.ciphertext`
1533+
1534+
<!-- YAML
1535+
added: REPLACEME
1536+
-->
1537+
1538+
* Type: {ArrayBuffer}
1539+
1540+
#### `encapsulatedBits.sharedKey`
1541+
1542+
<!-- YAML
1543+
added: REPLACEME
1544+
-->
1545+
1546+
* Type: {ArrayBuffer}
1547+
1548+
### Class: `EncapsulatedKey`
1549+
1550+
<!-- YAML
1551+
added: REPLACEME
1552+
-->
1553+
1554+
#### `encapsulatedKey.ciphertext`
1555+
1556+
<!-- YAML
1557+
added: REPLACEME
1558+
-->
1559+
1560+
* Type: {ArrayBuffer}
1561+
1562+
#### `encapsulatedKey.sharedKey`
1563+
1564+
<!-- YAML
1565+
added: REPLACEME
1566+
-->
1567+
1568+
* Type: {CryptoKey}
1569+
14701570
### Class: `HkdfParams`
14711571
14721572
<!-- YAML

lib/internal/crypto/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ const kSupportedAlgorithms = {
266266
'unwrapKey': {
267267
'AES-KW': null,
268268
},
269+
'encapsulate': {},
270+
'decapsulate': {},
269271
};
270272

271273
const experimentalAlgorithms = ObjectEntries({

lib/internal/crypto/webcrypto.js

Lines changed: 189 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,140 @@ async function decrypt(algorithm, key, data) {
933933
return cipherOrWrap(kWebCryptoCipherDecrypt, algorithm, key, data, 'decrypt');
934934
}
935935

936+
async function encapsulateBits(encapsulationAlgorithm, encapsulationKey) {
937+
emitExperimentalWarning('The encapsulateBits Web Crypto API method');
938+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
939+
940+
webidl ??= require('internal/crypto/webidl');
941+
const prefix = "Failed to execute 'encapsulateBits' on 'SubtleCrypto'";
942+
webidl.requiredArguments(arguments.length, 2, { prefix });
943+
encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, {
944+
prefix,
945+
context: '1st argument',
946+
});
947+
encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, {
948+
prefix,
949+
context: '2nd argument',
950+
});
951+
952+
// eslint-disable-next-line no-unused-vars
953+
const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate');
954+
955+
// It is not possible to get here just yet
956+
const assert = require('internal/assert');
957+
assert.fail('Unreachable code');
958+
}
959+
960+
async function encapsulateKey(encapsulationAlgorithm, encapsulationKey, sharedKeyAlgorithm, extractable, usages) {
961+
emitExperimentalWarning('The encapsulateKey Web Crypto API method');
962+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
963+
964+
webidl ??= require('internal/crypto/webidl');
965+
const prefix = "Failed to execute 'encapsulateKey' on 'SubtleCrypto'";
966+
webidl.requiredArguments(arguments.length, 5, { prefix });
967+
encapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(encapsulationAlgorithm, {
968+
prefix,
969+
context: '1st argument',
970+
});
971+
encapsulationKey = webidl.converters.CryptoKey(encapsulationKey, {
972+
prefix,
973+
context: '2nd argument',
974+
});
975+
sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, {
976+
prefix,
977+
context: '3rd argument',
978+
});
979+
extractable = webidl.converters.boolean(extractable, {
980+
prefix,
981+
context: '4th argument',
982+
});
983+
usages = webidl.converters['sequence<KeyUsage>'](usages, {
984+
prefix,
985+
context: '5th argument',
986+
});
987+
988+
// eslint-disable-next-line no-unused-vars
989+
const normalizedEncapsulationAlgorithm = normalizeAlgorithm(encapsulationAlgorithm, 'encapsulate');
990+
// eslint-disable-next-line no-unused-vars
991+
const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey');
992+
993+
// It is not possible to get here just yet
994+
const assert = require('internal/assert');
995+
assert.fail('Unreachable code');
996+
}
997+
998+
async function decapsulateBits(decapsulationAlgorithm, decapsulationKey, ciphertext) {
999+
emitExperimentalWarning('The decapsulateBits Web Crypto API method');
1000+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
1001+
1002+
webidl ??= require('internal/crypto/webidl');
1003+
const prefix = "Failed to execute 'decapsulateBits' on 'SubtleCrypto'";
1004+
webidl.requiredArguments(arguments.length, 3, { prefix });
1005+
decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, {
1006+
prefix,
1007+
context: '1st argument',
1008+
});
1009+
decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, {
1010+
prefix,
1011+
context: '2nd argument',
1012+
});
1013+
ciphertext = webidl.converters.BufferSource(ciphertext, {
1014+
prefix,
1015+
context: '3rd argument',
1016+
});
1017+
1018+
// eslint-disable-next-line no-unused-vars
1019+
const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate');
1020+
1021+
// It is not possible to get here just yet
1022+
const assert = require('internal/assert');
1023+
assert.fail('Unreachable code');
1024+
}
1025+
1026+
async function decapsulateKey(
1027+
decapsulationAlgorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, usages,
1028+
) {
1029+
emitExperimentalWarning('The decapsulateKey Web Crypto API method');
1030+
if (this !== subtle) throw new ERR_INVALID_THIS('SubtleCrypto');
1031+
1032+
webidl ??= require('internal/crypto/webidl');
1033+
const prefix = "Failed to execute 'decapsulateKey' on 'SubtleCrypto'";
1034+
webidl.requiredArguments(arguments.length, 6, { prefix });
1035+
decapsulationAlgorithm = webidl.converters.AlgorithmIdentifier(decapsulationAlgorithm, {
1036+
prefix,
1037+
context: '1st argument',
1038+
});
1039+
decapsulationKey = webidl.converters.CryptoKey(decapsulationKey, {
1040+
prefix,
1041+
context: '2nd argument',
1042+
});
1043+
ciphertext = webidl.converters.BufferSource(ciphertext, {
1044+
prefix,
1045+
context: '3rd argument',
1046+
});
1047+
sharedKeyAlgorithm = webidl.converters.AlgorithmIdentifier(sharedKeyAlgorithm, {
1048+
prefix,
1049+
context: '4th argument',
1050+
});
1051+
extractable = webidl.converters.boolean(extractable, {
1052+
prefix,
1053+
context: '5th argument',
1054+
});
1055+
usages = webidl.converters['sequence<KeyUsage>'](usages, {
1056+
prefix,
1057+
context: '6th argument',
1058+
});
1059+
1060+
// eslint-disable-next-line no-unused-vars
1061+
const normalizedDecapsulationAlgorithm = normalizeAlgorithm(decapsulationAlgorithm, 'decapsulate');
1062+
// eslint-disable-next-line no-unused-vars
1063+
const normalizedSharedKeyAlgorithm = normalizeAlgorithm(sharedKeyAlgorithm, 'importKey');
1064+
1065+
// It is not possible to get here just yet
1066+
const assert = require('internal/assert');
1067+
assert.fail('Unreachable code');
1068+
}
1069+
9361070
// The SubtleCrypto and Crypto classes are defined as part of the
9371071
// Web Crypto API standard: https://www.w3.org/TR/WebCryptoAPI/
9381072

@@ -958,18 +1092,22 @@ class SubtleCrypto {
9581092
});
9591093

9601094
switch (operation) {
961-
case 'encrypt':
1095+
case 'decapsulateBits':
1096+
case 'decapsulateKey':
9621097
case 'decrypt':
963-
case 'sign':
964-
case 'verify':
1098+
case 'deriveBits':
1099+
case 'deriveKey':
9651100
case 'digest':
1101+
case 'encapsulateBits':
1102+
case 'encapsulateKey':
1103+
case 'encrypt':
1104+
case 'exportKey':
9661105
case 'generateKey':
967-
case 'deriveKey':
968-
case 'deriveBits':
9691106
case 'importKey':
970-
case 'exportKey':
971-
case 'wrapKey':
1107+
case 'sign':
9721108
case 'unwrapKey':
1109+
case 'verify':
1110+
case 'wrapKey':
9731111
break;
9741112
default:
9751113
return false;
@@ -1003,7 +1141,7 @@ class SubtleCrypto {
10031141
if (!check('exportKey', additionalAlgorithm)) {
10041142
return false;
10051143
}
1006-
} else if (operation === 'unwrapKey') {
1144+
} else if (operation === 'unwrapKey' || operation === 'encapsulateKey' || operation === 'decapsulateKey') {
10071145
additionalAlgorithm = webidl.converters.AlgorithmIdentifier(lengthOrAdditionalAlgorithm, {
10081146
prefix,
10091147
context: '3rd argument',
@@ -1027,6 +1165,14 @@ class SubtleCrypto {
10271165
}
10281166

10291167
function check(op, alg, length) {
1168+
if (op === 'encapsulateBits' || op === 'encapsulateKey') {
1169+
op = 'encapsulate';
1170+
}
1171+
1172+
if (op === 'decapsulateBits' || op === 'decapsulateKey') {
1173+
op = 'decapsulate';
1174+
}
1175+
10301176
let normalizedAlgorithm;
10311177
try {
10321178
normalizedAlgorithm = normalizeAlgorithm(alg, op);
@@ -1043,16 +1189,18 @@ function check(op, alg, length) {
10431189
}
10441190

10451191
switch (op) {
1046-
case 'encrypt':
1192+
case 'decapsulate':
10471193
case 'decrypt':
1048-
case 'sign':
1049-
case 'verify':
10501194
case 'digest':
1195+
case 'encapsulate':
1196+
case 'encrypt':
1197+
case 'exportKey':
10511198
case 'generateKey':
10521199
case 'importKey':
1053-
case 'exportKey':
1054-
case 'wrapKey':
1200+
case 'sign':
10551201
case 'unwrapKey':
1202+
case 'verify':
1203+
case 'wrapKey':
10561204
return true;
10571205
case 'deriveBits': {
10581206
if (normalizedAlgorithm.name === 'HKDF') {
@@ -1230,6 +1378,34 @@ ObjectDefineProperties(
12301378
writable: true,
12311379
value: unwrapKey,
12321380
},
1381+
encapsulateBits: {
1382+
__proto__: null,
1383+
enumerable: true,
1384+
configurable: true,
1385+
writable: true,
1386+
value: encapsulateBits,
1387+
},
1388+
encapsulateKey: {
1389+
__proto__: null,
1390+
enumerable: true,
1391+
configurable: true,
1392+
writable: true,
1393+
value: encapsulateKey,
1394+
},
1395+
decapsulateBits: {
1396+
__proto__: null,
1397+
enumerable: true,
1398+
configurable: true,
1399+
writable: true,
1400+
value: decapsulateBits,
1401+
},
1402+
decapsulateKey: {
1403+
__proto__: null,
1404+
enumerable: true,
1405+
configurable: true,
1406+
writable: true,
1407+
value: decapsulateKey,
1408+
},
12331409
});
12341410

12351411
module.exports = {

0 commit comments

Comments
 (0)