@cubist-labs/cubesigner-sdk-key-import 0.4.68-0
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 +90 -0
- package/dist/import.d.ts +23 -0
- package/dist/import.d.ts.map +1 -0
- package/dist/import.js +333 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/mnemonic.d.ts +35 -0
- package/dist/mnemonic.d.ts.map +1 -0
- package/dist/mnemonic.js +93 -0
- package/dist/util.d.ts +22 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +49 -0
- package/package.json +39 -0
- package/src/import.ts +426 -0
- package/src/index.ts +2 -0
- package/src/mnemonic.ts +91 -0
- package/src/util.ts +47 -0
package/dist/mnemonic.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.newMnemonicKeyPackage = newMnemonicKeyPackage;
|
|
27
|
+
exports.parseDerivationPath = parseDerivationPath;
|
|
28
|
+
const msgpackr_1 = require("msgpackr");
|
|
29
|
+
const english_1 = require("@scure/bip39/wordlists/english");
|
|
30
|
+
const bip39 = __importStar(require("@scure/bip39"));
|
|
31
|
+
/**
|
|
32
|
+
* Create a new MnemonicKeyPackage value
|
|
33
|
+
*
|
|
34
|
+
* @param { MnemonicToImport } mne A BIP39 mnemonic and optional BIP39 password and BIP32 derivation path
|
|
35
|
+
* @return { Uint8Array } A serialized key package for import to CubeSigner
|
|
36
|
+
*/
|
|
37
|
+
function newMnemonicKeyPackage(mne) {
|
|
38
|
+
const entropy = bip39.mnemonicToEntropy(mne.mnemonic, english_1.wordlist);
|
|
39
|
+
const path = !mne.derivationPath ? [] : parseDerivationPath(mne.derivationPath);
|
|
40
|
+
const password = mne.password ?? "";
|
|
41
|
+
const mnePkg = {
|
|
42
|
+
EnglishMnemonic: {
|
|
43
|
+
mnemonic: {
|
|
44
|
+
entropy,
|
|
45
|
+
},
|
|
46
|
+
der_path: {
|
|
47
|
+
path,
|
|
48
|
+
},
|
|
49
|
+
password,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return (0, msgpackr_1.encode)(mnePkg);
|
|
53
|
+
}
|
|
54
|
+
// constants for derivation path parsing
|
|
55
|
+
const DER_HARDENED = 1n << 31n;
|
|
56
|
+
const DER_MAX = 1n << 32n;
|
|
57
|
+
/**
|
|
58
|
+
* Parse a derivation path into a sequence of 32-bit integers
|
|
59
|
+
*
|
|
60
|
+
* @param { string } derp The derivation path to parse; must start with 'm/'
|
|
61
|
+
* @return { number[] } The parsed path
|
|
62
|
+
*/
|
|
63
|
+
function parseDerivationPath(derp) {
|
|
64
|
+
derp = derp.toLowerCase();
|
|
65
|
+
if (derp === "m") {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
if (!derp.startsWith("m/")) {
|
|
69
|
+
throw new Error('Derivation path must start with "m/"');
|
|
70
|
+
}
|
|
71
|
+
const parts = derp.slice(2).split("/");
|
|
72
|
+
const ret = [];
|
|
73
|
+
for (let part of parts) {
|
|
74
|
+
let hardened = false;
|
|
75
|
+
if (part.endsWith("'") || part.endsWith("h")) {
|
|
76
|
+
hardened = true;
|
|
77
|
+
part = part.slice(0, part.length - 1);
|
|
78
|
+
}
|
|
79
|
+
if (part === "") {
|
|
80
|
+
throw new Error("Invalid derivation path: empty element");
|
|
81
|
+
}
|
|
82
|
+
let value = BigInt(part);
|
|
83
|
+
if (value >= DER_MAX) {
|
|
84
|
+
throw new Error("Derivation path element greater than 2^32 is invalid");
|
|
85
|
+
}
|
|
86
|
+
if (hardened) {
|
|
87
|
+
value = value | DER_HARDENED;
|
|
88
|
+
}
|
|
89
|
+
ret.push(Number(value));
|
|
90
|
+
}
|
|
91
|
+
return ret;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW5lbW9uaWMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvbW5lbW9uaWMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQWlDQSxzREFnQkM7QUFZRCxrREE2QkM7QUExRkQsdUNBQThDO0FBQzlDLDREQUEwRDtBQUMxRCxvREFBc0M7QUF5QnRDOzs7OztHQUtHO0FBQ0gsU0FBZ0IscUJBQXFCLENBQUMsR0FBcUI7SUFDekQsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsa0JBQVEsQ0FBQyxDQUFDO0lBQ2hFLE1BQU0sSUFBSSxHQUFHLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDaEYsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLFFBQVEsSUFBSSxFQUFFLENBQUM7SUFDcEMsTUFBTSxNQUFNLEdBQUc7UUFDYixlQUFlLEVBQUU7WUFDZixRQUFRLEVBQUU7Z0JBQ1IsT0FBTzthQUNSO1lBQ0QsUUFBUSxFQUFFO2dCQUNSLElBQUk7YUFDTDtZQUNELFFBQVE7U0FDVDtLQUNGLENBQUM7SUFDRixPQUFPLElBQUEsaUJBQVEsRUFBQyxNQUFNLENBQUMsQ0FBQztBQUMxQixDQUFDO0FBRUQsd0NBQXdDO0FBQ3hDLE1BQU0sWUFBWSxHQUFHLEVBQUUsSUFBSSxHQUFHLENBQUM7QUFDL0IsTUFBTSxPQUFPLEdBQUcsRUFBRSxJQUFJLEdBQUcsQ0FBQztBQUUxQjs7Ozs7R0FLRztBQUNILFNBQWdCLG1CQUFtQixDQUFDLElBQVk7SUFDOUMsSUFBSSxHQUFHLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUMxQixJQUFJLElBQUksS0FBSyxHQUFHLEVBQUUsQ0FBQztRQUNqQixPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUM7SUFDRCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzNCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBQ0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdkMsTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFDO0lBQ2YsS0FBSyxJQUFJLElBQUksSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUN2QixJQUFJLFFBQVEsR0FBRyxLQUFLLENBQUM7UUFDckIsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM3QyxRQUFRLEdBQUcsSUFBSSxDQUFDO1lBQ2hCLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3hDLENBQUM7UUFDRCxJQUFJLElBQUksS0FBSyxFQUFFLEVBQUUsQ0FBQztZQUNoQixNQUFNLElBQUksS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7UUFDNUQsQ0FBQztRQUNELElBQUksS0FBSyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN6QixJQUFJLEtBQUssSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUNyQixNQUFNLElBQUksS0FBSyxDQUFDLHNEQUFzRCxDQUFDLENBQUM7UUFDMUUsQ0FBQztRQUNELElBQUksUUFBUSxFQUFFLENBQUM7WUFDYixLQUFLLEdBQUcsS0FBSyxHQUFHLFlBQVksQ0FBQztRQUMvQixDQUFDO1FBQ0QsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUMxQixDQUFDO0lBQ0QsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZW5jb2RlIGFzIG1wRW5jb2RlIH0gZnJvbSBcIm1zZ3BhY2tyXCI7XG5pbXBvcnQgeyB3b3JkbGlzdCB9IGZyb20gXCJAc2N1cmUvYmlwMzkvd29yZGxpc3RzL2VuZ2xpc2hcIjtcbmltcG9ydCAqIGFzIGJpcDM5IGZyb20gXCJAc2N1cmUvYmlwMzlcIjtcblxuLy8gVGhlIEtleVBhY2thZ2UgdHlwZSBmcm9tIEN1YmVTaWduZXJcbmV4cG9ydCB0eXBlIE1uZW1vbmljS2V5UGFja2FnZSA9IHtcbiAgRW5nbGlzaE1uZW1vbmljOiB7XG4gICAgbW5lbW9uaWM6IHtcbiAgICAgIGVudHJvcHk6IFVpbnQ4QXJyYXk7XG4gICAgfTtcbiAgICBkZXJfcGF0aDoge1xuICAgICAgcGF0aDogbnVtYmVyW107XG4gICAgfTtcbiAgICBwYXNzd29yZDogc3RyaW5nO1xuICB9O1xufTtcblxuLyoqXG4gKiBBIEJJUDM5IG1uZW1vbmljIHRvIGJlIGltcG9ydGVkLCBwbHVzIG9wdGlvbmFsIEJJUDM5IHBhc3N3b3JkXG4gKiBhbmQgQklQMzIgZGVyaXZhdGlvbiBwYXRoLlxuICovXG5leHBvcnQgdHlwZSBNbmVtb25pY1RvSW1wb3J0ID0ge1xuICBtbmVtb25pYzogc3RyaW5nO1xuICBkZXJpdmF0aW9uUGF0aD86IHN0cmluZztcbiAgcGFzc3dvcmQ/OiBzdHJpbmc7XG59O1xuXG4vKipcbiAqIENyZWF0ZSBhIG5ldyBNbmVtb25pY0tleVBhY2thZ2UgdmFsdWVcbiAqXG4gKiBAcGFyYW0geyBNbmVtb25pY1RvSW1wb3J0IH0gbW5lIEEgQklQMzkgbW5lbW9uaWMgYW5kIG9wdGlvbmFsIEJJUDM5IHBhc3N3b3JkIGFuZCBCSVAzMiBkZXJpdmF0aW9uIHBhdGhcbiAqIEByZXR1cm4geyBVaW50OEFycmF5IH0gQSBzZXJpYWxpemVkIGtleSBwYWNrYWdlIGZvciBpbXBvcnQgdG8gQ3ViZVNpZ25lclxuICovXG5leHBvcnQgZnVuY3Rpb24gbmV3TW5lbW9uaWNLZXlQYWNrYWdlKG1uZTogTW5lbW9uaWNUb0ltcG9ydCk6IFVpbnQ4QXJyYXkge1xuICBjb25zdCBlbnRyb3B5ID0gYmlwMzkubW5lbW9uaWNUb0VudHJvcHkobW5lLm1uZW1vbmljLCB3b3JkbGlzdCk7XG4gIGNvbnN0IHBhdGggPSAhbW5lLmRlcml2YXRpb25QYXRoID8gW10gOiBwYXJzZURlcml2YXRpb25QYXRoKG1uZS5kZXJpdmF0aW9uUGF0aCk7XG4gIGNvbnN0IHBhc3N3b3JkID0gbW5lLnBhc3N3b3JkID8/IFwiXCI7XG4gIGNvbnN0IG1uZVBrZyA9IHtcbiAgICBFbmdsaXNoTW5lbW9uaWM6IHtcbiAgICAgIG1uZW1vbmljOiB7XG4gICAgICAgIGVudHJvcHksXG4gICAgICB9LFxuICAgICAgZGVyX3BhdGg6IHtcbiAgICAgICAgcGF0aCxcbiAgICAgIH0sXG4gICAgICBwYXNzd29yZCxcbiAgICB9LFxuICB9O1xuICByZXR1cm4gbXBFbmNvZGUobW5lUGtnKTtcbn1cblxuLy8gY29uc3RhbnRzIGZvciBkZXJpdmF0aW9uIHBhdGggcGFyc2luZ1xuY29uc3QgREVSX0hBUkRFTkVEID0gMW4gPDwgMzFuO1xuY29uc3QgREVSX01BWCA9IDFuIDw8IDMybjtcblxuLyoqXG4gKiBQYXJzZSBhIGRlcml2YXRpb24gcGF0aCBpbnRvIGEgc2VxdWVuY2Ugb2YgMzItYml0IGludGVnZXJzXG4gKlxuICogQHBhcmFtIHsgc3RyaW5nIH0gZGVycCBUaGUgZGVyaXZhdGlvbiBwYXRoIHRvIHBhcnNlOyBtdXN0IHN0YXJ0IHdpdGggJ20vJ1xuICogQHJldHVybiB7IG51bWJlcltdIH0gVGhlIHBhcnNlZCBwYXRoXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZURlcml2YXRpb25QYXRoKGRlcnA6IHN0cmluZyk6IG51bWJlcltdIHtcbiAgZGVycCA9IGRlcnAudG9Mb3dlckNhc2UoKTtcbiAgaWYgKGRlcnAgPT09IFwibVwiKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG4gIGlmICghZGVycC5zdGFydHNXaXRoKFwibS9cIikpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0Rlcml2YXRpb24gcGF0aCBtdXN0IHN0YXJ0IHdpdGggXCJtL1wiJyk7XG4gIH1cbiAgY29uc3QgcGFydHMgPSBkZXJwLnNsaWNlKDIpLnNwbGl0KFwiL1wiKTtcbiAgY29uc3QgcmV0ID0gW107XG4gIGZvciAobGV0IHBhcnQgb2YgcGFydHMpIHtcbiAgICBsZXQgaGFyZGVuZWQgPSBmYWxzZTtcbiAgICBpZiAocGFydC5lbmRzV2l0aChcIidcIikgfHwgcGFydC5lbmRzV2l0aChcImhcIikpIHtcbiAgICAgIGhhcmRlbmVkID0gdHJ1ZTtcbiAgICAgIHBhcnQgPSBwYXJ0LnNsaWNlKDAsIHBhcnQubGVuZ3RoIC0gMSk7XG4gICAgfVxuICAgIGlmIChwYXJ0ID09PSBcIlwiKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbnZhbGlkIGRlcml2YXRpb24gcGF0aDogZW1wdHkgZWxlbWVudFwiKTtcbiAgICB9XG4gICAgbGV0IHZhbHVlID0gQmlnSW50KHBhcnQpO1xuICAgIGlmICh2YWx1ZSA+PSBERVJfTUFYKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJEZXJpdmF0aW9uIHBhdGggZWxlbWVudCBncmVhdGVyIHRoYW4gMl4zMiBpcyBpbnZhbGlkXCIpO1xuICAgIH1cbiAgICBpZiAoaGFyZGVuZWQpIHtcbiAgICAgIHZhbHVlID0gdmFsdWUgfCBERVJfSEFSREVORUQ7XG4gICAgfVxuICAgIHJldC5wdXNoKE51bWJlcih2YWx1ZSkpO1xuICB9XG4gIHJldHVybiByZXQ7XG59XG4iXX0=
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a bigint to a big-endian Uint8Array of a specified length
|
|
3
|
+
*
|
|
4
|
+
* @param { bigint } n The value to convert
|
|
5
|
+
* @param { number } l The length in bytes
|
|
6
|
+
* @return { Uint8Array } The big-endian bytes
|
|
7
|
+
*/
|
|
8
|
+
export declare function toBigEndian(n: bigint, l: number): Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* Concatenates an array of Uint8Arrays into a single array
|
|
11
|
+
*
|
|
12
|
+
* @param { Uint8Array[] } parts The parts to be concatenated
|
|
13
|
+
* @return { Uint8Array } The concatenated array
|
|
14
|
+
*/
|
|
15
|
+
export declare function concatArrays(parts: Uint8Array[]): Uint8Array;
|
|
16
|
+
/**
|
|
17
|
+
* Get the current time in seconds since UNIX epoch
|
|
18
|
+
*
|
|
19
|
+
* @return { BigInt } Seconds since UNIX epoch
|
|
20
|
+
*/
|
|
21
|
+
export declare function nowEpochMillis(): bigint;
|
|
22
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAW5D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,CAW5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toBigEndian = toBigEndian;
|
|
4
|
+
exports.concatArrays = concatArrays;
|
|
5
|
+
exports.nowEpochMillis = nowEpochMillis;
|
|
6
|
+
/**
|
|
7
|
+
* Converts a bigint to a big-endian Uint8Array of a specified length
|
|
8
|
+
*
|
|
9
|
+
* @param { bigint } n The value to convert
|
|
10
|
+
* @param { number } l The length in bytes
|
|
11
|
+
* @return { Uint8Array } The big-endian bytes
|
|
12
|
+
*/
|
|
13
|
+
function toBigEndian(n, l) {
|
|
14
|
+
if (n >= 1n << (8n * BigInt(l))) {
|
|
15
|
+
throw new Error(`Cannot convert ${n} to ${l} big-endian bytes (overflow)`);
|
|
16
|
+
}
|
|
17
|
+
let nn = n;
|
|
18
|
+
const ret = new Uint8Array(l);
|
|
19
|
+
for (let i = l - 1; i >= 0; --i) {
|
|
20
|
+
ret[i] = Number(nn % 256n);
|
|
21
|
+
nn = nn >> 8n;
|
|
22
|
+
}
|
|
23
|
+
return ret;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Concatenates an array of Uint8Arrays into a single array
|
|
27
|
+
*
|
|
28
|
+
* @param { Uint8Array[] } parts The parts to be concatenated
|
|
29
|
+
* @return { Uint8Array } The concatenated array
|
|
30
|
+
*/
|
|
31
|
+
function concatArrays(parts) {
|
|
32
|
+
const totalLen = parts.reduce((len, part) => len + part.length, 0);
|
|
33
|
+
let lenSoFar = 0;
|
|
34
|
+
const ret = new Uint8Array(totalLen);
|
|
35
|
+
parts.forEach((part) => {
|
|
36
|
+
ret.set(part, lenSoFar);
|
|
37
|
+
lenSoFar += part.length;
|
|
38
|
+
});
|
|
39
|
+
return ret;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the current time in seconds since UNIX epoch
|
|
43
|
+
*
|
|
44
|
+
* @return { BigInt } Seconds since UNIX epoch
|
|
45
|
+
*/
|
|
46
|
+
function nowEpochMillis() {
|
|
47
|
+
return BigInt(Date.now());
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBT0Esa0NBV0M7QUFRRCxvQ0FXQztBQU9ELHdDQUVDO0FBOUNEOzs7Ozs7R0FNRztBQUNILFNBQWdCLFdBQVcsQ0FBQyxDQUFTLEVBQUUsQ0FBUztJQUM5QyxJQUFJLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNoQyxNQUFNLElBQUksS0FBSyxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFDRCxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDWCxNQUFNLEdBQUcsR0FBRyxJQUFJLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBRSxDQUFDO1FBQ2hDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxNQUFNLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQzNCLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxPQUFPLEdBQUcsQ0FBQztBQUNiLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQWdCLFlBQVksQ0FBQyxLQUFtQjtJQUM5QyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsR0FBRyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFFbkUsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDO0lBQ2pCLE1BQU0sR0FBRyxHQUFHLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3JDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUNyQixHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztRQUN4QixRQUFRLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUMxQixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFnQixjQUFjO0lBQzVCLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO0FBQzVCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbnZlcnRzIGEgYmlnaW50IHRvIGEgYmlnLWVuZGlhbiBVaW50OEFycmF5IG9mIGEgc3BlY2lmaWVkIGxlbmd0aFxuICpcbiAqIEBwYXJhbSB7IGJpZ2ludCB9IG4gVGhlIHZhbHVlIHRvIGNvbnZlcnRcbiAqIEBwYXJhbSB7IG51bWJlciB9IGwgVGhlIGxlbmd0aCBpbiBieXRlc1xuICogQHJldHVybiB7IFVpbnQ4QXJyYXkgfSBUaGUgYmlnLWVuZGlhbiBieXRlc1xuICovXG5leHBvcnQgZnVuY3Rpb24gdG9CaWdFbmRpYW4objogYmlnaW50LCBsOiBudW1iZXIpOiBVaW50OEFycmF5IHtcbiAgaWYgKG4gPj0gMW4gPDwgKDhuICogQmlnSW50KGwpKSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgQ2Fubm90IGNvbnZlcnQgJHtufSB0byAke2x9IGJpZy1lbmRpYW4gYnl0ZXMgKG92ZXJmbG93KWApO1xuICB9XG4gIGxldCBubiA9IG47XG4gIGNvbnN0IHJldCA9IG5ldyBVaW50OEFycmF5KGwpO1xuICBmb3IgKGxldCBpID0gbCAtIDE7IGkgPj0gMDsgLS1pKSB7XG4gICAgcmV0W2ldID0gTnVtYmVyKG5uICUgMjU2bik7XG4gICAgbm4gPSBubiA+PiA4bjtcbiAgfVxuICByZXR1cm4gcmV0O1xufVxuXG4vKipcbiAqIENvbmNhdGVuYXRlcyBhbiBhcnJheSBvZiBVaW50OEFycmF5cyBpbnRvIGEgc2luZ2xlIGFycmF5XG4gKlxuICogQHBhcmFtIHsgVWludDhBcnJheVtdIH0gcGFydHMgVGhlIHBhcnRzIHRvIGJlIGNvbmNhdGVuYXRlZFxuICogQHJldHVybiB7IFVpbnQ4QXJyYXkgfSBUaGUgY29uY2F0ZW5hdGVkIGFycmF5XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb25jYXRBcnJheXMocGFydHM6IFVpbnQ4QXJyYXlbXSk6IFVpbnQ4QXJyYXkge1xuICBjb25zdCB0b3RhbExlbiA9IHBhcnRzLnJlZHVjZSgobGVuLCBwYXJ0KSA9PiBsZW4gKyBwYXJ0Lmxlbmd0aCwgMCk7XG5cbiAgbGV0IGxlblNvRmFyID0gMDtcbiAgY29uc3QgcmV0ID0gbmV3IFVpbnQ4QXJyYXkodG90YWxMZW4pO1xuICBwYXJ0cy5mb3JFYWNoKChwYXJ0KSA9PiB7XG4gICAgcmV0LnNldChwYXJ0LCBsZW5Tb0Zhcik7XG4gICAgbGVuU29GYXIgKz0gcGFydC5sZW5ndGg7XG4gIH0pO1xuXG4gIHJldHVybiByZXQ7XG59XG5cbi8qKlxuICogR2V0IHRoZSBjdXJyZW50IHRpbWUgaW4gc2Vjb25kcyBzaW5jZSBVTklYIGVwb2NoXG4gKlxuICogQHJldHVybiB7IEJpZ0ludCB9IFNlY29uZHMgc2luY2UgVU5JWCBlcG9jaFxuICovXG5leHBvcnQgZnVuY3Rpb24gbm93RXBvY2hNaWxsaXMoKTogYmlnaW50IHtcbiAgcmV0dXJuIEJpZ0ludChEYXRlLm5vdygpKTtcbn1cbiJdfQ==
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cubist-labs/cubesigner-sdk-key-import",
|
|
3
|
+
"version": "0.4.68-0",
|
|
4
|
+
"description": "Client-side key-import machinery for CubeSigner",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"author": "Cubist, Inc.",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"tsconfig.json",
|
|
11
|
+
"src/**",
|
|
12
|
+
"dist/**",
|
|
13
|
+
"../../NOTICE",
|
|
14
|
+
"../../LICENSE-APACHE",
|
|
15
|
+
"../../LICENSE_MIT"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "npx tsc",
|
|
19
|
+
"prepack": "npx tsc",
|
|
20
|
+
"repl": "npx node --import tsx",
|
|
21
|
+
"test": "npx --node-options='--experimental-vm-modules' jest --maxWorkers=1"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@cubist-labs/cubesigner-sdk": "^0.4.68-0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsx": "^4.19.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@auth0/cose": "^1.0.2",
|
|
31
|
+
"@hpke/core": "^1.3.1",
|
|
32
|
+
"@peculiar/asn1-ecc": "^2.3.13",
|
|
33
|
+
"@peculiar/asn1-schema": "^2.3.13",
|
|
34
|
+
"@peculiar/x509": "^1.12.1",
|
|
35
|
+
"@scure/bip39": "^1.4.0",
|
|
36
|
+
"cbor-x": "^1.6.0",
|
|
37
|
+
"msgpackr": "^1.11.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/import.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateKeyImportKeyResponse,
|
|
3
|
+
ImportKeyRequest,
|
|
4
|
+
ImportKeyRequestMaterial,
|
|
5
|
+
Key,
|
|
6
|
+
KeyType,
|
|
7
|
+
Org,
|
|
8
|
+
} from "@cubist-labs/cubesigner-sdk";
|
|
9
|
+
import { loadCrypto, loadSubtleCrypto } from "@cubist-labs/cubesigner-sdk";
|
|
10
|
+
import { CipherSuite, Aes256Gcm, HkdfSha384, DhkemP384HkdfSha384 } from "@hpke/core";
|
|
11
|
+
import { ECParameters } from "@peculiar/asn1-ecc";
|
|
12
|
+
import { AsnParser } from "@peculiar/asn1-schema";
|
|
13
|
+
import {
|
|
14
|
+
AlgorithmProvider,
|
|
15
|
+
X509Certificate,
|
|
16
|
+
cryptoProvider as x509CryptoProvider,
|
|
17
|
+
} from "@peculiar/x509";
|
|
18
|
+
|
|
19
|
+
import type { MnemonicToImport } from "./mnemonic";
|
|
20
|
+
import { newMnemonicKeyPackage } from "./mnemonic";
|
|
21
|
+
import { toBigEndian, concatArrays, nowEpochMillis } from "./util";
|
|
22
|
+
|
|
23
|
+
// domain-separation tag used when generating signing hash for import key
|
|
24
|
+
const IMPORT_KEY_SIGNING_DST = new TextEncoder().encode("CUBESIGNER_EPHEMERAL_IMPORT_P384");
|
|
25
|
+
|
|
26
|
+
// attestation document slack times
|
|
27
|
+
const MAX_ATTESTATION_AGE_MINUTES = 15n;
|
|
28
|
+
const MAX_ATTESTATION_FUTURE_MINUTES = 5n;
|
|
29
|
+
const WIK_REFRESH_EARLY_MILLIS = 60_000n;
|
|
30
|
+
|
|
31
|
+
// OIDs for elliptic curve X509 certs
|
|
32
|
+
const EC_PUBLIC_KEY = "1.2.840.10045.2.1";
|
|
33
|
+
const NIST_P384 = "1.3.132.0.34";
|
|
34
|
+
|
|
35
|
+
// Maximum number of keys to import in a single API call
|
|
36
|
+
const MAX_IMPORTS_PER_API_CALL = 32n;
|
|
37
|
+
|
|
38
|
+
// AWS Nitro Enclaves root CA certificate
|
|
39
|
+
// https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip
|
|
40
|
+
//
|
|
41
|
+
// See the documentation about AWS Nitro Enclaves verification:
|
|
42
|
+
// https://docs.aws.amazon.com/enclaves/latest/user/verify-root.html
|
|
43
|
+
const AWS_CA_CERT =
|
|
44
|
+
"MIICETCCAZagAwIBAgIRAPkxdWgbkK/hHUbMtOTn+FYwCgYIKoZIzj0EAwMwSTELMAkGA1UEBhMCVVMxDzANBgNVBAoMBkFtYXpvbjEMMAoGA1UECwwDQVdTMRswGQYDVQQDDBJhd3Mubml0cm8tZW5jbGF2ZXMwHhcNMTkxMDI4MTMyODA1WhcNNDkxMDI4MTQyODA1WjBJMQswCQYDVQQGEwJVUzEPMA0GA1UECgwGQW1hem9uMQwwCgYDVQQLDANBV1MxGzAZBgNVBAMMEmF3cy5uaXRyby1lbmNsYXZlczB2MBAGByqGSM49AgEGBSuBBAAiA2IABPwCVOumCMHzaHDimtqQvkY4MpJzbolL//Zy2YlES1BR5TSksfbb48C8WBoyt7F2Bw7eEtaaP+ohG2bnUs990d0JX28TcPQXCEPZ3BABIeTPYwEoCWZEh8l5YoQwTcU/9KNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUkCW1DdkFR+eWw5b6cp3PmanfS5YwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCjfy+Rocm9Xue4YnwWmNJVA44fA0P5W2OpYow9OYCVRaEevL8uO1XYru5xtMPWrfMCMQCi85sWBbJwKKXdS6BptQFuZbT73o/gBh1qUxl/nNr12UO8Yfwr6wPLb+6NIwLz3/Y=";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The result of deserializing a CreateKeyImportKeyResponse
|
|
48
|
+
*/
|
|
49
|
+
class WrappedImportKey {
|
|
50
|
+
readonly verifiedHash: Uint8Array;
|
|
51
|
+
|
|
52
|
+
readonly publicKey: Uint8Array;
|
|
53
|
+
readonly publicKeyBase64: string;
|
|
54
|
+
|
|
55
|
+
readonly skEnc: Uint8Array;
|
|
56
|
+
readonly skEncBase64: string;
|
|
57
|
+
|
|
58
|
+
readonly dkEnc: Uint8Array;
|
|
59
|
+
readonly dkEncBase64: string;
|
|
60
|
+
|
|
61
|
+
readonly expEpochSeconds: bigint;
|
|
62
|
+
readonly #enclaveAttestation: Uint8Array;
|
|
63
|
+
readonly #enclaveSignature: Uint8Array;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Constructor. This is only called from `WrappedImportKey.createAndVerify()`.
|
|
67
|
+
*
|
|
68
|
+
* @param { CreateKeyImportKeyResponse } resp The response from CubeSigner
|
|
69
|
+
*/
|
|
70
|
+
private constructor(resp: CreateKeyImportKeyResponse) {
|
|
71
|
+
if (!resp.enclave_attestation || !resp.enclave_signature) {
|
|
72
|
+
throw new Error("No attestation found in CreateKeyImportKeyResponse");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// parse the response
|
|
76
|
+
this.publicKey = new Uint8Array(Buffer.from(resp.public_key, "base64"));
|
|
77
|
+
this.publicKeyBase64 = resp.public_key;
|
|
78
|
+
|
|
79
|
+
this.skEnc = new Uint8Array(Buffer.from(resp.sk_enc, "base64"));
|
|
80
|
+
this.skEncBase64 = resp.sk_enc;
|
|
81
|
+
|
|
82
|
+
this.dkEnc = new Uint8Array(Buffer.from(resp.dk_enc, "base64"));
|
|
83
|
+
this.dkEncBase64 = resp.dk_enc;
|
|
84
|
+
|
|
85
|
+
this.#enclaveAttestation = new Uint8Array(Buffer.from(resp.enclave_attestation, "base64"));
|
|
86
|
+
this.#enclaveSignature = new Uint8Array(Buffer.from(resp.enclave_signature, "base64"));
|
|
87
|
+
this.expEpochSeconds = BigInt(resp.expires);
|
|
88
|
+
|
|
89
|
+
// this array is updated in createAndVerify once verification succeeds
|
|
90
|
+
this.verifiedHash = new Uint8Array(32);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create and verify an instance of this type
|
|
95
|
+
*
|
|
96
|
+
* @param { CreateKeyImportKeyResponse } resp The response from CubeSigner
|
|
97
|
+
* @param { SubtleCrypto } subtle An instance of SubtleCrypto used for verification
|
|
98
|
+
* @return { Promise<WrappedImportKey> } A newly constructed instance
|
|
99
|
+
*/
|
|
100
|
+
public static async createAndVerify(
|
|
101
|
+
resp: CreateKeyImportKeyResponse,
|
|
102
|
+
subtle: SubtleCrypto,
|
|
103
|
+
): Promise<WrappedImportKey> {
|
|
104
|
+
const ret = new WrappedImportKey(resp);
|
|
105
|
+
const hash = await ret.#verifyImportKey(subtle);
|
|
106
|
+
ret.verifiedHash.set(hash);
|
|
107
|
+
return ret;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Verify this wrapped import key.
|
|
112
|
+
*
|
|
113
|
+
* @param { SubtleCrypto } subtle An instance of SubtleCrypto used for verification
|
|
114
|
+
* @return { Promise<Uint8Array> } The hash of the successfully verified wrapped import key
|
|
115
|
+
*/
|
|
116
|
+
async #verifyImportKey(subtle: SubtleCrypto): Promise<Uint8Array> {
|
|
117
|
+
// check expiration date
|
|
118
|
+
if (nowEpochMillis() > this.expEpochSeconds * 1000n) {
|
|
119
|
+
throw new Error("Import key is expired");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// make sure that there is an attestation
|
|
123
|
+
if (!this.#enclaveSignature || !this.#enclaveAttestation) {
|
|
124
|
+
throw new Error("No attestation found");
|
|
125
|
+
}
|
|
126
|
+
const signing_key = await verifyAttestationKey(this.#enclaveAttestation);
|
|
127
|
+
|
|
128
|
+
// we use subtlecrypto's impl of RSA-PSS verification
|
|
129
|
+
const rsaPssKeyParams = {
|
|
130
|
+
name: "RSA-PSS",
|
|
131
|
+
hash: "SHA-256",
|
|
132
|
+
};
|
|
133
|
+
const pubkey = await subtle.importKey("spki", signing_key, rsaPssKeyParams, true, ["verify"]);
|
|
134
|
+
const pubkeyAlg = pubkey.algorithm as unknown as { modulusLength: number };
|
|
135
|
+
|
|
136
|
+
// compute the signing hash and verify the signature
|
|
137
|
+
const message = this.#signedData();
|
|
138
|
+
const mlen = Number(BigInt(pubkeyAlg.modulusLength) / 8n);
|
|
139
|
+
const rsaPssParams = {
|
|
140
|
+
name: "RSA-PSS",
|
|
141
|
+
saltLength: mlen - 2 - 32,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (await subtle.verify(rsaPssParams, pubkey, this.#enclaveSignature, message)) {
|
|
145
|
+
return new Uint8Array(await subtle.digest("SHA-256", message));
|
|
146
|
+
}
|
|
147
|
+
throw new Error("Import key signature verification failed");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Returns `true` if this WrappedImportKey needs to be refreshed.
|
|
152
|
+
*
|
|
153
|
+
* @return { boolean } True just if this key needs to be refreshed.
|
|
154
|
+
*/
|
|
155
|
+
public needsRefresh(): boolean {
|
|
156
|
+
// force refresh if we're within WIK_REFRESH_EARLY_MILLIS of the expiration
|
|
157
|
+
return nowEpochMillis() + WIK_REFRESH_EARLY_MILLIS > this.expEpochSeconds * 1000n;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Computes the signing hash for a wrapped import key
|
|
162
|
+
*
|
|
163
|
+
* @return { Uint8Array } The signing hash
|
|
164
|
+
*/
|
|
165
|
+
#signedData(): Uint8Array {
|
|
166
|
+
const parts: Uint8Array[] = [
|
|
167
|
+
// domain separation tag
|
|
168
|
+
toBigEndian(BigInt(IMPORT_KEY_SIGNING_DST.length), 2),
|
|
169
|
+
IMPORT_KEY_SIGNING_DST,
|
|
170
|
+
|
|
171
|
+
// public key
|
|
172
|
+
toBigEndian(BigInt(this.publicKey.length), 2),
|
|
173
|
+
this.publicKey,
|
|
174
|
+
|
|
175
|
+
// sk_enc
|
|
176
|
+
toBigEndian(BigInt(this.skEnc.length), 2),
|
|
177
|
+
this.skEnc,
|
|
178
|
+
|
|
179
|
+
// dk_enc
|
|
180
|
+
toBigEndian(BigInt(this.dkEnc.length), 2),
|
|
181
|
+
this.dkEnc,
|
|
182
|
+
|
|
183
|
+
// 8-byte big-endian expiration time in seconds since UNIX epoch
|
|
184
|
+
toBigEndian(this.expEpochSeconds, 8),
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
return concatArrays(parts);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The return value from KeyImporter.#getWrappedImportAndPubKey()
|
|
193
|
+
*/
|
|
194
|
+
type WrappedImportAndPubKey = {
|
|
195
|
+
wik: WrappedImportKey;
|
|
196
|
+
ipk: CryptoKey;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* An import encryption key and the corresponding attestation document
|
|
201
|
+
*/
|
|
202
|
+
export class KeyImporter {
|
|
203
|
+
#wrappedImportKey: null | WrappedImportKey = null;
|
|
204
|
+
#subtleCrypto: null | SubtleCrypto = null;
|
|
205
|
+
#publicKeyHandle: null | CryptoKey = null;
|
|
206
|
+
readonly #hpkeSuite: CipherSuite;
|
|
207
|
+
readonly #cs: Org;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Construct from a CubeSigner `Org` instance
|
|
211
|
+
*
|
|
212
|
+
* @param { Org } cs A CubeSigner `Org` instance
|
|
213
|
+
*/
|
|
214
|
+
constructor(cs: Org) {
|
|
215
|
+
this.#cs = cs;
|
|
216
|
+
this.#hpkeSuite = new CipherSuite({
|
|
217
|
+
kem: new DhkemP384HkdfSha384(),
|
|
218
|
+
kdf: new HkdfSha384(),
|
|
219
|
+
aead: new Aes256Gcm(),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check that the wrapped import key is unexpired and verified. Otherwise,
|
|
225
|
+
* request a new one, verify it, and update the verified signing hash.
|
|
226
|
+
*
|
|
227
|
+
* @return { Promise<[WrappedImportKey, CryptoKey]> } The verified signing hash.
|
|
228
|
+
*/
|
|
229
|
+
async #getWrappedImportAndPubKey(): Promise<WrappedImportAndPubKey> {
|
|
230
|
+
if (!this.#wrappedImportKey) {
|
|
231
|
+
// first time we load a WrappedImportKey, make sure the x509 crypto
|
|
232
|
+
// provider is set correctly.
|
|
233
|
+
x509CryptoProvider.set(await loadCrypto());
|
|
234
|
+
}
|
|
235
|
+
if (!this.#wrappedImportKey || this.#wrappedImportKey.needsRefresh()) {
|
|
236
|
+
const kikResp = await this.#cs.createKeyImportKey();
|
|
237
|
+
const subtle = await this.#getSubtleCrypto();
|
|
238
|
+
const wik = await WrappedImportKey.createAndVerify(kikResp, subtle);
|
|
239
|
+
|
|
240
|
+
// import the public key from the WrappedImportKey
|
|
241
|
+
const p384Params = {
|
|
242
|
+
name: "ECDH",
|
|
243
|
+
namedCurve: "P-384",
|
|
244
|
+
};
|
|
245
|
+
this.#publicKeyHandle = await subtle.importKey("raw", wik.publicKey, p384Params, true, []);
|
|
246
|
+
this.#wrappedImportKey = wik;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
wik: this.#wrappedImportKey,
|
|
250
|
+
ipk: this.#publicKeyHandle!,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get or create an instance of SubtleCrypto.
|
|
256
|
+
*
|
|
257
|
+
* @return { SubtleCrypto } The instance of SubtleCrypto.
|
|
258
|
+
*/
|
|
259
|
+
async #getSubtleCrypto(): Promise<SubtleCrypto> {
|
|
260
|
+
if (!this.#subtleCrypto) {
|
|
261
|
+
this.#subtleCrypto = await loadSubtleCrypto();
|
|
262
|
+
}
|
|
263
|
+
return this.#subtleCrypto;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Encrypts a set of mnemonics and imports them.
|
|
268
|
+
*
|
|
269
|
+
* @param { KeyType } keyType The type of key to import
|
|
270
|
+
* @param { MnemonicToImport[] } mnes The mnemonics to import, with optional derivation paths and passwords
|
|
271
|
+
* @return { Promise<Key[]> } `Key` objects for each imported key.
|
|
272
|
+
*/
|
|
273
|
+
public async importMnemonics(keyType: KeyType, mnes: MnemonicToImport[]): Promise<Key[]> {
|
|
274
|
+
const nChunks = Number(
|
|
275
|
+
(BigInt(mnes.length) + MAX_IMPORTS_PER_API_CALL - 1n) / MAX_IMPORTS_PER_API_CALL,
|
|
276
|
+
);
|
|
277
|
+
const keys = [];
|
|
278
|
+
|
|
279
|
+
for (let i = 0; i < nChunks; ++i) {
|
|
280
|
+
// first, make sure that the wrapped import key is valid, i.e., that
|
|
281
|
+
// we have retrieved it and that it hasn't expired. We do this here
|
|
282
|
+
// for a couple reasons:
|
|
283
|
+
//
|
|
284
|
+
// - all encryptions in a give request must use the same import key, and
|
|
285
|
+
//
|
|
286
|
+
// - when importing a huge number of keys the import pubkey might expire
|
|
287
|
+
// during the import, so we check for expiration before each request
|
|
288
|
+
const { wik, ipk } = await this.#getWrappedImportAndPubKey();
|
|
289
|
+
|
|
290
|
+
// next, encrypt this chunk of mnemonics
|
|
291
|
+
const start = Number(MAX_IMPORTS_PER_API_CALL) * i;
|
|
292
|
+
const end = Number(MAX_IMPORTS_PER_API_CALL) + start;
|
|
293
|
+
const mneSlice = mnes.slice(start, end);
|
|
294
|
+
const key_material = [];
|
|
295
|
+
for (const mne of mneSlice) {
|
|
296
|
+
const keyPkg = newMnemonicKeyPackage(mne);
|
|
297
|
+
const material = await this.#encrypt(keyPkg, wik.verifiedHash, ipk);
|
|
298
|
+
key_material.push(material);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// construct the request
|
|
302
|
+
const req: ImportKeyRequest = {
|
|
303
|
+
public_key: wik.publicKeyBase64,
|
|
304
|
+
sk_enc: wik.skEncBase64,
|
|
305
|
+
dk_enc: wik.dkEncBase64,
|
|
306
|
+
expires: Number(wik.expEpochSeconds),
|
|
307
|
+
key_type: keyType,
|
|
308
|
+
key_material,
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// send it and append the result to the return value
|
|
312
|
+
const resp = await this.#cs.importKeys(req);
|
|
313
|
+
keys.push(...resp);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return keys;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Encrypt to this wrapped import key. Stores the result in `this.encrypted_keys`
|
|
321
|
+
*
|
|
322
|
+
* @param { Uint8Array } data The data to encrypt
|
|
323
|
+
* @param { Uint8Array } verifiedHash The verified signing hash of the wrapped import key to which to encrypt
|
|
324
|
+
* @param { CryptoKey } pubkey The public key to encrypt to
|
|
325
|
+
* @return { Promise<ImportKeyRequestMaterial> } The encrypted key material
|
|
326
|
+
*/
|
|
327
|
+
async #encrypt(
|
|
328
|
+
data: Uint8Array,
|
|
329
|
+
verifiedHash: Uint8Array,
|
|
330
|
+
pubkey: CryptoKey,
|
|
331
|
+
): Promise<ImportKeyRequestMaterial> {
|
|
332
|
+
// set up the HPKE sender
|
|
333
|
+
const sender = await this.#hpkeSuite.createSenderContext({
|
|
334
|
+
recipientPublicKey: pubkey,
|
|
335
|
+
info: verifiedHash,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// encrypt and construct the return value
|
|
339
|
+
const senderCtext = await sender.seal(data);
|
|
340
|
+
return {
|
|
341
|
+
salt: "",
|
|
342
|
+
client_public_key: Buffer.from(sender.enc).toString("base64"),
|
|
343
|
+
ikm_enc: Buffer.from(senderCtext).toString("base64"),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* An AWS Nitro attestation document
|
|
350
|
+
*
|
|
351
|
+
* https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/4b851f3006c6fa98f23dcffb2cba03b39de9b8af/src/api/mod.rs#L208
|
|
352
|
+
*/
|
|
353
|
+
type AttestationDoc = {
|
|
354
|
+
module_id: string;
|
|
355
|
+
digest: "SHA256" | "SHA384" | "SHA512";
|
|
356
|
+
timestamp: bigint;
|
|
357
|
+
pcrs: Map<number, Uint8Array>;
|
|
358
|
+
certificate: Uint8Array;
|
|
359
|
+
cabundle: Uint8Array[];
|
|
360
|
+
public_key?: Uint8Array;
|
|
361
|
+
user_data?: Uint8Array;
|
|
362
|
+
nonce?: Uint8Array;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Verifies the attestation key against the AWS Nitro Enclaves signing
|
|
367
|
+
* key and returns the attested signing key.
|
|
368
|
+
*
|
|
369
|
+
* @param { Uint8Array } attBytes An attestation from an AWS nitro enclave
|
|
370
|
+
* @return { Promise<Uint8Array> } The signing key that was attested, or null if verification failed
|
|
371
|
+
*/
|
|
372
|
+
async function verifyAttestationKey(attBytes: Uint8Array): Promise<Uint8Array> {
|
|
373
|
+
// cbor-x is being imported as ESM, so we must asynchronously import it here.
|
|
374
|
+
// Because we only use that and auth0/cose here, we import both this way.
|
|
375
|
+
const { Sign1 } = await import("@auth0/cose");
|
|
376
|
+
const { decode: cborDecode } = await import("cbor-x");
|
|
377
|
+
|
|
378
|
+
const att = Sign1.decode(attBytes);
|
|
379
|
+
const attDoc = cborDecode(att.payload) as AttestationDoc;
|
|
380
|
+
|
|
381
|
+
// check expiration date of attestation
|
|
382
|
+
const latest = nowEpochMillis() + MAX_ATTESTATION_FUTURE_MINUTES * 60n * 1000n;
|
|
383
|
+
const earliest =
|
|
384
|
+
latest - (MAX_ATTESTATION_FUTURE_MINUTES + MAX_ATTESTATION_AGE_MINUTES) * 60n * 1000n;
|
|
385
|
+
if (attDoc.timestamp < earliest || attDoc.timestamp > latest) {
|
|
386
|
+
throw new Error("Attestation is expired");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// if there's no public key in this attestation, give up
|
|
390
|
+
if (!attDoc.public_key) {
|
|
391
|
+
throw new Error("Attestation did not include a signing public key");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Verify certificate chain starting with AWS Nitro CA cert
|
|
395
|
+
let parent = new X509Certificate(AWS_CA_CERT);
|
|
396
|
+
for (let i = 0; i < attDoc.cabundle.length; ++i) {
|
|
397
|
+
const cert = new X509Certificate(attDoc.cabundle[i]);
|
|
398
|
+
if (!(await cert.verify(parent))) {
|
|
399
|
+
throw new Error(`Attestation certificate chain failed at index ${i}`);
|
|
400
|
+
}
|
|
401
|
+
parent = cert;
|
|
402
|
+
}
|
|
403
|
+
const cert = new X509Certificate(attDoc.certificate);
|
|
404
|
+
if (!(await cert.verify(parent))) {
|
|
405
|
+
throw new Error("Attestation certificate chain failed at leaf");
|
|
406
|
+
}
|
|
407
|
+
const pubkey = cert.publicKey;
|
|
408
|
+
|
|
409
|
+
// make sure that we got the expected public key type
|
|
410
|
+
const alg = new AlgorithmProvider().toAsnAlgorithm(pubkey.algorithm);
|
|
411
|
+
if (alg.algorithm != EC_PUBLIC_KEY) {
|
|
412
|
+
// not the expected algorithm, i.e., elliptic curve signing
|
|
413
|
+
throw new Error("Attestation contained unexpected signature algorithm");
|
|
414
|
+
}
|
|
415
|
+
const params = AsnParser.parse(alg.parameters!, ECParameters);
|
|
416
|
+
if (!params.namedCurve || params.namedCurve !== NIST_P384) {
|
|
417
|
+
// not the expected params, i.e., NIST P384
|
|
418
|
+
throw new Error("Attestation contained unexpected signature algorithm");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// verify the cose signature with the key, which we verified against
|
|
422
|
+
// the AWS Nitro CA certificate above
|
|
423
|
+
await att.verify(await pubkey.export());
|
|
424
|
+
|
|
425
|
+
return attDoc.public_key;
|
|
426
|
+
}
|
package/src/index.ts
ADDED