@storacha/encrypt-upload-client 0.0.39 → 1.0.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/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +19 -6
- package/dist/core/client.d.ts +8 -12
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +12 -21
- package/dist/core/metadata/encrypted-metadata.d.ts +8 -0
- package/dist/core/metadata/encrypted-metadata.d.ts.map +1 -0
- package/dist/core/metadata/encrypted-metadata.js +69 -0
- package/dist/core/metadata/kms-metadata.d.ts +36 -0
- package/dist/core/metadata/kms-metadata.d.ts.map +1 -0
- package/dist/core/metadata/kms-metadata.js +156 -0
- package/dist/core/{encrypted-metadata.d.ts → metadata/lit-metadata.d.ts} +11 -11
- package/dist/core/metadata/lit-metadata.d.ts.map +1 -0
- package/dist/core/{encrypted-metadata.js → metadata/lit-metadata.js} +32 -42
- package/dist/crypto/adapters/kms-crypto-adapter.d.ts +148 -0
- package/dist/crypto/adapters/kms-crypto-adapter.d.ts.map +1 -0
- package/dist/crypto/adapters/kms-crypto-adapter.js +321 -0
- package/dist/crypto/adapters/lit-crypto-adapter.d.ts +96 -0
- package/dist/crypto/adapters/lit-crypto-adapter.d.ts.map +1 -0
- package/dist/crypto/adapters/lit-crypto-adapter.js +210 -0
- package/dist/crypto/factories.browser.d.ts +11 -0
- package/dist/crypto/factories.browser.d.ts.map +1 -0
- package/dist/crypto/factories.browser.js +16 -0
- package/dist/crypto/factories.node.d.ts +26 -0
- package/dist/crypto/factories.node.d.ts.map +1 -0
- package/dist/crypto/factories.node.js +38 -0
- package/dist/crypto/index.d.ts +5 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +7 -0
- package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.d.ts +76 -0
- package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.d.ts.map +1 -0
- package/dist/crypto/symmetric/generic-aes-ctr-streaming-crypto.js +177 -0
- package/dist/crypto/symmetric/node-aes-cbc-crypto.d.ts +43 -0
- package/dist/crypto/symmetric/node-aes-cbc-crypto.d.ts.map +1 -0
- package/dist/crypto/symmetric/node-aes-cbc-crypto.js +110 -0
- package/dist/handlers/decrypt-handler.d.ts +9 -4
- package/dist/handlers/decrypt-handler.d.ts.map +1 -1
- package/dist/handlers/decrypt-handler.js +62 -93
- package/dist/handlers/encrypt-handler.d.ts +1 -1
- package/dist/handlers/encrypt-handler.d.ts.map +1 -1
- package/dist/handlers/encrypt-handler.js +31 -41
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/protocols/lit.d.ts +1 -3
- package/dist/protocols/lit.d.ts.map +1 -1
- package/dist/types.d.ts +135 -20
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -18
- package/dist/core/encrypted-metadata.d.ts.map +0 -1
- package/dist/crypto-adapters/browser-crypto-adapter.d.ts +0 -42
- package/dist/crypto-adapters/browser-crypto-adapter.d.ts.map +0 -1
- package/dist/crypto-adapters/browser-crypto-adapter.js +0 -109
- package/dist/crypto-adapters/node-crypto-adapter.d.ts +0 -17
- package/dist/crypto-adapters/node-crypto-adapter.d.ts.map +0 -1
- package/dist/crypto-adapters/node-crypto-adapter.js +0 -66
|
@@ -3,11 +3,11 @@ import * as dagCBOR from '@ipld/dag-cbor';
|
|
|
3
3
|
import * as Link from 'multiformats/link';
|
|
4
4
|
import { sha256 } from 'multiformats/hashes/sha2';
|
|
5
5
|
import { CAR, ok, error, Schema } from '@ucanto/core';
|
|
6
|
-
import * as Types from '
|
|
7
|
-
import { UnknownFormat } from '
|
|
8
|
-
import { stringToBytes, bytesToString } from '
|
|
6
|
+
import * as Types from '../../types.js';
|
|
7
|
+
import { UnknownFormat } from '../errors.js';
|
|
8
|
+
import { stringToBytes, bytesToString } from '../../utils.js';
|
|
9
9
|
export const version = 'encrypted-metadata@0.1';
|
|
10
|
-
export const
|
|
10
|
+
export const LitMetadataSchema = Schema.variant({
|
|
11
11
|
[version]: Schema.struct({
|
|
12
12
|
encryptedDataCID: Schema.link(),
|
|
13
13
|
identityBoundCiphertext: Schema.bytes(),
|
|
@@ -18,7 +18,7 @@ export const EncryptedMetadataSchema = Schema.variant({
|
|
|
18
18
|
}).array(),
|
|
19
19
|
}),
|
|
20
20
|
});
|
|
21
|
-
export const
|
|
21
|
+
export const LitMetadataInputSchema = Schema.struct({
|
|
22
22
|
encryptedDataCID: Schema.string(),
|
|
23
23
|
identityBoundCiphertext: Schema.string(),
|
|
24
24
|
plaintextKeyHash: Schema.string(),
|
|
@@ -27,22 +27,22 @@ export const EncryptedMetadataInputSchema = Schema.struct({
|
|
|
27
27
|
value: Schema.unknown(),
|
|
28
28
|
}).array(),
|
|
29
29
|
});
|
|
30
|
-
/** @implements {Types.
|
|
31
|
-
class
|
|
30
|
+
/** @implements {Types.LitMetadataView} */
|
|
31
|
+
class LitEncryptedMetadata {
|
|
32
32
|
#encryptedDataCID;
|
|
33
33
|
#identityBoundCiphertext;
|
|
34
34
|
#plaintextKeyHash;
|
|
35
35
|
#accessControlConditions;
|
|
36
|
-
/** @param {Types.
|
|
36
|
+
/** @param {Types.LitMetadata|Types.LitMetadataInput} encryptedMetadataInput */
|
|
37
37
|
constructor(encryptedMetadataInput) {
|
|
38
|
-
/** @type {Types.
|
|
38
|
+
/** @type {Types.LitMetadata} */
|
|
39
39
|
let encryptedMetadata;
|
|
40
|
-
if (
|
|
40
|
+
if (LitMetadataInputSchema.is(encryptedMetadataInput)) {
|
|
41
41
|
encryptedMetadata = parse(
|
|
42
|
-
/** @type {Types.
|
|
42
|
+
/** @type {Types.LitMetadataInput} */ (encryptedMetadataInput));
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
encryptedMetadata = /** @type {Types.
|
|
45
|
+
encryptedMetadata = /** @type {Types.LitMetadata} */ (encryptedMetadataInput);
|
|
46
46
|
}
|
|
47
47
|
this.#encryptedDataCID = encryptedMetadata.encryptedDataCID;
|
|
48
48
|
this.#identityBoundCiphertext = encryptedMetadata.identityBoundCiphertext;
|
|
@@ -62,18 +62,8 @@ class EncryptedMetadata {
|
|
|
62
62
|
get accessControlConditions() {
|
|
63
63
|
return this.#accessControlConditions;
|
|
64
64
|
}
|
|
65
|
-
archive() {
|
|
66
|
-
/** @type {Types.EncryptedMetadata} */
|
|
67
|
-
const input = {
|
|
68
|
-
encryptedDataCID: this.encryptedDataCID,
|
|
69
|
-
identityBoundCiphertext: this.identityBoundCiphertext,
|
|
70
|
-
plaintextKeyHash: this.plaintextKeyHash,
|
|
71
|
-
accessControlConditions: this.accessControlConditions,
|
|
72
|
-
};
|
|
73
|
-
return archive(input);
|
|
74
|
-
}
|
|
75
65
|
archiveBlock() {
|
|
76
|
-
/** @type {Types.
|
|
66
|
+
/** @type {Types.LitMetadata} */
|
|
77
67
|
const input = {
|
|
78
68
|
encryptedDataCID: this.encryptedDataCID,
|
|
79
69
|
identityBoundCiphertext: this.identityBoundCiphertext,
|
|
@@ -82,19 +72,19 @@ class EncryptedMetadata {
|
|
|
82
72
|
};
|
|
83
73
|
return archiveBlock(input);
|
|
84
74
|
}
|
|
85
|
-
/** @returns {Types.
|
|
75
|
+
/** @returns {Types.LitMetadataInput} */
|
|
86
76
|
toJSON() {
|
|
87
77
|
return toJSON(this);
|
|
88
78
|
}
|
|
89
79
|
}
|
|
90
80
|
/**
|
|
91
|
-
* @param {Types.
|
|
92
|
-
* @returns {Types.
|
|
81
|
+
* @param {Types.LitMetadata|Types.LitMetadataInput} encryptedMetadataInput
|
|
82
|
+
* @returns {Types.LitMetadataView}
|
|
93
83
|
*/
|
|
94
|
-
export const create = (encryptedMetadataInput) => new
|
|
84
|
+
export const create = (encryptedMetadataInput) => new LitEncryptedMetadata(encryptedMetadataInput);
|
|
95
85
|
/**
|
|
96
|
-
* @param {Types.
|
|
97
|
-
* @returns {Types.
|
|
86
|
+
* @param {Types.LitMetadataView} encryptedMetadata
|
|
87
|
+
* @returns {Types.LitMetadataInput}
|
|
98
88
|
*/
|
|
99
89
|
export const toJSON = (encryptedMetadata) => ({
|
|
100
90
|
encryptedDataCID: encryptedMetadata.encryptedDataCID.toString(),
|
|
@@ -103,8 +93,8 @@ export const toJSON = (encryptedMetadata) => ({
|
|
|
103
93
|
accessControlConditions: encryptedMetadata.accessControlConditions,
|
|
104
94
|
});
|
|
105
95
|
/**
|
|
106
|
-
* @param {Types.
|
|
107
|
-
* @returns {Types.
|
|
96
|
+
* @param {Types.LitMetadataInput} encryptedMetadataInput
|
|
97
|
+
* @returns {Types.LitMetadata}
|
|
108
98
|
*/
|
|
109
99
|
export const parse = (encryptedMetadataInput) => ({
|
|
110
100
|
encryptedDataCID: CID.parse(encryptedMetadataInput.encryptedDataCID),
|
|
@@ -113,7 +103,7 @@ export const parse = (encryptedMetadataInput) => ({
|
|
|
113
103
|
accessControlConditions: encryptedMetadataInput.accessControlConditions,
|
|
114
104
|
});
|
|
115
105
|
/**
|
|
116
|
-
* @param {Types.
|
|
106
|
+
* @param {Types.LitMetadata} encryptedMetadataInput
|
|
117
107
|
* @returns {Promise<import('@ucanto/interface').Block>}
|
|
118
108
|
*/
|
|
119
109
|
export const archiveBlock = async (encryptedMetadataInput) => {
|
|
@@ -123,16 +113,16 @@ export const archiveBlock = async (encryptedMetadataInput) => {
|
|
|
123
113
|
return { cid, bytes };
|
|
124
114
|
};
|
|
125
115
|
/**
|
|
126
|
-
* @param {Types.
|
|
116
|
+
* @param {Types.LitMetadata} encryptedMetadata
|
|
127
117
|
* @returns {Promise<Types.Result<Uint8Array>>}
|
|
128
118
|
*/
|
|
129
|
-
export const archive = async (
|
|
130
|
-
const block = await archiveBlock(
|
|
119
|
+
export const archive = async (encryptedMetadata) => {
|
|
120
|
+
const block = await archiveBlock(encryptedMetadata);
|
|
131
121
|
return ok(CAR.encode({ roots: [block] }));
|
|
132
122
|
};
|
|
133
123
|
/**
|
|
134
124
|
* @param {Uint8Array} archive
|
|
135
|
-
* @returns {Types.Result<Types.
|
|
125
|
+
* @returns {Types.Result<Types.LitMetadataView, Types.UnknownFormat>}
|
|
136
126
|
*/
|
|
137
127
|
export const extract = (archive) => {
|
|
138
128
|
const { roots } = CAR.decode(archive);
|
|
@@ -148,19 +138,19 @@ export const extract = (archive) => {
|
|
|
148
138
|
/**
|
|
149
139
|
* @param {object} source
|
|
150
140
|
* @param {Types.IPLDBlock} source.root
|
|
151
|
-
* @returns {Types.Result<Types.
|
|
141
|
+
* @returns {Types.Result<Types.LitMetadataView, Types.UnknownFormat>}
|
|
152
142
|
*/
|
|
153
143
|
export const view = ({ root }) => {
|
|
154
144
|
const value = dagCBOR.decode(root.bytes);
|
|
155
|
-
const [
|
|
156
|
-
switch (
|
|
145
|
+
const [matchedVersion, encryptedMetadataData] = LitMetadataSchema.match(value);
|
|
146
|
+
switch (matchedVersion) {
|
|
157
147
|
case version: {
|
|
158
148
|
const encryptedMetadata = create(
|
|
159
|
-
/** @type {Types.
|
|
149
|
+
/** @type {Types.LitMetadata}*/ (encryptedMetadataData));
|
|
160
150
|
return ok(encryptedMetadata);
|
|
161
151
|
}
|
|
162
152
|
default:
|
|
163
|
-
return error(new UnknownFormat(`unknown
|
|
153
|
+
return error(new UnknownFormat(`unknown Lit metadata version: ${matchedVersion}`));
|
|
164
154
|
}
|
|
165
155
|
};
|
|
166
|
-
//# sourceMappingURL=
|
|
156
|
+
//# sourceMappingURL=lit-metadata.js.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KMSCryptoAdapter implements the complete CryptoAdapter interface using KMS.
|
|
3
|
+
* It uses composition with a SymmetricCrypto implementation for file encryption/decryption
|
|
4
|
+
* and KMS via private gateway for key management operations.
|
|
5
|
+
*
|
|
6
|
+
* @class
|
|
7
|
+
* @implements {Type.CryptoAdapter}
|
|
8
|
+
*/
|
|
9
|
+
export class KMSCryptoAdapter implements Type.CryptoAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new KMS crypto adapter
|
|
12
|
+
*
|
|
13
|
+
* @param {Type.SymmetricCrypto} symmetricCrypto - The symmetric crypto implementation (browser or node)
|
|
14
|
+
* @param {URL|string} keyManagerServiceURL - The key manager service URL
|
|
15
|
+
* @param {`did:${string}:${string}`} keyManagerServiceDID - The key manager service DID
|
|
16
|
+
* @param {object} [options] - Optional configuration
|
|
17
|
+
* @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production)
|
|
18
|
+
*/
|
|
19
|
+
constructor(symmetricCrypto: Type.SymmetricCrypto, keyManagerServiceURL: URL | string, keyManagerServiceDID: `did:${string}:${string}`, options?: {
|
|
20
|
+
allowInsecureHttp?: boolean | undefined;
|
|
21
|
+
});
|
|
22
|
+
symmetricCrypto: Type.SymmetricCrypto;
|
|
23
|
+
keyManagerServiceURL: URL;
|
|
24
|
+
keyManagerServiceDID: import("@ucanto/client").PrincipalView<`did:${string}:${string}`>;
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt a stream of data using the symmetric crypto
|
|
27
|
+
*
|
|
28
|
+
* @param {Type.BlobLike} data
|
|
29
|
+
*/
|
|
30
|
+
encryptStream(data: Type.BlobLike): Promise<Type.EncryptOutput>;
|
|
31
|
+
/**
|
|
32
|
+
* Decrypt a stream of data using the symmetric crypto
|
|
33
|
+
*
|
|
34
|
+
* @param {ReadableStream} encryptedData
|
|
35
|
+
* @param {Uint8Array} key
|
|
36
|
+
* @param {Uint8Array} iv
|
|
37
|
+
*/
|
|
38
|
+
decryptStream(encryptedData: ReadableStream, key: Uint8Array, iv: Uint8Array): Promise<ReadableStream<any>>;
|
|
39
|
+
/**
|
|
40
|
+
* Encrypt a symmetric key using the KMS
|
|
41
|
+
*
|
|
42
|
+
* @param {Uint8Array} key
|
|
43
|
+
* @param {Uint8Array} iv
|
|
44
|
+
* @param {Type.EncryptionConfig} encryptionConfig
|
|
45
|
+
* @returns {Promise<Type.EncryptedKeyResult>}
|
|
46
|
+
*/
|
|
47
|
+
encryptSymmetricKey(key: Uint8Array, iv: Uint8Array, encryptionConfig: Type.EncryptionConfig): Promise<Type.EncryptedKeyResult>;
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} encryptedKey
|
|
50
|
+
* @param {object} configs
|
|
51
|
+
* @param {Type.DecryptionOptions} configs.decryptionOptions
|
|
52
|
+
* @param {Type.ExtractedMetadata} configs.metadata
|
|
53
|
+
* @param {Uint8Array} configs.delegationCAR
|
|
54
|
+
* @param {Type.AnyLink} configs.resourceCID
|
|
55
|
+
* @param {import('@storacha/client/types').Signer<import('@storacha/client/types').DID, import('@storacha/client/types').SigAlg>} configs.issuer
|
|
56
|
+
* @param {import('@storacha/client/types').DID} configs.audience
|
|
57
|
+
*/
|
|
58
|
+
decryptSymmetricKey(encryptedKey: string, configs: {
|
|
59
|
+
decryptionOptions: Type.DecryptionOptions;
|
|
60
|
+
metadata: Type.ExtractedMetadata;
|
|
61
|
+
delegationCAR: Uint8Array;
|
|
62
|
+
resourceCID: Type.AnyLink;
|
|
63
|
+
issuer: import("@storacha/client/types").Signer<import("@storacha/client/types").DID, import("@storacha/client/types").SigAlg>;
|
|
64
|
+
audience: import("@storacha/client/types").DID;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
key: Uint8Array;
|
|
67
|
+
iv: Uint8Array;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Get decrypted symmetric key in base64 string from KMS via private gateway
|
|
71
|
+
*
|
|
72
|
+
* @param {string} encryptedSymmetricKey - The encrypted symmetric key (base64-encoded)
|
|
73
|
+
* @param {Type.SpaceDID} spaceDID - The space DID
|
|
74
|
+
* @param {import('@ucanto/interface').Proof} delegationProof - The delegation proof
|
|
75
|
+
* @param {import('@storacha/client/types').Signer<import('@storacha/client/types').DID, import('@storacha/client/types').SigAlg>} issuer - The issuer
|
|
76
|
+
* @returns {Promise<{decryptedSymmetricKey: string}>} - The decrypted symmetric key (base64-encoded)
|
|
77
|
+
*/
|
|
78
|
+
getDecryptedSymmetricKey(encryptedSymmetricKey: string, spaceDID: Type.SpaceDID, delegationProof: import("@ucanto/interface").Proof, issuer: import("@storacha/client/types").Signer<import("@storacha/client/types").DID, import("@storacha/client/types").SigAlg>): Promise<{
|
|
79
|
+
decryptedSymmetricKey: string;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Extract the encrypted metadata from the CAR file
|
|
83
|
+
* KMS adapter only handles KMS format (encrypted-metadata@0.2)
|
|
84
|
+
*
|
|
85
|
+
* @param {Uint8Array} car
|
|
86
|
+
* @returns {Type.ExtractedMetadata}
|
|
87
|
+
*/
|
|
88
|
+
extractEncryptedMetadata(car: Uint8Array): Type.ExtractedMetadata;
|
|
89
|
+
/**
|
|
90
|
+
* @param {Type.ExtractedMetadata} metadata
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
getEncryptedKey(metadata: Type.ExtractedMetadata): string;
|
|
94
|
+
/**
|
|
95
|
+
* Encode metadata for upload
|
|
96
|
+
*
|
|
97
|
+
* @param {string} encryptedDataCID - The CID of the encrypted data
|
|
98
|
+
* @param {string} encryptedKey - The encrypted key
|
|
99
|
+
* @param {Type.KMSKeyMetadata} metadata - The metadata to encode
|
|
100
|
+
* @returns {Promise<{ cid: import('@storacha/upload-client/types').AnyLink, bytes: Uint8Array }>} - The encoded metadata
|
|
101
|
+
*/
|
|
102
|
+
encodeMetadata(encryptedDataCID: string, encryptedKey: string, metadata: Type.KMSKeyMetadata): Promise<{
|
|
103
|
+
cid: import("@storacha/upload-client/types").AnyLink;
|
|
104
|
+
bytes: Uint8Array;
|
|
105
|
+
}>;
|
|
106
|
+
/**
|
|
107
|
+
* Get the RSA public key from the space/encryption/setup
|
|
108
|
+
*
|
|
109
|
+
* @param {Type.EncryptionConfig} encryptionConfig
|
|
110
|
+
* @returns {Promise<{ publicKey: string, provider: string, algorithm: string }>}
|
|
111
|
+
*/
|
|
112
|
+
getSpacePublicKey(encryptionConfig: Type.EncryptionConfig): Promise<{
|
|
113
|
+
publicKey: string;
|
|
114
|
+
provider: string;
|
|
115
|
+
algorithm: string;
|
|
116
|
+
}>;
|
|
117
|
+
/**
|
|
118
|
+
* Get the Web Crypto API SubtleCrypto interface (universal compatibility)
|
|
119
|
+
*
|
|
120
|
+
* @returns {SubtleCrypto} - The SubtleCrypto interface
|
|
121
|
+
*/
|
|
122
|
+
getSubtleCrypto(): SubtleCrypto;
|
|
123
|
+
/**
|
|
124
|
+
* Encrypt data with RSA-OAEP using the public key
|
|
125
|
+
*
|
|
126
|
+
* @param {Uint8Array} dataToEncrypt
|
|
127
|
+
* @param {string} publicKeyPem
|
|
128
|
+
* @returns {Promise<Uint8Array>}
|
|
129
|
+
*/
|
|
130
|
+
encryptWithRSA(dataToEncrypt: Uint8Array, publicKeyPem: string): Promise<Uint8Array>;
|
|
131
|
+
/**
|
|
132
|
+
* Convert PEM-encoded public key to ArrayBuffer for Web Crypto API
|
|
133
|
+
*
|
|
134
|
+
* @param {string} pem - PEM-encoded public key string
|
|
135
|
+
* @returns {ArrayBuffer} - DER-encoded key data for crypto.subtle.importKey()
|
|
136
|
+
*/
|
|
137
|
+
pemToArrayBuffer(pem: string): ArrayBuffer;
|
|
138
|
+
newKeyManagerServiceConnection(): import("@ucanto/interface").ConnectionView<any>;
|
|
139
|
+
/**
|
|
140
|
+
* Sanitize the space DID for the KMS key ID
|
|
141
|
+
*
|
|
142
|
+
* @param {Type.SpaceDID} spaceDID
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
sanitizeSpaceDIDForKMSKeyId(spaceDID: Type.SpaceDID): string;
|
|
146
|
+
}
|
|
147
|
+
import * as Type from '../../types.js';
|
|
148
|
+
//# sourceMappingURL=kms-crypto-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kms-crypto-adapter.d.ts","sourceRoot":"","sources":["../../../src/crypto/adapters/kms-crypto-adapter.js"],"names":[],"mappings":"AAWA;;;;;;;GAOG;AACH,yCAFgB,IAAI,CAAC,aAAa;IAGhC;;;;;;;;OAQG;IACH,6BANW,IAAI,CAAC,eAAe,wBACpB,GAAG,GAAC,MAAM,wBACV,OAAO,MAAM,IAAI,MAAM,EAAE,YAEjC;QAA0B,iBAAiB;KAC7C,EA0BA;IAnBC,sCAAsC;IAiBtC,0BAA+B;IAC/B,wFAA2D;IAG7D;;;;OAIG;IACH,oBAFW,IAAI,CAAC,QAAQ,+BAIvB;IAED;;;;;;OAMG;IACH,6BAJW,cAAc,OACd,UAAU,MACV,UAAU,gCAIpB;IAED;;;;;;;OAOG;IACH,yBALW,UAAU,MACV,UAAU,oBACV,IAAI,CAAC,gBAAgB,GACnB,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CA4B5C;IAED;;;;;;;;;OASG;IACH,kCATW,MAAM,WAEd;QAAwC,iBAAiB,EAAjD,IAAI,CAAC,iBAAiB;QACU,QAAQ,EAAxC,IAAI,CAAC,iBAAiB;QACF,aAAa,EAAjC,UAAU;QACY,WAAW,EAAjC,IAAI,CAAC,OAAO;QACoH,MAAM,EAAtI,OAAO,wBAAwB,EAAE,MAAM,CAAC,OAAO,wBAAwB,EAAE,GAAG,EAAE,OAAO,wBAAwB,EAAE,MAAM,CAAC;QACxE,QAAQ,EAAtD,OAAO,wBAAwB,EAAE,GAAG;KAC9C;;;OA4BA;IAED;;;;;;;;OAQG;IACH,gDANW,MAAM,YACN,IAAI,CAAC,QAAQ,mBACb,OAAO,mBAAmB,EAAE,KAAK,UACjC,OAAO,wBAAwB,EAAE,MAAM,CAAC,OAAO,wBAAwB,EAAE,GAAG,EAAE,OAAO,wBAAwB,EAAE,MAAM,CAAC,GACpH,OAAO,CAAC;QAAC,qBAAqB,EAAE,MAAM,CAAA;KAAC,CAAC,CA4BpD;IAED;;;;;;OAMG;IACH,8BAHW,UAAU,GACR,IAAI,CAAC,iBAAiB,CAiClC;IAED;;;OAGG;IACH,0BAHW,IAAI,CAAC,iBAAiB,GACpB,MAAM,CASlB;IAED;;;;;;;OAOG;IACH,iCALW,MAAM,gBACN,MAAM,YACN,IAAI,CAAC,cAAc,GACjB,OAAO,CAAC;QAAE,GAAG,EAAE,OAAO,+BAA+B,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,CAAC,CAoBhG;IAED;;;;;OAKG;IACH,oCAHW,IAAI,CAAC,gBAAgB,GACnB,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAyB/E;IAED;;;;OAIG;IACH,mBAFa,YAAY,CAWxB;IAED;;;;;;OAMG;IACH,8BAJW,UAAU,gBACV,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAyB/B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,WAAW,CA6BvB;IAED,kFASC;IAED;;;;;OAKG;IACH,sCAHW,IAAI,CAAC,QAAQ,GACX,MAAM,CAIlB;CACF;sBAhZqB,gBAAgB"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { connect } from '@ucanto/client';
|
|
2
|
+
import { CAR, HTTP } from '@ucanto/transport';
|
|
3
|
+
import { base64 } from 'multiformats/bases/base64';
|
|
4
|
+
import * as Type from '../../types.js';
|
|
5
|
+
import { EncryptionSetup, EncryptionKeyDecrypt, } from '@storacha/capabilities/space';
|
|
6
|
+
import { KMSMetadata } from '../../core/metadata/encrypted-metadata.js';
|
|
7
|
+
import * as DID from '@ipld/dag-ucan/did';
|
|
8
|
+
/**
|
|
9
|
+
* KMSCryptoAdapter implements the complete CryptoAdapter interface using KMS.
|
|
10
|
+
* It uses composition with a SymmetricCrypto implementation for file encryption/decryption
|
|
11
|
+
* and KMS via private gateway for key management operations.
|
|
12
|
+
*
|
|
13
|
+
* @class
|
|
14
|
+
* @implements {Type.CryptoAdapter}
|
|
15
|
+
*/
|
|
16
|
+
export class KMSCryptoAdapter {
|
|
17
|
+
/**
|
|
18
|
+
* Create a new KMS crypto adapter
|
|
19
|
+
*
|
|
20
|
+
* @param {Type.SymmetricCrypto} symmetricCrypto - The symmetric crypto implementation (browser or node)
|
|
21
|
+
* @param {URL|string} keyManagerServiceURL - The key manager service URL
|
|
22
|
+
* @param {`did:${string}:${string}`} keyManagerServiceDID - The key manager service DID
|
|
23
|
+
* @param {object} [options] - Optional configuration
|
|
24
|
+
* @param {boolean} [options.allowInsecureHttp] - Allow HTTP for testing (NOT for production)
|
|
25
|
+
*/
|
|
26
|
+
constructor(symmetricCrypto, keyManagerServiceURL, keyManagerServiceDID, options = {}) {
|
|
27
|
+
this.symmetricCrypto = symmetricCrypto;
|
|
28
|
+
// SECURITY: Enforce HTTPS protocol for key manager service communications (P1.1)
|
|
29
|
+
const url = keyManagerServiceURL instanceof URL
|
|
30
|
+
? keyManagerServiceURL
|
|
31
|
+
: new URL(keyManagerServiceURL);
|
|
32
|
+
const { allowInsecureHttp = false } = options;
|
|
33
|
+
if (url.protocol !== 'https:' && !allowInsecureHttp) {
|
|
34
|
+
throw new Error(`Key manager service must use HTTPS protocol for security. Received: ${url.protocol}. ` +
|
|
35
|
+
`Please update the service URL to use HTTPS (e.g., https://your-key-manager-service.com). ` +
|
|
36
|
+
`For testing only, you can pass { allowInsecureHttp: true } as the fourth parameter.`);
|
|
37
|
+
}
|
|
38
|
+
this.keyManagerServiceURL = url;
|
|
39
|
+
this.keyManagerServiceDID = DID.parse(keyManagerServiceDID);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Encrypt a stream of data using the symmetric crypto
|
|
43
|
+
*
|
|
44
|
+
* @param {Type.BlobLike} data
|
|
45
|
+
*/
|
|
46
|
+
async encryptStream(data) {
|
|
47
|
+
return this.symmetricCrypto.encryptStream(data);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Decrypt a stream of data using the symmetric crypto
|
|
51
|
+
*
|
|
52
|
+
* @param {ReadableStream} encryptedData
|
|
53
|
+
* @param {Uint8Array} key
|
|
54
|
+
* @param {Uint8Array} iv
|
|
55
|
+
*/
|
|
56
|
+
async decryptStream(encryptedData, key, iv) {
|
|
57
|
+
return this.symmetricCrypto.decryptStream(encryptedData, key, iv);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Encrypt a symmetric key using the KMS
|
|
61
|
+
*
|
|
62
|
+
* @param {Uint8Array} key
|
|
63
|
+
* @param {Uint8Array} iv
|
|
64
|
+
* @param {Type.EncryptionConfig} encryptionConfig
|
|
65
|
+
* @returns {Promise<Type.EncryptedKeyResult>}
|
|
66
|
+
*/
|
|
67
|
+
async encryptSymmetricKey(key, iv, encryptionConfig) {
|
|
68
|
+
// Step 1: Combine key and IV to encrypt a single string
|
|
69
|
+
const combinedKeyAndIV = this.symmetricCrypto.combineKeyAndIV(key, iv);
|
|
70
|
+
// Step 2: Get RSA public key from space/encryption/setup
|
|
71
|
+
const setupResponse = await this.getSpacePublicKey(encryptionConfig);
|
|
72
|
+
// Step 3: Encrypt with RSA-OAEP
|
|
73
|
+
const encryptedKey = await this.encryptWithRSA(combinedKeyAndIV, setupResponse.publicKey);
|
|
74
|
+
// Step 4: Return the encrypted key and metadata
|
|
75
|
+
return {
|
|
76
|
+
strategy: 'kms',
|
|
77
|
+
encryptedKey: base64.encode(encryptedKey), // base64-encoded for transport/storage
|
|
78
|
+
metadata: {
|
|
79
|
+
space: encryptionConfig.spaceDID,
|
|
80
|
+
kms: {
|
|
81
|
+
provider: setupResponse.provider,
|
|
82
|
+
keyId: this.sanitizeSpaceDIDForKMSKeyId(encryptionConfig.spaceDID),
|
|
83
|
+
algorithm: setupResponse.algorithm,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} encryptedKey
|
|
90
|
+
* @param {object} configs
|
|
91
|
+
* @param {Type.DecryptionOptions} configs.decryptionOptions
|
|
92
|
+
* @param {Type.ExtractedMetadata} configs.metadata
|
|
93
|
+
* @param {Uint8Array} configs.delegationCAR
|
|
94
|
+
* @param {Type.AnyLink} configs.resourceCID
|
|
95
|
+
* @param {import('@storacha/client/types').Signer<import('@storacha/client/types').DID, import('@storacha/client/types').SigAlg>} configs.issuer
|
|
96
|
+
* @param {import('@storacha/client/types').DID} configs.audience
|
|
97
|
+
*/
|
|
98
|
+
async decryptSymmetricKey(encryptedKey, configs) {
|
|
99
|
+
// Step 1: Validate configs
|
|
100
|
+
const { decryptionOptions, metadata, issuer } = configs;
|
|
101
|
+
if (metadata.strategy !== 'kms') {
|
|
102
|
+
throw new Error('KMSCryptoAdapter can only handle KMS metadata');
|
|
103
|
+
}
|
|
104
|
+
const { spaceDID, delegationProof } = decryptionOptions;
|
|
105
|
+
if (!spaceDID || !delegationProof) {
|
|
106
|
+
throw new Error('SpaceDID and delegationProof are required');
|
|
107
|
+
}
|
|
108
|
+
if (!issuer) {
|
|
109
|
+
throw new Error('Issuer is required');
|
|
110
|
+
}
|
|
111
|
+
// Step 2: Get the decrypted key from KMS via gateway
|
|
112
|
+
const { decryptedSymmetricKey } = await this.getDecryptedSymmetricKey(encryptedKey, spaceDID, delegationProof, issuer);
|
|
113
|
+
// Step 3: Decode and split the combined key and IV
|
|
114
|
+
const combinedKeyAndIV = base64.decode(decryptedSymmetricKey);
|
|
115
|
+
return this.symmetricCrypto.splitKeyAndIV(combinedKeyAndIV);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get decrypted symmetric key in base64 string from KMS via private gateway
|
|
119
|
+
*
|
|
120
|
+
* @param {string} encryptedSymmetricKey - The encrypted symmetric key (base64-encoded)
|
|
121
|
+
* @param {Type.SpaceDID} spaceDID - The space DID
|
|
122
|
+
* @param {import('@ucanto/interface').Proof} delegationProof - The delegation proof
|
|
123
|
+
* @param {import('@storacha/client/types').Signer<import('@storacha/client/types').DID, import('@storacha/client/types').SigAlg>} issuer - The issuer
|
|
124
|
+
* @returns {Promise<{decryptedSymmetricKey: string}>} - The decrypted symmetric key (base64-encoded)
|
|
125
|
+
*/
|
|
126
|
+
async getDecryptedSymmetricKey(encryptedSymmetricKey, spaceDID, delegationProof, issuer) {
|
|
127
|
+
// Step 1: Invoke the KeyDecrypt capability passing the decryption proof
|
|
128
|
+
const result = await EncryptionKeyDecrypt.invoke({
|
|
129
|
+
issuer,
|
|
130
|
+
audience: this.keyManagerServiceDID,
|
|
131
|
+
with: spaceDID,
|
|
132
|
+
nb: {
|
|
133
|
+
key: base64.decode(encryptedSymmetricKey), // Convert base64 string to bytes
|
|
134
|
+
},
|
|
135
|
+
proofs: [delegationProof],
|
|
136
|
+
}).execute(this.newKeyManagerServiceConnection());
|
|
137
|
+
// Step 2: Handle the result
|
|
138
|
+
if (result.out.error) {
|
|
139
|
+
throw new Error(`KMS decryption failed: ${JSON.stringify(result.out.error)}`);
|
|
140
|
+
}
|
|
141
|
+
// Step 3: Return the base64-encoded decrypted key from the gateway response
|
|
142
|
+
return /** @type {{decryptedSymmetricKey: string}} */ (result.out.ok);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extract the encrypted metadata from the CAR file
|
|
146
|
+
* KMS adapter only handles KMS format (encrypted-metadata@0.2)
|
|
147
|
+
*
|
|
148
|
+
* @param {Uint8Array} car
|
|
149
|
+
* @returns {Type.ExtractedMetadata}
|
|
150
|
+
*/
|
|
151
|
+
extractEncryptedMetadata(car) {
|
|
152
|
+
const kmsContentResult = KMSMetadata.extract(car);
|
|
153
|
+
if (kmsContentResult.error) {
|
|
154
|
+
throw kmsContentResult.error;
|
|
155
|
+
}
|
|
156
|
+
const kmsContent = kmsContentResult.ok.toJSON();
|
|
157
|
+
// Validate it's KMS format
|
|
158
|
+
if (!kmsContent.encryptedSymmetricKey ||
|
|
159
|
+
!kmsContent.space ||
|
|
160
|
+
!kmsContent.kms) {
|
|
161
|
+
throw new Error('Invalid KMS metadata format - missing encryptedSymmetricKey, space, or kms fields');
|
|
162
|
+
}
|
|
163
|
+
// Return with strategy identifier
|
|
164
|
+
return {
|
|
165
|
+
strategy: 'kms',
|
|
166
|
+
encryptedDataCID: kmsContent.encryptedDataCID,
|
|
167
|
+
encryptedSymmetricKey: kmsContent.encryptedSymmetricKey,
|
|
168
|
+
space: /** @type {Type.SpaceDID} */ (kmsContent.space),
|
|
169
|
+
kms: {
|
|
170
|
+
provider: kmsContent.kms.provider,
|
|
171
|
+
keyId: kmsContent.kms.keyId,
|
|
172
|
+
algorithm: kmsContent.kms.algorithm,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @param {Type.ExtractedMetadata} metadata
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
getEncryptedKey(metadata) {
|
|
181
|
+
// For KMS metadata, we need to handle the different structure
|
|
182
|
+
if (metadata.strategy === 'kms') {
|
|
183
|
+
return /** @type {Type.KMSExtractedMetadata} */ (metadata)
|
|
184
|
+
.encryptedSymmetricKey;
|
|
185
|
+
}
|
|
186
|
+
throw new Error('KMSCryptoAdapter can only handle KMS metadata');
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Encode metadata for upload
|
|
190
|
+
*
|
|
191
|
+
* @param {string} encryptedDataCID - The CID of the encrypted data
|
|
192
|
+
* @param {string} encryptedKey - The encrypted key
|
|
193
|
+
* @param {Type.KMSKeyMetadata} metadata - The metadata to encode
|
|
194
|
+
* @returns {Promise<{ cid: import('@storacha/upload-client/types').AnyLink, bytes: Uint8Array }>} - The encoded metadata
|
|
195
|
+
*/
|
|
196
|
+
async encodeMetadata(encryptedDataCID, encryptedKey, metadata) {
|
|
197
|
+
const kmsKeyMetadata =
|
|
198
|
+
/** @type {import('../../types.js').KMSKeyMetadata} */ (metadata);
|
|
199
|
+
/** @type {Type.KMSMetadataInput} */
|
|
200
|
+
const uploadData = {
|
|
201
|
+
encryptedDataCID,
|
|
202
|
+
encryptedSymmetricKey: encryptedKey,
|
|
203
|
+
space: kmsKeyMetadata.space,
|
|
204
|
+
kms: {
|
|
205
|
+
provider: kmsKeyMetadata.kms.provider,
|
|
206
|
+
keyId: kmsKeyMetadata.kms.keyId,
|
|
207
|
+
algorithm: kmsKeyMetadata.kms.algorithm,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const kmsMetadata = KMSMetadata.create(uploadData);
|
|
211
|
+
return await kmsMetadata.archiveBlock();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the RSA public key from the space/encryption/setup
|
|
215
|
+
*
|
|
216
|
+
* @param {Type.EncryptionConfig} encryptionConfig
|
|
217
|
+
* @returns {Promise<{ publicKey: string, provider: string, algorithm: string }>}
|
|
218
|
+
*/
|
|
219
|
+
async getSpacePublicKey(encryptionConfig) {
|
|
220
|
+
// Step 1: Invoke the EncryptionSetup capability
|
|
221
|
+
const setupResult = await EncryptionSetup.invoke({
|
|
222
|
+
issuer: encryptionConfig.issuer,
|
|
223
|
+
audience: this.keyManagerServiceDID,
|
|
224
|
+
with: encryptionConfig.spaceDID,
|
|
225
|
+
nb: {
|
|
226
|
+
location: encryptionConfig.location,
|
|
227
|
+
keyring: encryptionConfig.keyring,
|
|
228
|
+
},
|
|
229
|
+
}).execute(this.newKeyManagerServiceConnection());
|
|
230
|
+
// Step 2: Handle the result
|
|
231
|
+
if (setupResult.out.error) {
|
|
232
|
+
throw new Error(`Failed to get public key: ${JSON.stringify(setupResult.out.error)}`);
|
|
233
|
+
}
|
|
234
|
+
// Step 3: Return the public key and key reference
|
|
235
|
+
return /** @type {{ publicKey: string, provider: string, algorithm: string }} */ (setupResult.out.ok);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the Web Crypto API SubtleCrypto interface (universal compatibility)
|
|
239
|
+
*
|
|
240
|
+
* @returns {SubtleCrypto} - The SubtleCrypto interface
|
|
241
|
+
*/
|
|
242
|
+
getSubtleCrypto() {
|
|
243
|
+
// Web Crypto API is available in modern browsers (secure contexts) and Node.js 16+
|
|
244
|
+
if (globalThis.crypto?.subtle) {
|
|
245
|
+
return globalThis.crypto.subtle;
|
|
246
|
+
}
|
|
247
|
+
throw new Error('Web Crypto API (SubtleCrypto) not available. Ensure you are running in a secure context (HTTPS) or Node.js 16+.');
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Encrypt data with RSA-OAEP using the public key
|
|
251
|
+
*
|
|
252
|
+
* @param {Uint8Array} dataToEncrypt
|
|
253
|
+
* @param {string} publicKeyPem
|
|
254
|
+
* @returns {Promise<Uint8Array>}
|
|
255
|
+
*/
|
|
256
|
+
async encryptWithRSA(dataToEncrypt, publicKeyPem) {
|
|
257
|
+
const subtle = this.getSubtleCrypto();
|
|
258
|
+
// Step 1. Import the PEM public key
|
|
259
|
+
const publicKey = await subtle.importKey('spki', this.pemToArrayBuffer(publicKeyPem), {
|
|
260
|
+
name: 'RSA-OAEP',
|
|
261
|
+
hash: 'SHA-256',
|
|
262
|
+
}, false, ['encrypt']);
|
|
263
|
+
// Step 2. Encrypt the raw data directly
|
|
264
|
+
const encrypted = await subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, dataToEncrypt);
|
|
265
|
+
return new Uint8Array(encrypted);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Convert PEM-encoded public key to ArrayBuffer for Web Crypto API
|
|
269
|
+
*
|
|
270
|
+
* @param {string} pem - PEM-encoded public key string
|
|
271
|
+
* @returns {ArrayBuffer} - DER-encoded key data for crypto.subtle.importKey()
|
|
272
|
+
*/
|
|
273
|
+
pemToArrayBuffer(pem) {
|
|
274
|
+
// Strip PEM headers, footers, and whitespace to get base64 DER data
|
|
275
|
+
const base64String = pem
|
|
276
|
+
.replace('-----BEGIN PUBLIC KEY-----', '')
|
|
277
|
+
.replace('-----END PUBLIC KEY-----', '')
|
|
278
|
+
.replace(/\s/g, '');
|
|
279
|
+
// For Node.js environment, use Buffer for standard base64 decoding
|
|
280
|
+
if (typeof Buffer !== 'undefined') {
|
|
281
|
+
return Buffer.from(base64String, 'base64');
|
|
282
|
+
}
|
|
283
|
+
// For browser environment, use atob for standard base64 decoding
|
|
284
|
+
let binaryString;
|
|
285
|
+
if (typeof globalThis !== 'undefined' && globalThis.atob) {
|
|
286
|
+
binaryString = globalThis.atob(base64String);
|
|
287
|
+
}
|
|
288
|
+
else if (typeof Buffer !== 'undefined') {
|
|
289
|
+
// @ts-ignore - Buffer exists in Node.js environment
|
|
290
|
+
binaryString = Buffer.from(base64String, 'base64').toString('binary');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
throw new Error('Neither atob nor Buffer available for base64 decoding');
|
|
294
|
+
}
|
|
295
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
296
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
297
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
298
|
+
}
|
|
299
|
+
return bytes;
|
|
300
|
+
}
|
|
301
|
+
newKeyManagerServiceConnection() {
|
|
302
|
+
return connect({
|
|
303
|
+
id: this.keyManagerServiceDID,
|
|
304
|
+
codec: CAR.outbound,
|
|
305
|
+
channel: HTTP.open({
|
|
306
|
+
url: this.keyManagerServiceURL,
|
|
307
|
+
method: 'POST',
|
|
308
|
+
}),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sanitize the space DID for the KMS key ID
|
|
313
|
+
*
|
|
314
|
+
* @param {Type.SpaceDID} spaceDID
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
317
|
+
sanitizeSpaceDIDForKMSKeyId(spaceDID) {
|
|
318
|
+
return spaceDID.replace(/^did:key:/, '');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=kms-crypto-adapter.js.map
|