simple-hpke 0.2.0 → 0.3.3
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/README.md +207 -27
- package/dist/constants.cjs +188 -47
- package/dist/constants.cjs.map +4 -4
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +188 -47
- package/dist/constants.js.map +4 -4
- package/dist/constants.min.cjs +1 -1
- package/dist/constants.min.cjs.map +4 -4
- package/dist/constants.min.js +1 -1
- package/dist/constants.min.js.map +4 -4
- package/dist/errors.cjs +38 -0
- package/dist/errors.cjs.map +7 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +7 -0
- package/dist/errors.min.cjs +2 -0
- package/dist/errors.min.cjs.map +7 -0
- package/dist/errors.min.js +2 -0
- package/dist/errors.min.js.map +7 -0
- package/dist/index.cjs +231 -50
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +34 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +231 -50
- package/dist/index.js.map +4 -4
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +4 -4
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +81 -20
- package/dist/util.cjs +228 -48
- package/dist/util.cjs.map +4 -4
- package/dist/util.d.ts +56 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +228 -48
- package/dist/util.js.map +4 -4
- package/dist/util.min.cjs +1 -1
- package/dist/util.min.cjs.map +4 -4
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +4 -4
- package/package.json +6 -5
package/dist/meta.json
CHANGED
|
@@ -690,8 +690,19 @@
|
|
|
690
690
|
],
|
|
691
691
|
"format": "esm"
|
|
692
692
|
},
|
|
693
|
+
"src/errors.ts": {
|
|
694
|
+
"bytes": 975,
|
|
695
|
+
"imports": [
|
|
696
|
+
{
|
|
697
|
+
"path": "<runtime>",
|
|
698
|
+
"kind": "import-statement",
|
|
699
|
+
"external": true
|
|
700
|
+
}
|
|
701
|
+
],
|
|
702
|
+
"format": "esm"
|
|
703
|
+
},
|
|
693
704
|
"src/index.ts": {
|
|
694
|
-
"bytes":
|
|
705
|
+
"bytes": 15697,
|
|
695
706
|
"imports": [
|
|
696
707
|
{
|
|
697
708
|
"path": "node_modules/uint8arrays/dist/src/index.js",
|
|
@@ -703,11 +714,21 @@
|
|
|
703
714
|
"kind": "import-statement",
|
|
704
715
|
"original": "./util"
|
|
705
716
|
},
|
|
717
|
+
{
|
|
718
|
+
"path": "src/errors.ts",
|
|
719
|
+
"kind": "import-statement",
|
|
720
|
+
"original": "./errors"
|
|
721
|
+
},
|
|
706
722
|
{
|
|
707
723
|
"path": "src/constants.ts",
|
|
708
724
|
"kind": "import-statement",
|
|
709
725
|
"original": "./constants"
|
|
710
726
|
},
|
|
727
|
+
{
|
|
728
|
+
"path": "src/errors.ts",
|
|
729
|
+
"kind": "import-statement",
|
|
730
|
+
"original": "./errors"
|
|
731
|
+
},
|
|
711
732
|
{
|
|
712
733
|
"path": "<runtime>",
|
|
713
734
|
"kind": "import-statement",
|
|
@@ -717,7 +738,7 @@
|
|
|
717
738
|
"format": "esm"
|
|
718
739
|
},
|
|
719
740
|
"src/util.ts": {
|
|
720
|
-
"bytes":
|
|
741
|
+
"bytes": 22056,
|
|
721
742
|
"imports": [
|
|
722
743
|
{
|
|
723
744
|
"path": "node_modules/uint8arrays/dist/src/index.js",
|
|
@@ -729,6 +750,11 @@
|
|
|
729
750
|
"kind": "import-statement",
|
|
730
751
|
"original": "./index.js"
|
|
731
752
|
},
|
|
753
|
+
{
|
|
754
|
+
"path": "src/errors.ts",
|
|
755
|
+
"kind": "import-statement",
|
|
756
|
+
"original": "./errors.js"
|
|
757
|
+
},
|
|
732
758
|
{
|
|
733
759
|
"path": "src/constants.ts",
|
|
734
760
|
"kind": "import-statement",
|
|
@@ -743,7 +769,7 @@
|
|
|
743
769
|
"format": "esm"
|
|
744
770
|
},
|
|
745
771
|
"src/constants.ts": {
|
|
746
|
-
"bytes":
|
|
772
|
+
"bytes": 2125,
|
|
747
773
|
"imports": [
|
|
748
774
|
{
|
|
749
775
|
"path": "src/util.ts",
|
|
@@ -759,7 +785,7 @@
|
|
|
759
785
|
"imports": [],
|
|
760
786
|
"exports": [],
|
|
761
787
|
"inputs": {},
|
|
762
|
-
"bytes":
|
|
788
|
+
"bytes": 128024
|
|
763
789
|
},
|
|
764
790
|
"dist/constants.js": {
|
|
765
791
|
"imports": [],
|
|
@@ -772,6 +798,8 @@
|
|
|
772
798
|
"KDF_ID",
|
|
773
799
|
"KEM_ID",
|
|
774
800
|
"KEM_SUITE_ID",
|
|
801
|
+
"KEY_ID_LABEL",
|
|
802
|
+
"KEY_ID_NONCE_LENGTH",
|
|
775
803
|
"MODE_BASE",
|
|
776
804
|
"NK",
|
|
777
805
|
"NN",
|
|
@@ -864,32 +892,57 @@
|
|
|
864
892
|
"node_modules/uint8arrays/dist/src/to-string.js": {
|
|
865
893
|
"bytesInOutput": 258
|
|
866
894
|
},
|
|
895
|
+
"src/errors.ts": {
|
|
896
|
+
"bytesInOutput": 215
|
|
897
|
+
},
|
|
867
898
|
"src/index.ts": {
|
|
868
|
-
"bytesInOutput":
|
|
899
|
+
"bytesInOutput": 6418
|
|
869
900
|
},
|
|
870
901
|
"src/util.ts": {
|
|
871
|
-
"bytesInOutput":
|
|
902
|
+
"bytesInOutput": 8581
|
|
872
903
|
},
|
|
873
904
|
"src/constants.ts": {
|
|
874
|
-
"bytesInOutput":
|
|
905
|
+
"bytesInOutput": 551
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
"bytes": 54253
|
|
909
|
+
},
|
|
910
|
+
"dist/errors.js.map": {
|
|
911
|
+
"imports": [],
|
|
912
|
+
"exports": [],
|
|
913
|
+
"inputs": {},
|
|
914
|
+
"bytes": 1293
|
|
915
|
+
},
|
|
916
|
+
"dist/errors.js": {
|
|
917
|
+
"imports": [],
|
|
918
|
+
"exports": [
|
|
919
|
+
"HpkeError"
|
|
920
|
+
],
|
|
921
|
+
"entryPoint": "src/errors.ts",
|
|
922
|
+
"inputs": {
|
|
923
|
+
"src/errors.ts": {
|
|
924
|
+
"bytesInOutput": 213
|
|
875
925
|
}
|
|
876
926
|
},
|
|
877
|
-
"bytes":
|
|
927
|
+
"bytes": 419
|
|
878
928
|
},
|
|
879
929
|
"dist/index.js.map": {
|
|
880
930
|
"imports": [],
|
|
881
931
|
"exports": [],
|
|
882
932
|
"inputs": {},
|
|
883
|
-
"bytes":
|
|
933
|
+
"bytes": 128969
|
|
884
934
|
},
|
|
885
935
|
"dist/index.js": {
|
|
886
936
|
"imports": [],
|
|
887
937
|
"exports": [
|
|
938
|
+
"HpkeError",
|
|
888
939
|
"create",
|
|
889
940
|
"decrypt",
|
|
890
941
|
"encrypt",
|
|
891
942
|
"encryptKey",
|
|
892
|
-
"
|
|
943
|
+
"keyId",
|
|
944
|
+
"open",
|
|
945
|
+
"verifyKeyId"
|
|
893
946
|
],
|
|
894
947
|
"entryPoint": "src/index.ts",
|
|
895
948
|
"inputs": {
|
|
@@ -977,23 +1030,26 @@
|
|
|
977
1030
|
"node_modules/uint8arrays/dist/src/to-string.js": {
|
|
978
1031
|
"bytesInOutput": 258
|
|
979
1032
|
},
|
|
1033
|
+
"src/errors.ts": {
|
|
1034
|
+
"bytesInOutput": 215
|
|
1035
|
+
},
|
|
980
1036
|
"src/constants.ts": {
|
|
981
|
-
"bytesInOutput":
|
|
1037
|
+
"bytesInOutput": 551
|
|
982
1038
|
},
|
|
983
1039
|
"src/util.ts": {
|
|
984
|
-
"bytesInOutput":
|
|
1040
|
+
"bytesInOutput": 9693
|
|
985
1041
|
},
|
|
986
1042
|
"src/index.ts": {
|
|
987
|
-
"bytesInOutput":
|
|
1043
|
+
"bytesInOutput": 6589
|
|
988
1044
|
}
|
|
989
1045
|
},
|
|
990
|
-
"bytes":
|
|
1046
|
+
"bytes": 55429
|
|
991
1047
|
},
|
|
992
1048
|
"dist/util.js.map": {
|
|
993
1049
|
"imports": [],
|
|
994
1050
|
"exports": [],
|
|
995
1051
|
"inputs": {},
|
|
996
|
-
"bytes":
|
|
1052
|
+
"bytes": 128835
|
|
997
1053
|
},
|
|
998
1054
|
"dist/util.js": {
|
|
999
1055
|
"imports": [],
|
|
@@ -1007,9 +1063,11 @@
|
|
|
1007
1063
|
"encryptToString",
|
|
1008
1064
|
"hmac",
|
|
1009
1065
|
"i2osp",
|
|
1066
|
+
"keyId",
|
|
1010
1067
|
"labeledExpand",
|
|
1011
1068
|
"labeledExtract",
|
|
1012
|
-
"resolveRecipientPublicKey"
|
|
1069
|
+
"resolveRecipientPublicKey",
|
|
1070
|
+
"verifyKeyId"
|
|
1013
1071
|
],
|
|
1014
1072
|
"entryPoint": "src/util.ts",
|
|
1015
1073
|
"inputs": {
|
|
@@ -1097,17 +1155,20 @@
|
|
|
1097
1155
|
"node_modules/uint8arrays/dist/src/to-string.js": {
|
|
1098
1156
|
"bytesInOutput": 258
|
|
1099
1157
|
},
|
|
1158
|
+
"src/errors.ts": {
|
|
1159
|
+
"bytesInOutput": 215
|
|
1160
|
+
},
|
|
1100
1161
|
"src/constants.ts": {
|
|
1101
|
-
"bytesInOutput":
|
|
1162
|
+
"bytesInOutput": 551
|
|
1102
1163
|
},
|
|
1103
1164
|
"src/index.ts": {
|
|
1104
|
-
"bytesInOutput":
|
|
1165
|
+
"bytesInOutput": 6418
|
|
1105
1166
|
},
|
|
1106
1167
|
"src/util.ts": {
|
|
1107
|
-
"bytesInOutput":
|
|
1168
|
+
"bytesInOutput": 9709
|
|
1108
1169
|
}
|
|
1109
1170
|
},
|
|
1110
|
-
"bytes":
|
|
1171
|
+
"bytes": 55374
|
|
1111
1172
|
}
|
|
1112
1173
|
}
|
|
1113
1174
|
}
|
package/dist/util.cjs
CHANGED
|
@@ -30,9 +30,11 @@ __export(util_exports, {
|
|
|
30
30
|
encryptToString: () => encryptToString,
|
|
31
31
|
hmac: () => hmac,
|
|
32
32
|
i2osp: () => i2osp,
|
|
33
|
+
keyId: () => keyId,
|
|
33
34
|
labeledExpand: () => labeledExpand,
|
|
34
35
|
labeledExtract: () => labeledExtract,
|
|
35
|
-
resolveRecipientPublicKey: () => resolveRecipientPublicKey
|
|
36
|
+
resolveRecipientPublicKey: () => resolveRecipientPublicKey,
|
|
37
|
+
verifyKeyId: () => verifyKeyId
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(util_exports);
|
|
38
40
|
|
|
@@ -1314,6 +1316,19 @@ function toString2(array, encoding = "utf8") {
|
|
|
1314
1316
|
}
|
|
1315
1317
|
__name(toString2, "toString");
|
|
1316
1318
|
|
|
1319
|
+
// src/errors.ts
|
|
1320
|
+
var HpkeError = class extends Error {
|
|
1321
|
+
static {
|
|
1322
|
+
__name(this, "HpkeError");
|
|
1323
|
+
}
|
|
1324
|
+
code;
|
|
1325
|
+
constructor(code2, message, opts) {
|
|
1326
|
+
super(message, opts);
|
|
1327
|
+
this.name = "HpkeError";
|
|
1328
|
+
this.code = code2;
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1317
1332
|
// src/constants.ts
|
|
1318
1333
|
var KEM_ID = 32;
|
|
1319
1334
|
var KDF_ID = 1;
|
|
@@ -1336,6 +1351,8 @@ var HPKE_SUITE_ID = concat2(
|
|
|
1336
1351
|
i2osp(KDF_ID, 2),
|
|
1337
1352
|
i2osp(AEAD_ID, 2)
|
|
1338
1353
|
);
|
|
1354
|
+
var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
|
|
1355
|
+
var KEY_ID_NONCE_LENGTH = 12;
|
|
1339
1356
|
|
|
1340
1357
|
// src/index.ts
|
|
1341
1358
|
var subtle = globalThis.crypto.subtle;
|
|
@@ -1351,13 +1368,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1351
1368
|
});
|
|
1352
1369
|
async function decryptBytes(keypair, message, opts) {
|
|
1353
1370
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1354
|
-
throw new
|
|
1371
|
+
throw new HpkeError(
|
|
1372
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1373
|
+
"malformed message: too short"
|
|
1374
|
+
);
|
|
1355
1375
|
}
|
|
1356
1376
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1357
1377
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1358
1378
|
const ctStart = ivStart + NN;
|
|
1359
1379
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1360
|
-
throw new
|
|
1380
|
+
throw new HpkeError(
|
|
1381
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1382
|
+
"malformed message: too short"
|
|
1383
|
+
);
|
|
1361
1384
|
}
|
|
1362
1385
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1363
1386
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1368,11 +1391,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1368
1391
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1369
1392
|
);
|
|
1370
1393
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1394
|
+
let pt;
|
|
1395
|
+
try {
|
|
1396
|
+
pt = await subtle.decrypt(
|
|
1397
|
+
{ name: "AES-GCM", iv },
|
|
1398
|
+
key,
|
|
1399
|
+
ciphertext
|
|
1400
|
+
);
|
|
1401
|
+
} catch (err) {
|
|
1402
|
+
throw new HpkeError(
|
|
1403
|
+
"ERR_DECRYPT_FAILED",
|
|
1404
|
+
"failed to decrypt the message: tampered ciphertext",
|
|
1405
|
+
{ cause: err }
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1376
1408
|
return new Uint8Array(pt);
|
|
1377
1409
|
}
|
|
1378
1410
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1382,7 +1414,16 @@ async function decryptToString(keypair, message, opts) {
|
|
|
1382
1414
|
}
|
|
1383
1415
|
__name(decryptToString, "decryptToString");
|
|
1384
1416
|
async function decryptFromString(keypair, message, opts) {
|
|
1385
|
-
|
|
1417
|
+
let bytes;
|
|
1418
|
+
try {
|
|
1419
|
+
bytes = fromString2(message, "base64url");
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
throw new HpkeError(
|
|
1422
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1423
|
+
"malformed message: not base64url",
|
|
1424
|
+
{ cause: err }
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1386
1427
|
const plaintext = await decryptBytes(
|
|
1387
1428
|
keypair,
|
|
1388
1429
|
bytes,
|
|
@@ -1393,7 +1434,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1393
1434
|
__name(decryptFromString, "decryptFromString");
|
|
1394
1435
|
function validateKeysize(keysize) {
|
|
1395
1436
|
if (keysize !== 128 && keysize !== 256) {
|
|
1396
|
-
throw new
|
|
1437
|
+
throw new HpkeError(
|
|
1438
|
+
"ERR_INVALID_KEYSIZE",
|
|
1397
1439
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1398
1440
|
);
|
|
1399
1441
|
}
|
|
@@ -1401,7 +1443,8 @@ function validateKeysize(keysize) {
|
|
|
1401
1443
|
__name(validateKeysize, "validateKeysize");
|
|
1402
1444
|
function validateRawKeyBytes(raw) {
|
|
1403
1445
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1404
|
-
throw new
|
|
1446
|
+
throw new HpkeError(
|
|
1447
|
+
"ERR_INVALID_AES_KEY",
|
|
1405
1448
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1406
1449
|
);
|
|
1407
1450
|
}
|
|
@@ -1415,19 +1458,20 @@ function normalizeInfo(info) {
|
|
|
1415
1458
|
__name(normalizeInfo, "normalizeInfo");
|
|
1416
1459
|
async function exportAesKeyBytes(key) {
|
|
1417
1460
|
if (!key.extractable) {
|
|
1418
|
-
throw new
|
|
1461
|
+
throw new HpkeError(
|
|
1462
|
+
"ERR_INVALID_AES_KEY",
|
|
1419
1463
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1420
1464
|
);
|
|
1421
1465
|
}
|
|
1422
1466
|
return new Uint8Array(await subtle.exportKey("raw", key));
|
|
1423
1467
|
}
|
|
1424
1468
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1425
|
-
async function importAesKey(raw) {
|
|
1469
|
+
async function importAesKey(raw, extractable) {
|
|
1426
1470
|
return subtle.importKey(
|
|
1427
1471
|
"raw",
|
|
1428
1472
|
raw,
|
|
1429
1473
|
{ name: "AES-GCM" },
|
|
1430
|
-
|
|
1474
|
+
extractable,
|
|
1431
1475
|
["encrypt", "decrypt"]
|
|
1432
1476
|
);
|
|
1433
1477
|
}
|
|
@@ -1482,7 +1526,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1482
1526
|
__name(keySchedule, "keySchedule");
|
|
1483
1527
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1484
1528
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1485
|
-
throw new
|
|
1529
|
+
throw new HpkeError(
|
|
1530
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1531
|
+
"malformed envelope: too short"
|
|
1532
|
+
);
|
|
1486
1533
|
}
|
|
1487
1534
|
const info = normalizeInfo(opts?.info);
|
|
1488
1535
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1494,7 +1541,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1494
1541
|
__name(openRawBytes, "openRawBytes");
|
|
1495
1542
|
async function openBytes(keypair, wrapped, opts) {
|
|
1496
1543
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1497
|
-
return importAesKey(keyBytes);
|
|
1544
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1498
1545
|
}
|
|
1499
1546
|
__name(openBytes, "openBytes");
|
|
1500
1547
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1504,6 +1551,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1504
1551
|
validateRawKeyBytes(aesKey);
|
|
1505
1552
|
keyBytes = aesKey;
|
|
1506
1553
|
} else if (aesKey) {
|
|
1554
|
+
if (aesKey.algorithm.name !== "AES-GCM") {
|
|
1555
|
+
throw new HpkeError(
|
|
1556
|
+
"ERR_INVALID_AES_KEY",
|
|
1557
|
+
"aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1507
1560
|
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1508
1561
|
validateRawKeyBytes(keyBytes);
|
|
1509
1562
|
} else {
|
|
@@ -1518,8 +1571,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1518
1571
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1519
1572
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1520
1573
|
const wrapped = concat2(enc, ciphertext);
|
|
1521
|
-
|
|
1522
|
-
|
|
1574
|
+
if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
|
|
1575
|
+
const aesGcmKey = await importAesKey(
|
|
1576
|
+
keyBytes,
|
|
1577
|
+
opts?.extractable ?? false
|
|
1578
|
+
);
|
|
1579
|
+
return { wrapped, key: aesGcmKey };
|
|
1523
1580
|
}
|
|
1524
1581
|
__name(encryptKey, "encryptKey");
|
|
1525
1582
|
|
|
@@ -1549,24 +1606,44 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1549
1606
|
false,
|
|
1550
1607
|
["decrypt"]
|
|
1551
1608
|
);
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1609
|
+
let pt;
|
|
1610
|
+
try {
|
|
1611
|
+
pt = await subtle2.decrypt(
|
|
1612
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1613
|
+
k,
|
|
1614
|
+
ciphertext
|
|
1615
|
+
);
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
throw new HpkeError(
|
|
1618
|
+
"ERR_DECRYPT_FAILED",
|
|
1619
|
+
"failed to decrypt the wrapped key: wrong keypair, wrong info, or a tampered envelope",
|
|
1620
|
+
{ cause: err }
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1557
1623
|
return new Uint8Array(pt);
|
|
1558
1624
|
}
|
|
1559
1625
|
__name(aeadOpen, "aeadOpen");
|
|
1560
1626
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1627
|
+
if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
|
|
1628
|
+
throw new HpkeError(
|
|
1629
|
+
"ERR_INVALID_AES_KEY",
|
|
1630
|
+
'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1561
1633
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1562
|
-
const {
|
|
1634
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1563
1635
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1564
1636
|
const ct = new Uint8Array(await subtle2.encrypt(
|
|
1565
1637
|
{ name: "AES-GCM", iv },
|
|
1566
1638
|
key,
|
|
1567
1639
|
plaintext
|
|
1568
1640
|
));
|
|
1569
|
-
return concat2(
|
|
1641
|
+
return concat2(
|
|
1642
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1643
|
+
wrapped,
|
|
1644
|
+
iv,
|
|
1645
|
+
ct
|
|
1646
|
+
);
|
|
1570
1647
|
}
|
|
1571
1648
|
__name(encryptBytes, "encryptBytes");
|
|
1572
1649
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1632,7 +1709,8 @@ async function exportRawPublic(key) {
|
|
|
1632
1709
|
__name(exportRawPublic, "exportRawPublic");
|
|
1633
1710
|
async function importRawPublic(raw) {
|
|
1634
1711
|
if (raw.length !== ENC_LENGTH) {
|
|
1635
|
-
throw new
|
|
1712
|
+
throw new HpkeError(
|
|
1713
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1636
1714
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1637
1715
|
);
|
|
1638
1716
|
}
|
|
@@ -1650,44 +1728,83 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1650
1728
|
return importRawPublic(recipient);
|
|
1651
1729
|
}
|
|
1652
1730
|
if (recipient instanceof CryptoKey) {
|
|
1653
|
-
|
|
1654
|
-
throw new Error("recipient CryptoKey must be a public key");
|
|
1655
|
-
}
|
|
1656
|
-
assertX25519Algorithm(recipient, "recipient CryptoKey");
|
|
1657
|
-
return recipient;
|
|
1731
|
+
return assertRecipientPublicKey(recipient, "recipient CryptoKey");
|
|
1658
1732
|
}
|
|
1659
1733
|
if (typeof recipient.publicKey === "string") {
|
|
1660
1734
|
const { publicKey, encoding } = recipient;
|
|
1661
|
-
|
|
1735
|
+
let decoded;
|
|
1736
|
+
try {
|
|
1737
|
+
decoded = fromString2(publicKey, encoding ?? "base64url");
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
throw new HpkeError(
|
|
1740
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1741
|
+
`recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
|
|
1742
|
+
{ cause: err }
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
return importRawPublic(decoded);
|
|
1662
1746
|
}
|
|
1663
1747
|
const pair = recipient;
|
|
1664
1748
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
return pair.publicKey;
|
|
1749
|
+
return assertRecipientPublicKey(
|
|
1750
|
+
pair.publicKey,
|
|
1751
|
+
"recipient keypair publicKey"
|
|
1752
|
+
);
|
|
1670
1753
|
}
|
|
1671
|
-
throw new
|
|
1754
|
+
throw new HpkeError(
|
|
1755
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1756
|
+
"unrecognized recipient key form"
|
|
1757
|
+
);
|
|
1672
1758
|
}
|
|
1673
1759
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1674
|
-
function
|
|
1760
|
+
function assertRecipientPublicKey(key, context) {
|
|
1761
|
+
if (key.type !== "public") {
|
|
1762
|
+
throw new HpkeError(
|
|
1763
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1764
|
+
`${context} must be a public key`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
|
|
1768
|
+
if (!key.extractable) {
|
|
1769
|
+
throw new HpkeError(
|
|
1770
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1771
|
+
`${context} must be extractable (the KEM exports it)`
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
return key;
|
|
1775
|
+
}
|
|
1776
|
+
__name(assertRecipientPublicKey, "assertRecipientPublicKey");
|
|
1777
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1675
1778
|
if (key.algorithm.name !== "X25519") {
|
|
1676
|
-
throw new
|
|
1779
|
+
throw new HpkeError(
|
|
1780
|
+
code2,
|
|
1677
1781
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1678
1782
|
);
|
|
1679
1783
|
}
|
|
1680
1784
|
}
|
|
1681
1785
|
__name(assertX25519Algorithm, "assertX25519Algorithm");
|
|
1682
1786
|
async function dh(priv, pub) {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1787
|
+
let bits;
|
|
1788
|
+
try {
|
|
1789
|
+
bits = await subtle2.deriveBits(
|
|
1790
|
+
{ name: "X25519", public: pub },
|
|
1791
|
+
priv,
|
|
1792
|
+
256
|
|
1793
|
+
);
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
if (err?.name === "OperationError") {
|
|
1796
|
+
throw new HpkeError(
|
|
1797
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1798
|
+
"X25519 deriveBits rejected a small-order public key",
|
|
1799
|
+
{ cause: err }
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
throw err;
|
|
1803
|
+
}
|
|
1688
1804
|
const bytes = new Uint8Array(bits);
|
|
1689
1805
|
if (bytes.every((b) => b === 0)) {
|
|
1690
|
-
throw new
|
|
1806
|
+
throw new HpkeError(
|
|
1807
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1691
1808
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1692
1809
|
);
|
|
1693
1810
|
}
|
|
@@ -1726,9 +1843,33 @@ async function encap(pkR) {
|
|
|
1726
1843
|
__name(encap, "encap");
|
|
1727
1844
|
async function decap(enc, keypair) {
|
|
1728
1845
|
if (keypair.privateKey.type !== "private") {
|
|
1729
|
-
throw new
|
|
1846
|
+
throw new HpkeError(
|
|
1847
|
+
"ERR_INVALID_KEYPAIR",
|
|
1848
|
+
"keypair.privateKey must be a private key"
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
assertX25519Algorithm(
|
|
1852
|
+
keypair.privateKey,
|
|
1853
|
+
"keypair.privateKey",
|
|
1854
|
+
"ERR_INVALID_KEYPAIR"
|
|
1855
|
+
);
|
|
1856
|
+
if (!keypair.privateKey.usages.includes("deriveBits")) {
|
|
1857
|
+
throw new HpkeError(
|
|
1858
|
+
"ERR_INVALID_KEYPAIR",
|
|
1859
|
+
"keypair.privateKey must have the deriveBits usage"
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
|
|
1863
|
+
throw new HpkeError(
|
|
1864
|
+
"ERR_INVALID_KEYPAIR",
|
|
1865
|
+
"keypair.publicKey must be an extractable public key"
|
|
1866
|
+
);
|
|
1730
1867
|
}
|
|
1731
|
-
assertX25519Algorithm(
|
|
1868
|
+
assertX25519Algorithm(
|
|
1869
|
+
keypair.publicKey,
|
|
1870
|
+
"keypair.publicKey",
|
|
1871
|
+
"ERR_INVALID_KEYPAIR"
|
|
1872
|
+
);
|
|
1732
1873
|
const pkE = await subtle2.importKey(
|
|
1733
1874
|
"raw",
|
|
1734
1875
|
enc,
|
|
@@ -1742,6 +1883,45 @@ async function decap(enc, keypair) {
|
|
|
1742
1883
|
return extractAndExpand(dhBytes, kemContext);
|
|
1743
1884
|
}
|
|
1744
1885
|
__name(decap, "decap");
|
|
1886
|
+
async function keyId(key) {
|
|
1887
|
+
if (key.algorithm.name !== "AES-GCM") {
|
|
1888
|
+
throw new HpkeError(
|
|
1889
|
+
"ERR_INVALID_AES_KEY",
|
|
1890
|
+
`keyId needs an AES-GCM key (got ${key.algorithm.name})`
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
if (!key.usages.includes("encrypt")) {
|
|
1894
|
+
throw new HpkeError(
|
|
1895
|
+
"ERR_INVALID_AES_KEY",
|
|
1896
|
+
"keyId needs a key with the 'encrypt' usage"
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
const labelHash = new Uint8Array(
|
|
1900
|
+
await subtle2.digest("SHA-256", KEY_ID_LABEL)
|
|
1901
|
+
);
|
|
1902
|
+
const nonce = labelHash.slice(0, KEY_ID_NONCE_LENGTH);
|
|
1903
|
+
const ciphertext = new Uint8Array(await subtle2.encrypt(
|
|
1904
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1905
|
+
key,
|
|
1906
|
+
KEY_ID_LABEL
|
|
1907
|
+
));
|
|
1908
|
+
const digest2 = await subtle2.digest(
|
|
1909
|
+
"SHA-256",
|
|
1910
|
+
concat2(KEY_ID_LABEL, ciphertext)
|
|
1911
|
+
);
|
|
1912
|
+
return toString2(new Uint8Array(digest2), "base64url");
|
|
1913
|
+
}
|
|
1914
|
+
__name(keyId, "keyId");
|
|
1915
|
+
async function verifyKeyId(key, expected) {
|
|
1916
|
+
const actual = await keyId(key);
|
|
1917
|
+
if (actual.length !== expected.length) return false;
|
|
1918
|
+
let diff = 0;
|
|
1919
|
+
for (let i = 0; i < actual.length; i++) {
|
|
1920
|
+
diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
1921
|
+
}
|
|
1922
|
+
return diff === 0;
|
|
1923
|
+
}
|
|
1924
|
+
__name(verifyKeyId, "verifyKeyId");
|
|
1745
1925
|
function i2osp(n, len) {
|
|
1746
1926
|
const out = new Uint8Array(len);
|
|
1747
1927
|
let v = n;
|