@msssystems/mss-link-sdk 0.1.10 → 0.1.11

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.
@@ -1,16 +1,16 @@
1
1
  import type { DriveItemType, DriveWrappedItemKey } from './drive-sharing.contracts';
2
- export type DriveRecipientWrappingKeyAlgorithm = 'AES-256-GCM';
3
- export type DriveWrappedItemKeyAlgorithm = 'MSS-DRIVE-AES-256-GCM-WRAPPED-ITEM-KEY-v1';
4
- export interface DriveRecipientWrappingKey {
5
- algorithm: DriveRecipientWrappingKeyAlgorithm;
2
+ export type DriveRecipientPublicKeyAlgorithm = 'X25519';
3
+ export type DriveWrappedItemKeyAlgorithm = 'MSS-DRIVE-PUBLIC-KEY-WRAPPED-ITEM-KEY-v1';
4
+ export interface DriveRecipientPublicKey {
5
+ algorithm: DriveRecipientPublicKeyAlgorithm;
6
6
  keyId: string;
7
- rawKeyBase64: string;
7
+ publicKeyBase64: string;
8
8
  }
9
9
  export interface DriveRecipientKeyTarget {
10
10
  accountId?: string;
11
11
  userId?: string;
12
12
  username?: string;
13
- wrappingKey: DriveRecipientWrappingKey;
13
+ publicKey: DriveRecipientPublicKey;
14
14
  }
15
15
  export interface DriveItemContentKeyEnvelope {
16
16
  algorithm?: 'AES-256-GCM' | string | null;
@@ -18,11 +18,26 @@ export interface DriveItemContentKeyEnvelope {
18
18
  nonce?: string | null;
19
19
  wrappedKey: string;
20
20
  }
21
+ export interface DrivePublicKeyWrappingInput {
22
+ additionalData: string;
23
+ payload: DriveWrappedItemKeyPayload;
24
+ recipient: DriveRecipientKeyTarget;
25
+ }
26
+ export interface DrivePublicKeyWrappingOutput {
27
+ encryptedItemKey: string;
28
+ keyAlgorithm?: DriveWrappedItemKeyAlgorithm | string;
29
+ keyNonce?: string;
30
+ keyRecipientId?: string;
31
+ }
32
+ export interface DrivePublicKeyWrappingAdapter {
33
+ wrapDriveItemKey(input: DrivePublicKeyWrappingInput): Promise<DrivePublicKeyWrappingOutput>;
34
+ }
21
35
  export interface CreateDriveWrappedItemKeyForRecipientInput {
22
36
  itemId: string;
23
37
  itemType: DriveItemType;
24
38
  contentKey: DriveItemContentKeyEnvelope;
25
39
  recipient: DriveRecipientKeyTarget;
40
+ wrappingAdapter: DrivePublicKeyWrappingAdapter;
26
41
  }
27
42
  export interface DriveWrappedItemKeyPayload {
28
43
  contentAlgorithm: string;
@@ -1,38 +1,32 @@
1
1
  const CONTENT_KEY_SCHEMA = 'mss.link.drive.item-key.v1';
2
- const WRAPPED_ITEM_KEY_ALGORITHM = 'MSS-DRIVE-AES-256-GCM-WRAPPED-ITEM-KEY-v1';
3
- const AES_GCM_NONCE_LENGTH = 12;
4
- const AES_256_KEY_LENGTH = 32;
2
+ const WRAPPED_ITEM_KEY_ALGORITHM = 'MSS-DRIVE-PUBLIC-KEY-WRAPPED-ITEM-KEY-v1';
5
3
  export async function createDriveWrappedItemKeyForRecipient(input) {
6
4
  const itemId = normalizeRequiredText(input.itemId, 'Drive item id is required for key wrapping.');
7
5
  const itemType = input.itemType;
8
6
  const recipient = normalizeRecipient(input.recipient);
9
7
  const contentKey = normalizeContentKey(input.contentKey);
10
- const wrappingKey = await importRecipientWrappingKey(recipient.wrappingKey);
11
- const nonce = globalThis.crypto.getRandomValues(new Uint8Array(AES_GCM_NONCE_LENGTH));
8
+ const wrappingAdapter = normalizeWrappingAdapter(input.wrappingAdapter);
12
9
  const payload = buildWrappedItemKeyPayload({
13
10
  contentKey,
14
11
  itemId,
15
12
  itemType,
16
13
  });
17
- const plaintext = new TextEncoder().encode(JSON.stringify(payload));
18
14
  const additionalData = buildWrappingAdditionalData({
19
15
  itemId,
20
16
  itemType,
21
- recipientId: recipient.wrappingKey.keyId,
17
+ recipientPublicKeyId: recipient.publicKey.keyId,
18
+ });
19
+ const wrapped = await wrappingAdapter.wrapDriveItemKey({
20
+ additionalData,
21
+ payload,
22
+ recipient,
22
23
  });
23
- const encrypted = await globalThis.crypto.subtle.encrypt({
24
- additionalData: toArrayBuffer(additionalData),
25
- iv: nonce,
26
- name: 'AES-GCM',
27
- }, wrappingKey, toArrayBuffer(plaintext));
28
24
  return {
29
25
  payload,
30
- wrappedItemKey: {
31
- encryptedItemKey: encodeBase64Url(new Uint8Array(encrypted)),
32
- keyAlgorithm: WRAPPED_ITEM_KEY_ALGORITHM,
33
- keyNonce: encodeBase64Url(nonce),
34
- keyRecipientId: recipient.wrappingKey.keyId,
35
- },
26
+ wrappedItemKey: normalizeWrappedItemKeyOutput({
27
+ output: wrapped,
28
+ recipientPublicKeyId: recipient.publicKey.keyId,
29
+ }),
36
30
  };
37
31
  }
38
32
  function buildWrappedItemKeyPayload({ contentKey, itemId, itemType, }) {
@@ -51,7 +45,7 @@ function normalizeRecipient(recipient) {
51
45
  const accountId = normalizeOptionalText(recipient.accountId) ?? '';
52
46
  const userId = normalizeOptionalText(recipient.userId) ?? '';
53
47
  const username = normalizeOptionalText(recipient.username)?.replace(/^@/, '') ?? '';
54
- const wrappingKey = normalizeRecipientWrappingKey(recipient.wrappingKey);
48
+ const publicKey = normalizeRecipientPublicKey(recipient.publicKey);
55
49
  if (!accountId && !userId && !username) {
56
50
  throw new Error('Drive share recipient must include accountId, userId or username.');
57
51
  }
@@ -59,17 +53,17 @@ function normalizeRecipient(recipient) {
59
53
  accountId,
60
54
  userId,
61
55
  username,
62
- wrappingKey,
56
+ publicKey,
63
57
  };
64
58
  }
65
- function normalizeRecipientWrappingKey(key) {
66
- if (key.algorithm !== 'AES-256-GCM') {
67
- throw new Error(`Unsupported Drive recipient wrapping key algorithm: ${key.algorithm}`);
59
+ function normalizeRecipientPublicKey(key) {
60
+ if (key.algorithm !== 'X25519') {
61
+ throw new Error(`Unsupported Drive recipient public key algorithm: ${key.algorithm}`);
68
62
  }
69
63
  return {
70
64
  algorithm: key.algorithm,
71
- keyId: normalizeRequiredText(key.keyId, 'Drive recipient wrapping key id is required.'),
72
- rawKeyBase64: normalizeRequiredText(key.rawKeyBase64, 'Drive recipient wrapping key material is required.'),
65
+ keyId: normalizeRequiredText(key.keyId, 'Drive recipient public key id is required.'),
66
+ publicKeyBase64: normalizeRequiredText(key.publicKeyBase64, 'Drive recipient public key material is required.'),
73
67
  };
74
68
  }
75
69
  function normalizeContentKey(contentKey) {
@@ -84,17 +78,26 @@ function normalizeContentKey(contentKey) {
84
78
  wrappedKey: normalizeRequiredText(contentKey.wrappedKey, 'Drive item content key is required.'),
85
79
  };
86
80
  }
87
- async function importRecipientWrappingKey(key) {
88
- const keyBytes = decodeBase64Url(key.rawKeyBase64);
89
- if (keyBytes.byteLength !== AES_256_KEY_LENGTH) {
90
- throw new Error('Drive recipient wrapping key must be 32 bytes for AES-256-GCM.');
81
+ function normalizeWrappingAdapter(adapter) {
82
+ if (!adapter || typeof adapter.wrapDriveItemKey !== 'function') {
83
+ throw new Error('Drive public-key wrapping adapter is not configured.');
91
84
  }
92
- return globalThis.crypto.subtle.importKey('raw', toArrayBuffer(keyBytes), {
93
- name: 'AES-GCM',
94
- }, false, ['encrypt']);
85
+ return adapter;
86
+ }
87
+ function normalizeWrappedItemKeyOutput({ output, recipientPublicKeyId, }) {
88
+ const encryptedItemKey = normalizeRequiredText(output.encryptedItemKey, 'Drive public-key wrapping adapter returned empty encrypted item key.');
89
+ const keyAlgorithm = normalizeOptionalText(output.keyAlgorithm) ?? WRAPPED_ITEM_KEY_ALGORITHM;
90
+ const keyNonce = normalizeOptionalText(output.keyNonce);
91
+ const keyRecipientId = normalizeOptionalText(output.keyRecipientId) ?? recipientPublicKeyId;
92
+ return {
93
+ encryptedItemKey,
94
+ keyAlgorithm,
95
+ ...(keyNonce ? { keyNonce } : {}),
96
+ keyRecipientId,
97
+ };
95
98
  }
96
- function buildWrappingAdditionalData({ itemId, itemType, recipientId, }) {
97
- return new TextEncoder().encode(`${CONTENT_KEY_SCHEMA}:${itemType}:${itemId}:${recipientId}`);
99
+ function buildWrappingAdditionalData({ itemId, itemType, recipientPublicKeyId, }) {
100
+ return `${CONTENT_KEY_SCHEMA}:${itemType}:${itemId}:${recipientPublicKeyId}`;
98
101
  }
99
102
  function normalizeRequiredText(value, message) {
100
103
  const normalized = normalizeOptionalText(value);
@@ -107,27 +110,3 @@ function normalizeOptionalText(value) {
107
110
  const normalized = value?.trim();
108
111
  return normalized ? normalized : undefined;
109
112
  }
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
- }
@@ -1,4 +1,4 @@
1
- const DEFAULT_KEY_ALGORITHM = 'X25519-AES-256-GCM';
1
+ const DEFAULT_KEY_ALGORITHM = 'MSS-DRIVE-PUBLIC-KEY-WRAPPED-ITEM-KEY-v1';
2
2
  export function createDriveShareGrantPayload(input) {
3
3
  return {
4
4
  ...normalizeWrappedItemKey(input.wrappedItemKey),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msssystems/mss-link-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Official managed application SDK for MSS ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",