@rapidmx/web-client 0.35.0 → 0.37.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/apps/escrow/matters/[uid].tsx +4 -3
- package/apps/shared/calendar/calendarReminders.ts +35 -0
- package/apps/shared/calendar/sealedEvents.ts +113 -0
- package/apps/shared/calendar/useCalendarReminders.ts +28 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -0
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +89 -3
- package/apps/shared/components/calendar/EventDetails.tsx +13 -2
- package/apps/shared/components/calendar/EventEditor.tsx +124 -17
- package/apps/shared/components/calendar/EventFormParts.tsx +1 -0
- package/apps/shared/components/calendar/EventModal.tsx +19 -2
- package/apps/shared/components/calendar/RequestChangeForm.tsx +4 -1
- package/apps/shared/components/calendar/eventForm.ts +2 -0
- package/apps/shared/components/contacts/ContactCard.tsx +13 -1
- package/apps/shared/components/contacts/ContactDetailPane.tsx +189 -9
- package/apps/shared/components/contacts/ContactForm.tsx +56 -7
- package/apps/shared/components/contacts/KeyChangeReview.tsx +5 -1
- package/apps/shared/components/contacts/contactCardData.ts +8 -4
- package/apps/shared/components/contacts/contactKeys.ts +3 -3
- package/apps/shared/components/layout/AppShell.tsx +34 -5
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +30 -1
- package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +122 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +34 -0
- package/apps/shared/components/mail/ConversationList.tsx +1 -1
- package/apps/shared/components/mail/InviteCard.tsx +11 -1
- package/apps/shared/components/mail/MessageDetailPane.tsx +59 -26
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +9 -2
- package/apps/shared/components/mail/compose/RecipientInput.tsx +2 -1
- package/apps/shared/components/mail/compose/draftResume.ts +28 -3
- package/apps/shared/components/mail/compose/protectedContactSuggestions.ts +121 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +4 -3
- package/apps/shared/components/mail/invite/inviteStore.ts +17 -1
- package/apps/shared/components/mail/layout/MailShell.tsx +2 -0
- package/apps/shared/components/mail/pinnedSigners.ts +14 -0
- package/apps/shared/components/mail/reading/conversationPrint.ts +3 -3
- package/apps/shared/components/mail/reading/useMessageActions.ts +4 -1
- package/apps/shared/components/settings/KeyExportSetting.tsx +173 -0
- package/apps/shared/components/settings/OpenPgpKeySetting.tsx +240 -0
- package/apps/shared/components/settings/PolicyLockedNotice.tsx +23 -0
- package/apps/shared/components/settings/ZeroAccessSetting.tsx +128 -0
- package/apps/shared/components/settings/ZeroAccessTypes.tsx +175 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +9 -2
- package/apps/shared/mail/decryptedMessages.ts +11 -3
- package/apps/shared/mail/outbox/sendJob.ts +349 -50
- package/apps/shared/mail/outbox/sendOutcomes.ts +91 -20
- package/apps/shared/mail/reportNotices.ts +3 -1
- package/apps/shared/mail/zeroAccessSource.ts +27 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +84 -24
- package/apps/shared/search/localIndexBuilder.ts +57 -20
- package/apps/shared/search/localIndexRpcClient.ts +96 -12
- package/apps/shared/search/localIndexSchema.ts +20 -4
- package/apps/shared/search/localIndexTransport.ts +6 -2
- package/apps/shared/search/localIndexWorker.ts +37 -18
- package/apps/shared/search/searchTier2.ts +5 -0
- package/apps/shared/search/zeroAccessIndexSync.ts +679 -0
- package/apps/shared/search/zeroAccessSyncState.ts +70 -0
- package/apps/shared/zeroAccess/ProtectedMark.tsx +19 -0
- package/apps/shared/zeroAccess/useKeysLocked.ts +25 -0
- package/apps/shared/zeroAccess/useOpenedRecord.ts +71 -0
- package/apps/www/_shell.tsx +2 -0
- package/apps/www/calendar/index.tsx +225 -61
- package/apps/www/contacts/index.tsx +67 -11
- package/apps/www/index.tsx +19 -3
- package/apps/www/settings/encryption/index.tsx +21 -1
- package/apps/www/settings/privacy/index.tsx +89 -39
- package/apps/www/tasks/index.tsx +24 -6
- package/dist/apps/escrow/matters/[uid].js +2 -2
- package/dist/apps/shared/calendar/calendarReminders.d.ts +14 -0
- package/dist/apps/shared/calendar/calendarReminders.js +26 -0
- package/dist/apps/shared/calendar/sealedEvents.d.ts +14 -0
- package/dist/apps/shared/calendar/sealedEvents.js +85 -0
- package/dist/apps/shared/calendar/useCalendarReminders.d.ts +2 -1
- package/dist/apps/shared/calendar/useCalendarReminders.js +27 -9
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +3 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +25 -2
- package/dist/apps/shared/components/calendar/EventDetails.js +13 -2
- package/dist/apps/shared/components/calendar/EventEditor.js +115 -18
- package/dist/apps/shared/components/calendar/EventFormParts.js +1 -1
- package/dist/apps/shared/components/calendar/EventModal.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventModal.js +15 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +3 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +1 -1
- package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -0
- package/dist/apps/shared/components/contacts/ContactCard.js +12 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +2 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +103 -9
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +4 -1
- package/dist/apps/shared/components/contacts/ContactForm.js +37 -7
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +3 -1
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +3 -2
- package/dist/apps/shared/components/contacts/contactCardData.js +8 -4
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +1 -1
- package/dist/apps/shared/components/contacts/contactKeys.js +3 -3
- package/dist/apps/shared/components/layout/AppShell.d.ts +18 -1
- package/dist/apps/shared/components/layout/AppShell.js +17 -6
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +27 -2
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +17 -0
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +98 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +3 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +32 -0
- package/dist/apps/shared/components/mail/ConversationList.js +1 -1
- package/dist/apps/shared/components/mail/InviteCard.js +11 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +46 -21
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +2 -2
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +3 -2
- package/dist/apps/shared/components/mail/compose/draftResume.d.ts +2 -0
- package/dist/apps/shared/components/mail/compose/draftResume.js +28 -3
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.js +96 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +1 -1
- package/dist/apps/shared/components/mail/compose/quotedBody.js +4 -3
- package/dist/apps/shared/components/mail/invite/inviteStore.js +16 -1
- package/dist/apps/shared/components/mail/layout/MailShell.js +2 -1
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +6 -0
- package/dist/apps/shared/components/mail/pinnedSigners.js +10 -1
- package/dist/apps/shared/components/mail/reading/conversationPrint.d.ts +2 -2
- package/dist/apps/shared/components/mail/reading/conversationPrint.js +3 -3
- package/dist/apps/shared/components/mail/reading/useMessageActions.js +4 -1
- package/dist/apps/shared/components/settings/KeyExportSetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/KeyExportSetting.js +90 -0
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.js +143 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.d.ts +11 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.js +9 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +16 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.js +80 -0
- package/dist/apps/shared/components/settings/ZeroAccessTypes.d.ts +29 -0
- package/dist/apps/shared/components/settings/ZeroAccessTypes.js +107 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +3 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +3 -2
- package/dist/apps/shared/mail/decryptedMessages.d.ts +1 -1
- package/dist/apps/shared/mail/decryptedMessages.js +11 -3
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +37 -4
- package/dist/apps/shared/mail/outbox/sendJob.js +287 -42
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +12 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +76 -20
- package/dist/apps/shared/mail/reportNotices.js +3 -1
- package/dist/apps/shared/mail/zeroAccessSource.d.ts +8 -0
- package/dist/apps/shared/mail/zeroAccessSource.js +26 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +74 -24
- package/dist/apps/shared/search/localIndexBuilder.d.ts +9 -1
- package/dist/apps/shared/search/localIndexBuilder.js +50 -17
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +5 -39
- package/dist/apps/shared/search/localIndexRpcClient.js +129 -11
- package/dist/apps/shared/search/localIndexSchema.d.ts +14 -2
- package/dist/apps/shared/search/localIndexSchema.js +18 -4
- package/dist/apps/shared/search/localIndexTransport.d.ts +6 -2
- package/dist/apps/shared/search/localIndexWorker.d.ts +11 -0
- package/dist/apps/shared/search/localIndexWorker.js +22 -15
- package/dist/apps/shared/search/searchTier2.js +5 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.d.ts +102 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.js +544 -0
- package/dist/apps/shared/search/zeroAccessSyncState.d.ts +20 -0
- package/dist/apps/shared/search/zeroAccessSyncState.js +65 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.d.ts +7 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.js +10 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.d.ts +6 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.js +20 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.d.ts +18 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.js +66 -0
- package/dist/apps/www/_shell.d.ts +1 -1
- package/dist/apps/www/_shell.js +2 -2
- package/dist/apps/www/calendar/index.js +88 -20
- package/dist/apps/www/contacts/index.js +64 -11
- package/dist/apps/www/index.js +19 -3
- package/dist/apps/www/settings/encryption/index.js +16 -4
- package/dist/apps/www/settings/privacy/index.js +47 -6
- package/dist/apps/www/tasks/index.js +21 -7
- package/dist/lib/admin/domainsApi.d.ts +1 -1
- package/dist/lib/admin/escrowAccessRequestsApi.d.ts +6 -0
- package/dist/lib/calendar/calendarApi.d.ts +52 -6
- package/dist/lib/calendar/calendarApi.js +110 -17
- package/dist/lib/calendar/calendarMutations.d.ts +20 -7
- package/dist/lib/calendar/calendarMutations.js +73 -14
- package/dist/lib/calendar/inviteApi.d.ts +17 -3
- package/dist/lib/calendar/inviteApi.js +86 -7
- package/dist/lib/calendar/protectedEvents.d.ts +31 -0
- package/dist/lib/calendar/protectedEvents.js +113 -0
- package/dist/lib/calendar/recurrence.d.ts +2 -0
- package/dist/lib/calendar/recurrence.js +16 -0
- package/dist/lib/calendar/sealedInvites.d.ts +9 -0
- package/dist/lib/calendar/sealedInvites.js +65 -0
- package/dist/lib/contacts/contactsApi.d.ts +29 -3
- package/dist/lib/contacts/contactsApi.js +23 -7
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +5 -2
- package/dist/lib/contacts/useContactPhotoSrc.js +91 -11
- package/dist/lib/crypto/composeSecurity.d.ts +11 -3
- package/dist/lib/crypto/composeSecurity.js +13 -5
- package/dist/lib/crypto/keyExport.d.ts +54 -0
- package/dist/lib/crypto/keyExport.js +161 -0
- package/dist/lib/crypto/keyPairMatch.d.ts +9 -0
- package/dist/lib/crypto/keyPairMatch.js +65 -0
- package/dist/lib/crypto/keySession.d.ts +64 -3
- package/dist/lib/crypto/keySession.js +179 -12
- package/dist/lib/crypto/keyvaultApi.d.ts +129 -8
- package/dist/lib/crypto/keyvaultApi.js +45 -3
- package/dist/lib/crypto/meetingKeys.d.ts +25 -0
- package/dist/lib/crypto/meetingKeys.js +87 -0
- package/dist/lib/crypto/messageSecurity.d.ts +56 -3
- package/dist/lib/crypto/messageSecurity.js +201 -13
- package/dist/lib/crypto/mime.d.ts +5 -0
- package/dist/lib/crypto/mime.js +25 -0
- package/dist/lib/crypto/openPgpKey.d.ts +39 -0
- package/dist/lib/crypto/openPgpKey.js +89 -0
- package/dist/lib/crypto/openpgp.d.ts +47 -0
- package/dist/lib/crypto/openpgp.js +88 -0
- package/dist/lib/crypto/pgpMessage.d.ts +51 -0
- package/dist/lib/crypto/pgpMessage.js +271 -0
- package/dist/lib/crypto/sharedMailboxKeys.d.ts +62 -0
- package/dist/lib/crypto/sharedMailboxKeys.js +337 -0
- package/dist/lib/crypto/smimeMessage.d.ts +21 -0
- package/dist/lib/crypto/smimeMessage.js +11 -7
- package/dist/lib/crypto/zeroAccessKey.d.ts +32 -0
- package/dist/lib/crypto/zeroAccessKey.js +71 -0
- package/dist/lib/mail/conversationsApi.d.ts +6 -0
- package/dist/lib/mail/mailApi.d.ts +63 -2
- package/dist/lib/mail/mailApi.js +5 -1
- package/dist/lib/mail/mailboxImportApi.d.ts +10 -0
- package/dist/lib/mail/mailboxImportApi.js +15 -0
- package/dist/lib/search/zeroAccessIndexApi.d.ts +85 -0
- package/dist/lib/search/zeroAccessIndexApi.js +35 -0
- package/dist/lib/tasks/tasksApi.d.ts +11 -1
- package/dist/lib/tasks/tasksApi.js +10 -4
- package/dist/lib/util/clientPlatform.d.ts +14 -0
- package/dist/lib/util/clientPlatform.js +26 -0
- package/dist/lib/videoconf/protectedMeetings.d.ts +74 -0
- package/dist/lib/videoconf/protectedMeetings.js +181 -0
- package/dist/lib/videoconf/videoMeetingsApi.d.ts +25 -4
- package/dist/lib/videoconf/videoMeetingsApi.js +1 -1
- package/dist/lib/zeroAccess/sealedMoves.d.ts +41 -0
- package/dist/lib/zeroAccess/sealedMoves.js +63 -0
- package/dist/lib/zeroAccess/sealedRecords.d.ts +29 -0
- package/dist/lib/zeroAccess/sealedRecords.js +136 -0
- package/lib/admin/domainsApi.ts +1 -1
- package/lib/admin/escrowAccessRequestsApi.ts +6 -0
- package/lib/calendar/calendarApi.ts +143 -17
- package/lib/calendar/calendarMutations.ts +79 -12
- package/lib/calendar/inviteApi.ts +101 -8
- package/lib/calendar/protectedEvents.ts +143 -0
- package/lib/calendar/recurrence.ts +17 -0
- package/lib/calendar/sealedInvites.ts +73 -0
- package/lib/contacts/contactsApi.ts +43 -17
- package/lib/contacts/useContactPhotoSrc.ts +103 -13
- package/lib/crypto/composeSecurity.ts +20 -5
- package/lib/crypto/keyExport.ts +229 -0
- package/lib/crypto/keyPairMatch.ts +69 -0
- package/lib/crypto/keySession.ts +216 -12
- package/lib/crypto/keyvaultApi.ts +174 -10
- package/lib/crypto/meetingKeys.ts +118 -0
- package/lib/crypto/messageSecurity.ts +284 -16
- package/lib/crypto/mime.ts +26 -0
- package/lib/crypto/openPgpKey.ts +118 -0
- package/lib/crypto/openpgp.ts +115 -0
- package/lib/crypto/pgpMessage.ts +336 -0
- package/lib/crypto/sharedMailboxKeys.ts +402 -0
- package/lib/crypto/smimeMessage.ts +11 -7
- package/lib/crypto/zeroAccessKey.ts +102 -0
- package/lib/mail/conversationsApi.ts +3 -0
- package/lib/mail/mailApi.ts +60 -3
- package/lib/mail/mailboxImportApi.ts +24 -0
- package/lib/search/zeroAccessIndexApi.ts +123 -0
- package/lib/tasks/tasksApi.ts +19 -5
- package/lib/util/clientPlatform.ts +50 -0
- package/lib/videoconf/protectedMeetings.ts +221 -0
- package/lib/videoconf/videoMeetingsApi.ts +15 -4
- package/lib/zeroAccess/sealedMoves.ts +79 -0
- package/lib/zeroAccess/sealedRecords.ts +153 -0
- package/package.json +14 -5
|
@@ -15,9 +15,10 @@ import { ApiClient, ApiRequestError, withClient } from "../util/api.js";
|
|
|
15
15
|
/** A cryptographic public key used to sign or encrypt messages — safe to expose publicly. Mirrors
|
|
16
16
|
* `@rapidmx/restapi`'s `PublicKey` type exactly. */
|
|
17
17
|
export interface PublicKey {
|
|
18
|
-
/** Base64-encoded DER X.509 certificate. */
|
|
18
|
+
/** Base64-encoded DER X.509 certificate - or, for `type: "openpgp"`, the binary OpenPGP transferable public key. */
|
|
19
19
|
publicKey: string;
|
|
20
|
-
/** The key's type/format
|
|
20
|
+
/** The key's type/format: `x509` (S/MIME), or `openpgp` - an OpenPGP certificate, which both encrypts and signs and is listed
|
|
21
|
+
* once, with `useType: "encrypt"` (restapi `specs/end-to-end_encryption.md` *OpenPGP Interoperability*). */
|
|
21
22
|
type: string;
|
|
22
23
|
useType: "sign" | "encrypt";
|
|
23
24
|
/** SHA-256 fingerprint of the certificate, hex encoded. */
|
|
@@ -47,7 +48,35 @@ export interface WrappedPrivateKey {
|
|
|
47
48
|
/** AEAD algorithm identifier (e.g. `AES-256-GCM`). */
|
|
48
49
|
algorithm: string;
|
|
49
50
|
fingerprint: string;
|
|
50
|
-
useType:
|
|
51
|
+
useType: KeyUseType;
|
|
52
|
+
/** `"openpgp"` for an OpenPGP secret key (sealed with `OPENPGP_PRIVATE_KEY_AAD_PURPOSE`); absent for a PKCS#8 key. */
|
|
53
|
+
type?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `PublicKey.type` of an OpenPGP certificate. */
|
|
57
|
+
export const OPENPGP_KEY_TYPE = "openpgp";
|
|
58
|
+
|
|
59
|
+
/** Whether `key` is an OpenPGP certificate (or an OpenPGP secret key's wrap). */
|
|
60
|
+
export function isOpenPgpKey(key: { type?: string } | undefined): boolean {
|
|
61
|
+
return key?.type === OPENPGP_KEY_TYPE;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A key's format: `"openpgp"`, or `"x509"` for every other key (S/MIME). */
|
|
65
|
+
export type KeyFormat = "x509" | "openpgp";
|
|
66
|
+
|
|
67
|
+
export function keyFormat(key: { type?: string }): KeyFormat {
|
|
68
|
+
return isOpenPgpKey(key) ? "openpgp" : "x509";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** What a mailbox's own key is used for. `sign`/`encrypt` keys are published (`Mailbox.keys`); `zero-access` keys never are.
|
|
72
|
+
* Mirrors `@rapidmx/restapi`'s `KeyUseType`. */
|
|
73
|
+
export type KeyUseType = "sign" | "encrypt" | "zero-access";
|
|
74
|
+
|
|
75
|
+
/** A mailbox's zero-access key (restapi `specs/search.md` §3): the certificate the server encrypts mail at rest to on
|
|
76
|
+
* arrival. Same shape as `PublicKey`, but never published - only the vault's reader gets it (`KeyVault.zeroAccessKeys`).
|
|
77
|
+
* Mirrors `@rapidmx/restapi`'s `ZeroAccessKey`. */
|
|
78
|
+
export interface ZeroAccessKey extends Omit<PublicKey, "useType" | "issuerCertificate"> {
|
|
79
|
+
useType: "zero-access";
|
|
51
80
|
}
|
|
52
81
|
|
|
53
82
|
/** One wrapped copy of the mailbox master key (MK), per unlock method. Mirrors `@rapidmx/restapi`'s
|
|
@@ -72,8 +101,14 @@ export interface MasterKeyWrap {
|
|
|
72
101
|
export interface EncryptionPreference {
|
|
73
102
|
lastSeen?: number;
|
|
74
103
|
preferEncrypt: "mutual" | "nopreference";
|
|
104
|
+
/** Which end-to-end format the address would rather receive when a sender can use either (restapi
|
|
105
|
+
* `specs/end-to-end_encryption.md` *Choosing the format*). Absent: no preference. */
|
|
106
|
+
preferFormat?: EndToEndFormat;
|
|
75
107
|
}
|
|
76
108
|
|
|
109
|
+
/** The two end-to-end encryption formats. */
|
|
110
|
+
export type EndToEndFormat = "smime" | "openpgp";
|
|
111
|
+
|
|
77
112
|
/** A newly observed key that differs from a contact's pinned key of the same `useType` - restapi records it instead of
|
|
78
113
|
* silently replacing the pin (the spec's Key Conflict Handling) until the user resolves it with `resolveKeyConflict()`.
|
|
79
114
|
* A contact holds at most one per `useType`. Mirrors `@rapidmx/restapi`'s `KeyConflict`. */
|
|
@@ -114,6 +149,9 @@ export interface KeyVault {
|
|
|
114
149
|
* (`enrollKey()`, `startSignEnrollment()`, `addMasterKeyWrap()`, `rekey()`), so restapi refuses it with `409` if
|
|
115
150
|
* another device rotated the master key meanwhile. Absent from servers that don't track it yet. */
|
|
116
151
|
masterKeyGeneration?: number;
|
|
152
|
+
/** The mailbox's zero-access certificates, newest last - returned with the vault because they are never published and
|
|
153
|
+
* an unlock needs them to decrypt zero-access mail. Absent from servers without zero-access encryption. */
|
|
154
|
+
zeroAccessKeys?: ZeroAccessKey[];
|
|
117
155
|
}
|
|
118
156
|
|
|
119
157
|
/** The optional optimistic check restapi applies to vault writes carrying key material sealed under the master key. */
|
|
@@ -138,10 +176,10 @@ export function getKeyVault(mailboxUid: string, client?: ApiClient): Promise<Key
|
|
|
138
176
|
* `revocationReason` - a superseded key may still verify old mail (`isTrustedForVerification()`) but is never used for
|
|
139
177
|
* new mail. Shared by `crypto/keySession.ts` (the
|
|
140
178
|
* mailbox's own keys) and `crypto/composeSecurity.ts` (a recipient's discovered keys). */
|
|
141
|
-
export function findActivePublicKey(keys: PublicKey[], useType: "sign" | "encrypt"): PublicKey | undefined {
|
|
179
|
+
export function findActivePublicKey(keys: PublicKey[], useType: "sign" | "encrypt", format: KeyFormat = "x509"): PublicKey | undefined {
|
|
142
180
|
const now = Date.now();
|
|
143
181
|
return keys
|
|
144
|
-
.filter((k) => k.useType === useType && !k.revokedAt && k.notAfter > now)
|
|
182
|
+
.filter((k) => k.useType === useType && keyFormat(k) === format && !k.revokedAt && k.notAfter > now)
|
|
145
183
|
.sort((a, b) => b.notBefore - a.notBefore)[0];
|
|
146
184
|
}
|
|
147
185
|
|
|
@@ -179,13 +217,17 @@ export function signingKeyFingerprints(keys: PublicKey[] | undefined, previousKe
|
|
|
179
217
|
}
|
|
180
218
|
|
|
181
219
|
export interface EnrollKeyInput extends ExpectedMasterKeyGeneration {
|
|
182
|
-
useType:
|
|
183
|
-
/** PEM-encoded PKCS#10 CSR — required (and only meaningful) for `useType: "encrypt"`; the server
|
|
220
|
+
useType: KeyUseType;
|
|
221
|
+
/** PEM-encoded PKCS#10 CSR — required (and only meaningful) for `useType: "encrypt"` and `"zero-access"`; the server
|
|
184
222
|
* calls its own internal CA against this CSR. */
|
|
185
223
|
csr?: string;
|
|
186
224
|
/** An already-issued PEM certificate — required (and only meaningful) for `useType: "sign"`. */
|
|
187
225
|
certificate?: string;
|
|
188
|
-
|
|
226
|
+
/** `"openpgp"` to enroll the mailbox's OpenPGP key (with `useType: "encrypt"`): self-certified, no CA. */
|
|
227
|
+
type?: string;
|
|
228
|
+
/** The OpenPGP public key (armored or base64 binary) - required, and only meaningful, with `type: "openpgp"`. */
|
|
229
|
+
publicKey?: string;
|
|
230
|
+
wrappedKey: Omit<WrappedPrivateKey, "fingerprint" | "useType" | "type">;
|
|
189
231
|
/** Only meaningful the very first time a mailbox enrolls a key at all (bootstraps its master key). */
|
|
190
232
|
masterKeyWraps?: MasterKeyWrap[];
|
|
191
233
|
}
|
|
@@ -508,6 +550,110 @@ export function removeMasterKeyWrap(mailboxUid: string, method: string, methodId
|
|
|
508
550
|
});
|
|
509
551
|
}
|
|
510
552
|
|
|
553
|
+
/** One member's copy of a shared mailbox's zero-access private key: base64 CMS of its PKCS#8, to the member's own zero-access certificate. */
|
|
554
|
+
export interface MemberKeyWrap {
|
|
555
|
+
memberUserUid: string;
|
|
556
|
+
recipientFingerprint: string;
|
|
557
|
+
zeroAccessKeyFingerprint: string;
|
|
558
|
+
cms: string;
|
|
559
|
+
createdAt?: number;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** One member in `SharedKeyStatus`. */
|
|
563
|
+
export interface SharedKeyMember {
|
|
564
|
+
userUid: string;
|
|
565
|
+
full: boolean;
|
|
566
|
+
/** Their own zero-access certificate - absent until they first sign in and get one. */
|
|
567
|
+
certificate?: ZeroAccessKey;
|
|
568
|
+
/** The mailbox's zero-access keys they hold a wrap of. */
|
|
569
|
+
wrapped: string[];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Where a shared mailbox's zero-access key stands with its members - restapi `GET /mail/mailboxes/:id/keyvault/members`. */
|
|
573
|
+
export interface SharedKeyStatus {
|
|
574
|
+
ownerless: boolean;
|
|
575
|
+
/** The caller: the mailbox's owner, one of its members, or neither (an administrator acting through a role). */
|
|
576
|
+
role?: "owner" | "member" | "other";
|
|
577
|
+
activeKeyFingerprint?: string;
|
|
578
|
+
zeroAccessKeys: ZeroAccessKey[];
|
|
579
|
+
members: SharedKeyMember[];
|
|
580
|
+
/** Members with a certificate but no wrap of the active key. */
|
|
581
|
+
pending: string[];
|
|
582
|
+
/** Users holding wraps who are no longer members. */
|
|
583
|
+
departed: string[];
|
|
584
|
+
/** A departed user holds the active key: a new key must be provisioned. */
|
|
585
|
+
rotationPending: boolean;
|
|
586
|
+
/** The caller may change the wraps. */
|
|
587
|
+
manager: boolean;
|
|
588
|
+
/** A mailbox without an owner assigned to an escrow scope: that scope. */
|
|
589
|
+
escrowScopeId?: string;
|
|
590
|
+
/** Its zero-access keys (fingerprints) with no escrow copy for `escrowScopeId` yet. Absent from servers that predate it. */
|
|
591
|
+
escrowPending?: string[];
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** An escrow copy of a mailbox-without-an-owner's zero-access private key, as `putZeroAccessEscrowWraps()` sends it: CMS of its PKCS#8 to
|
|
595
|
+
* the escrow scope's certificate (`getEscrowInfo()`). */
|
|
596
|
+
export interface ZeroAccessEscrowWrapInput {
|
|
597
|
+
escrowScopeId: string;
|
|
598
|
+
zeroAccessKeyFingerprint: string;
|
|
599
|
+
/** Base64 DER CMS `EnvelopedData`. */
|
|
600
|
+
cms: string;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const vaultPath = (mailboxUid: string): string => `/mail/mailboxes/${encodeURIComponent(mailboxUid)}/keyvault`;
|
|
604
|
+
|
|
605
|
+
export function getSharedKeyStatus(mailboxUid: string, client?: ApiClient): Promise<SharedKeyStatus> {
|
|
606
|
+
return withClient(client, `${vaultPath(mailboxUid)}/members`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** The caller's own member key wraps of `mailboxUid` (`[]` for its owner and for a member still waiting). */
|
|
610
|
+
export function getMyMemberKeyWraps(mailboxUid: string, client?: ApiClient): Promise<MemberKeyWrap[]> {
|
|
611
|
+
return withClient(client, `${vaultPath(mailboxUid)}/member-wraps`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function putMemberKeyWraps(mailboxUid: string, wraps: MemberKeyWrap[], client?: ApiClient): Promise<SharedKeyStatus> {
|
|
615
|
+
return withClient(client, `${vaultPath(mailboxUid)}/member-wraps`, { method: "PUT", body: JSON.stringify({ wraps }) });
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Removes every wrap of a user who is no longer a member - the last step of a rotation. */
|
|
619
|
+
export function removeMemberKeyWraps(mailboxUid: string, memberUserUid: string, client?: ApiClient): Promise<SharedKeyStatus> {
|
|
620
|
+
return withClient(client, `${vaultPath(mailboxUid)}/member-wraps/${encodeURIComponent(memberUserUid)}`, { method: "DELETE" });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Provisions (or rotates) a mailbox-without-an-owner's zero-access key with its member wraps, in one write - and, for a mailbox assigned to
|
|
624
|
+
* an escrow scope, its escrow copy (required then). */
|
|
625
|
+
export function enrollSharedZeroAccessKey(
|
|
626
|
+
mailboxUid: string,
|
|
627
|
+
input: {
|
|
628
|
+
csr: string;
|
|
629
|
+
wraps: Omit<MemberKeyWrap, "zeroAccessKeyFingerprint">[];
|
|
630
|
+
escrowWrap?: Omit<ZeroAccessEscrowWrapInput, "zeroAccessKeyFingerprint">;
|
|
631
|
+
},
|
|
632
|
+
client?: ApiClient,
|
|
633
|
+
): Promise<SharedKeyStatus> {
|
|
634
|
+
return withClient(client, `${vaultPath(mailboxUid)}/shared-zero-access-key`, { method: "POST", body: JSON.stringify(input) });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** Adds the escrow copies of a mailbox-without-an-owner's zero-access keys its escrow scope is missing (`SharedKeyStatus.escrowPending`). */
|
|
638
|
+
export function putZeroAccessEscrowWraps(mailboxUid: string, wraps: ZeroAccessEscrowWrapInput[], client?: ApiClient): Promise<SharedKeyStatus> {
|
|
639
|
+
return withClient(client, `${vaultPath(mailboxUid)}/escrow-wraps`, { method: "PUT", body: JSON.stringify({ wraps }) });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** One key a "Export keys for Outlook/phone" file held, as `recordKeyExport()` reports it. */
|
|
643
|
+
export interface ExportedKeyRecord {
|
|
644
|
+
kind: "zero-access" | "encrypt" | "sign";
|
|
645
|
+
fingerprint: string;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Records with restapi that the owner exported these private keys (`key_vault.export` in the audit log). `keyExport.ts`
|
|
649
|
+
* calls it before handing over the file, so an export the server refuses to record isn't handed over. Owner-only. */
|
|
650
|
+
export async function recordKeyExport(mailboxUid: string, keys: ExportedKeyRecord[], client?: ApiClient): Promise<void> {
|
|
651
|
+
await withClient(client, `/mail/mailboxes/${encodeURIComponent(mailboxUid)}/keyvault/exports`, {
|
|
652
|
+
method: "POST",
|
|
653
|
+
body: JSON.stringify({ keys }),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
511
657
|
export interface RekeyInput extends ExpectedMasterKeyGeneration {
|
|
512
658
|
wrappedKeys: WrappedPrivateKey[];
|
|
513
659
|
/** Every wrap of the new master key. For a mailbox assigned an escrow scope, this must include a fresh escrow wrap
|
|
@@ -537,6 +683,11 @@ export interface KeyLookupResult {
|
|
|
537
683
|
keyConflicts?: KeyConflict[];
|
|
538
684
|
/** Keys that were pinned before, newest first - see `PreviousKey`. */
|
|
539
685
|
previousKeys?: PreviousKey[];
|
|
686
|
+
/** The format to use for a recipient reachable in both (restapi *Choosing the format*): the user's choice for the contact, else what
|
|
687
|
+
* the address advertises, else what it was last seen using. Absent when nothing says - S/MIME then. */
|
|
688
|
+
preferredFormat?: EndToEndFormat;
|
|
689
|
+
/** Where `preferredFormat` came from. */
|
|
690
|
+
preferredFormatSource?: "contact" | "advertised" | "observed";
|
|
540
691
|
}
|
|
541
692
|
|
|
542
693
|
/** Server-side Discovery: the server itself performs the `_rapidmx` DNS lookup and remote key-endpoint
|
|
@@ -551,8 +702,11 @@ export function lookupKeys(mailboxUid: string, addr: string, client?: ApiClient)
|
|
|
551
702
|
/** What `trustSigner()` pins: the sender's address and the certificate that signed their message. */
|
|
552
703
|
export interface TrustSignerInput {
|
|
553
704
|
address: string;
|
|
554
|
-
/** Base64 DER of the signer certificate - `MessageSecurityResult.signerCertificate
|
|
705
|
+
/** Base64 DER of the signer certificate - `MessageSecurityResult.signerCertificate` - or, with `type: "openpgp"`, an OpenPGP
|
|
706
|
+
* public key (armored or base64 binary): a signer's, a key attached to a message, or one the user pasted. */
|
|
555
707
|
certificate: string;
|
|
708
|
+
/** `"openpgp"` to pin an OpenPGP key (it encrypts and signs); omitted for an X.509 signing certificate. */
|
|
709
|
+
type?: string;
|
|
556
710
|
}
|
|
557
711
|
|
|
558
712
|
/**
|
|
@@ -578,7 +732,7 @@ export async function trustSigner(mailboxUid: string, input: TrustSignerInput, c
|
|
|
578
732
|
try {
|
|
579
733
|
return await withClient<KeyLookupResult>(client, `/mail/mailboxes/${encodeURIComponent(mailboxUid)}/keys/trust`, {
|
|
580
734
|
method: "POST",
|
|
581
|
-
body: JSON.stringify({ address: input.address, certificate: input.certificate }),
|
|
735
|
+
body: JSON.stringify({ address: input.address, certificate: input.certificate, ...(input.type ? { type: input.type } : {}) }),
|
|
582
736
|
});
|
|
583
737
|
} catch (err) {
|
|
584
738
|
if (err instanceof ApiRequestError && err.status === 409) {
|
|
@@ -603,6 +757,8 @@ export interface ResolveKeyConflictInput {
|
|
|
603
757
|
/** For `"accept"`: base64 DER of the certificate to pin (e.g. `MessageSecurityResult.signerCertificate` of a
|
|
604
758
|
* `signer_key_changed` result). Omitted, restapi pins the recorded conflict's `observedKey`. */
|
|
605
759
|
certificate?: string;
|
|
760
|
+
/** `"openpgp"` for the contact's OpenPGP key (always `useType: "encrypt"`); omitted for its S/MIME certificate. */
|
|
761
|
+
type?: string;
|
|
606
762
|
}
|
|
607
763
|
|
|
608
764
|
/** The contact's resulting key state after `resolveKeyConflict()`. */
|
|
@@ -637,6 +793,7 @@ export async function resolveKeyConflict(
|
|
|
637
793
|
action: input.action,
|
|
638
794
|
expectedPinnedFingerprint: input.expectedPinnedFingerprint,
|
|
639
795
|
...(input.certificate !== undefined ? { certificate: input.certificate } : {}),
|
|
796
|
+
...(input.type ? { type: input.type } : {}),
|
|
640
797
|
};
|
|
641
798
|
try {
|
|
642
799
|
return await withClient<ResolveKeyConflictResult>(client, `/mail/mailboxes/${encodeURIComponent(mailboxUid)}/keys/resolve`, {
|
|
@@ -657,6 +814,13 @@ export interface EncryptionPolicy {
|
|
|
657
814
|
encryptSameOrg: PolicyState;
|
|
658
815
|
encryptFederated: PolicyState;
|
|
659
816
|
encryptExternal: PolicyState;
|
|
817
|
+
/** "Allow zero-access encryption": whether mailboxes may store mail encrypted at rest. Always present from a server that
|
|
818
|
+
* supports zero-access encryption (its stored value or the server default); absent from one that doesn't, which a
|
|
819
|
+
* caller treats as not allowed. */
|
|
820
|
+
allowZeroAccess?: boolean;
|
|
821
|
+
/** "Allow OpenPGP": whether mailboxes may enroll and publish OpenPGP keys, for mail with OpenPGP users. Absent from a server
|
|
822
|
+
* that predates it, which a caller treats as not allowed. */
|
|
823
|
+
allowOpenPgp?: boolean;
|
|
660
824
|
}
|
|
661
825
|
|
|
662
826
|
/** The system-wide encryption policy (readable by any authenticated user, used to decide what encryption
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* The temporary per-meeting key of a meeting with external guests (meet-plugin `specs/meeting_privacy.md` §3-4): a P-256 keypair made
|
|
7
|
+
* in the browser of whoever creates the meeting, with a self-signed certificate so the CMS code in `smime.ts` seals to it and opens with
|
|
8
|
+
* it unchanged. Every participant receives the private key in their link's `#k=` part; the organizer also keeps it sealed to their own
|
|
9
|
+
* zero-access certificate. A live AES-256-GCM key derived from it encrypts the meeting's signaling and relayed media. Shared by the
|
|
10
|
+
* organizer's calendar editor, meet-plugin and booking-plugin.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here talks to the server: the private key never leaves the browser except inside a link or a CMS wrap.
|
|
13
|
+
*/
|
|
14
|
+
import { fromBase64Url, toBase64Url } from "./encoding.js";
|
|
15
|
+
import type { DecryptionKey } from "./smime.js";
|
|
16
|
+
|
|
17
|
+
/** The `#` parameter of a link that carries a meeting key. */
|
|
18
|
+
export const MEETING_KEY_FRAGMENT_PARAM = "k";
|
|
19
|
+
|
|
20
|
+
/** HKDF `info` of the live key (`specs/meeting_privacy.md` §3). */
|
|
21
|
+
const LIVE_KEY_INFO = "rapidmx-meeting-live-v1";
|
|
22
|
+
|
|
23
|
+
const EC_PARAMS: EcKeyImportParams = { name: "ECDH", namedCurve: "P-256" };
|
|
24
|
+
|
|
25
|
+
/** A meeting key as its creator holds it. */
|
|
26
|
+
export interface GeneratedMeetingKey {
|
|
27
|
+
/** PKCS#8 DER of the private key - what a link carries and a wrap seals. */
|
|
28
|
+
pkcs8: Uint8Array;
|
|
29
|
+
/** The self-signed certificate's DER - what the server stores and data is sealed to. */
|
|
30
|
+
certificateDer: Uint8Array;
|
|
31
|
+
/** SHA-256 of `certificateDer`, lowercase hex. */
|
|
32
|
+
fingerprint: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadX509(): Promise<typeof import("@peculiar/x509")> {
|
|
36
|
+
await import("reflect-metadata");
|
|
37
|
+
const x509 = await import("@peculiar/x509");
|
|
38
|
+
x509.cryptoProvider.set(crypto);
|
|
39
|
+
return x509;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
|
43
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes as BufferSource));
|
|
44
|
+
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A new meeting key valid until `expiresAt` (its certificate's `notAfter`). */
|
|
48
|
+
export async function generateMeetingKey(expiresAt: Date): Promise<GeneratedMeetingKey> {
|
|
49
|
+
const x509 = await loadX509();
|
|
50
|
+
// ECDSA to self-sign the certificate; the same P-256 key is re-imported as ECDH to open what is sealed to it (`importMeetingKey()`).
|
|
51
|
+
const keys = (await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]));
|
|
52
|
+
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
53
|
+
serialNumber: toHex(crypto.getRandomValues(new Uint8Array(16))),
|
|
54
|
+
name: "CN=meeting",
|
|
55
|
+
notBefore: new Date(Date.now() - 60_000),
|
|
56
|
+
notAfter: expiresAt,
|
|
57
|
+
signingAlgorithm: { name: "ECDSA", hash: "SHA-256" },
|
|
58
|
+
keys,
|
|
59
|
+
});
|
|
60
|
+
const certificateDer = new Uint8Array(cert.rawData);
|
|
61
|
+
const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", keys.privateKey));
|
|
62
|
+
return { pkcs8, certificateDer, fingerprint: await sha256Hex(certificateDer) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function toHex(bytes: Uint8Array): string {
|
|
66
|
+
// A positive serial: the top bit clear.
|
|
67
|
+
const copy = Uint8Array.from(bytes);
|
|
68
|
+
copy[0] &= 0x7f;
|
|
69
|
+
return Array.from(copy, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The `#` part a guest's link carries for `pkcs8`: `k=<base64url>`. */
|
|
73
|
+
export function meetingKeyFragment(pkcs8: Uint8Array): string {
|
|
74
|
+
return `${MEETING_KEY_FRAGMENT_PARAM}=${toBase64Url(pkcs8)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The meeting key in `hash` (a `location.hash`, with or without its `#`), or `undefined` when it carries none or it is malformed. */
|
|
78
|
+
export function readMeetingKeyFragment(hash: string): Uint8Array | undefined {
|
|
79
|
+
const params = new URLSearchParams(hash.replace(/^#/, ""));
|
|
80
|
+
const value = params.get(MEETING_KEY_FRAGMENT_PARAM);
|
|
81
|
+
if (!value || !/^[A-Za-z0-9_-]{16,1024}$/.test(value)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return fromBase64Url(value);
|
|
86
|
+
} catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The meeting key `pkcs8` as the CMS code opens with it, and its live key for meeting `meetingUid`. Rejects a key that isn't P-256. */
|
|
92
|
+
export async function importMeetingKey(
|
|
93
|
+
pkcs8: Uint8Array,
|
|
94
|
+
certificateDer: Uint8Array,
|
|
95
|
+
meetingUid: string,
|
|
96
|
+
): Promise<{ decryptionKey: DecryptionKey; liveKey: CryptoKey }> {
|
|
97
|
+
// Extractable once, only to read its scalar for the live key; the key kept for CMS isn't.
|
|
98
|
+
const readable = await crypto.subtle.importKey("pkcs8", pkcs8 as BufferSource, EC_PARAMS, true, ["deriveBits"]);
|
|
99
|
+
const jwk = await crypto.subtle.exportKey("jwk", readable);
|
|
100
|
+
/* v8 ignore next 3 -- an imported PKCS#8 private key always exports its scalar */
|
|
101
|
+
if (!jwk.d) {
|
|
102
|
+
throw new Error("This is not a meeting key.");
|
|
103
|
+
}
|
|
104
|
+
const privateKey = await crypto.subtle.importKey("pkcs8", pkcs8 as BufferSource, EC_PARAMS, false, ["deriveBits"]);
|
|
105
|
+
return { decryptionKey: { certDer: certificateDer, privateKey }, liveKey: await deriveLiveKey(fromBase64Url(jwk.d), meetingUid) };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The AES-256-GCM live key of meeting `meetingUid` from `ikm`, the meeting key's private scalar. Non-extractable. */
|
|
109
|
+
export async function deriveLiveKey(ikm: Uint8Array, meetingUid: string): Promise<CryptoKey> {
|
|
110
|
+
const base = await crypto.subtle.importKey("raw", ikm as BufferSource, "HKDF", false, ["deriveKey"]);
|
|
111
|
+
return crypto.subtle.deriveKey(
|
|
112
|
+
{ name: "HKDF", hash: "SHA-256", salt: new TextEncoder().encode(meetingUid), info: new TextEncoder().encode(LIVE_KEY_INFO) },
|
|
113
|
+
base,
|
|
114
|
+
{ name: "AES-GCM", length: 256 },
|
|
115
|
+
false,
|
|
116
|
+
["encrypt", "decrypt"],
|
|
117
|
+
);
|
|
118
|
+
}
|