@msssystems/mss-link-sdk 0.1.10 → 0.1.12
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/client/drive-key-wrapping.contracts.d.ts +33 -6
- package/dist/client/drive-key-wrapping.d.ts +2 -1
- package/dist/client/drive-key-wrapping.js +67 -58
- package/dist/client/drive-sharing.js +1 -1
- package/dist/client/mss-link-browser-client.d.ts +2 -1
- package/dist/client/mss-link-browser-client.js +4 -1
- package/package.json +1 -1
|
@@ -1,33 +1,60 @@
|
|
|
1
1
|
import type { DriveItemType, DriveWrappedItemKey } from './drive-sharing.contracts';
|
|
2
|
-
export type
|
|
3
|
-
export type DriveWrappedItemKeyAlgorithm = 'MSS-DRIVE-
|
|
4
|
-
export
|
|
5
|
-
|
|
2
|
+
export type DriveRecipientPublicKeyAlgorithm = 'X25519';
|
|
3
|
+
export type DriveWrappedItemKeyAlgorithm = 'MSS-DRIVE-PUBLIC-KEY-WRAPPED-ITEM-KEY-v1';
|
|
4
|
+
export type DriveItemKeyMaterialKind = 'content-key' | 'folder-metadata-envelope';
|
|
5
|
+
export interface DriveRecipientPublicKey {
|
|
6
|
+
algorithm: DriveRecipientPublicKeyAlgorithm;
|
|
6
7
|
keyId: string;
|
|
7
|
-
|
|
8
|
+
publicKeyBase64: string;
|
|
8
9
|
}
|
|
9
10
|
export interface DriveRecipientKeyTarget {
|
|
10
11
|
accountId?: string;
|
|
11
12
|
userId?: string;
|
|
12
13
|
username?: string;
|
|
13
|
-
|
|
14
|
+
publicKey: DriveRecipientPublicKey;
|
|
14
15
|
}
|
|
15
16
|
export interface DriveItemContentKeyEnvelope {
|
|
16
17
|
algorithm?: 'AES-256-GCM' | string | null;
|
|
18
|
+
keyMaterialKind?: DriveItemKeyMaterialKind | string | null;
|
|
17
19
|
keyId?: string | null;
|
|
18
20
|
nonce?: string | null;
|
|
19
21
|
wrappedKey: string;
|
|
20
22
|
}
|
|
23
|
+
export interface DrivePublicKeyWrappingInput {
|
|
24
|
+
additionalData: string;
|
|
25
|
+
payload: DriveWrappedItemKeyPayload;
|
|
26
|
+
recipient: DriveRecipientKeyTarget;
|
|
27
|
+
}
|
|
28
|
+
export interface DrivePublicKeyWrappingOutput {
|
|
29
|
+
encryptedItemKey: string;
|
|
30
|
+
keyAlgorithm?: DriveWrappedItemKeyAlgorithm | string;
|
|
31
|
+
keyNonce?: string;
|
|
32
|
+
keyRecipientId?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DrivePublicKeyWrappingAdapter {
|
|
35
|
+
wrapDriveItemKey(input: DrivePublicKeyWrappingInput): Promise<DrivePublicKeyWrappingOutput>;
|
|
36
|
+
}
|
|
21
37
|
export interface CreateDriveWrappedItemKeyForRecipientInput {
|
|
22
38
|
itemId: string;
|
|
23
39
|
itemType: DriveItemType;
|
|
24
40
|
contentKey: DriveItemContentKeyEnvelope;
|
|
25
41
|
recipient: DriveRecipientKeyTarget;
|
|
42
|
+
wrappingAdapter: DrivePublicKeyWrappingAdapter;
|
|
43
|
+
}
|
|
44
|
+
export interface CreateDriveWrappedFolderKeyForRecipientInput {
|
|
45
|
+
encryptedFolderMetadata: {
|
|
46
|
+
encryptedName: string;
|
|
47
|
+
schema?: 'mss.link.drive-folder-metadata.v1' | string;
|
|
48
|
+
};
|
|
49
|
+
folderId: string;
|
|
50
|
+
recipient: DriveRecipientKeyTarget;
|
|
51
|
+
wrappingAdapter: DrivePublicKeyWrappingAdapter;
|
|
26
52
|
}
|
|
27
53
|
export interface DriveWrappedItemKeyPayload {
|
|
28
54
|
contentAlgorithm: string;
|
|
29
55
|
contentKey: string;
|
|
30
56
|
contentKeyId?: string;
|
|
57
|
+
contentKeyMaterialKind: DriveItemKeyMaterialKind;
|
|
31
58
|
contentNonce?: string;
|
|
32
59
|
createdAt: string;
|
|
33
60
|
itemId: string;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { CreateDriveWrappedItemKeyForRecipientInput, DriveWrappedItemKeyForRecipientResult } from './drive-key-wrapping.contracts';
|
|
1
|
+
import type { CreateDriveWrappedFolderKeyForRecipientInput, CreateDriveWrappedItemKeyForRecipientInput, DriveWrappedItemKeyForRecipientResult } from './drive-key-wrapping.contracts';
|
|
2
2
|
export declare function createDriveWrappedItemKeyForRecipient(input: CreateDriveWrappedItemKeyForRecipientInput): Promise<DriveWrappedItemKeyForRecipientResult>;
|
|
3
|
+
export declare function createDriveWrappedFolderKeyForRecipient(input: CreateDriveWrappedFolderKeyForRecipientInput): Promise<DriveWrappedItemKeyForRecipientResult>;
|
|
@@ -1,40 +1,54 @@
|
|
|
1
|
+
import { normalizeEncryptedDriveFolderMetadataInput } from './storage-metadata-crypto';
|
|
1
2
|
const CONTENT_KEY_SCHEMA = 'mss.link.drive.item-key.v1';
|
|
2
|
-
const WRAPPED_ITEM_KEY_ALGORITHM = 'MSS-DRIVE-
|
|
3
|
-
const
|
|
4
|
-
const AES_256_KEY_LENGTH = 32;
|
|
3
|
+
const WRAPPED_ITEM_KEY_ALGORITHM = 'MSS-DRIVE-PUBLIC-KEY-WRAPPED-ITEM-KEY-v1';
|
|
4
|
+
const FOLDER_METADATA_KEY_ID_PREFIX = 'drive-folder-metadata';
|
|
5
5
|
export async function createDriveWrappedItemKeyForRecipient(input) {
|
|
6
6
|
const itemId = normalizeRequiredText(input.itemId, 'Drive item id is required for key wrapping.');
|
|
7
7
|
const itemType = input.itemType;
|
|
8
8
|
const recipient = normalizeRecipient(input.recipient);
|
|
9
9
|
const contentKey = normalizeContentKey(input.contentKey);
|
|
10
|
-
const
|
|
11
|
-
const nonce = globalThis.crypto.getRandomValues(new Uint8Array(AES_GCM_NONCE_LENGTH));
|
|
10
|
+
const wrappingAdapter = normalizeWrappingAdapter(input.wrappingAdapter);
|
|
12
11
|
const payload = buildWrappedItemKeyPayload({
|
|
13
12
|
contentKey,
|
|
14
13
|
itemId,
|
|
15
14
|
itemType,
|
|
16
15
|
});
|
|
17
|
-
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
18
16
|
const additionalData = buildWrappingAdditionalData({
|
|
19
17
|
itemId,
|
|
20
18
|
itemType,
|
|
21
|
-
|
|
19
|
+
recipientPublicKeyId: recipient.publicKey.keyId,
|
|
20
|
+
});
|
|
21
|
+
const wrapped = await wrappingAdapter.wrapDriveItemKey({
|
|
22
|
+
additionalData,
|
|
23
|
+
payload,
|
|
24
|
+
recipient,
|
|
22
25
|
});
|
|
23
|
-
const encrypted = await globalThis.crypto.subtle.encrypt({
|
|
24
|
-
additionalData: toArrayBuffer(additionalData),
|
|
25
|
-
iv: nonce,
|
|
26
|
-
name: 'AES-GCM',
|
|
27
|
-
}, wrappingKey, toArrayBuffer(plaintext));
|
|
28
26
|
return {
|
|
29
27
|
payload,
|
|
30
|
-
wrappedItemKey: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
keyRecipientId: recipient.wrappingKey.keyId,
|
|
35
|
-
},
|
|
28
|
+
wrappedItemKey: normalizeWrappedItemKeyOutput({
|
|
29
|
+
output: wrapped,
|
|
30
|
+
recipientPublicKeyId: recipient.publicKey.keyId,
|
|
31
|
+
}),
|
|
36
32
|
};
|
|
37
33
|
}
|
|
34
|
+
export function createDriveWrappedFolderKeyForRecipient(input) {
|
|
35
|
+
const folderId = normalizeRequiredText(input.folderId, 'Drive folder id is required for key wrapping.');
|
|
36
|
+
const metadata = normalizeEncryptedDriveFolderMetadataInput({
|
|
37
|
+
encryptedName: input.encryptedFolderMetadata.encryptedName,
|
|
38
|
+
});
|
|
39
|
+
return createDriveWrappedItemKeyForRecipient({
|
|
40
|
+
contentKey: {
|
|
41
|
+
algorithm: 'AES-256-GCM',
|
|
42
|
+
keyId: `${FOLDER_METADATA_KEY_ID_PREFIX}:${folderId}`,
|
|
43
|
+
keyMaterialKind: 'folder-metadata-envelope',
|
|
44
|
+
wrappedKey: metadata.encryptedName,
|
|
45
|
+
},
|
|
46
|
+
itemId: folderId,
|
|
47
|
+
itemType: 'folder',
|
|
48
|
+
recipient: input.recipient,
|
|
49
|
+
wrappingAdapter: input.wrappingAdapter,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
38
52
|
function buildWrappedItemKeyPayload({ contentKey, itemId, itemType, }) {
|
|
39
53
|
return {
|
|
40
54
|
schema: CONTENT_KEY_SCHEMA,
|
|
@@ -42,6 +56,7 @@ function buildWrappedItemKeyPayload({ contentKey, itemId, itemType, }) {
|
|
|
42
56
|
itemType,
|
|
43
57
|
contentAlgorithm: contentKey.algorithm,
|
|
44
58
|
contentKey: contentKey.wrappedKey,
|
|
59
|
+
contentKeyMaterialKind: contentKey.keyMaterialKind,
|
|
45
60
|
createdAt: new Date().toISOString(),
|
|
46
61
|
...(contentKey.keyId ? { contentKeyId: contentKey.keyId } : {}),
|
|
47
62
|
...(contentKey.nonce ? { contentNonce: contentKey.nonce } : {}),
|
|
@@ -51,7 +66,7 @@ function normalizeRecipient(recipient) {
|
|
|
51
66
|
const accountId = normalizeOptionalText(recipient.accountId) ?? '';
|
|
52
67
|
const userId = normalizeOptionalText(recipient.userId) ?? '';
|
|
53
68
|
const username = normalizeOptionalText(recipient.username)?.replace(/^@/, '') ?? '';
|
|
54
|
-
const
|
|
69
|
+
const publicKey = normalizeRecipientPublicKey(recipient.publicKey);
|
|
55
70
|
if (!accountId && !userId && !username) {
|
|
56
71
|
throw new Error('Drive share recipient must include accountId, userId or username.');
|
|
57
72
|
}
|
|
@@ -59,17 +74,17 @@ function normalizeRecipient(recipient) {
|
|
|
59
74
|
accountId,
|
|
60
75
|
userId,
|
|
61
76
|
username,
|
|
62
|
-
|
|
77
|
+
publicKey,
|
|
63
78
|
};
|
|
64
79
|
}
|
|
65
|
-
function
|
|
66
|
-
if (key.algorithm !== '
|
|
67
|
-
throw new Error(`Unsupported Drive recipient
|
|
80
|
+
function normalizeRecipientPublicKey(key) {
|
|
81
|
+
if (key.algorithm !== 'X25519') {
|
|
82
|
+
throw new Error(`Unsupported Drive recipient public key algorithm: ${key.algorithm}`);
|
|
68
83
|
}
|
|
69
84
|
return {
|
|
70
85
|
algorithm: key.algorithm,
|
|
71
|
-
keyId: normalizeRequiredText(key.keyId, 'Drive recipient
|
|
72
|
-
|
|
86
|
+
keyId: normalizeRequiredText(key.keyId, 'Drive recipient public key id is required.'),
|
|
87
|
+
publicKeyBase64: normalizeRequiredText(key.publicKeyBase64, 'Drive recipient public key material is required.'),
|
|
73
88
|
};
|
|
74
89
|
}
|
|
75
90
|
function normalizeContentKey(contentKey) {
|
|
@@ -80,21 +95,39 @@ function normalizeContentKey(contentKey) {
|
|
|
80
95
|
return {
|
|
81
96
|
algorithm,
|
|
82
97
|
keyId: normalizeOptionalText(contentKey.keyId) ?? '',
|
|
98
|
+
keyMaterialKind: normalizeKeyMaterialKind(contentKey.keyMaterialKind),
|
|
83
99
|
nonce: normalizeOptionalText(contentKey.nonce) ?? '',
|
|
84
100
|
wrappedKey: normalizeRequiredText(contentKey.wrappedKey, 'Drive item content key is required.'),
|
|
85
101
|
};
|
|
86
102
|
}
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
|
|
103
|
+
function normalizeKeyMaterialKind(kind) {
|
|
104
|
+
const normalized = normalizeOptionalText(kind) ?? 'content-key';
|
|
105
|
+
if (normalized !== 'content-key' &&
|
|
106
|
+
normalized !== 'folder-metadata-envelope') {
|
|
107
|
+
throw new Error(`Unsupported Drive item key material kind: ${normalized}`);
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
function normalizeWrappingAdapter(adapter) {
|
|
112
|
+
if (!adapter || typeof adapter.wrapDriveItemKey !== 'function') {
|
|
113
|
+
throw new Error('Drive public-key wrapping adapter is not configured.');
|
|
91
114
|
}
|
|
92
|
-
return
|
|
93
|
-
name: 'AES-GCM',
|
|
94
|
-
}, false, ['encrypt']);
|
|
115
|
+
return adapter;
|
|
95
116
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
117
|
+
function normalizeWrappedItemKeyOutput({ output, recipientPublicKeyId, }) {
|
|
118
|
+
const encryptedItemKey = normalizeRequiredText(output.encryptedItemKey, 'Drive public-key wrapping adapter returned empty encrypted item key.');
|
|
119
|
+
const keyAlgorithm = normalizeOptionalText(output.keyAlgorithm) ?? WRAPPED_ITEM_KEY_ALGORITHM;
|
|
120
|
+
const keyNonce = normalizeOptionalText(output.keyNonce);
|
|
121
|
+
const keyRecipientId = normalizeOptionalText(output.keyRecipientId) ?? recipientPublicKeyId;
|
|
122
|
+
return {
|
|
123
|
+
encryptedItemKey,
|
|
124
|
+
keyAlgorithm,
|
|
125
|
+
...(keyNonce ? { keyNonce } : {}),
|
|
126
|
+
keyRecipientId,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function buildWrappingAdditionalData({ itemId, itemType, recipientPublicKeyId, }) {
|
|
130
|
+
return `${CONTENT_KEY_SCHEMA}:${itemType}:${itemId}:${recipientPublicKeyId}`;
|
|
98
131
|
}
|
|
99
132
|
function normalizeRequiredText(value, message) {
|
|
100
133
|
const normalized = normalizeOptionalText(value);
|
|
@@ -107,27 +140,3 @@ function normalizeOptionalText(value) {
|
|
|
107
140
|
const normalized = value?.trim();
|
|
108
141
|
return normalized ? normalized : undefined;
|
|
109
142
|
}
|
|
110
|
-
function decodeBase64Url(value) {
|
|
111
|
-
const normalized = value.trim().replace(/-/g, '+').replace(/_/g, '/');
|
|
112
|
-
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=');
|
|
113
|
-
const decoded = globalThis.atob(padded);
|
|
114
|
-
const bytes = new Uint8Array(decoded.length);
|
|
115
|
-
for (let index = 0; index < decoded.length; index += 1) {
|
|
116
|
-
bytes[index] = decoded.charCodeAt(index);
|
|
117
|
-
}
|
|
118
|
-
return bytes;
|
|
119
|
-
}
|
|
120
|
-
function encodeBase64Url(bytes) {
|
|
121
|
-
let binary = '';
|
|
122
|
-
for (const byte of bytes) {
|
|
123
|
-
binary += String.fromCharCode(byte);
|
|
124
|
-
}
|
|
125
|
-
return globalThis
|
|
126
|
-
.btoa(binary)
|
|
127
|
-
.replace(/\+/g, '-')
|
|
128
|
-
.replace(/\//g, '_')
|
|
129
|
-
.replace(/=+$/g, '');
|
|
130
|
-
}
|
|
131
|
-
function toArrayBuffer(bytes) {
|
|
132
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
133
|
-
}
|
|
@@ -4,7 +4,7 @@ import type { DecryptMediaBlobInput, DecryptedMediaBlobResult, EncryptedMediaBlo
|
|
|
4
4
|
import type { SendMediaMessageParams } from './media-message.contracts';
|
|
5
5
|
import type { DecryptedMessage } from './message.contracts';
|
|
6
6
|
import type { DecryptDriveFileMetadataInput, DecryptDriveFolderMetadataInput, DecryptedDriveFileMetadata, DecryptedDriveFolderMetadata, EncryptedDriveFileMetadata, EncryptedDriveFolderMetadata, EncryptDriveFileMetadataInput, EncryptDriveFolderMetadataInput } from './storage-metadata-crypto.contracts';
|
|
7
|
-
import type { CreateDriveWrappedItemKeyForRecipientInput, DriveWrappedItemKeyForRecipientResult } from './drive-key-wrapping.contracts';
|
|
7
|
+
import type { CreateDriveWrappedFolderKeyForRecipientInput, CreateDriveWrappedItemKeyForRecipientInput, DriveWrappedItemKeyForRecipientResult } from './drive-key-wrapping.contracts';
|
|
8
8
|
export declare class MssLinkBrowserClient {
|
|
9
9
|
private readonly userId;
|
|
10
10
|
private readonly apiBaseUrl;
|
|
@@ -36,6 +36,7 @@ export declare class MssLinkBrowserClient {
|
|
|
36
36
|
encryptDriveFileMetadata(input: EncryptDriveFileMetadataInput): Promise<EncryptedDriveFileMetadata>;
|
|
37
37
|
decryptDriveFileMetadata(input: DecryptDriveFileMetadataInput): Promise<DecryptedDriveFileMetadata>;
|
|
38
38
|
createDriveWrappedItemKeyForRecipient(input: CreateDriveWrappedItemKeyForRecipientInput): Promise<DriveWrappedItemKeyForRecipientResult>;
|
|
39
|
+
createDriveWrappedFolderKeyForRecipient(input: CreateDriveWrappedFolderKeyForRecipientInput): Promise<DriveWrappedItemKeyForRecipientResult>;
|
|
39
40
|
syncMessages(): Promise<void>;
|
|
40
41
|
getLocalMessages(chatId: string): Promise<DecryptedMessage[]>;
|
|
41
42
|
reset(): void;
|
|
@@ -4,7 +4,7 @@ import { checkLocalCryptoHealth } from './local-crypto-health';
|
|
|
4
4
|
import { buildMediaMessageMetadataPayload, extractMediaAssetIds, serializeMediaMessageMetadata, } from './media-message-metadata';
|
|
5
5
|
import { mapLocalSdkMessage } from './message.mapper';
|
|
6
6
|
import { buildDecryptedDriveFileMetadata, buildDecryptedDriveFolderMetadata, buildEncryptedDriveFileMetadata, buildEncryptedDriveFolderMetadata, normalizeDriveFileMetadataInput, normalizeDriveFolderMetadataInput, normalizeEncryptedDriveFileMetadataInput, normalizeEncryptedDriveFolderMetadataInput, } from './storage-metadata-crypto';
|
|
7
|
-
import { createDriveWrappedItemKeyForRecipient } from './drive-key-wrapping';
|
|
7
|
+
import { createDriveWrappedFolderKeyForRecipient, createDriveWrappedItemKeyForRecipient, } from './drive-key-wrapping';
|
|
8
8
|
import { WasmCallQueue } from './wasm-call-queue';
|
|
9
9
|
const DEFAULT_DEVICE_ID = 1;
|
|
10
10
|
const DEFAULT_REGISTRATION_ID = 1;
|
|
@@ -213,6 +213,9 @@ export class MssLinkBrowserClient {
|
|
|
213
213
|
createDriveWrappedItemKeyForRecipient(input) {
|
|
214
214
|
return createDriveWrappedItemKeyForRecipient(input);
|
|
215
215
|
}
|
|
216
|
+
createDriveWrappedFolderKeyForRecipient(input) {
|
|
217
|
+
return createDriveWrappedFolderKeyForRecipient(input);
|
|
218
|
+
}
|
|
216
219
|
async syncMessages() {
|
|
217
220
|
try {
|
|
218
221
|
await this.runWithClient((client) => client.syncMessages());
|