@wishknish/knishio-client-ts 0.9.8 → 1.1.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 +13 -0
- package/dist/index.cjs +1500 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -48
- package/dist/index.d.ts +188 -48
- package/dist/index.iife.js +1494 -156
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +1492 -165
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/AuthToken.ts +47 -25
- package/src/KnishIOClient.ts +40 -12
- package/src/core/Molecule.ts +17 -7
- package/src/core/Wallet.ts +161 -32
- package/src/exception/SecretStorageException.ts +9 -0
- package/src/index.ts +17 -3
- package/src/libraries/GraphQLClient.ts +2 -2
- package/src/schemas/index.ts +2 -1
- package/src/storage/FileStorageBackend.ts +176 -0
- package/src/storage/MemorySecretStorageProvider.ts +75 -3
- package/src/storage/NonExtractableKeySecretStorageProvider.ts +547 -0
- package/src/storage/WebAuthnPrfSecretStorageProvider.ts +679 -0
- package/src/storage/WebCryptoSecretStorageProvider.ts +105 -134
- package/src/storage/WebStorageBackend.ts +102 -0
- package/src/storage/index.ts +26 -2
- package/src/storage/secretEnvelope.ts +200 -0
- package/src/types/crypto.ts +9 -4
- package/src/types/storage.ts +22 -2
- package/src/validation/schemas.ts +2 -1
package/dist/index.iife.js
CHANGED
|
@@ -20109,6 +20109,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
20109
20109
|
}
|
|
20110
20110
|
);
|
|
20111
20111
|
}
|
|
20112
|
+
/**
|
|
20113
|
+
* Validation error for storage options or parameters
|
|
20114
|
+
*/
|
|
20115
|
+
static validationError(message) {
|
|
20116
|
+
return new _SecretStorageException(message, {
|
|
20117
|
+
code: "VALIDATION_ERROR"
|
|
20118
|
+
});
|
|
20119
|
+
}
|
|
20112
20120
|
};
|
|
20113
20121
|
}
|
|
20114
20122
|
});
|
|
@@ -42562,8 +42570,14 @@ ${operationTypes.join("\n")}
|
|
|
42562
42570
|
...params
|
|
42563
42571
|
});
|
|
42564
42572
|
var ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
|
42573
|
+
var ml_kem1024 = /* @__PURE__ */ (() => mk(PARAMS[1024]))();
|
|
42565
42574
|
|
|
42566
42575
|
// src/core/Wallet.ts
|
|
42576
|
+
var ML_KEM_PARAMS = {
|
|
42577
|
+
1024: { kem: ml_kem1024, pkBytes: 1568, skBytes: 3168, ctBytes: 1568 },
|
|
42578
|
+
768: { kem: ml_kem768, pkBytes: 1184, skBytes: 2400, ctBytes: 1088 }
|
|
42579
|
+
};
|
|
42580
|
+
var DEFAULT_ML_KEM_PARAMETER_SET = 1024;
|
|
42567
42581
|
var Wallet = class _Wallet {
|
|
42568
42582
|
token;
|
|
42569
42583
|
balance;
|
|
@@ -42578,6 +42592,7 @@ ${operationTypes.join("\n")}
|
|
|
42578
42592
|
tokenUnits;
|
|
42579
42593
|
tradeRates;
|
|
42580
42594
|
molecules;
|
|
42595
|
+
mlKemParameterSet;
|
|
42581
42596
|
// Token metadata (populated from query responses)
|
|
42582
42597
|
tokenName;
|
|
42583
42598
|
tokenAmount;
|
|
@@ -42591,8 +42606,14 @@ ${operationTypes.join("\n")}
|
|
|
42591
42606
|
address = null,
|
|
42592
42607
|
position = null,
|
|
42593
42608
|
batchId = null,
|
|
42594
|
-
characters = null
|
|
42609
|
+
characters = null,
|
|
42610
|
+
mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
|
|
42595
42611
|
} = {}) {
|
|
42612
|
+
const paramSetNum = Number(mlKemParameterSet);
|
|
42613
|
+
if (!ML_KEM_PARAMS[paramSetNum]) {
|
|
42614
|
+
throw new Error(`KnishIO: unsupported ML-KEM parameter set ${mlKemParameterSet}; expected 1024 or 768.`);
|
|
42615
|
+
}
|
|
42616
|
+
this.mlKemParameterSet = paramSetNum;
|
|
42596
42617
|
this.token = token;
|
|
42597
42618
|
this.balance = "0";
|
|
42598
42619
|
this.molecules = {};
|
|
@@ -42628,7 +42649,8 @@ ${operationTypes.join("\n")}
|
|
|
42628
42649
|
bundle = null,
|
|
42629
42650
|
token = "USER",
|
|
42630
42651
|
batchId = null,
|
|
42631
|
-
characters = null
|
|
42652
|
+
characters = null,
|
|
42653
|
+
mlKemParameterSet = DEFAULT_ML_KEM_PARAMETER_SET
|
|
42632
42654
|
}) {
|
|
42633
42655
|
let position = null;
|
|
42634
42656
|
if (!secret && !bundle) {
|
|
@@ -42644,7 +42666,8 @@ ${operationTypes.join("\n")}
|
|
|
42644
42666
|
token,
|
|
42645
42667
|
position,
|
|
42646
42668
|
batchId,
|
|
42647
|
-
characters
|
|
42669
|
+
characters,
|
|
42670
|
+
mlKemParameterSet
|
|
42648
42671
|
});
|
|
42649
42672
|
}
|
|
42650
42673
|
/**
|
|
@@ -42803,20 +42826,80 @@ ${operationTypes.join("\n")}
|
|
|
42803
42826
|
return (typeof this.position === "undefined" || this.position === null) && (typeof this.address === "undefined" || this.address === null);
|
|
42804
42827
|
}
|
|
42805
42828
|
// =============================================================================
|
|
42806
|
-
// POST-QUANTUM CRYPTOGRAPHY - ML-
|
|
42829
|
+
// POST-QUANTUM CRYPTOGRAPHY - ML-KEM INTEGRATION
|
|
42807
42830
|
// =============================================================================
|
|
42808
42831
|
/**
|
|
42809
|
-
*
|
|
42832
|
+
* Derive an ML-KEM keypair for an arbitrary parameter set from the wallet's key seed,
|
|
42833
|
+
* without mutating the wallet. The 64-byte `d‖z` seed takes no parameter-set input — only
|
|
42834
|
+
* the final `keygen` call differs — so one KnishIO wallet owns both an ML-KEM-768 and an
|
|
42835
|
+
* ML-KEM-1024 identity and either can be reconstructed on demand.
|
|
42836
|
+
*
|
|
42837
|
+
* Returns `null` when the wallet holds no key — a secret-less wallet, which is what a molecule
|
|
42838
|
+
* deserializer builds for validation context. `generateSecret(null, …)` does NOT throw, so
|
|
42839
|
+
* without this the wallet would derive a plausible-looking identity from a bogus seed and fail
|
|
42840
|
+
* three layers down at AES-GCM instead of at the missing key. The guard lives here rather than
|
|
42841
|
+
* at each call site so a new caller cannot miss it.
|
|
42842
|
+
*
|
|
42843
|
+
* @param parameterSet - 1024 or 768
|
|
42810
42844
|
*/
|
|
42811
|
-
|
|
42845
|
+
deriveMlKemKeypair(parameterSet) {
|
|
42846
|
+
const params = ML_KEM_PARAMS[parameterSet];
|
|
42847
|
+
if (!params) {
|
|
42848
|
+
throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
|
|
42849
|
+
}
|
|
42850
|
+
if (!this.key) {
|
|
42851
|
+
return null;
|
|
42852
|
+
}
|
|
42812
42853
|
const seedHex = generateSecret(this.key, 128);
|
|
42813
42854
|
const seed = new Uint8Array(64);
|
|
42814
42855
|
for (let i4 = 0; i4 < 64; i4++) {
|
|
42815
42856
|
seed[i4] = parseInt(seedHex.substr(i4 * 2, 2), 16);
|
|
42816
42857
|
}
|
|
42817
|
-
const { publicKey, secretKey } =
|
|
42818
|
-
|
|
42819
|
-
|
|
42858
|
+
const { publicKey, secretKey } = params.kem.keygen(seed);
|
|
42859
|
+
return {
|
|
42860
|
+
pubkey: this.serializeKey(publicKey),
|
|
42861
|
+
privkey: secretKey,
|
|
42862
|
+
params
|
|
42863
|
+
};
|
|
42864
|
+
}
|
|
42865
|
+
/**
|
|
42866
|
+
* ML-KEM parameter set implied by a serialized public key's raw byte length. FIPS 203's key
|
|
42867
|
+
* lengths are disjoint (1568 bytes → ML-KEM-1024, 1184 bytes → ML-KEM-768), so a stored peer
|
|
42868
|
+
* key recovers the parameter set of the session it belongs to without a wire-format change.
|
|
42869
|
+
* Used by AuthToken.restore to resolve a snapshot that predates the field.
|
|
42870
|
+
*
|
|
42871
|
+
* @param pubkey - Base64-serialized ML-KEM public key
|
|
42872
|
+
* @return 1024, 768, or null when the length matches neither
|
|
42873
|
+
*/
|
|
42874
|
+
static mlKemParameterSetFromPubkey(pubkey) {
|
|
42875
|
+
if (!pubkey) {
|
|
42876
|
+
return null;
|
|
42877
|
+
}
|
|
42878
|
+
let byteLength;
|
|
42879
|
+
try {
|
|
42880
|
+
byteLength = typeof Buffer !== "undefined" ? Buffer.from(pubkey, "base64").length : atob(pubkey).length;
|
|
42881
|
+
} catch {
|
|
42882
|
+
return null;
|
|
42883
|
+
}
|
|
42884
|
+
if (byteLength === ML_KEM_PARAMS[1024].pkBytes) {
|
|
42885
|
+
return 1024;
|
|
42886
|
+
}
|
|
42887
|
+
if (byteLength === ML_KEM_PARAMS[768].pkBytes) {
|
|
42888
|
+
return 768;
|
|
42889
|
+
}
|
|
42890
|
+
return null;
|
|
42891
|
+
}
|
|
42892
|
+
/**
|
|
42893
|
+
* Initializes the ML-KEM key pair (matches JavaScript SDK exactly). Only ever reached from the
|
|
42894
|
+
* constructor's `secret` branch, so the derivation cannot come back empty here.
|
|
42895
|
+
*/
|
|
42896
|
+
initializeMLKEM() {
|
|
42897
|
+
const derived = this.deriveMlKemKeypair(this.mlKemParameterSet);
|
|
42898
|
+
if (!derived) {
|
|
42899
|
+
return;
|
|
42900
|
+
}
|
|
42901
|
+
this.pubkey = derived.pubkey;
|
|
42902
|
+
this.privkey = derived.privkey;
|
|
42820
42903
|
}
|
|
42821
42904
|
// =============================================================================
|
|
42822
42905
|
// HIGH-LEVEL MESSAGE ENCRYPTION (JavaScript SDK Compatibility)
|
|
@@ -42825,13 +42908,13 @@ ${operationTypes.join("\n")}
|
|
|
42825
42908
|
const messageString = JSON.stringify(message);
|
|
42826
42909
|
const messageUint8 = new TextEncoder().encode(messageString);
|
|
42827
42910
|
const deserializedPubkey = this.deserializeKey(recipientPubkey);
|
|
42828
|
-
const
|
|
42829
|
-
if (deserializedPubkey.length !==
|
|
42911
|
+
const params = ML_KEM_PARAMS[this.mlKemParameterSet];
|
|
42912
|
+
if (deserializedPubkey.length !== params.pkBytes) {
|
|
42830
42913
|
throw new Error(
|
|
42831
|
-
`KnishIO: cannot ML-KEM-encrypt \u2014 recipient public key is ${deserializedPubkey.length} bytes, expected ${
|
|
42914
|
+
`KnishIO: cannot ML-KEM-encrypt \u2014 recipient public key is ${deserializedPubkey.length} bytes, expected ${params.pkBytes} (ML-KEM-${this.mlKemParameterSet}). The peer is not running ML-KEM-${this.mlKemParameterSet}; upgrade the peer, or step this client back to the other parameter set.`
|
|
42832
42915
|
);
|
|
42833
42916
|
}
|
|
42834
|
-
const { cipherText, sharedSecret } =
|
|
42917
|
+
const { cipherText, sharedSecret } = params.kem.encapsulate(deserializedPubkey);
|
|
42835
42918
|
const encryptedMessage = await this.encryptWithSharedSecret(messageUint8, sharedSecret);
|
|
42836
42919
|
return {
|
|
42837
42920
|
cipherText: this.serializeKey(cipherText),
|
|
@@ -42843,15 +42926,37 @@ ${operationTypes.join("\n")}
|
|
|
42843
42926
|
return decryptedString === null ? null : JSON.parse(decryptedString);
|
|
42844
42927
|
}
|
|
42845
42928
|
/**
|
|
42846
|
-
* ML-
|
|
42929
|
+
* ML-KEM decapsulate + AES-256-GCM decrypt → the RAW decrypted UTF-8 string (no JSON.parse).
|
|
42847
42930
|
* Shared by {@link decryptMessage} (which JSON.parses the result) and the PQ CipherHash transport
|
|
42848
|
-
* ({@link
|
|
42931
|
+
* ({@link decryptMyMessageML}, which needs the raw response JSON text). PQ-transport Phase E.
|
|
42849
42932
|
*/
|
|
42850
42933
|
async _mlkemDecryptToString(encryptedData) {
|
|
42851
42934
|
const { cipherText, encryptedMessage } = encryptedData;
|
|
42935
|
+
const configuredParams = ML_KEM_PARAMS[this.mlKemParameterSet];
|
|
42936
|
+
const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
|
|
42937
|
+
const deserializedCipherText = this.deserializeKey(cipherText);
|
|
42938
|
+
let params = configuredParams;
|
|
42939
|
+
let decapsPrivkey = this.privkey;
|
|
42940
|
+
if (deserializedCipherText.length !== configuredParams.ctBytes) {
|
|
42941
|
+
if (deserializedCipherText.length !== ML_KEM_PARAMS[otherSet].ctBytes) {
|
|
42942
|
+
console.error(
|
|
42943
|
+
`Wallet::decryptMessage() - Ciphertext length mismatch: got ${deserializedCipherText.length}, expected ${configuredParams.ctBytes}`
|
|
42944
|
+
);
|
|
42945
|
+
return null;
|
|
42946
|
+
}
|
|
42947
|
+
const derived = this.deriveMlKemKeypair(otherSet);
|
|
42948
|
+
if (!derived) {
|
|
42949
|
+
console.error(
|
|
42950
|
+
`Wallet::decryptMessage() - cannot derive the ML-KEM-${otherSet} identity: wallet has no key`
|
|
42951
|
+
);
|
|
42952
|
+
return null;
|
|
42953
|
+
}
|
|
42954
|
+
params = derived.params;
|
|
42955
|
+
decapsPrivkey = derived.privkey;
|
|
42956
|
+
}
|
|
42852
42957
|
let sharedSecret;
|
|
42853
42958
|
try {
|
|
42854
|
-
sharedSecret =
|
|
42959
|
+
sharedSecret = params.kem.decapsulate(deserializedCipherText, decapsPrivkey);
|
|
42855
42960
|
} catch (e4) {
|
|
42856
42961
|
console.error("Wallet::decryptMessage() - Decapsulation failed", e4);
|
|
42857
42962
|
console.info("Wallet::decryptMessage() - my public key", this.pubkey);
|
|
@@ -42901,11 +43006,11 @@ ${operationTypes.join("\n")}
|
|
|
42901
43006
|
return this.serializeKey(bytes);
|
|
42902
43007
|
}
|
|
42903
43008
|
/**
|
|
42904
|
-
* Post-quantum (ML-
|
|
43009
|
+
* Post-quantum (ML-KEM) CipherHash request envelope: a stringified single-recipient map
|
|
42905
43010
|
* `{ "<hashShare(recipientPubkey)>": {cipherText, encryptedMessage} }` (object-valued, via
|
|
42906
43011
|
* {@link encryptMessage}). Matches the Rust validator's CipherHash handler. PQ-transport Phase E.
|
|
42907
43012
|
*/
|
|
42908
|
-
async
|
|
43013
|
+
async encryptStringML(message, recipientPubkey) {
|
|
42909
43014
|
const envelope = await this.encryptMessage(message, recipientPubkey);
|
|
42910
43015
|
return JSON.stringify({ [this.hashShare(recipientPubkey)]: envelope });
|
|
42911
43016
|
}
|
|
@@ -42913,9 +43018,21 @@ ${operationTypes.join("\n")}
|
|
|
42913
43018
|
* Decrypt a CipherHash response map addressed to THIS wallet's ML-KEM pubkey
|
|
42914
43019
|
* (`hashShare(this.pubkey)`) → the RAW decrypted GraphQL response JSON text (NOT JSON.parsed;
|
|
42915
43020
|
* it replaces the HTTP response body for the normal parser). `null` if no entry / decrypt fails.
|
|
43021
|
+
*
|
|
43022
|
+
* A pre-bump peer addressed its envelope to `hashShare(our_768_pubkey)`, which a wallet
|
|
43023
|
+
* configured at ML-KEM-1024 would never find — so the other identity's share is tried too.
|
|
43024
|
+
* Without this, the permissive length dispatch in {@link _mlkemDecryptToString} is
|
|
43025
|
+
* unreachable on the transport path.
|
|
42916
43026
|
*/
|
|
42917
|
-
async
|
|
42918
|
-
|
|
43027
|
+
async decryptMyMessageML(map3) {
|
|
43028
|
+
let envelope = map3[this.hashShare(this.pubkey)];
|
|
43029
|
+
if (!envelope) {
|
|
43030
|
+
const otherSet = this.mlKemParameterSet === 1024 ? 768 : 1024;
|
|
43031
|
+
const other = this.deriveMlKemKeypair(otherSet);
|
|
43032
|
+
if (other) {
|
|
43033
|
+
envelope = map3[this.hashShare(other.pubkey)];
|
|
43034
|
+
}
|
|
43035
|
+
}
|
|
42919
43036
|
if (!envelope) {
|
|
42920
43037
|
return null;
|
|
42921
43038
|
}
|
|
@@ -43072,7 +43189,8 @@ ${operationTypes.join("\n")}
|
|
|
43072
43189
|
serverSdkVersion: external_exports.number().int().min(1).optional(),
|
|
43073
43190
|
logging: external_exports.boolean().optional(),
|
|
43074
43191
|
defaultRequestPolicy: external_exports.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
|
|
43075
|
-
secretStorage: external_exports.unknown().optional()
|
|
43192
|
+
secretStorage: external_exports.unknown().optional(),
|
|
43193
|
+
mlKemParameterSet: external_exports.union([external_exports.literal(1024), external_exports.literal(768)]).optional()
|
|
43076
43194
|
}).strict();
|
|
43077
43195
|
external_exports.object({
|
|
43078
43196
|
token: external_exports.string().min(1, "Auth token cannot be empty"),
|
|
@@ -44162,6 +44280,7 @@ ${operationTypes.join("\n")}
|
|
|
44162
44280
|
continuIdPosition;
|
|
44163
44281
|
parentHashes;
|
|
44164
44282
|
local;
|
|
44283
|
+
mlKemParameterSet = 1024;
|
|
44165
44284
|
/**
|
|
44166
44285
|
* Create new Molecule instance
|
|
44167
44286
|
* Matches JavaScript SDK constructor signature
|
|
@@ -44173,7 +44292,8 @@ ${operationTypes.join("\n")}
|
|
|
44173
44292
|
remainderWallet = null,
|
|
44174
44293
|
cellSlug = null,
|
|
44175
44294
|
version: version3 = null,
|
|
44176
|
-
continuIdPosition = null
|
|
44295
|
+
continuIdPosition = null,
|
|
44296
|
+
mlKemParameterSet = null
|
|
44177
44297
|
} = {}) {
|
|
44178
44298
|
this.status = null;
|
|
44179
44299
|
this.molecularHash = null;
|
|
@@ -44185,6 +44305,7 @@ ${operationTypes.join("\n")}
|
|
|
44185
44305
|
this.continuIdPosition = continuIdPosition;
|
|
44186
44306
|
this.atoms = [];
|
|
44187
44307
|
this.parentHashes = [];
|
|
44308
|
+
this.mlKemParameterSet = mlKemParameterSet || sourceWallet?.mlKemParameterSet || 1024;
|
|
44188
44309
|
const versionRegistry = versions_default;
|
|
44189
44310
|
if (version3 !== null && Object.prototype.hasOwnProperty.call(versionRegistry, version3)) {
|
|
44190
44311
|
this.version = String(version3);
|
|
@@ -44195,7 +44316,8 @@ ${operationTypes.join("\n")}
|
|
|
44195
44316
|
bundle,
|
|
44196
44317
|
token: sourceWallet.token,
|
|
44197
44318
|
batchId: sourceWallet.batchId,
|
|
44198
|
-
characters: sourceWallet.characters
|
|
44319
|
+
characters: sourceWallet.characters,
|
|
44320
|
+
mlKemParameterSet: this.mlKemParameterSet
|
|
44199
44321
|
});
|
|
44200
44322
|
} else {
|
|
44201
44323
|
this.remainderWallet = null;
|
|
@@ -44256,7 +44378,8 @@ ${operationTypes.join("\n")}
|
|
|
44256
44378
|
if (!this.remainderWallet || this.remainderWallet.token !== "USER") {
|
|
44257
44379
|
this.remainderWallet = Wallet.create({
|
|
44258
44380
|
secret: this.secret,
|
|
44259
|
-
bundle: this.bundle
|
|
44381
|
+
bundle: this.bundle,
|
|
44382
|
+
mlKemParameterSet: this.mlKemParameterSet
|
|
44260
44383
|
});
|
|
44261
44384
|
}
|
|
44262
44385
|
const continuIdMeta = {};
|
|
@@ -44643,7 +44766,8 @@ ${operationTypes.join("\n")}
|
|
|
44643
44766
|
}
|
|
44644
44767
|
const burnWallet = new Wallet({
|
|
44645
44768
|
bundle: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
44646
|
-
token: this.sourceWallet.token
|
|
44769
|
+
token: this.sourceWallet.token,
|
|
44770
|
+
mlKemParameterSet: this.mlKemParameterSet
|
|
44647
44771
|
});
|
|
44648
44772
|
this.addAtom(Atom.create({
|
|
44649
44773
|
isotope: "V",
|
|
@@ -44915,7 +45039,8 @@ ${operationTypes.join("\n")}
|
|
|
44915
45039
|
position: data.sourceWallet.position,
|
|
44916
45040
|
bundle: data.sourceWallet.bundle,
|
|
44917
45041
|
batchId: data.sourceWallet.batchId,
|
|
44918
|
-
characters: data.sourceWallet.characters
|
|
45042
|
+
characters: data.sourceWallet.characters,
|
|
45043
|
+
mlKemParameterSet: molecule.mlKemParameterSet
|
|
44919
45044
|
});
|
|
44920
45045
|
molecule.sourceWallet.balance = String(data.sourceWallet.balance != null ? data.sourceWallet.balance : 0);
|
|
44921
45046
|
molecule.sourceWallet.address = data.sourceWallet.address;
|
|
@@ -44933,7 +45058,8 @@ ${operationTypes.join("\n")}
|
|
|
44933
45058
|
position: data.remainderWallet.position,
|
|
44934
45059
|
bundle: data.remainderWallet.bundle,
|
|
44935
45060
|
batchId: data.remainderWallet.batchId,
|
|
44936
|
-
characters: data.remainderWallet.characters
|
|
45061
|
+
characters: data.remainderWallet.characters,
|
|
45062
|
+
mlKemParameterSet: molecule.mlKemParameterSet
|
|
44937
45063
|
});
|
|
44938
45064
|
molecule.remainderWallet.balance = String(data.remainderWallet.balance != null ? data.remainderWallet.balance : 0);
|
|
44939
45065
|
molecule.remainderWallet.address = data.remainderWallet.address;
|
|
@@ -45031,7 +45157,8 @@ ${operationTypes.join("\n")}
|
|
|
45031
45157
|
secret: this.secret,
|
|
45032
45158
|
bundle: this.bundle,
|
|
45033
45159
|
token: this.sourceWallet.token,
|
|
45034
|
-
batchId: this.sourceWallet.batchId
|
|
45160
|
+
batchId: this.sourceWallet.batchId,
|
|
45161
|
+
mlKemParameterSet: this.mlKemParameterSet
|
|
45035
45162
|
});
|
|
45036
45163
|
if (tradeRates) {
|
|
45037
45164
|
bufferWallet.tradeRates = tradeRates;
|
|
@@ -47728,7 +47855,7 @@ ${operationTypes.join("\n")}
|
|
|
47728
47855
|
let encryptedRequest = false;
|
|
47729
47856
|
let requestInit = init;
|
|
47730
47857
|
if (wallet && serverPubkey && init && typeof init.body === "string" && this.shouldEncrypt(init.body)) {
|
|
47731
|
-
const hashVar = await wallet.
|
|
47858
|
+
const hashVar = await wallet.encryptStringML(init.body, serverPubkey);
|
|
47732
47859
|
requestInit = { ...init, body: JSON.stringify({ query: CIPHER_HASH_QUERY, variables: { Hash: hashVar } }) };
|
|
47733
47860
|
encryptedRequest = true;
|
|
47734
47861
|
}
|
|
@@ -47748,7 +47875,7 @@ ${operationTypes.join("\n")}
|
|
|
47748
47875
|
if (typeof hash2 !== "string") {
|
|
47749
47876
|
return new Response(text, init2);
|
|
47750
47877
|
}
|
|
47751
|
-
const decrypted = await wallet.
|
|
47878
|
+
const decrypted = await wallet.decryptMyMessageML(JSON.parse(hash2));
|
|
47752
47879
|
return new Response(decrypted != null ? decrypted : text, init2);
|
|
47753
47880
|
}
|
|
47754
47881
|
setAuthData({
|
|
@@ -47917,6 +48044,22 @@ ${operationTypes.join("\n")}
|
|
|
47917
48044
|
authToken.setWallet(wallet);
|
|
47918
48045
|
return authToken;
|
|
47919
48046
|
}
|
|
48047
|
+
/**
|
|
48048
|
+
* ML-KEM parameter set a restored session must use, resolved in three tiers:
|
|
48049
|
+
* an explicit snapshot field, then the stored validator key's length, then ML-KEM-768.
|
|
48050
|
+
*
|
|
48051
|
+
* The final tier is deliberately NOT the constructor default. A snapshot with neither an
|
|
48052
|
+
* explicit field nor a recognisable key can only have come from a pre-bump build, and every
|
|
48053
|
+
* pre-bump build was 768-only — defaulting to 1024 would make the restored wallet advertise
|
|
48054
|
+
* a public key the validator never recorded for that token.
|
|
48055
|
+
*/
|
|
48056
|
+
static resolveMlKemParameterSet(snapshot) {
|
|
48057
|
+
const explicit = snapshot.wallet?.mlKemParameterSet;
|
|
48058
|
+
if (explicit) {
|
|
48059
|
+
return Number(explicit) === 768 ? 768 : 1024;
|
|
48060
|
+
}
|
|
48061
|
+
return Wallet.mlKemParameterSetFromPubkey(snapshot.pubkey) ?? 768;
|
|
48062
|
+
}
|
|
47920
48063
|
/**
|
|
47921
48064
|
* Restore AuthToken from snapshot
|
|
47922
48065
|
*/
|
|
@@ -47924,8 +48067,9 @@ ${operationTypes.join("\n")}
|
|
|
47924
48067
|
const wallet = new Wallet({
|
|
47925
48068
|
secret,
|
|
47926
48069
|
token: "AUTH",
|
|
47927
|
-
position: snapshot.wallet
|
|
47928
|
-
characters: snapshot.wallet
|
|
48070
|
+
position: snapshot.wallet?.position ?? null,
|
|
48071
|
+
characters: snapshot.wallet?.characters ?? null,
|
|
48072
|
+
mlKemParameterSet: _AuthToken.resolveMlKemParameterSet(snapshot)
|
|
47929
48073
|
});
|
|
47930
48074
|
return _AuthToken.create({
|
|
47931
48075
|
token: snapshot.token,
|
|
@@ -47993,7 +48137,9 @@ ${operationTypes.join("\n")}
|
|
|
47993
48137
|
};
|
|
47994
48138
|
}
|
|
47995
48139
|
/**
|
|
47996
|
-
* Create snapshot for persistence
|
|
48140
|
+
* Create snapshot for persistence. The wallet's ML-KEM parameter set is recorded beside its
|
|
48141
|
+
* position and characters so a stepped-back ML-KEM-768 session restores as 768 rather than
|
|
48142
|
+
* silently taking the constructor default.
|
|
47997
48143
|
*/
|
|
47998
48144
|
toSnapshot() {
|
|
47999
48145
|
return {
|
|
@@ -48004,7 +48150,8 @@ ${operationTypes.join("\n")}
|
|
|
48004
48150
|
...this.$__wallet ? {
|
|
48005
48151
|
wallet: {
|
|
48006
48152
|
position: this.$__wallet.position,
|
|
48007
|
-
characters: this.$__wallet.characters
|
|
48153
|
+
characters: this.$__wallet.characters,
|
|
48154
|
+
mlKemParameterSet: this.$__wallet.mlKemParameterSet
|
|
48008
48155
|
}
|
|
48009
48156
|
} : {}
|
|
48010
48157
|
};
|
|
@@ -48304,7 +48451,8 @@ ${operationTypes.join("\n")}
|
|
|
48304
48451
|
// isn't rejected.
|
|
48305
48452
|
defaultRequestPolicy: external_exports.enum(["cache-first", "cache-only", "network-only", "cache-and-network"]).nullable().optional(),
|
|
48306
48453
|
// Pluggable hardware envelope encryption secret storage provider
|
|
48307
|
-
secretStorage: external_exports.unknown().optional()
|
|
48454
|
+
secretStorage: external_exports.unknown().optional(),
|
|
48455
|
+
mlKemParameterSet: external_exports.union([external_exports.literal(1024), external_exports.literal(768)]).optional()
|
|
48308
48456
|
}).strict();
|
|
48309
48457
|
var EnvironmentConfigSchema = external_exports.object({
|
|
48310
48458
|
NODE_ENV: external_exports.enum(["development", "production", "test"]).optional(),
|
|
@@ -51932,10 +52080,121 @@ ${operationTypes.join("\n")}
|
|
|
51932
52080
|
return result === 0;
|
|
51933
52081
|
}
|
|
51934
52082
|
|
|
52083
|
+
// src/storage/secretEnvelope.ts
|
|
52084
|
+
init_SecretStorageException();
|
|
52085
|
+
var ENVELOPE_ALGORITHM = "AES-GCM";
|
|
52086
|
+
var DEFAULT_ITERATIONS = 1e5;
|
|
52087
|
+
var SECRET_KEY_PREFIX = "knishio:secret:";
|
|
52088
|
+
var RECOVERY_KEY_PREFIX = "knishio:recovery:";
|
|
52089
|
+
var GCM_IV_LENGTH = 12;
|
|
52090
|
+
var SALT_LENGTH = 16;
|
|
52091
|
+
var textEncoder2 = new TextEncoder();
|
|
52092
|
+
function uint8ArrayToBase642(bytes) {
|
|
52093
|
+
let binary = "";
|
|
52094
|
+
const len = bytes.byteLength;
|
|
52095
|
+
for (let i4 = 0; i4 < len; i4++) {
|
|
52096
|
+
const byte = bytes[i4];
|
|
52097
|
+
if (byte !== void 0) {
|
|
52098
|
+
binary += String.fromCharCode(byte);
|
|
52099
|
+
}
|
|
52100
|
+
}
|
|
52101
|
+
return btoa(binary);
|
|
52102
|
+
}
|
|
52103
|
+
function base64ToUint8Array2(base643) {
|
|
52104
|
+
const binary = atob(base643);
|
|
52105
|
+
const len = binary.length;
|
|
52106
|
+
const bytes = new Uint8Array(len);
|
|
52107
|
+
for (let i4 = 0; i4 < len; i4++) {
|
|
52108
|
+
bytes[i4] = binary.charCodeAt(i4);
|
|
52109
|
+
}
|
|
52110
|
+
return bytes;
|
|
52111
|
+
}
|
|
52112
|
+
async function deriveEnvelopeKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
|
|
52113
|
+
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
52114
|
+
throw new exports.SecretStorageException("WebCrypto API is not available");
|
|
52115
|
+
}
|
|
52116
|
+
const passphraseBytes = textEncoder2.encode(passphrase);
|
|
52117
|
+
try {
|
|
52118
|
+
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
52119
|
+
"raw",
|
|
52120
|
+
passphraseBytes,
|
|
52121
|
+
"PBKDF2",
|
|
52122
|
+
false,
|
|
52123
|
+
["deriveKey"]
|
|
52124
|
+
);
|
|
52125
|
+
return await globalThis.crypto.subtle.deriveKey(
|
|
52126
|
+
{
|
|
52127
|
+
name: "PBKDF2",
|
|
52128
|
+
salt,
|
|
52129
|
+
iterations,
|
|
52130
|
+
hash: "SHA-256"
|
|
52131
|
+
},
|
|
52132
|
+
baseKey,
|
|
52133
|
+
{ name: "AES-GCM", length: 256 },
|
|
52134
|
+
false,
|
|
52135
|
+
["encrypt", "decrypt"]
|
|
52136
|
+
);
|
|
52137
|
+
} finally {
|
|
52138
|
+
zeroizeBytes(passphraseBytes);
|
|
52139
|
+
}
|
|
52140
|
+
}
|
|
52141
|
+
async function sealEnvelope(secret, passphrase, metadata) {
|
|
52142
|
+
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
52143
|
+
throw new exports.SecretStorageException("WebCrypto API is not available");
|
|
52144
|
+
}
|
|
52145
|
+
const salt = new Uint8Array(SALT_LENGTH);
|
|
52146
|
+
const iv = new Uint8Array(GCM_IV_LENGTH);
|
|
52147
|
+
globalThis.crypto.getRandomValues(salt);
|
|
52148
|
+
globalThis.crypto.getRandomValues(iv);
|
|
52149
|
+
const key = await deriveEnvelopeKey(passphrase, salt, DEFAULT_ITERATIONS);
|
|
52150
|
+
const secretBytes = textEncoder2.encode(secret);
|
|
52151
|
+
try {
|
|
52152
|
+
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
52153
|
+
{
|
|
52154
|
+
name: ENVELOPE_ALGORITHM,
|
|
52155
|
+
iv
|
|
52156
|
+
},
|
|
52157
|
+
key,
|
|
52158
|
+
secretBytes
|
|
52159
|
+
);
|
|
52160
|
+
const ciphertext = uint8ArrayToBase642(new Uint8Array(encryptedBuffer));
|
|
52161
|
+
return {
|
|
52162
|
+
version: 1,
|
|
52163
|
+
ciphertext,
|
|
52164
|
+
iv: uint8ArrayToBase642(iv),
|
|
52165
|
+
salt: uint8ArrayToBase642(salt),
|
|
52166
|
+
algorithm: ENVELOPE_ALGORITHM,
|
|
52167
|
+
iterations: DEFAULT_ITERATIONS,
|
|
52168
|
+
metadata
|
|
52169
|
+
};
|
|
52170
|
+
} finally {
|
|
52171
|
+
zeroizeBytes(secretBytes);
|
|
52172
|
+
}
|
|
52173
|
+
}
|
|
52174
|
+
async function openEnvelope(payload, passphrase) {
|
|
52175
|
+
if (typeof globalThis.crypto === "undefined" || typeof globalThis.crypto.subtle === "undefined") {
|
|
52176
|
+
throw new exports.SecretStorageException("WebCrypto API is not available");
|
|
52177
|
+
}
|
|
52178
|
+
const salt = base64ToUint8Array2(payload.salt);
|
|
52179
|
+
const iv = base64ToUint8Array2(payload.iv);
|
|
52180
|
+
const ciphertext = base64ToUint8Array2(payload.ciphertext);
|
|
52181
|
+
const key = await deriveEnvelopeKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
|
|
52182
|
+
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
52183
|
+
{
|
|
52184
|
+
name: ENVELOPE_ALGORITHM,
|
|
52185
|
+
iv
|
|
52186
|
+
},
|
|
52187
|
+
key,
|
|
52188
|
+
ciphertext
|
|
52189
|
+
);
|
|
52190
|
+
return new Uint8Array(decryptedBuffer);
|
|
52191
|
+
}
|
|
52192
|
+
|
|
51935
52193
|
// src/storage/MemorySecretStorageProvider.ts
|
|
51936
52194
|
var MemorySecretStorageProvider = class {
|
|
51937
52195
|
providerType = "memory";
|
|
51938
52196
|
secrets = /* @__PURE__ */ new Map();
|
|
52197
|
+
recoverySecrets = /* @__PURE__ */ new Map();
|
|
51939
52198
|
/**
|
|
51940
52199
|
* Memory storage is not hardware backed
|
|
51941
52200
|
*/
|
|
@@ -51966,6 +52225,17 @@ ${operationTypes.join("\n")}
|
|
|
51966
52225
|
providerType: this.providerType
|
|
51967
52226
|
};
|
|
51968
52227
|
this.secrets.set(bundleHash, { secret, metadata });
|
|
52228
|
+
if (options?.recoveryPassphrase) {
|
|
52229
|
+
const recoveryMetadata = {
|
|
52230
|
+
bundleHash,
|
|
52231
|
+
label: options?.label,
|
|
52232
|
+
createdAt: Date.now(),
|
|
52233
|
+
hardwareBacked: false,
|
|
52234
|
+
providerType: "webcrypto-aes-gcm"
|
|
52235
|
+
};
|
|
52236
|
+
const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
|
|
52237
|
+
this.recoverySecrets.set(bundleHash, JSON.stringify(recoveryPayload));
|
|
52238
|
+
}
|
|
51969
52239
|
}
|
|
51970
52240
|
/**
|
|
51971
52241
|
* Retrieve a secret from memory
|
|
@@ -51978,6 +52248,7 @@ ${operationTypes.join("\n")}
|
|
|
51978
52248
|
* Delete a stored secret
|
|
51979
52249
|
*/
|
|
51980
52250
|
async deleteSecret(bundleHash) {
|
|
52251
|
+
this.recoverySecrets.delete(bundleHash);
|
|
51981
52252
|
return this.secrets.delete(bundleHash);
|
|
51982
52253
|
}
|
|
51983
52254
|
/**
|
|
@@ -52007,6 +52278,48 @@ ${operationTypes.join("\n")}
|
|
|
52007
52278
|
*/
|
|
52008
52279
|
clear() {
|
|
52009
52280
|
this.secrets.clear();
|
|
52281
|
+
this.recoverySecrets.clear();
|
|
52282
|
+
}
|
|
52283
|
+
/**
|
|
52284
|
+
* Recover a secret using its recovery envelope and restore it
|
|
52285
|
+
*/
|
|
52286
|
+
async recoverSecret(bundleHash, recoveryPassphrase, options) {
|
|
52287
|
+
if (!bundleHash) {
|
|
52288
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
52289
|
+
}
|
|
52290
|
+
if (!recoveryPassphrase) {
|
|
52291
|
+
throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
|
|
52292
|
+
}
|
|
52293
|
+
const raw = this.recoverySecrets.get(bundleHash);
|
|
52294
|
+
if (!raw) {
|
|
52295
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
52296
|
+
}
|
|
52297
|
+
let payload;
|
|
52298
|
+
try {
|
|
52299
|
+
payload = JSON.parse(raw);
|
|
52300
|
+
} catch {
|
|
52301
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
|
|
52302
|
+
}
|
|
52303
|
+
let decryptedBytes;
|
|
52304
|
+
try {
|
|
52305
|
+
decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
|
|
52306
|
+
} catch (err) {
|
|
52307
|
+
if (err instanceof exports.SecretStorageException) {
|
|
52308
|
+
throw err;
|
|
52309
|
+
}
|
|
52310
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
52311
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
52312
|
+
}
|
|
52313
|
+
let secretStr;
|
|
52314
|
+
try {
|
|
52315
|
+
secretStr = new TextDecoder().decode(decryptedBytes);
|
|
52316
|
+
} finally {
|
|
52317
|
+
zeroizeBytes(decryptedBytes);
|
|
52318
|
+
}
|
|
52319
|
+
await this.storeSecret(bundleHash, secretStr, {
|
|
52320
|
+
...options,
|
|
52321
|
+
recoveryPassphrase
|
|
52322
|
+
});
|
|
52010
52323
|
}
|
|
52011
52324
|
};
|
|
52012
52325
|
|
|
@@ -52028,6 +52341,7 @@ ${operationTypes.join("\n")}
|
|
|
52028
52341
|
$__authTokenObjects = {};
|
|
52029
52342
|
$__authToken = null;
|
|
52030
52343
|
$__authInProcess = false;
|
|
52344
|
+
$__mlKemParameterSet = 1024;
|
|
52031
52345
|
$__remainderWallet = null;
|
|
52032
52346
|
lastMoleculeQuery = null;
|
|
52033
52347
|
abortControllers = /* @__PURE__ */ new Map();
|
|
@@ -52072,7 +52386,8 @@ ${operationTypes.join("\n")}
|
|
|
52072
52386
|
client,
|
|
52073
52387
|
serverSdkVersion,
|
|
52074
52388
|
logging,
|
|
52075
|
-
defaultRequestPolicy
|
|
52389
|
+
defaultRequestPolicy,
|
|
52390
|
+
mlKemParameterSet: config2.mlKemParameterSet ?? 1024
|
|
52076
52391
|
});
|
|
52077
52392
|
if (config2.secretStorage) {
|
|
52078
52393
|
this.$__secretStorage = config2.secretStorage;
|
|
@@ -52088,10 +52403,12 @@ ${operationTypes.join("\n")}
|
|
|
52088
52403
|
client = null,
|
|
52089
52404
|
serverSdkVersion = 3,
|
|
52090
52405
|
logging = false,
|
|
52091
|
-
defaultRequestPolicy = null
|
|
52406
|
+
defaultRequestPolicy = null,
|
|
52407
|
+
mlKemParameterSet = 1024
|
|
52092
52408
|
}) {
|
|
52093
52409
|
this.reset();
|
|
52094
52410
|
this.$__logging = logging;
|
|
52411
|
+
this.setMlKemParameterSet(mlKemParameterSet);
|
|
52095
52412
|
this.$__authTokenObjects = {};
|
|
52096
52413
|
this.setUri(uri);
|
|
52097
52414
|
if (cellSlug) {
|
|
@@ -52112,6 +52429,17 @@ ${operationTypes.join("\n")}
|
|
|
52112
52429
|
this.$__serverSdkVersion = serverSdkVersion;
|
|
52113
52430
|
this.$__defaultRequestPolicy = defaultRequestPolicy;
|
|
52114
52431
|
}
|
|
52432
|
+
getMlKemParameterSet() {
|
|
52433
|
+
return this.$__mlKemParameterSet || 1024;
|
|
52434
|
+
}
|
|
52435
|
+
setMlKemParameterSet(parameterSet) {
|
|
52436
|
+
const paramNum = Number(parameterSet);
|
|
52437
|
+
if (![1024, 768].includes(paramNum)) {
|
|
52438
|
+
throw new Error(`KnishIO: unsupported ML-KEM parameter set ${parameterSet}; expected 1024 or 768.`);
|
|
52439
|
+
}
|
|
52440
|
+
this.$__mlKemParameterSet = paramNum;
|
|
52441
|
+
return this;
|
|
52442
|
+
}
|
|
52115
52443
|
/**
|
|
52116
52444
|
* Get random uri from specified this.$__uris
|
|
52117
52445
|
*/
|
|
@@ -52348,7 +52676,8 @@ ${operationTypes.join("\n")}
|
|
|
52348
52676
|
bundle,
|
|
52349
52677
|
token: "USER",
|
|
52350
52678
|
batchId: sourceWallet.batchId,
|
|
52351
|
-
characters: sourceWallet.characters
|
|
52679
|
+
characters: sourceWallet.characters,
|
|
52680
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
52352
52681
|
}));
|
|
52353
52682
|
return new Molecule({
|
|
52354
52683
|
secret,
|
|
@@ -52357,7 +52686,8 @@ ${operationTypes.join("\n")}
|
|
|
52357
52686
|
remainderWallet: this.getRemainderWallet(),
|
|
52358
52687
|
cellSlug: this.getCellSlug(),
|
|
52359
52688
|
version: this.getServerSdkVersion(),
|
|
52360
|
-
continuIdPosition
|
|
52689
|
+
continuIdPosition,
|
|
52690
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
52361
52691
|
});
|
|
52362
52692
|
}
|
|
52363
52693
|
/**
|
|
@@ -52469,7 +52799,8 @@ ${operationTypes.join("\n")}
|
|
|
52469
52799
|
}))?.payload();
|
|
52470
52800
|
if (!sourceWallet) {
|
|
52471
52801
|
sourceWallet = new Wallet({
|
|
52472
|
-
secret: this.getSecret()
|
|
52802
|
+
secret: this.getSecret(),
|
|
52803
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
52473
52804
|
});
|
|
52474
52805
|
} else {
|
|
52475
52806
|
sourceWallet.key = Wallet.generateKey({
|
|
@@ -52512,7 +52843,8 @@ ${operationTypes.join("\n")}
|
|
|
52512
52843
|
}
|
|
52513
52844
|
const recipientWallet = Wallet.create({
|
|
52514
52845
|
bundle: bundleHash,
|
|
52515
|
-
token
|
|
52846
|
+
token,
|
|
52847
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
52516
52848
|
});
|
|
52517
52849
|
if (batchId !== null) {
|
|
52518
52850
|
recipientWallet.batchId = batchId;
|
|
@@ -52579,7 +52911,8 @@ ${operationTypes.join("\n")}
|
|
|
52579
52911
|
const recipientWallets = recipients.map((recipient) => {
|
|
52580
52912
|
const recipientWallet = Wallet.create({
|
|
52581
52913
|
bundle: recipient.bundleHash,
|
|
52582
|
-
token
|
|
52914
|
+
token,
|
|
52915
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
52583
52916
|
});
|
|
52584
52917
|
if (recipient.batchId !== null && recipient.batchId !== void 0) {
|
|
52585
52918
|
recipientWallet.batchId = recipient.batchId;
|
|
@@ -53203,7 +53536,8 @@ ${operationTypes.join("\n")}
|
|
|
53203
53536
|
secret: this.getSecret(),
|
|
53204
53537
|
bundle: this.getBundle(),
|
|
53205
53538
|
token,
|
|
53206
|
-
batchId
|
|
53539
|
+
batchId,
|
|
53540
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
53207
53541
|
});
|
|
53208
53542
|
await mutation.fillMolecule({
|
|
53209
53543
|
recipientWallet,
|
|
@@ -53271,7 +53605,8 @@ ${operationTypes.join("\n")}
|
|
|
53271
53605
|
const recipientWallet = new Wallet({
|
|
53272
53606
|
secret: this.getSecret(),
|
|
53273
53607
|
bundle: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
53274
|
-
token
|
|
53608
|
+
token,
|
|
53609
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
53275
53610
|
});
|
|
53276
53611
|
await mutation.fillMolecule({
|
|
53277
53612
|
recipientWallet,
|
|
@@ -53348,7 +53683,8 @@ ${operationTypes.join("\n")}
|
|
|
53348
53683
|
const newWallet = new Wallet({
|
|
53349
53684
|
secret: this.getSecret(),
|
|
53350
53685
|
bundle: this.getBundle(),
|
|
53351
|
-
token
|
|
53686
|
+
token,
|
|
53687
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
53352
53688
|
});
|
|
53353
53689
|
await mutation.fillMolecule(newWallet);
|
|
53354
53690
|
const response = await this.executeQuery(mutation);
|
|
@@ -53676,7 +54012,8 @@ ${operationTypes.join("\n")}
|
|
|
53676
54012
|
this.setSecret(secret);
|
|
53677
54013
|
const wallet = new Wallet({
|
|
53678
54014
|
secret,
|
|
53679
|
-
token: "AUTH"
|
|
54015
|
+
token: "AUTH",
|
|
54016
|
+
mlKemParameterSet: this.getMlKemParameterSet()
|
|
53680
54017
|
});
|
|
53681
54018
|
const molecule = await this.createMolecule({
|
|
53682
54019
|
secret,
|
|
@@ -53804,45 +54141,25 @@ ${operationTypes.join("\n")}
|
|
|
53804
54141
|
return Array.from(this.store.keys());
|
|
53805
54142
|
}
|
|
53806
54143
|
};
|
|
53807
|
-
function uint8ArrayToBase642(bytes) {
|
|
53808
|
-
let binary = "";
|
|
53809
|
-
const len = bytes.byteLength;
|
|
53810
|
-
for (let i4 = 0; i4 < len; i4++) {
|
|
53811
|
-
const byte = bytes[i4];
|
|
53812
|
-
if (byte !== void 0) {
|
|
53813
|
-
binary += String.fromCharCode(byte);
|
|
53814
|
-
}
|
|
53815
|
-
}
|
|
53816
|
-
return btoa(binary);
|
|
53817
|
-
}
|
|
53818
|
-
function base64ToUint8Array2(base643) {
|
|
53819
|
-
const binary = atob(base643);
|
|
53820
|
-
const len = binary.length;
|
|
53821
|
-
const bytes = new Uint8Array(len);
|
|
53822
|
-
for (let i4 = 0; i4 < len; i4++) {
|
|
53823
|
-
bytes[i4] = binary.charCodeAt(i4);
|
|
53824
|
-
}
|
|
53825
|
-
return bytes;
|
|
53826
|
-
}
|
|
53827
|
-
var textEncoder2 = new TextEncoder();
|
|
53828
54144
|
var textDecoder = new TextDecoder();
|
|
53829
|
-
var KEY_PREFIX =
|
|
53830
|
-
var DEFAULT_ITERATIONS = 1e5;
|
|
54145
|
+
var KEY_PREFIX = SECRET_KEY_PREFIX;
|
|
53831
54146
|
var WebCryptoSecretStorageProvider = class {
|
|
53832
54147
|
providerType = "webcrypto-aes-gcm";
|
|
53833
54148
|
backend;
|
|
53834
54149
|
defaultPassphrase;
|
|
53835
|
-
hardwareBacked;
|
|
53836
54150
|
constructor(options = {}) {
|
|
53837
54151
|
this.backend = options.backend ?? new MemoryStorageBackend();
|
|
53838
54152
|
this.defaultPassphrase = options.defaultPassphrase;
|
|
53839
|
-
this.hardwareBacked = options.hardwareBacked ?? false;
|
|
53840
54153
|
}
|
|
53841
54154
|
/**
|
|
53842
|
-
*
|
|
54155
|
+
* True only when this provider holds a non-exportable key inside platform-secure
|
|
54156
|
+
* hardware (Android TEE/StrongBox, Secure Enclave, TPM) and learned that from the
|
|
54157
|
+
* platform itself — never from a caller argument. Software envelope providers
|
|
54158
|
+
* return false. The value is persisted as `metadata.hardwareBacked` in every
|
|
54159
|
+
* envelope this provider writes.
|
|
53843
54160
|
*/
|
|
53844
54161
|
isHardwareBacked() {
|
|
53845
|
-
return
|
|
54162
|
+
return false;
|
|
53846
54163
|
}
|
|
53847
54164
|
/**
|
|
53848
54165
|
* Check if WebCrypto subtle API is available
|
|
@@ -53850,38 +54167,6 @@ ${operationTypes.join("\n")}
|
|
|
53850
54167
|
async isAvailable() {
|
|
53851
54168
|
return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
|
|
53852
54169
|
}
|
|
53853
|
-
/**
|
|
53854
|
-
* Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
|
|
53855
|
-
*/
|
|
53856
|
-
async deriveKey(passphrase, salt, iterations = DEFAULT_ITERATIONS) {
|
|
53857
|
-
if (!await this.isAvailable()) {
|
|
53858
|
-
throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
|
|
53859
|
-
}
|
|
53860
|
-
const passphraseBytes = textEncoder2.encode(passphrase);
|
|
53861
|
-
try {
|
|
53862
|
-
const baseKey = await globalThis.crypto.subtle.importKey(
|
|
53863
|
-
"raw",
|
|
53864
|
-
passphraseBytes,
|
|
53865
|
-
"PBKDF2",
|
|
53866
|
-
false,
|
|
53867
|
-
["deriveKey"]
|
|
53868
|
-
);
|
|
53869
|
-
return await globalThis.crypto.subtle.deriveKey(
|
|
53870
|
-
{
|
|
53871
|
-
name: "PBKDF2",
|
|
53872
|
-
salt,
|
|
53873
|
-
iterations,
|
|
53874
|
-
hash: "SHA-256"
|
|
53875
|
-
},
|
|
53876
|
-
baseKey,
|
|
53877
|
-
{ name: "AES-GCM", length: 256 },
|
|
53878
|
-
false,
|
|
53879
|
-
["encrypt", "decrypt"]
|
|
53880
|
-
);
|
|
53881
|
-
} finally {
|
|
53882
|
-
zeroizeBytes(passphraseBytes);
|
|
53883
|
-
}
|
|
53884
|
-
}
|
|
53885
54170
|
/**
|
|
53886
54171
|
* Store and encrypt a master secret
|
|
53887
54172
|
*/
|
|
@@ -53896,44 +54181,36 @@ ${operationTypes.join("\n")}
|
|
|
53896
54181
|
if (!passphrase) {
|
|
53897
54182
|
throw new exports.SecretStorageException("Passphrase required for envelope encryption");
|
|
53898
54183
|
}
|
|
53899
|
-
|
|
53900
|
-
|
|
53901
|
-
|
|
53902
|
-
globalThis.crypto.getRandomValues(iv);
|
|
53903
|
-
const key = await this.deriveKey(passphrase, salt, DEFAULT_ITERATIONS);
|
|
53904
|
-
const secretBytes = textEncoder2.encode(secret);
|
|
54184
|
+
if (!await this.isAvailable()) {
|
|
54185
|
+
throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
|
|
54186
|
+
}
|
|
53905
54187
|
try {
|
|
53906
|
-
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
53907
|
-
{
|
|
53908
|
-
name: "AES-GCM",
|
|
53909
|
-
iv
|
|
53910
|
-
},
|
|
53911
|
-
key,
|
|
53912
|
-
secretBytes
|
|
53913
|
-
);
|
|
53914
|
-
const ciphertext = uint8ArrayToBase642(new Uint8Array(encryptedBuffer));
|
|
53915
54188
|
const metadata = {
|
|
53916
54189
|
bundleHash,
|
|
53917
54190
|
label: options?.label,
|
|
53918
54191
|
createdAt: Date.now(),
|
|
53919
|
-
hardwareBacked:
|
|
54192
|
+
hardwareBacked: false,
|
|
53920
54193
|
providerType: this.providerType
|
|
53921
54194
|
};
|
|
53922
|
-
const payload =
|
|
53923
|
-
version: 1,
|
|
53924
|
-
ciphertext,
|
|
53925
|
-
iv: uint8ArrayToBase642(iv),
|
|
53926
|
-
salt: uint8ArrayToBase642(salt),
|
|
53927
|
-
algorithm: "AES-GCM",
|
|
53928
|
-
iterations: DEFAULT_ITERATIONS,
|
|
53929
|
-
metadata
|
|
53930
|
-
};
|
|
54195
|
+
const payload = await sealEnvelope(secret, passphrase, metadata);
|
|
53931
54196
|
await this.backend.setItem(`${KEY_PREFIX}${bundleHash}`, JSON.stringify(payload));
|
|
54197
|
+
if (options?.recoveryPassphrase) {
|
|
54198
|
+
const recoveryMetadata = {
|
|
54199
|
+
bundleHash,
|
|
54200
|
+
label: options?.label,
|
|
54201
|
+
createdAt: Date.now(),
|
|
54202
|
+
hardwareBacked: false,
|
|
54203
|
+
providerType: "webcrypto-aes-gcm"
|
|
54204
|
+
};
|
|
54205
|
+
const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
|
|
54206
|
+
await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
|
|
54207
|
+
}
|
|
53932
54208
|
} catch (err) {
|
|
54209
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54210
|
+
throw err;
|
|
54211
|
+
}
|
|
53933
54212
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53934
54213
|
throw new exports.SecretStorageException(`Encryption failed: ${msg}`);
|
|
53935
|
-
} finally {
|
|
53936
|
-
zeroizeBytes(secretBytes);
|
|
53937
54214
|
}
|
|
53938
54215
|
}
|
|
53939
54216
|
/**
|
|
@@ -53954,26 +54231,20 @@ ${operationTypes.join("\n")}
|
|
|
53954
54231
|
if (!passphrase) {
|
|
53955
54232
|
throw new exports.SecretStorageException("Passphrase required for secret decryption");
|
|
53956
54233
|
}
|
|
53957
|
-
|
|
53958
|
-
|
|
53959
|
-
|
|
54234
|
+
if (!await this.isAvailable()) {
|
|
54235
|
+
throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
|
|
54236
|
+
}
|
|
53960
54237
|
try {
|
|
53961
|
-
const
|
|
53962
|
-
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
53963
|
-
{
|
|
53964
|
-
name: "AES-GCM",
|
|
53965
|
-
iv
|
|
53966
|
-
},
|
|
53967
|
-
key,
|
|
53968
|
-
ciphertext
|
|
53969
|
-
);
|
|
53970
|
-
const decryptedBytes = new Uint8Array(decryptedBuffer);
|
|
54238
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
53971
54239
|
try {
|
|
53972
54240
|
return textDecoder.decode(decryptedBytes);
|
|
53973
54241
|
} finally {
|
|
53974
54242
|
zeroizeBytes(decryptedBytes);
|
|
53975
54243
|
}
|
|
53976
54244
|
} catch (err) {
|
|
54245
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54246
|
+
throw err;
|
|
54247
|
+
}
|
|
53977
54248
|
const msg = err instanceof Error ? err.message : String(err);
|
|
53978
54249
|
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
53979
54250
|
}
|
|
@@ -53983,7 +54254,9 @@ ${operationTypes.join("\n")}
|
|
|
53983
54254
|
*/
|
|
53984
54255
|
async deleteSecret(bundleHash) {
|
|
53985
54256
|
const key = `${KEY_PREFIX}${bundleHash}`;
|
|
54257
|
+
const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
|
|
53986
54258
|
const result = await this.backend.removeItem(key);
|
|
54259
|
+
await this.backend.removeItem(recoveryKey);
|
|
53987
54260
|
return result !== false;
|
|
53988
54261
|
}
|
|
53989
54262
|
/**
|
|
@@ -53998,7 +54271,7 @@ ${operationTypes.join("\n")}
|
|
|
53998
54271
|
*/
|
|
53999
54272
|
async listSecrets() {
|
|
54000
54273
|
const keys = await this.backend.keys();
|
|
54001
|
-
const matchingKeys = keys.filter((k2) => k2.startsWith(KEY_PREFIX));
|
|
54274
|
+
const matchingKeys = keys.filter((k2) => k2.startsWith(KEY_PREFIX) && !k2.startsWith(RECOVERY_KEY_PREFIX));
|
|
54002
54275
|
const results = [];
|
|
54003
54276
|
for (const key of matchingKeys) {
|
|
54004
54277
|
const raw = await this.backend.getItem(key);
|
|
@@ -54032,22 +54305,1008 @@ ${operationTypes.join("\n")}
|
|
|
54032
54305
|
if (!passphrase) {
|
|
54033
54306
|
throw new exports.SecretStorageException("Passphrase required for secret decryption");
|
|
54034
54307
|
}
|
|
54035
|
-
|
|
54036
|
-
|
|
54037
|
-
|
|
54308
|
+
if (!await this.isAvailable()) {
|
|
54309
|
+
throw exports.SecretStorageException.unavailable(this.providerType, "WebCrypto API is not available");
|
|
54310
|
+
}
|
|
54311
|
+
try {
|
|
54312
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
54313
|
+
return await withSecureBytes(decryptedBytes, async (bytes) => {
|
|
54314
|
+
const secretString = textDecoder.decode(bytes);
|
|
54315
|
+
return await fn(secretString);
|
|
54316
|
+
});
|
|
54317
|
+
} catch (err) {
|
|
54318
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54319
|
+
throw err;
|
|
54320
|
+
}
|
|
54321
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54322
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
54323
|
+
}
|
|
54324
|
+
}
|
|
54325
|
+
/**
|
|
54326
|
+
* Recover a secret using its recovery envelope and re-enroll it
|
|
54327
|
+
*/
|
|
54328
|
+
async recoverSecret(bundleHash, recoveryPassphrase, options) {
|
|
54329
|
+
if (!bundleHash) {
|
|
54330
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
54331
|
+
}
|
|
54332
|
+
if (!recoveryPassphrase) {
|
|
54333
|
+
throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
|
|
54334
|
+
}
|
|
54335
|
+
const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
|
|
54336
|
+
if (!raw) {
|
|
54337
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
54338
|
+
}
|
|
54339
|
+
let payload;
|
|
54340
|
+
try {
|
|
54341
|
+
payload = JSON.parse(raw);
|
|
54342
|
+
} catch {
|
|
54343
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
|
|
54344
|
+
}
|
|
54345
|
+
let decryptedBytes;
|
|
54346
|
+
try {
|
|
54347
|
+
decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
|
|
54348
|
+
} catch (err) {
|
|
54349
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54350
|
+
throw err;
|
|
54351
|
+
}
|
|
54352
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54353
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
54354
|
+
}
|
|
54355
|
+
let secretStr;
|
|
54356
|
+
try {
|
|
54357
|
+
secretStr = textDecoder.decode(decryptedBytes);
|
|
54358
|
+
} finally {
|
|
54359
|
+
zeroizeBytes(decryptedBytes);
|
|
54360
|
+
}
|
|
54361
|
+
const storePassphrase = options?.passphrase ?? this.defaultPassphrase ?? recoveryPassphrase;
|
|
54362
|
+
await this.storeSecret(bundleHash, secretStr, {
|
|
54363
|
+
...options,
|
|
54364
|
+
passphrase: storePassphrase,
|
|
54365
|
+
recoveryPassphrase
|
|
54366
|
+
});
|
|
54367
|
+
}
|
|
54368
|
+
};
|
|
54369
|
+
|
|
54370
|
+
// src/storage/FileStorageBackend.ts
|
|
54371
|
+
init_SecretStorageException();
|
|
54372
|
+
var FileStorageBackend = class {
|
|
54373
|
+
filePath;
|
|
54374
|
+
store = /* @__PURE__ */ new Map();
|
|
54375
|
+
loaded = false;
|
|
54376
|
+
constructor(filePath) {
|
|
54377
|
+
if (!filePath) {
|
|
54378
|
+
throw new exports.SecretStorageException("Storage file path cannot be empty");
|
|
54379
|
+
}
|
|
54380
|
+
this.filePath = filePath;
|
|
54381
|
+
}
|
|
54382
|
+
async getFs() {
|
|
54383
|
+
try {
|
|
54384
|
+
const fs = await import('fs/promises');
|
|
54385
|
+
const path = await import('path');
|
|
54386
|
+
return { fs, path };
|
|
54387
|
+
} catch {
|
|
54388
|
+
throw exports.SecretStorageException.unavailable(
|
|
54389
|
+
"file-storage",
|
|
54390
|
+
"FileStorageBackend is only supported in Node.js environments with node:fs access"
|
|
54391
|
+
);
|
|
54392
|
+
}
|
|
54393
|
+
}
|
|
54394
|
+
async ensureLoaded() {
|
|
54395
|
+
if (this.loaded) {
|
|
54396
|
+
return this.store;
|
|
54397
|
+
}
|
|
54398
|
+
const { fs } = await this.getFs();
|
|
54399
|
+
try {
|
|
54400
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
54401
|
+
let parsed;
|
|
54402
|
+
try {
|
|
54403
|
+
parsed = JSON.parse(content);
|
|
54404
|
+
} catch {
|
|
54405
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted storage file format");
|
|
54406
|
+
}
|
|
54407
|
+
if (parsed && typeof parsed === "object") {
|
|
54408
|
+
this.store = new Map(Object.entries(parsed).map(([k2, v3]) => [k2, String(v3)]));
|
|
54409
|
+
}
|
|
54410
|
+
} catch (err) {
|
|
54411
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54412
|
+
throw err;
|
|
54413
|
+
}
|
|
54414
|
+
const nodeErr = err;
|
|
54415
|
+
if (nodeErr?.code !== "ENOENT") {
|
|
54416
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54417
|
+
throw new exports.SecretStorageException(`Failed to read storage file: ${msg}`);
|
|
54418
|
+
}
|
|
54419
|
+
this.store = /* @__PURE__ */ new Map();
|
|
54420
|
+
}
|
|
54421
|
+
this.loaded = true;
|
|
54422
|
+
return this.store;
|
|
54423
|
+
}
|
|
54424
|
+
async persist() {
|
|
54425
|
+
const { fs, path } = await this.getFs();
|
|
54426
|
+
const dir = path.dirname(this.filePath);
|
|
54427
|
+
if (dir && dir !== ".") {
|
|
54428
|
+
await fs.mkdir(dir, { recursive: true });
|
|
54429
|
+
}
|
|
54430
|
+
const tmpPath = `${this.filePath}.tmp.${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
54431
|
+
const data = JSON.stringify(Object.fromEntries(this.store), null, 2);
|
|
54432
|
+
try {
|
|
54433
|
+
await fs.writeFile(tmpPath, data, { mode: 384, encoding: "utf8" });
|
|
54434
|
+
if (typeof process !== "undefined" && process.platform !== "win32") {
|
|
54435
|
+
try {
|
|
54436
|
+
await fs.chmod(tmpPath, 384);
|
|
54437
|
+
} catch {
|
|
54438
|
+
}
|
|
54439
|
+
}
|
|
54440
|
+
await fs.rename(tmpPath, this.filePath);
|
|
54441
|
+
} catch (err) {
|
|
54442
|
+
try {
|
|
54443
|
+
await fs.unlink(tmpPath);
|
|
54444
|
+
} catch {
|
|
54445
|
+
}
|
|
54446
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54447
|
+
throw new exports.SecretStorageException(`Failed to persist storage file: ${msg}`);
|
|
54448
|
+
}
|
|
54449
|
+
}
|
|
54450
|
+
async getItem(key) {
|
|
54451
|
+
await this.ensureLoaded();
|
|
54452
|
+
return this.store.get(key) ?? null;
|
|
54453
|
+
}
|
|
54454
|
+
async setItem(key, value2) {
|
|
54455
|
+
await this.ensureLoaded();
|
|
54456
|
+
this.store.set(key, value2);
|
|
54457
|
+
await this.persist();
|
|
54458
|
+
}
|
|
54459
|
+
async removeItem(key) {
|
|
54460
|
+
await this.ensureLoaded();
|
|
54461
|
+
const existed = this.store.delete(key);
|
|
54462
|
+
if (existed) {
|
|
54463
|
+
await this.persist();
|
|
54464
|
+
}
|
|
54465
|
+
return existed;
|
|
54466
|
+
}
|
|
54467
|
+
async keys() {
|
|
54468
|
+
await this.ensureLoaded();
|
|
54469
|
+
return Array.from(this.store.keys());
|
|
54470
|
+
}
|
|
54471
|
+
};
|
|
54472
|
+
|
|
54473
|
+
// src/storage/WebStorageBackend.ts
|
|
54474
|
+
init_SecretStorageException();
|
|
54475
|
+
var WebStorageBackend = class {
|
|
54476
|
+
storage;
|
|
54477
|
+
prefix;
|
|
54478
|
+
constructor(storage, prefix = "knishio:") {
|
|
54479
|
+
if (storage) {
|
|
54480
|
+
this.storage = storage;
|
|
54481
|
+
} else if (typeof globalThis !== "undefined" && globalThis.localStorage) {
|
|
54482
|
+
this.storage = globalThis.localStorage;
|
|
54483
|
+
} else {
|
|
54484
|
+
throw exports.SecretStorageException.unavailable(
|
|
54485
|
+
"web-storage",
|
|
54486
|
+
"WebStorageBackend requires a Storage object or global localStorage"
|
|
54487
|
+
);
|
|
54488
|
+
}
|
|
54489
|
+
this.prefix = prefix;
|
|
54490
|
+
}
|
|
54491
|
+
getItem(key) {
|
|
54492
|
+
return this.storage.getItem(key);
|
|
54493
|
+
}
|
|
54494
|
+
setItem(key, value2) {
|
|
54495
|
+
this.storage.setItem(key, value2);
|
|
54496
|
+
}
|
|
54497
|
+
removeItem(key) {
|
|
54498
|
+
const existed = this.storage.getItem(key) !== null;
|
|
54499
|
+
this.storage.removeItem(key);
|
|
54500
|
+
return existed;
|
|
54501
|
+
}
|
|
54502
|
+
keys() {
|
|
54503
|
+
const result = [];
|
|
54504
|
+
const len = this.storage.length;
|
|
54505
|
+
for (let i4 = 0; i4 < len; i4++) {
|
|
54506
|
+
const k2 = this.storage.key(i4);
|
|
54507
|
+
if (k2 !== null) {
|
|
54508
|
+
if (!this.prefix || k2.startsWith(this.prefix)) {
|
|
54509
|
+
result.push(k2);
|
|
54510
|
+
}
|
|
54511
|
+
}
|
|
54512
|
+
}
|
|
54513
|
+
return result;
|
|
54514
|
+
}
|
|
54515
|
+
};
|
|
54516
|
+
|
|
54517
|
+
// src/storage/WebAuthnPrfSecretStorageProvider.ts
|
|
54518
|
+
init_SecretStorageException();
|
|
54519
|
+
var PRF_SALT_LABEL = "knishio:secret-storage:webauthn-prf:v1";
|
|
54520
|
+
var KEK_INFO = "knishio:secret-storage:kek:v1";
|
|
54521
|
+
var KEY_PREFIX2 = SECRET_KEY_PREFIX;
|
|
54522
|
+
var GCM_IV_LENGTH2 = 12;
|
|
54523
|
+
var textEncoder3 = new TextEncoder();
|
|
54524
|
+
var textDecoder2 = new TextDecoder();
|
|
54525
|
+
function base64UrlEncode(bytes) {
|
|
54526
|
+
return uint8ArrayToBase642(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
54527
|
+
}
|
|
54528
|
+
function base64UrlDecode(str) {
|
|
54529
|
+
let base643 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
54530
|
+
while (base643.length % 4 !== 0) {
|
|
54531
|
+
base643 += "=";
|
|
54532
|
+
}
|
|
54533
|
+
return base64ToUint8Array2(base643);
|
|
54534
|
+
}
|
|
54535
|
+
function toUint8Array(buf) {
|
|
54536
|
+
if (buf instanceof Uint8Array) {
|
|
54537
|
+
return buf;
|
|
54538
|
+
}
|
|
54539
|
+
if (ArrayBuffer.isView(buf)) {
|
|
54540
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
54541
|
+
}
|
|
54542
|
+
return new Uint8Array(buf);
|
|
54543
|
+
}
|
|
54544
|
+
async function computePrfSalt() {
|
|
54545
|
+
const hash2 = await globalThis.crypto.subtle.digest("SHA-256", textEncoder3.encode(PRF_SALT_LABEL));
|
|
54546
|
+
return new Uint8Array(hash2);
|
|
54547
|
+
}
|
|
54548
|
+
async function deriveKekFromPrf(prfOutput, prfSalt) {
|
|
54549
|
+
const hkdfKey = await globalThis.crypto.subtle.importKey(
|
|
54550
|
+
"raw",
|
|
54551
|
+
prfOutput,
|
|
54552
|
+
"HKDF",
|
|
54553
|
+
false,
|
|
54554
|
+
["deriveKey"]
|
|
54555
|
+
);
|
|
54556
|
+
return await globalThis.crypto.subtle.deriveKey(
|
|
54557
|
+
{
|
|
54558
|
+
name: "HKDF",
|
|
54559
|
+
hash: "SHA-256",
|
|
54560
|
+
salt: prfSalt,
|
|
54561
|
+
info: textEncoder3.encode(KEK_INFO)
|
|
54562
|
+
},
|
|
54563
|
+
hkdfKey,
|
|
54564
|
+
{ name: "AES-GCM", length: 256 },
|
|
54565
|
+
false,
|
|
54566
|
+
["encrypt", "decrypt"]
|
|
54567
|
+
);
|
|
54568
|
+
}
|
|
54569
|
+
var WebAuthnPrfSecretStorageProvider = class {
|
|
54570
|
+
providerType = "webauthn-prf";
|
|
54571
|
+
backend;
|
|
54572
|
+
rp;
|
|
54573
|
+
user;
|
|
54574
|
+
credentialsContainer;
|
|
54575
|
+
alias;
|
|
54576
|
+
cachedPassphrase;
|
|
54577
|
+
constructor(options) {
|
|
54578
|
+
this.backend = options.backend;
|
|
54579
|
+
this.rp = options.rp;
|
|
54580
|
+
this.user = options.user;
|
|
54581
|
+
this.credentialsContainer = options.credentials;
|
|
54582
|
+
this.alias = options.alias ?? "default";
|
|
54583
|
+
}
|
|
54584
|
+
get credentials() {
|
|
54585
|
+
if (this.credentialsContainer) {
|
|
54586
|
+
return this.credentialsContainer;
|
|
54587
|
+
}
|
|
54588
|
+
if (typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials) {
|
|
54589
|
+
return globalThis.navigator.credentials;
|
|
54590
|
+
}
|
|
54591
|
+
throw exports.SecretStorageException.unavailable(
|
|
54592
|
+
this.providerType,
|
|
54593
|
+
"WebAuthn credentials container is not available"
|
|
54594
|
+
);
|
|
54595
|
+
}
|
|
54596
|
+
get recordKey() {
|
|
54597
|
+
return `knishio:webauthn-prf:${this.alias}`;
|
|
54598
|
+
}
|
|
54599
|
+
isHardwareBacked() {
|
|
54600
|
+
return false;
|
|
54601
|
+
}
|
|
54602
|
+
async isAvailable() {
|
|
54603
|
+
const hasWebCrypto = typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
|
|
54604
|
+
const hasCredentials = Boolean(
|
|
54605
|
+
this.credentialsContainer || typeof globalThis.navigator !== "undefined" && globalThis.navigator.credentials && typeof globalThis.PublicKeyCredential !== "undefined"
|
|
54606
|
+
);
|
|
54607
|
+
return hasWebCrypto && hasCredentials;
|
|
54608
|
+
}
|
|
54609
|
+
/**
|
|
54610
|
+
* Enroll a new passkey credential with PRF support and wrap a random device passphrase
|
|
54611
|
+
*/
|
|
54612
|
+
async enroll() {
|
|
54613
|
+
const existing = await this.backend.getItem(this.recordKey);
|
|
54614
|
+
if (existing) {
|
|
54615
|
+
return;
|
|
54616
|
+
}
|
|
54617
|
+
if (!await this.isAvailable()) {
|
|
54618
|
+
throw exports.SecretStorageException.unavailable(this.providerType, "WebAuthn PRF is not available");
|
|
54619
|
+
}
|
|
54620
|
+
const challenge = new Uint8Array(32);
|
|
54621
|
+
globalThis.crypto.getRandomValues(challenge);
|
|
54622
|
+
const credential = await this.credentials.create({
|
|
54623
|
+
publicKey: {
|
|
54624
|
+
rp: this.rp,
|
|
54625
|
+
user: {
|
|
54626
|
+
id: this.user.id,
|
|
54627
|
+
name: this.user.name,
|
|
54628
|
+
displayName: this.user.displayName
|
|
54629
|
+
},
|
|
54630
|
+
challenge,
|
|
54631
|
+
pubKeyCredParams: [
|
|
54632
|
+
{ type: "public-key", alg: -7 },
|
|
54633
|
+
{ type: "public-key", alg: -257 }
|
|
54634
|
+
],
|
|
54635
|
+
authenticatorSelection: {
|
|
54636
|
+
residentKey: "required",
|
|
54637
|
+
userVerification: "required"
|
|
54638
|
+
},
|
|
54639
|
+
extensions: {
|
|
54640
|
+
prf: {}
|
|
54641
|
+
}
|
|
54642
|
+
}
|
|
54643
|
+
});
|
|
54644
|
+
if (!credential) {
|
|
54645
|
+
throw exports.SecretStorageException.unavailable(this.providerType, "Authenticator creation returned null");
|
|
54646
|
+
}
|
|
54647
|
+
const extResults = credential.getClientExtensionResults?.();
|
|
54648
|
+
if (extResults?.prf?.enabled !== true) {
|
|
54649
|
+
throw exports.SecretStorageException.unavailable(
|
|
54650
|
+
this.providerType,
|
|
54651
|
+
"authenticator does not support the PRF extension"
|
|
54652
|
+
);
|
|
54653
|
+
}
|
|
54654
|
+
const credentialIdBytes = new Uint8Array(credential.rawId);
|
|
54655
|
+
const prfSalt = await computePrfSalt();
|
|
54656
|
+
const getChallenge = new Uint8Array(32);
|
|
54657
|
+
globalThis.crypto.getRandomValues(getChallenge);
|
|
54658
|
+
let assertion;
|
|
54659
|
+
try {
|
|
54660
|
+
assertion = await this.credentials.get({
|
|
54661
|
+
publicKey: {
|
|
54662
|
+
challenge: getChallenge,
|
|
54663
|
+
rpId: this.rp.id,
|
|
54664
|
+
allowCredentials: [
|
|
54665
|
+
{
|
|
54666
|
+
type: "public-key",
|
|
54667
|
+
id: credentialIdBytes
|
|
54668
|
+
}
|
|
54669
|
+
],
|
|
54670
|
+
userVerification: "required",
|
|
54671
|
+
extensions: {
|
|
54672
|
+
prf: {
|
|
54673
|
+
eval: {
|
|
54674
|
+
first: prfSalt
|
|
54675
|
+
}
|
|
54676
|
+
}
|
|
54677
|
+
}
|
|
54678
|
+
}
|
|
54679
|
+
});
|
|
54680
|
+
} catch (err) {
|
|
54681
|
+
const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
|
|
54682
|
+
if (isNotAllowed) {
|
|
54683
|
+
throw exports.SecretStorageException.unavailable(
|
|
54684
|
+
this.providerType,
|
|
54685
|
+
"authenticator refused or credential missing"
|
|
54686
|
+
);
|
|
54687
|
+
}
|
|
54688
|
+
throw err;
|
|
54689
|
+
}
|
|
54690
|
+
if (!assertion) {
|
|
54691
|
+
throw exports.SecretStorageException.unavailable(
|
|
54692
|
+
this.providerType,
|
|
54693
|
+
"authenticator refused or credential missing"
|
|
54694
|
+
);
|
|
54695
|
+
}
|
|
54696
|
+
const getExtResults = assertion?.getClientExtensionResults?.();
|
|
54697
|
+
const firstOutput = getExtResults?.prf?.results?.first;
|
|
54698
|
+
if (!firstOutput) {
|
|
54699
|
+
throw exports.SecretStorageException.unavailable(
|
|
54700
|
+
this.providerType,
|
|
54701
|
+
"authenticator returned no PRF result"
|
|
54702
|
+
);
|
|
54703
|
+
}
|
|
54704
|
+
const prfBytes = toUint8Array(firstOutput);
|
|
54705
|
+
const kek = await deriveKekFromPrf(prfBytes, prfSalt);
|
|
54706
|
+
const devicePassphraseBytes = new Uint8Array(32);
|
|
54707
|
+
globalThis.crypto.getRandomValues(devicePassphraseBytes);
|
|
54708
|
+
const devicePassphrase = uint8ArrayToBase642(devicePassphraseBytes);
|
|
54709
|
+
const iv = new Uint8Array(GCM_IV_LENGTH2);
|
|
54710
|
+
globalThis.crypto.getRandomValues(iv);
|
|
54711
|
+
const passphraseBytes = textEncoder3.encode(devicePassphrase);
|
|
54712
|
+
try {
|
|
54713
|
+
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
54714
|
+
{
|
|
54715
|
+
name: "AES-GCM",
|
|
54716
|
+
iv
|
|
54717
|
+
},
|
|
54718
|
+
kek,
|
|
54719
|
+
passphraseBytes
|
|
54720
|
+
);
|
|
54721
|
+
const record2 = {
|
|
54722
|
+
version: 1,
|
|
54723
|
+
credentialId: base64UrlEncode(credentialIdBytes),
|
|
54724
|
+
iv: uint8ArrayToBase642(iv),
|
|
54725
|
+
ciphertext: uint8ArrayToBase642(new Uint8Array(encryptedBuffer))
|
|
54726
|
+
};
|
|
54727
|
+
await this.backend.setItem(this.recordKey, JSON.stringify(record2));
|
|
54728
|
+
this.cachedPassphrase = devicePassphrase;
|
|
54729
|
+
} finally {
|
|
54730
|
+
zeroizeBytes(passphraseBytes);
|
|
54731
|
+
zeroizeBytes(devicePassphraseBytes);
|
|
54732
|
+
}
|
|
54733
|
+
}
|
|
54734
|
+
/**
|
|
54735
|
+
* Unlock the device passphrase using the enrolled WebAuthn PRF credential
|
|
54736
|
+
*/
|
|
54737
|
+
async unlock() {
|
|
54738
|
+
if (this.cachedPassphrase) {
|
|
54739
|
+
return this.cachedPassphrase;
|
|
54740
|
+
}
|
|
54741
|
+
const rawRecord = await this.backend.getItem(this.recordKey);
|
|
54742
|
+
if (!rawRecord) {
|
|
54743
|
+
throw exports.SecretStorageException.unavailable(
|
|
54744
|
+
this.providerType,
|
|
54745
|
+
"no enrolled credential; call enroll() first"
|
|
54746
|
+
);
|
|
54747
|
+
}
|
|
54748
|
+
let record2;
|
|
54749
|
+
try {
|
|
54750
|
+
record2 = JSON.parse(rawRecord);
|
|
54751
|
+
} catch {
|
|
54752
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted PRF record format");
|
|
54753
|
+
}
|
|
54754
|
+
const credentialIdBytes = base64UrlDecode(record2.credentialId);
|
|
54755
|
+
const prfSalt = await computePrfSalt();
|
|
54756
|
+
const challenge = new Uint8Array(32);
|
|
54757
|
+
globalThis.crypto.getRandomValues(challenge);
|
|
54758
|
+
let assertion;
|
|
54759
|
+
try {
|
|
54760
|
+
assertion = await this.credentials.get({
|
|
54761
|
+
publicKey: {
|
|
54762
|
+
challenge,
|
|
54763
|
+
rpId: this.rp.id,
|
|
54764
|
+
allowCredentials: [
|
|
54765
|
+
{
|
|
54766
|
+
type: "public-key",
|
|
54767
|
+
id: credentialIdBytes
|
|
54768
|
+
}
|
|
54769
|
+
],
|
|
54770
|
+
userVerification: "required",
|
|
54771
|
+
extensions: {
|
|
54772
|
+
prf: {
|
|
54773
|
+
eval: {
|
|
54774
|
+
first: prfSalt
|
|
54775
|
+
}
|
|
54776
|
+
}
|
|
54777
|
+
}
|
|
54778
|
+
}
|
|
54779
|
+
});
|
|
54780
|
+
} catch (err) {
|
|
54781
|
+
const isNotAllowed = err instanceof Error && err.name === "NotAllowedError" || err?.name === "NotAllowedError";
|
|
54782
|
+
if (isNotAllowed) {
|
|
54783
|
+
throw exports.SecretStorageException.unavailable(
|
|
54784
|
+
this.providerType,
|
|
54785
|
+
"authenticator refused or credential missing"
|
|
54786
|
+
);
|
|
54787
|
+
}
|
|
54788
|
+
throw err;
|
|
54789
|
+
}
|
|
54790
|
+
if (!assertion) {
|
|
54791
|
+
throw exports.SecretStorageException.unavailable(
|
|
54792
|
+
this.providerType,
|
|
54793
|
+
"authenticator refused or credential missing"
|
|
54794
|
+
);
|
|
54795
|
+
}
|
|
54796
|
+
const extResults = assertion?.getClientExtensionResults?.();
|
|
54797
|
+
const firstOutput = extResults?.prf?.results?.first;
|
|
54798
|
+
if (!firstOutput) {
|
|
54799
|
+
throw exports.SecretStorageException.unavailable(
|
|
54800
|
+
this.providerType,
|
|
54801
|
+
"authenticator returned no PRF result"
|
|
54802
|
+
);
|
|
54803
|
+
}
|
|
54804
|
+
const prfBytes = toUint8Array(firstOutput);
|
|
54805
|
+
const kek = await deriveKekFromPrf(prfBytes, prfSalt);
|
|
54806
|
+
const iv = base64ToUint8Array2(record2.iv);
|
|
54807
|
+
const ciphertext = base64ToUint8Array2(record2.ciphertext);
|
|
54038
54808
|
try {
|
|
54039
|
-
const key = await this.deriveKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS);
|
|
54040
54809
|
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
54041
54810
|
{
|
|
54042
54811
|
name: "AES-GCM",
|
|
54043
54812
|
iv
|
|
54044
54813
|
},
|
|
54045
|
-
|
|
54814
|
+
kek,
|
|
54046
54815
|
ciphertext
|
|
54047
54816
|
);
|
|
54048
54817
|
const decryptedBytes = new Uint8Array(decryptedBuffer);
|
|
54818
|
+
try {
|
|
54819
|
+
this.cachedPassphrase = textDecoder2.decode(decryptedBytes);
|
|
54820
|
+
return this.cachedPassphrase;
|
|
54821
|
+
} finally {
|
|
54822
|
+
zeroizeBytes(decryptedBytes);
|
|
54823
|
+
}
|
|
54824
|
+
} catch {
|
|
54825
|
+
throw exports.SecretStorageException.decryptionFailed(
|
|
54826
|
+
"wrapped device passphrase failed authentication under the enrolled credential"
|
|
54827
|
+
);
|
|
54828
|
+
}
|
|
54829
|
+
}
|
|
54830
|
+
/**
|
|
54831
|
+
* Lock the provider by clearing cached passphrase material
|
|
54832
|
+
*/
|
|
54833
|
+
lock() {
|
|
54834
|
+
this.cachedPassphrase = void 0;
|
|
54835
|
+
}
|
|
54836
|
+
/**
|
|
54837
|
+
* Unenroll the current credential, removing the stored PRF record and clearing cached passphrase
|
|
54838
|
+
*/
|
|
54839
|
+
async unenroll() {
|
|
54840
|
+
this.lock();
|
|
54841
|
+
await this.backend.removeItem(this.recordKey);
|
|
54842
|
+
}
|
|
54843
|
+
async storeSecret(bundleHash, secret, options) {
|
|
54844
|
+
if (!bundleHash) {
|
|
54845
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
54846
|
+
}
|
|
54847
|
+
if (!secret) {
|
|
54848
|
+
throw new exports.SecretStorageException("Secret cannot be empty");
|
|
54849
|
+
}
|
|
54850
|
+
if (options?.passphrase) {
|
|
54851
|
+
throw new exports.SecretStorageException(
|
|
54852
|
+
"WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
|
|
54853
|
+
);
|
|
54854
|
+
}
|
|
54855
|
+
if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
|
|
54856
|
+
throw exports.SecretStorageException.validationError(
|
|
54857
|
+
"Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
|
|
54858
|
+
);
|
|
54859
|
+
}
|
|
54860
|
+
const passphrase = await this.unlock();
|
|
54861
|
+
const metadata = {
|
|
54862
|
+
bundleHash,
|
|
54863
|
+
label: options?.label,
|
|
54864
|
+
createdAt: Date.now(),
|
|
54865
|
+
hardwareBacked: false,
|
|
54866
|
+
providerType: this.providerType
|
|
54867
|
+
};
|
|
54868
|
+
const payload = await sealEnvelope(secret, passphrase, metadata);
|
|
54869
|
+
await this.backend.setItem(`${KEY_PREFIX2}${bundleHash}`, JSON.stringify(payload));
|
|
54870
|
+
if (options?.recoveryPassphrase) {
|
|
54871
|
+
const recoveryMetadata = {
|
|
54872
|
+
bundleHash,
|
|
54873
|
+
label: options?.label,
|
|
54874
|
+
createdAt: Date.now(),
|
|
54875
|
+
hardwareBacked: false,
|
|
54876
|
+
providerType: "webcrypto-aes-gcm"
|
|
54877
|
+
};
|
|
54878
|
+
const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
|
|
54879
|
+
await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
|
|
54880
|
+
}
|
|
54881
|
+
}
|
|
54882
|
+
async retrieveSecret(bundleHash, options) {
|
|
54883
|
+
if (options?.passphrase) {
|
|
54884
|
+
throw new exports.SecretStorageException(
|
|
54885
|
+
"WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
|
|
54886
|
+
);
|
|
54887
|
+
}
|
|
54888
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
|
|
54889
|
+
if (!raw) {
|
|
54890
|
+
return null;
|
|
54891
|
+
}
|
|
54892
|
+
let payload;
|
|
54893
|
+
try {
|
|
54894
|
+
payload = JSON.parse(raw);
|
|
54895
|
+
} catch {
|
|
54896
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
|
|
54897
|
+
}
|
|
54898
|
+
const passphrase = await this.unlock();
|
|
54899
|
+
try {
|
|
54900
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
54901
|
+
try {
|
|
54902
|
+
return textDecoder2.decode(decryptedBytes);
|
|
54903
|
+
} finally {
|
|
54904
|
+
zeroizeBytes(decryptedBytes);
|
|
54905
|
+
}
|
|
54906
|
+
} catch (err) {
|
|
54907
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54908
|
+
throw err;
|
|
54909
|
+
}
|
|
54910
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54911
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
54912
|
+
}
|
|
54913
|
+
}
|
|
54914
|
+
async withSecret(bundleHash, fn, options) {
|
|
54915
|
+
if (options?.passphrase) {
|
|
54916
|
+
throw new exports.SecretStorageException(
|
|
54917
|
+
"WebAuthnPrfSecretStorageProvider derives its passphrase from the authenticator; options.passphrase is not accepted"
|
|
54918
|
+
);
|
|
54919
|
+
}
|
|
54920
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
|
|
54921
|
+
if (!raw) {
|
|
54922
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
54923
|
+
}
|
|
54924
|
+
let payload;
|
|
54925
|
+
try {
|
|
54926
|
+
payload = JSON.parse(raw);
|
|
54927
|
+
} catch {
|
|
54928
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
|
|
54929
|
+
}
|
|
54930
|
+
const passphrase = await this.unlock();
|
|
54931
|
+
try {
|
|
54932
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
54049
54933
|
return await withSecureBytes(decryptedBytes, async (bytes) => {
|
|
54050
|
-
const secretString =
|
|
54934
|
+
const secretString = textDecoder2.decode(bytes);
|
|
54935
|
+
return await fn(secretString);
|
|
54936
|
+
});
|
|
54937
|
+
} catch (err) {
|
|
54938
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54939
|
+
throw err;
|
|
54940
|
+
}
|
|
54941
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
54942
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
54943
|
+
}
|
|
54944
|
+
}
|
|
54945
|
+
async deleteSecret(bundleHash) {
|
|
54946
|
+
const key = `${KEY_PREFIX2}${bundleHash}`;
|
|
54947
|
+
const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
|
|
54948
|
+
const result = await this.backend.removeItem(key);
|
|
54949
|
+
await this.backend.removeItem(recoveryKey);
|
|
54950
|
+
return result !== false;
|
|
54951
|
+
}
|
|
54952
|
+
async hasSecret(bundleHash) {
|
|
54953
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX2}${bundleHash}`);
|
|
54954
|
+
return raw !== null;
|
|
54955
|
+
}
|
|
54956
|
+
async listSecrets() {
|
|
54957
|
+
const keys = await this.backend.keys();
|
|
54958
|
+
const matchingKeys = keys.filter((k2) => k2.startsWith(KEY_PREFIX2) && !k2.startsWith(RECOVERY_KEY_PREFIX));
|
|
54959
|
+
const results = [];
|
|
54960
|
+
for (const key of matchingKeys) {
|
|
54961
|
+
const raw = await this.backend.getItem(key);
|
|
54962
|
+
if (raw) {
|
|
54963
|
+
try {
|
|
54964
|
+
const payload = JSON.parse(raw);
|
|
54965
|
+
if (payload.metadata) {
|
|
54966
|
+
results.push(payload.metadata);
|
|
54967
|
+
}
|
|
54968
|
+
} catch {
|
|
54969
|
+
}
|
|
54970
|
+
}
|
|
54971
|
+
}
|
|
54972
|
+
return results;
|
|
54973
|
+
}
|
|
54974
|
+
/**
|
|
54975
|
+
* Recover a secret using its recovery envelope and re-enroll it under a fresh WebAuthn PRF credential
|
|
54976
|
+
*/
|
|
54977
|
+
async recoverSecret(bundleHash, recoveryPassphrase, options) {
|
|
54978
|
+
if (!bundleHash) {
|
|
54979
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
54980
|
+
}
|
|
54981
|
+
if (!recoveryPassphrase) {
|
|
54982
|
+
throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
|
|
54983
|
+
}
|
|
54984
|
+
const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
|
|
54985
|
+
if (!raw) {
|
|
54986
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
54987
|
+
}
|
|
54988
|
+
let payload;
|
|
54989
|
+
try {
|
|
54990
|
+
payload = JSON.parse(raw);
|
|
54991
|
+
} catch {
|
|
54992
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
|
|
54993
|
+
}
|
|
54994
|
+
let decryptedBytes;
|
|
54995
|
+
try {
|
|
54996
|
+
decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
|
|
54997
|
+
} catch (err) {
|
|
54998
|
+
if (err instanceof exports.SecretStorageException) {
|
|
54999
|
+
throw err;
|
|
55000
|
+
}
|
|
55001
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55002
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
55003
|
+
}
|
|
55004
|
+
let secretStr;
|
|
55005
|
+
try {
|
|
55006
|
+
secretStr = textDecoder2.decode(decryptedBytes);
|
|
55007
|
+
} finally {
|
|
55008
|
+
zeroizeBytes(decryptedBytes);
|
|
55009
|
+
}
|
|
55010
|
+
await this.storeSecret(bundleHash, secretStr, {
|
|
55011
|
+
...options,
|
|
55012
|
+
recoveryPassphrase
|
|
55013
|
+
});
|
|
55014
|
+
}
|
|
55015
|
+
};
|
|
55016
|
+
|
|
55017
|
+
// src/storage/NonExtractableKeySecretStorageProvider.ts
|
|
55018
|
+
init_SecretStorageException();
|
|
55019
|
+
var KEY_PREFIX3 = SECRET_KEY_PREFIX;
|
|
55020
|
+
var GCM_IV_LENGTH3 = 12;
|
|
55021
|
+
var textEncoder4 = new TextEncoder();
|
|
55022
|
+
var textDecoder3 = new TextDecoder();
|
|
55023
|
+
var MemoryKeyStore = class {
|
|
55024
|
+
keys = /* @__PURE__ */ new Map();
|
|
55025
|
+
async get(name2) {
|
|
55026
|
+
return this.keys.get(name2);
|
|
55027
|
+
}
|
|
55028
|
+
async put(name2, key) {
|
|
55029
|
+
this.keys.set(name2, key);
|
|
55030
|
+
}
|
|
55031
|
+
async delete(name2) {
|
|
55032
|
+
return this.keys.delete(name2);
|
|
55033
|
+
}
|
|
55034
|
+
};
|
|
55035
|
+
var IndexedDbKeyStore = class {
|
|
55036
|
+
dbName;
|
|
55037
|
+
storeName = "keys";
|
|
55038
|
+
constructor(dbName = "knishio-secret-storage") {
|
|
55039
|
+
this.dbName = dbName;
|
|
55040
|
+
}
|
|
55041
|
+
// Executor form intentionally retained for browser runtime compatibility with ES2022 / browsers without Promise.withResolvers polyfill
|
|
55042
|
+
async getDb() {
|
|
55043
|
+
if (typeof globalThis.indexedDB === "undefined") {
|
|
55044
|
+
throw exports.SecretStorageException.unavailable(
|
|
55045
|
+
"webcrypto-nonextractable",
|
|
55046
|
+
"IndexedDB is not available"
|
|
55047
|
+
);
|
|
55048
|
+
}
|
|
55049
|
+
return new Promise((resolve, reject) => {
|
|
55050
|
+
const request = globalThis.indexedDB.open(this.dbName, 1);
|
|
55051
|
+
request.onupgradeneeded = () => {
|
|
55052
|
+
const db = request.result;
|
|
55053
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
55054
|
+
db.createObjectStore(this.storeName);
|
|
55055
|
+
}
|
|
55056
|
+
};
|
|
55057
|
+
request.onsuccess = () => resolve(request.result);
|
|
55058
|
+
request.onerror = () => reject(request.error);
|
|
55059
|
+
});
|
|
55060
|
+
}
|
|
55061
|
+
async get(name2) {
|
|
55062
|
+
const db = await this.getDb();
|
|
55063
|
+
return new Promise((resolve, reject) => {
|
|
55064
|
+
const tx = db.transaction(this.storeName, "readonly");
|
|
55065
|
+
const store = tx.objectStore(this.storeName);
|
|
55066
|
+
const request = store.get(name2);
|
|
55067
|
+
request.onsuccess = () => resolve(request.result);
|
|
55068
|
+
request.onerror = () => reject(request.error);
|
|
55069
|
+
});
|
|
55070
|
+
}
|
|
55071
|
+
async put(name2, key) {
|
|
55072
|
+
const db = await this.getDb();
|
|
55073
|
+
return new Promise((resolve, reject) => {
|
|
55074
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
55075
|
+
const store = tx.objectStore(this.storeName);
|
|
55076
|
+
const request = store.put(key, name2);
|
|
55077
|
+
request.onsuccess = () => resolve();
|
|
55078
|
+
request.onerror = () => reject(request.error);
|
|
55079
|
+
});
|
|
55080
|
+
}
|
|
55081
|
+
async delete(name2) {
|
|
55082
|
+
const db = await this.getDb();
|
|
55083
|
+
return new Promise((resolve, reject) => {
|
|
55084
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
55085
|
+
const store = tx.objectStore(this.storeName);
|
|
55086
|
+
const request = store.delete(name2);
|
|
55087
|
+
request.onsuccess = () => resolve(true);
|
|
55088
|
+
request.onerror = () => reject(request.error);
|
|
55089
|
+
});
|
|
55090
|
+
}
|
|
55091
|
+
};
|
|
55092
|
+
var NonExtractableKeySecretStorageProvider = class {
|
|
55093
|
+
providerType = "webcrypto-nonextractable";
|
|
55094
|
+
backend;
|
|
55095
|
+
keyStore;
|
|
55096
|
+
alias;
|
|
55097
|
+
cachedPassphrase;
|
|
55098
|
+
constructor(options) {
|
|
55099
|
+
this.backend = options.backend;
|
|
55100
|
+
this.keyStore = options.keyStore ?? new IndexedDbKeyStore();
|
|
55101
|
+
this.alias = options.alias ?? "default";
|
|
55102
|
+
}
|
|
55103
|
+
get recordKey() {
|
|
55104
|
+
return `knishio:kek:webcrypto-nonextractable:${this.alias}`;
|
|
55105
|
+
}
|
|
55106
|
+
get kekStoreKey() {
|
|
55107
|
+
return `knishio:kek:${this.alias}`;
|
|
55108
|
+
}
|
|
55109
|
+
isHardwareBacked() {
|
|
55110
|
+
return false;
|
|
55111
|
+
}
|
|
55112
|
+
async isAvailable() {
|
|
55113
|
+
return typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined";
|
|
55114
|
+
}
|
|
55115
|
+
/**
|
|
55116
|
+
* Unlock or initialize the device passphrase using the non-extractable KEK
|
|
55117
|
+
*/
|
|
55118
|
+
async unlock() {
|
|
55119
|
+
if (this.cachedPassphrase) {
|
|
55120
|
+
return this.cachedPassphrase;
|
|
55121
|
+
}
|
|
55122
|
+
if (!await this.isAvailable()) {
|
|
55123
|
+
throw exports.SecretStorageException.unavailable(
|
|
55124
|
+
this.providerType,
|
|
55125
|
+
"WebCrypto API is not available"
|
|
55126
|
+
);
|
|
55127
|
+
}
|
|
55128
|
+
const rawRecord = await this.backend.getItem(this.recordKey);
|
|
55129
|
+
if (!rawRecord) {
|
|
55130
|
+
let kek2 = await this.keyStore.get(this.kekStoreKey);
|
|
55131
|
+
if (!kek2) {
|
|
55132
|
+
kek2 = await globalThis.crypto.subtle.generateKey(
|
|
55133
|
+
{ name: "AES-GCM", length: 256 },
|
|
55134
|
+
false,
|
|
55135
|
+
["encrypt", "decrypt"]
|
|
55136
|
+
);
|
|
55137
|
+
await this.keyStore.put(this.kekStoreKey, kek2);
|
|
55138
|
+
}
|
|
55139
|
+
const devicePassphraseBytes = new Uint8Array(32);
|
|
55140
|
+
globalThis.crypto.getRandomValues(devicePassphraseBytes);
|
|
55141
|
+
const devicePassphrase = uint8ArrayToBase642(devicePassphraseBytes);
|
|
55142
|
+
const iv2 = new Uint8Array(GCM_IV_LENGTH3);
|
|
55143
|
+
globalThis.crypto.getRandomValues(iv2);
|
|
55144
|
+
const passphraseBytes = textEncoder4.encode(devicePassphrase);
|
|
55145
|
+
try {
|
|
55146
|
+
const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
|
|
55147
|
+
{
|
|
55148
|
+
name: "AES-GCM",
|
|
55149
|
+
iv: iv2
|
|
55150
|
+
},
|
|
55151
|
+
kek2,
|
|
55152
|
+
passphraseBytes
|
|
55153
|
+
);
|
|
55154
|
+
const record3 = {
|
|
55155
|
+
version: 1,
|
|
55156
|
+
iv: uint8ArrayToBase642(iv2),
|
|
55157
|
+
ciphertext: uint8ArrayToBase642(new Uint8Array(encryptedBuffer))
|
|
55158
|
+
};
|
|
55159
|
+
await this.backend.setItem(this.recordKey, JSON.stringify(record3));
|
|
55160
|
+
this.cachedPassphrase = devicePassphrase;
|
|
55161
|
+
return devicePassphrase;
|
|
55162
|
+
} finally {
|
|
55163
|
+
zeroizeBytes(passphraseBytes);
|
|
55164
|
+
zeroizeBytes(devicePassphraseBytes);
|
|
55165
|
+
}
|
|
55166
|
+
}
|
|
55167
|
+
let record2;
|
|
55168
|
+
try {
|
|
55169
|
+
record2 = JSON.parse(rawRecord);
|
|
55170
|
+
} catch {
|
|
55171
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted key record format");
|
|
55172
|
+
}
|
|
55173
|
+
const kek = await this.keyStore.get(this.kekStoreKey);
|
|
55174
|
+
if (!kek) {
|
|
55175
|
+
throw exports.SecretStorageException.unavailable(
|
|
55176
|
+
this.providerType,
|
|
55177
|
+
`no non-extractable key found for alias '${this.alias}'`
|
|
55178
|
+
);
|
|
55179
|
+
}
|
|
55180
|
+
const iv = base64ToUint8Array2(record2.iv);
|
|
55181
|
+
const ciphertext = base64ToUint8Array2(record2.ciphertext);
|
|
55182
|
+
try {
|
|
55183
|
+
const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
|
|
55184
|
+
{
|
|
55185
|
+
name: "AES-GCM",
|
|
55186
|
+
iv
|
|
55187
|
+
},
|
|
55188
|
+
kek,
|
|
55189
|
+
ciphertext
|
|
55190
|
+
);
|
|
55191
|
+
const decryptedBytes = new Uint8Array(decryptedBuffer);
|
|
55192
|
+
try {
|
|
55193
|
+
this.cachedPassphrase = textDecoder3.decode(decryptedBytes);
|
|
55194
|
+
return this.cachedPassphrase;
|
|
55195
|
+
} finally {
|
|
55196
|
+
zeroizeBytes(decryptedBytes);
|
|
55197
|
+
}
|
|
55198
|
+
} catch {
|
|
55199
|
+
throw exports.SecretStorageException.decryptionFailed(
|
|
55200
|
+
"wrapped device passphrase failed authentication under non-extractable key"
|
|
55201
|
+
);
|
|
55202
|
+
}
|
|
55203
|
+
}
|
|
55204
|
+
/**
|
|
55205
|
+
* Lock the provider by clearing cached passphrase material
|
|
55206
|
+
*/
|
|
55207
|
+
lock() {
|
|
55208
|
+
this.cachedPassphrase = void 0;
|
|
55209
|
+
}
|
|
55210
|
+
/**
|
|
55211
|
+
* Unenroll the non-extractable key, removing the stored wrapped record and KEK
|
|
55212
|
+
*/
|
|
55213
|
+
async unenroll() {
|
|
55214
|
+
this.lock();
|
|
55215
|
+
await this.backend.removeItem(this.recordKey);
|
|
55216
|
+
await this.keyStore.delete(this.kekStoreKey);
|
|
55217
|
+
}
|
|
55218
|
+
async storeSecret(bundleHash, secret, options) {
|
|
55219
|
+
if (!bundleHash) {
|
|
55220
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
55221
|
+
}
|
|
55222
|
+
if (!secret) {
|
|
55223
|
+
throw new exports.SecretStorageException("Secret cannot be empty");
|
|
55224
|
+
}
|
|
55225
|
+
if (options?.passphrase) {
|
|
55226
|
+
throw new exports.SecretStorageException(
|
|
55227
|
+
"NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
|
|
55228
|
+
);
|
|
55229
|
+
}
|
|
55230
|
+
if (!options?.recoveryPassphrase && !options?.allowUnrecoverable) {
|
|
55231
|
+
throw exports.SecretStorageException.validationError(
|
|
55232
|
+
"Recovery passphrase required for non-exportable hardware key unless allowUnrecoverable is true"
|
|
55233
|
+
);
|
|
55234
|
+
}
|
|
55235
|
+
const passphrase = await this.unlock();
|
|
55236
|
+
const metadata = {
|
|
55237
|
+
bundleHash,
|
|
55238
|
+
label: options?.label,
|
|
55239
|
+
createdAt: Date.now(),
|
|
55240
|
+
hardwareBacked: false,
|
|
55241
|
+
providerType: this.providerType
|
|
55242
|
+
};
|
|
55243
|
+
const payload = await sealEnvelope(secret, passphrase, metadata);
|
|
55244
|
+
await this.backend.setItem(`${KEY_PREFIX3}${bundleHash}`, JSON.stringify(payload));
|
|
55245
|
+
if (options?.recoveryPassphrase) {
|
|
55246
|
+
const recoveryMetadata = {
|
|
55247
|
+
bundleHash,
|
|
55248
|
+
label: options?.label,
|
|
55249
|
+
createdAt: Date.now(),
|
|
55250
|
+
hardwareBacked: false,
|
|
55251
|
+
providerType: "webcrypto-aes-gcm"
|
|
55252
|
+
};
|
|
55253
|
+
const recoveryPayload = await sealEnvelope(secret, options.recoveryPassphrase, recoveryMetadata);
|
|
55254
|
+
await this.backend.setItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`, JSON.stringify(recoveryPayload));
|
|
55255
|
+
}
|
|
55256
|
+
}
|
|
55257
|
+
async retrieveSecret(bundleHash, options) {
|
|
55258
|
+
if (options?.passphrase) {
|
|
55259
|
+
throw new exports.SecretStorageException(
|
|
55260
|
+
"NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
|
|
55261
|
+
);
|
|
55262
|
+
}
|
|
55263
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
|
|
55264
|
+
if (!raw) {
|
|
55265
|
+
return null;
|
|
55266
|
+
}
|
|
55267
|
+
let payload;
|
|
55268
|
+
try {
|
|
55269
|
+
payload = JSON.parse(raw);
|
|
55270
|
+
} catch {
|
|
55271
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
|
|
55272
|
+
}
|
|
55273
|
+
const passphrase = await this.unlock();
|
|
55274
|
+
try {
|
|
55275
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
55276
|
+
try {
|
|
55277
|
+
return textDecoder3.decode(decryptedBytes);
|
|
55278
|
+
} finally {
|
|
55279
|
+
zeroizeBytes(decryptedBytes);
|
|
55280
|
+
}
|
|
55281
|
+
} catch (err) {
|
|
55282
|
+
if (err instanceof exports.SecretStorageException) {
|
|
55283
|
+
throw err;
|
|
55284
|
+
}
|
|
55285
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55286
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
55287
|
+
}
|
|
55288
|
+
}
|
|
55289
|
+
async withSecret(bundleHash, fn, options) {
|
|
55290
|
+
if (options?.passphrase) {
|
|
55291
|
+
throw new exports.SecretStorageException(
|
|
55292
|
+
"NonExtractableKeySecretStorageProvider derives its passphrase from the non-extractable device key; options.passphrase is not accepted"
|
|
55293
|
+
);
|
|
55294
|
+
}
|
|
55295
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
|
|
55296
|
+
if (!raw) {
|
|
55297
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
55298
|
+
}
|
|
55299
|
+
let payload;
|
|
55300
|
+
try {
|
|
55301
|
+
payload = JSON.parse(raw);
|
|
55302
|
+
} catch {
|
|
55303
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted payload format");
|
|
55304
|
+
}
|
|
55305
|
+
const passphrase = await this.unlock();
|
|
55306
|
+
try {
|
|
55307
|
+
const decryptedBytes = await openEnvelope(payload, passphrase);
|
|
55308
|
+
return await withSecureBytes(decryptedBytes, async (bytes) => {
|
|
55309
|
+
const secretString = textDecoder3.decode(bytes);
|
|
54051
55310
|
return await fn(secretString);
|
|
54052
55311
|
});
|
|
54053
55312
|
} catch (err) {
|
|
@@ -54058,6 +55317,76 @@ ${operationTypes.join("\n")}
|
|
|
54058
55317
|
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
54059
55318
|
}
|
|
54060
55319
|
}
|
|
55320
|
+
async deleteSecret(bundleHash) {
|
|
55321
|
+
const key = `${KEY_PREFIX3}${bundleHash}`;
|
|
55322
|
+
const recoveryKey = `${RECOVERY_KEY_PREFIX}${bundleHash}`;
|
|
55323
|
+
const result = await this.backend.removeItem(key);
|
|
55324
|
+
await this.backend.removeItem(recoveryKey);
|
|
55325
|
+
return result !== false;
|
|
55326
|
+
}
|
|
55327
|
+
async hasSecret(bundleHash) {
|
|
55328
|
+
const raw = await this.backend.getItem(`${KEY_PREFIX3}${bundleHash}`);
|
|
55329
|
+
return raw !== null;
|
|
55330
|
+
}
|
|
55331
|
+
async listSecrets() {
|
|
55332
|
+
const keys = await this.backend.keys();
|
|
55333
|
+
const matchingKeys = keys.filter((k2) => k2.startsWith(KEY_PREFIX3) && !k2.startsWith(RECOVERY_KEY_PREFIX));
|
|
55334
|
+
const results = [];
|
|
55335
|
+
for (const key of matchingKeys) {
|
|
55336
|
+
const raw = await this.backend.getItem(key);
|
|
55337
|
+
if (raw) {
|
|
55338
|
+
try {
|
|
55339
|
+
const payload = JSON.parse(raw);
|
|
55340
|
+
if (payload.metadata) {
|
|
55341
|
+
results.push(payload.metadata);
|
|
55342
|
+
}
|
|
55343
|
+
} catch {
|
|
55344
|
+
}
|
|
55345
|
+
}
|
|
55346
|
+
}
|
|
55347
|
+
return results;
|
|
55348
|
+
}
|
|
55349
|
+
/**
|
|
55350
|
+
* Recover a secret using its recovery envelope and re-enroll it under a fresh non-extractable KEK
|
|
55351
|
+
*/
|
|
55352
|
+
async recoverSecret(bundleHash, recoveryPassphrase, options) {
|
|
55353
|
+
if (!bundleHash) {
|
|
55354
|
+
throw new exports.SecretStorageException("Bundle hash cannot be empty");
|
|
55355
|
+
}
|
|
55356
|
+
if (!recoveryPassphrase) {
|
|
55357
|
+
throw new exports.SecretStorageException("Recovery passphrase cannot be empty");
|
|
55358
|
+
}
|
|
55359
|
+
const raw = await this.backend.getItem(`${RECOVERY_KEY_PREFIX}${bundleHash}`);
|
|
55360
|
+
if (!raw) {
|
|
55361
|
+
throw exports.SecretStorageException.notFound(bundleHash);
|
|
55362
|
+
}
|
|
55363
|
+
let payload;
|
|
55364
|
+
try {
|
|
55365
|
+
payload = JSON.parse(raw);
|
|
55366
|
+
} catch {
|
|
55367
|
+
throw exports.SecretStorageException.decryptionFailed("Corrupted recovery payload format");
|
|
55368
|
+
}
|
|
55369
|
+
let decryptedBytes;
|
|
55370
|
+
try {
|
|
55371
|
+
decryptedBytes = await openEnvelope(payload, recoveryPassphrase);
|
|
55372
|
+
} catch (err) {
|
|
55373
|
+
if (err instanceof exports.SecretStorageException) {
|
|
55374
|
+
throw err;
|
|
55375
|
+
}
|
|
55376
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55377
|
+
throw exports.SecretStorageException.decryptionFailed(msg);
|
|
55378
|
+
}
|
|
55379
|
+
let secretStr;
|
|
55380
|
+
try {
|
|
55381
|
+
secretStr = textDecoder3.decode(decryptedBytes);
|
|
55382
|
+
} finally {
|
|
55383
|
+
zeroizeBytes(decryptedBytes);
|
|
55384
|
+
}
|
|
55385
|
+
await this.storeSecret(bundleHash, secretStr, {
|
|
55386
|
+
...options,
|
|
55387
|
+
recoveryPassphrase
|
|
55388
|
+
});
|
|
55389
|
+
}
|
|
54061
55390
|
};
|
|
54062
55391
|
|
|
54063
55392
|
// src/storage/index.ts
|
|
@@ -54068,15 +55397,14 @@ ${operationTypes.join("\n")}
|
|
|
54068
55397
|
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined") {
|
|
54069
55398
|
return new WebCryptoSecretStorageProvider({
|
|
54070
55399
|
backend: options.backend,
|
|
54071
|
-
defaultPassphrase: options.defaultPassphrase
|
|
54072
|
-
hardwareBacked: options.hardwareBacked
|
|
55400
|
+
defaultPassphrase: options.defaultPassphrase
|
|
54073
55401
|
});
|
|
54074
55402
|
}
|
|
54075
55403
|
return new MemorySecretStorageProvider();
|
|
54076
55404
|
}
|
|
54077
55405
|
|
|
54078
55406
|
// src/index.ts
|
|
54079
|
-
var SDK_VERSION = "
|
|
55407
|
+
var SDK_VERSION = "1.1.0";
|
|
54080
55408
|
var SDK_NAME = "KnishIO-Client-TS";
|
|
54081
55409
|
var COMPATIBLE_SERVER_VERSIONS = [4, 5];
|
|
54082
55410
|
var SDK_INFO = {
|
|
@@ -54085,7 +55413,7 @@ ${operationTypes.join("\n")}
|
|
|
54085
55413
|
description: "TypeScript SDK for Knish.IO post-blockchain distributed ledger",
|
|
54086
55414
|
compatibleServerVersions: COMPATIBLE_SERVER_VERSIONS,
|
|
54087
55415
|
features: [
|
|
54088
|
-
"Post-quantum cryptography (XMSS, ML-
|
|
55416
|
+
"Post-quantum cryptography (XMSS, ML-KEM-1024)",
|
|
54089
55417
|
"Cross-platform compatibility",
|
|
54090
55418
|
"Type-safe APIs",
|
|
54091
55419
|
"DAG-based transaction processing",
|
|
@@ -54184,9 +55512,12 @@ ${operationTypes.join("\n")}
|
|
|
54184
55512
|
exports.CheckMolecule = CheckMolecule;
|
|
54185
55513
|
exports.DevUtils = DevUtils;
|
|
54186
55514
|
exports.EXTENDED_COMPATIBILITY_TEST_VECTORS = EXTENDED_COMPATIBILITY_TEST_VECTORS;
|
|
55515
|
+
exports.FileStorageBackend = FileStorageBackend;
|
|
54187
55516
|
exports.GraphQLClient = GraphQLClient;
|
|
55517
|
+
exports.IndexedDbKeyStore = IndexedDbKeyStore;
|
|
54188
55518
|
exports.KnishIO = KnishIO;
|
|
54189
55519
|
exports.KnishIOClient = KnishIOClient;
|
|
55520
|
+
exports.MemoryKeyStore = MemoryKeyStore;
|
|
54190
55521
|
exports.MemorySecretStorageProvider = MemorySecretStorageProvider;
|
|
54191
55522
|
exports.MemoryStorageBackend = MemoryStorageBackend;
|
|
54192
55523
|
exports.Meta = Meta;
|
|
@@ -54201,6 +55532,7 @@ ${operationTypes.join("\n")}
|
|
|
54201
55532
|
exports.MutationRequestAuthorization = MutationRequestAuthorization;
|
|
54202
55533
|
exports.MutationRequestTokens = MutationRequestTokens;
|
|
54203
55534
|
exports.MutationTransferTokens = MutationTransferTokens;
|
|
55535
|
+
exports.NonExtractableKeySecretStorageProvider = NonExtractableKeySecretStorageProvider;
|
|
54204
55536
|
exports.PolicyMeta = PolicyMeta;
|
|
54205
55537
|
exports.Query = Query;
|
|
54206
55538
|
exports.QueryAtom = QueryAtom;
|
|
@@ -54212,6 +55544,7 @@ ${operationTypes.join("\n")}
|
|
|
54212
55544
|
exports.QueryMetaTypeViaAtom = QueryMetaTypeViaAtom;
|
|
54213
55545
|
exports.QueryWalletBundle = QueryWalletBundle;
|
|
54214
55546
|
exports.QueryWalletList = QueryWalletList;
|
|
55547
|
+
exports.RECOVERY_KEY_PREFIX = RECOVERY_KEY_PREFIX;
|
|
54215
55548
|
exports.ResponseAppendRequest = ResponseAppendRequest;
|
|
54216
55549
|
exports.ResponseAtom = ResponseAtom;
|
|
54217
55550
|
exports.ResponseBalance = ResponseBalance;
|
|
@@ -54232,9 +55565,12 @@ ${operationTypes.join("\n")}
|
|
|
54232
55565
|
exports.SDK_INFO = SDK_INFO;
|
|
54233
55566
|
exports.SDK_NAME = SDK_NAME;
|
|
54234
55567
|
exports.SDK_VERSION = SDK_VERSION;
|
|
55568
|
+
exports.SECRET_KEY_PREFIX = SECRET_KEY_PREFIX;
|
|
54235
55569
|
exports.TokenUnit = TokenUnit;
|
|
54236
55570
|
exports.Wallet = Wallet;
|
|
55571
|
+
exports.WebAuthnPrfSecretStorageProvider = WebAuthnPrfSecretStorageProvider;
|
|
54237
55572
|
exports.WebCryptoSecretStorageProvider = WebCryptoSecretStorageProvider;
|
|
55573
|
+
exports.WebStorageBackend = WebStorageBackend;
|
|
54238
55574
|
exports.base64ToHex = base64ToHex;
|
|
54239
55575
|
exports.bufferToHexString = bufferToHexString;
|
|
54240
55576
|
exports.capitalize = capitalize;
|
|
@@ -54273,9 +55609,11 @@ ${operationTypes.join("\n")}
|
|
|
54273
55609
|
exports.isPosition = isPosition2;
|
|
54274
55610
|
exports.isWalletAddress = isWalletAddress2;
|
|
54275
55611
|
exports.normalizeMolecularHash = normalizeMolecularHash;
|
|
55612
|
+
exports.openEnvelope = openEnvelope;
|
|
54276
55613
|
exports.randomString = randomString;
|
|
54277
55614
|
exports.runCompatibilityTests = runCompatibilityTests;
|
|
54278
55615
|
exports.runExtendedCompatibilityTests = runExtendedCompatibilityTests;
|
|
55616
|
+
exports.sealEnvelope = sealEnvelope;
|
|
54279
55617
|
exports.shake256 = shake256;
|
|
54280
55618
|
exports.toCamelCase = toCamelCase;
|
|
54281
55619
|
exports.toSnakeCase = toSnakeCase;
|