From 4e7d907663026b74484b37942c7ac4cfb1ff6cf9 Mon Sep 17 00:00:00 2001 From: Mathieu Amiot Date: Mon, 24 Apr 2023 18:42:47 +0200 Subject: [PATCH] feat: CoreCrypto draft-20 upgrade --- Cargo.toml | 41 +- crypto-ffi/Cargo.toml | 1 + crypto-ffi/bindings/js/CoreCrypto.ts | 122 ++-- .../bindings/js/test/CoreCrypto.test.js | 72 +- .../kt/main/com/wire/crypto/CoreCrypto.kt | 608 ++++++++-------- .../main/com/wire/crypto/client/MLSClient.kt | 20 +- .../swift/Sources/CoreCrypto/CoreCrypto.swift | 107 ++- crypto-ffi/src/CoreCrypto.udl | 25 +- crypto-ffi/src/generic.rs | 126 ++-- crypto-ffi/src/wasm.rs | 131 ++-- crypto/Cargo.toml | 12 +- crypto/benches/commit.rs | 29 +- crypto/benches/create_group.rs | 42 +- .../{public_group_state.rs => group_info.rs} | 10 +- crypto/benches/mls_proteus.rs | 17 +- crypto/benches/proposal.rs | 3 +- crypto/benches/scripts/run.sh | 2 +- crypto/benches/scripts/test.sh | 2 +- crypto/benches/utils/mls.rs | 46 +- crypto/src/e2e_identity/crypto.rs | 18 +- crypto/src/e2e_identity/degraded.rs | 8 +- crypto/src/e2e_identity/identity.rs | 17 +- crypto/src/e2e_identity/mod.rs | 112 ++- crypto/src/error.rs | 52 +- crypto/src/lib.rs | 35 +- crypto/src/mls/client/identifier.rs | 10 +- crypto/src/mls/client/identities.rs | 8 +- crypto/src/mls/client/key_package.rs | 143 ++-- crypto/src/mls/client/mod.rs | 322 +++++---- crypto/src/mls/conversation/commit_delay.rs | 42 +- crypto/src/mls/conversation/config.rs | 26 +- crypto/src/mls/conversation/decrypt.rs | 471 ++++++------ crypto/src/mls/conversation/encrypt.rs | 10 +- crypto/src/mls/conversation/export.rs | 3 +- crypto/src/mls/conversation/group_info.rs | 105 +++ crypto/src/mls/conversation/handshake.rs | 385 +++++----- crypto/src/mls/conversation/merge.rs | 22 +- crypto/src/mls/conversation/mod.rs | 66 +- .../mls/conversation/public_group_state.rs | 103 --- crypto/src/mls/conversation/renew.rs | 109 +-- crypto/src/mls/credential/ext.rs | 48 +- crypto/src/mls/credential/mod.rs | 196 +++-- crypto/src/mls/credential/typ.rs | 14 + crypto/src/mls/credential/x509.rs | 22 +- crypto/src/mls/external_commit.rs | 127 ++-- crypto/src/mls/external_proposal.rs | 497 +------------ crypto/src/mls/member.rs | 45 +- crypto/src/mls/mod.rs | 81 ++- crypto/src/mls/proposal.rs | 70 +- crypto/src/proteus.rs | 2 +- crypto/src/test_utils/central.rs | 147 ++-- crypto/src/test_utils/fixtures.rs | 17 +- crypto/src/test_utils/message.rs | 17 + crypto/src/test_utils/mod.rs | 2 + crypto/tests/sizes/group_info.rs | 74 -- crypto/tests/sizes/increment_message.rs | 61 -- deny.toml | 2 - extras/anycrypto/Cargo.toml | 26 - extras/anycrypto/LICENSE | 674 ------------------ extras/anycrypto/README.md | 5 - extras/anycrypto/src/central/config.rs | 87 --- extras/anycrypto/src/central/mod.rs | 173 ----- extras/anycrypto/src/error.rs | 54 -- extras/anycrypto/src/ffi.rs | 50 -- extras/anycrypto/src/lib.rs | 128 ---- extras/anycrypto/src/message.rs | 54 -- interop/Cargo.toml | 1 + interop/src/clients/corecrypto/native.rs | 4 +- interop/src/main.rs | 15 +- keystore/Cargo.toml | 15 +- keystore/benches/read.rs | 52 +- keystore/benches/write.rs | 45 +- .../generic/migrations/V1__schema.sql | 62 +- .../generic/migrations/V2__proteus.sql | 9 - .../generic/migrations/V3__parentgroups.sql | 2 - .../platform/generic/migrations/V4__e2ei.sql | 4 - .../src/connection/platform/generic/mod.rs | 23 +- keystore/src/connection/platform/wasm/mod.rs | 68 +- .../src/connection/platform/wasm/storage.rs | 64 +- keystore/src/entities/mls.rs | 69 +- keystore/src/entities/mod.rs | 26 +- .../platform/generic/mls/credential.rs | 185 +++++ .../generic/mls/encryption_keypair.rs | 208 ++++++ .../platform/generic/mls/enrollment.rs | 6 +- .../entities/platform/generic/mls/group.rs | 8 +- .../platform/generic/mls/hpke_private_key.rs | 188 +++++ .../entities/platform/generic/mls/identity.rs | 285 -------- .../platform/generic/mls/keypackage.rs | 145 ++-- .../src/entities/platform/generic/mls/mod.rs | 6 +- .../platform/generic/mls/pending_group.rs | 31 +- .../platform/generic/mls/psk_bundle.rs | 177 +++++ .../platform/generic/mls/signature_keypair.rs | 342 +++++++++ .../platform/generic/proteus/prekey.rs | 4 +- .../wasm/mls/{identity.rs => credential.rs} | 48 +- .../platform/wasm/mls/encryption_keypair.rs | 78 ++ .../entities/platform/wasm/mls/enrollment.rs | 4 +- .../src/entities/platform/wasm/mls/group.rs | 12 +- .../platform/wasm/mls/hpke_private_key.rs | 78 ++ .../entities/platform/wasm/mls/keypackage.rs | 33 +- .../src/entities/platform/wasm/mls/mod.rs | 6 +- .../entities/platform/wasm/mls/psk_bundle.rs | 78 ++ .../platform/wasm/mls/signature_keypair.rs | 103 +++ .../platform/wasm/proteus/identity.rs | 2 +- .../entities/platform/wasm/proteus/prekey.rs | 4 +- .../entities/platform/wasm/proteus/session.rs | 4 +- keystore/src/entities/proteus.rs | 18 - keystore/src/error.rs | 16 +- keystore/src/lib.rs | 1 + keystore/src/mls.rs | 394 +++++----- keystore/src/proteus.rs | 22 - keystore/tests/general.rs | 8 +- keystore/tests/mls.rs | 346 +++++---- keystore/tests/z_entities.rs | 153 +++- mls-provider/Cargo.toml | 18 +- mls-provider/src/crypto_provider.rs | 590 +++++++++------ mls-provider/tests/crypto.rs | 38 +- mls-provider/tests/randomness.rs | 10 +- 117 files changed, 5020 insertions(+), 5244 deletions(-) rename crypto/benches/{public_group_state.rs => group_info.rs} (79%) create mode 100644 crypto/src/mls/conversation/group_info.rs delete mode 100644 crypto/src/mls/conversation/public_group_state.rs create mode 100644 crypto/src/test_utils/message.rs delete mode 100644 crypto/tests/sizes/group_info.rs delete mode 100644 crypto/tests/sizes/increment_message.rs delete mode 100644 extras/anycrypto/Cargo.toml delete mode 100644 extras/anycrypto/LICENSE delete mode 100644 extras/anycrypto/README.md delete mode 100644 extras/anycrypto/src/central/config.rs delete mode 100644 extras/anycrypto/src/central/mod.rs delete mode 100644 extras/anycrypto/src/error.rs delete mode 100644 extras/anycrypto/src/ffi.rs delete mode 100644 extras/anycrypto/src/lib.rs delete mode 100644 extras/anycrypto/src/message.rs delete mode 100644 keystore/src/connection/platform/generic/migrations/V2__proteus.sql delete mode 100644 keystore/src/connection/platform/generic/migrations/V3__parentgroups.sql delete mode 100644 keystore/src/connection/platform/generic/migrations/V4__e2ei.sql create mode 100644 keystore/src/entities/platform/generic/mls/credential.rs create mode 100644 keystore/src/entities/platform/generic/mls/encryption_keypair.rs create mode 100644 keystore/src/entities/platform/generic/mls/hpke_private_key.rs delete mode 100644 keystore/src/entities/platform/generic/mls/identity.rs create mode 100644 keystore/src/entities/platform/generic/mls/psk_bundle.rs create mode 100644 keystore/src/entities/platform/generic/mls/signature_keypair.rs rename keystore/src/entities/platform/wasm/mls/{identity.rs => credential.rs} (58%) create mode 100644 keystore/src/entities/platform/wasm/mls/encryption_keypair.rs create mode 100644 keystore/src/entities/platform/wasm/mls/hpke_private_key.rs create mode 100644 keystore/src/entities/platform/wasm/mls/psk_bundle.rs create mode 100644 keystore/src/entities/platform/wasm/mls/signature_keypair.rs diff --git a/Cargo.toml b/Cargo.toml index 5ab77f115d..8c3d9c403c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,9 @@ exclude = [ ] resolver = "2" +[workspace.dependencies] +tls_codec = "0.3.0-pre.3" + [patch.crates-io.schnellru] git = "https://github.com/otak/schnellru" branch = "feat/try-insert" @@ -39,33 +42,32 @@ branch = "otak/2.0-error-codes" [patch.crates-io.openmls] package = "openmls" git = "https://github.com/wireapp/openmls" -tag = "v0.5.6-pre.core-crypto-0.7.0" +tag = "v0.20.0-pre.core-crypto-1.0.0" [patch.crates-io.openmls_traits] package = "openmls_traits" git = "https://github.com/wireapp/openmls" -tag = "v0.5.6-pre.core-crypto-0.7.0" + tag = "v0.20.0-pre.core-crypto-1.0.0" + +[patch.crates-io.openmls_basic_credential] +package = "openmls_basic_credential" +git = "https://github.com/wireapp/openmls" +tag = "v0.20.0-pre.core-crypto-1.0.0" + +[patch.crates-io.openmls_x509_credential] +package = "openmls_x509_credential" +git = "https://github.com/wireapp/openmls" +tag = "v0.20.0-pre.core-crypto-1.0.0" + +[patch.crates-io.hpke] +git = "https://github.com/rozbb/rust-hpke.git" +rev = "2867b0ae90a36f27e2c312fe741f268ad558abbd" [patch.crates-io.wire-e2e-identity] git = "https://github.com/wireapp/rusty-jwt-tools" package = "wire-e2e-identity" tag = "v0.4.3" -[patch.crates-io.hpke-rs] -git = "https://github.com/wireapp/hpke-rs" -branch = "feat/0.2" -package = "hpke-rs" - -[patch.crates-io.hpke-rs-crypto] -git = "https://github.com/wireapp/hpke-rs" -branch = "feat/0.2" -package = "hpke-rs-crypto" - -[patch.crates-io.hpke-rs-rust-crypto] -git = "https://github.com/wireapp/hpke-rs" -branch = "feat/0.2" -package = "hpke-rs-rust-crypto" - # aarch64-apple-ios-sim target support has not yet been released [patch.crates-io.openssl-src] git = "https://github.com/alexcrichton/openssl-src-rs.git" @@ -76,11 +78,6 @@ package = "openssl-src" git = "https://github.com/wireapp/rust-jwt-simple" tag = "v0.11.4-pre.core-crypto-0.7.0" -# Needed for quick mode and (later) result comparison see (https://www.tweag.io/blog/2022-03-03-criterion-rs/) -# TODO: remove once branch got merged in 0.4 release -[patch.crates-io.criterion] -git = "https://github.com/bheisler/criterion.rs" -branch = "version-0.4" [profile.release] lto = true diff --git a/crypto-ffi/Cargo.toml b/crypto-ffi/Cargo.toml index debf6909c4..e2e1567935 100644 --- a/crypto-ffi/Cargo.toml +++ b/crypto-ffi/Cargo.toml @@ -22,6 +22,7 @@ thiserror = "1.0" cfg-if = "1.0" futures-util = "0.3" async-trait = "0.1" +tls_codec = { workspace = true } # UniFFI - Android + iOS bindings - Runtime support uniffi = { version = "0.23", optional = true } diff --git a/crypto-ffi/bindings/js/CoreCrypto.ts b/crypto-ffi/bindings/js/CoreCrypto.ts index afd262e81a..d4262504ef 100644 --- a/crypto-ffi/bindings/js/CoreCrypto.ts +++ b/crypto-ffi/bindings/js/CoreCrypto.ts @@ -240,11 +240,11 @@ export interface MemberAddedMessages { */ welcome: Uint8Array; /** - * MLS PublicGroupState (GroupInfo in draft-15) which is required for joining a group by external commit + * MLS GroupInfo which is required for joining a group by external commit * * @readonly */ - publicGroupState: PublicGroupStateBundle; + groupInfo: GroupInfoBundle; } /** @@ -264,37 +264,37 @@ export interface CommitBundle { */ welcome?: Uint8Array; /** - * MLS PublicGroupState (GroupInfo in draft-15) which is required for joining a group by external commit + * MLS GroupInfo which is required for joining a group by external commit * * @readonly */ - publicGroupState: PublicGroupStateBundle; + groupInfo: GroupInfoBundle; } /** - * Wraps a PublicGroupState in order to efficiently upload it to the Delivery Service. + * Wraps a GroupInfo in order to efficiently upload it to the Delivery Service. * This is not part of MLS protocol but parts might be standardized at some point. */ -export interface PublicGroupStateBundle { +export interface GroupInfoBundle { /** - * see {@link PublicGroupStateEncryptionType} + * see {@link GroupInfoEncryptionType} */ - encryptionType: PublicGroupStateEncryptionType, + encryptionType: GroupInfoEncryptionType, /** * see {@link RatchetTreeType} */ ratchetTreeType: RatchetTreeType, /** - * TLS-serialized PublicGroupState + * TLS-serialized GroupInfo */ payload: Uint8Array, } /** - * Informs whether the PublicGroupState is confidential - * see [core_crypto::mls::conversation::public_group_state::PublicGroupStateEncryptionType] + * Informs whether the GroupInfo is confidential + * see [core_crypto::mls::conversation::group_info::GroupInfoEncryptionType] */ -export enum PublicGroupStateEncryptionType { +export enum GroupInfoEncryptionType { /** * Unencrypted */ @@ -307,11 +307,11 @@ export enum PublicGroupStateEncryptionType { /** * Represents different ways of carrying the Ratchet Tree with some optimizations to save some space - * see [core_crypto::mls::conversation::public_group_state::RatchetTreeType] + * see [core_crypto::mls::conversation::group_info::RatchetTreeType] */ export enum RatchetTreeType { /** - * Complete PublicGroupState + * Complete GroupInfo */ Full = 0x01, /** @@ -398,7 +398,7 @@ export interface ConversationInitBundle { * * @readonly */ - publicGroupState: PublicGroupStateBundle; + groupInfo: GroupInfoBundle; } /** @@ -536,10 +536,6 @@ export enum ExternalProposalType { * This allows to propose the addition of other clients to the MLS group/conversation */ Add, - /** - * This allows to propose the removal of clients from the MLS group/conversation - */ - Remove, } export interface ExternalProposalArgs { @@ -554,13 +550,6 @@ export interface ExternalProposalArgs { epoch: number; } -export interface ExternalRemoveProposalArgs extends ExternalProposalArgs { - /** - * KeyPackageRef of the client that needs to be removed in the proposal - */ - keyPackageRef: Uint8Array; -} - export interface ExternalAddProposalArgs extends ExternalProposalArgs { /** * {@link Ciphersuite} to propose to join the MLS group with. @@ -994,15 +983,15 @@ export class CoreCrypto { ffiClients.forEach(c => c.free()); - const pgs = ffiRet.public_group_state; + const gi = ffiRet.group_info; const ret: MemberAddedMessages = { welcome: ffiRet.welcome, commit: ffiRet.commit, - publicGroupState: { - encryptionType: pgs.encryption_type, - ratchetTreeType: pgs.ratchet_tree_type, - payload: pgs.payload + groupInfo: { + encryptionType: gi.encryption_type, + ratchetTreeType: gi.ratchet_tree_type, + payload: gi.payload }, }; @@ -1035,15 +1024,15 @@ export class CoreCrypto { clientIds )); - const pgs = ffiRet.public_group_state; + const gi = ffiRet.group_info; const ret: CommitBundle = { welcome: ffiRet.welcome, commit: ffiRet.commit, - publicGroupState: { - encryptionType: pgs.encryption_type, - ratchetTreeType: pgs.ratchet_tree_type, - payload: pgs.payload + groupInfo: { + encryptionType: gi.encryption_type, + ratchetTreeType: gi.ratchet_tree_type, + payload: gi.payload }, }; @@ -1070,15 +1059,15 @@ export class CoreCrypto { conversationId )); - const pgs = ffiRet.public_group_state; + const gi = ffiRet.group_info; const ret: CommitBundle = { welcome: ffiRet.welcome, commit: ffiRet.commit, - publicGroupState: { - encryptionType: pgs.encryption_type, - ratchetTreeType: pgs.ratchet_tree_type, - payload: pgs.payload + groupInfo: { + encryptionType: gi.encryption_type, + ratchetTreeType: gi.ratchet_tree_type, + payload: gi.payload }, }; @@ -1109,15 +1098,15 @@ export class CoreCrypto { return undefined; } - const pgs = ffiCommitBundle.public_group_state; + const gi = ffiCommitBundle.group_info; return { welcome: ffiCommitBundle.welcome, commit: ffiCommitBundle.commit, - publicGroupState: { - encryptionType: pgs.encryption_type, - ratchetTreeType: pgs.ratchet_tree_type, - payload: pgs.payload + groupInfo: { + encryptionType: gi.encryption_type, + ratchetTreeType: gi.ratchet_tree_type, + payload: gi.payload }, }; } catch(e) { @@ -1170,41 +1159,30 @@ export class CoreCrypto { async newExternalProposal( externalProposalType: ExternalProposalType, - args: ExternalAddProposalArgs | ExternalRemoveProposalArgs + args: ExternalAddProposalArgs ): Promise { switch (externalProposalType) { case ExternalProposalType.Add: { let addArgs = (args as ExternalAddProposalArgs); return await CoreCryptoError.asyncMapErr(this.#cc.new_external_add_proposal(args.conversationId, args.epoch, addArgs.ciphersuite, addArgs.credentialType)); } - case ExternalProposalType.Remove: { - if (!(args as ExternalRemoveProposalArgs).keyPackageRef) { - throw new Error("keyPackageRef is not contained in the external proposal arguments"); - } - - return await CoreCryptoError.asyncMapErr(this.#cc.new_external_remove_proposal( - args.conversationId, - args.epoch, - (args as ExternalRemoveProposalArgs).keyPackageRef - )); - } default: throw new Error("Invalid external proposal type!"); } } /** - * Exports public group state for use in external commits + * Exports GroupInfo for use in external commits * * @param conversationId - MLS Conversation ID - * @returns TLS-serialized MLS public group state + * @returns TLS-serialized MLS GroupInfo */ - async exportGroupState(conversationId: ConversationId): Promise { - return await CoreCryptoError.asyncMapErr(this.#cc.export_group_state(conversationId)); + async exportGroupInfo(conversationId: ConversationId): Promise { + return await CoreCryptoError.asyncMapErr(this.#cc.export_group_info(conversationId)); } /** - * Allows to create an external commit to "apply" to join a group through its public group state. + * Allows to create an external commit to "apply" to join a group through its GroupInfo. * * If the Delivery Service accepts the external commit, you have to {@link CoreCrypto.mergePendingGroupFromExternalCommit} * in order to get back a functional MLS group. On the opposite, if it rejects it, you can either retry by just @@ -1213,28 +1191,28 @@ export class CoreCrypto { * {@link CoreCrypto.clearPendingGroupFromExternalCommit} in order not to bloat the user's storage but nothing * bad can happen if you forget to except some storage space wasted. * - * @param publicGroupState - a TLS encoded PublicGroupState fetched from the Delivery Service + * @param groupInfo - a TLS encoded GroupInfo fetched from the Delivery Service * @param credentialType - kind of Credential to use for joining this group. If {@link CredentialType.Basic} is * chosen and no Credential has been created yet for it, a new one will be generated. * @param configuration - configuration of the MLS group * When {@link CredentialType.X509} is chosen, it fails when no Credential has been created for the given {@link Ciphersuite}. * @returns see {@link ConversationInitBundle} */ - async joinByExternalCommit(publicGroupState: Uint8Array, credentialType: CredentialType, configuration: CustomConfiguration = {}): Promise { + async joinByExternalCommit(groupInfo: Uint8Array, credentialType: CredentialType, configuration: CustomConfiguration = {}): Promise { try { const {keyRotationSpan, wirePolicy} = configuration || {}; const config = new CoreCrypto.#module.CustomConfiguration(keyRotationSpan, wirePolicy); - const ffiInitMessage: CoreCryptoFfiTypes.ConversationInitBundle = await CoreCryptoError.asyncMapErr(this.#cc.join_by_external_commit(publicGroupState, config, credentialType)); + const ffiInitMessage: CoreCryptoFfiTypes.ConversationInitBundle = await CoreCryptoError.asyncMapErr(this.#cc.join_by_external_commit(groupInfo, config, credentialType)); - const pgs = ffiInitMessage.public_group_state; + const gi = ffiInitMessage.group_info; const ret: ConversationInitBundle = { conversationId: ffiInitMessage.conversation_id, commit: ffiInitMessage.commit, - publicGroupState: { - encryptionType: pgs.encryption_type, - ratchetTreeType: pgs.ratchet_tree_type, - payload: pgs.payload + groupInfo: { + encryptionType: gi.encryption_type, + ratchetTreeType: gi.ratchet_tree_type, + payload: gi.payload }, }; @@ -1806,7 +1784,6 @@ export class WireE2eIdentity { * Verifies that the previous challenge has been completed. * * @param orderUrl `location` header from http response you got from {@link newOrderResponse} - * @param account you found after {@link newAccountResponse} * @param previousNonce `replay-nonce` response header from `POST /acme/{provisioner-name}/challenge/{challenge-id}` * @see https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4 */ @@ -1836,7 +1813,6 @@ export class WireE2eIdentity { /** * Final step before fetching the certificate. * - * @param order - order you got from {@link checkOrderResponse} * @param previousNonce - `replay-nonce` response header from `POST /acme/{provisioner-name}/order/{order-id}` * @see https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4 */ diff --git a/crypto-ffi/bindings/js/test/CoreCrypto.test.js b/crypto-ffi/bindings/js/test/CoreCrypto.test.js index be42c684cc..1f9bfbc87a 100644 --- a/crypto-ffi/bindings/js/test/CoreCrypto.test.js +++ b/crypto-ffi/bindings/js/test/CoreCrypto.test.js @@ -76,25 +76,25 @@ test("init", async () => { await ctx.close(); }); -test("can use pgs enums", async () => { +test("can use groupInfo enums", async () => { const [ctx, page] = await initBrowser(); - const [PublicGroupStateEncryptionType, RatchetTreeType] = await page.evaluate(async () => { - const { CoreCrypto, Ciphersuite, CredentialType, PublicGroupStateEncryptionType, RatchetTreeType } = await import ("./corecrypto.js"); + const [GroupInfoEncryptionType, RatchetTreeType] = await page.evaluate(async () => { + const { CoreCrypto, Ciphersuite, CredentialType, GroupInfoEncryptionType, RatchetTreeType } = await import ("./corecrypto.js"); - window.PublicGroupStateEncryptionType = PublicGroupStateEncryptionType; + window.GroupInfoEncryptionType = GroupInfoEncryptionType; window.RatchetTreeType = RatchetTreeType; window.CoreCrypto = CoreCrypto; window.Ciphersuite = Ciphersuite; window.CredentialType = CredentialType.Basic; - return [PublicGroupStateEncryptionType, RatchetTreeType]; + return [GroupInfoEncryptionType, RatchetTreeType]; }); - expect(PublicGroupStateEncryptionType.Plaintext).toBe(0x01); - expect(PublicGroupStateEncryptionType.JweEncrypted).toBe(0x02); - expect(await page.evaluate(() => window.PublicGroupStateEncryptionType.Plaintext)).toBe(0x01); - expect(await page.evaluate(() => window.PublicGroupStateEncryptionType.JweEncrypted)).toBe(0x02); + expect(GroupInfoEncryptionType.Plaintext).toBe(0x01); + expect(GroupInfoEncryptionType.JweEncrypted).toBe(0x02); + expect(await page.evaluate(() => window.GroupInfoEncryptionType.Plaintext)).toBe(0x01); + expect(await page.evaluate(() => window.GroupInfoEncryptionType.JweEncrypted)).toBe(0x02); expect(RatchetTreeType.Full).toBe(0x01); expect(RatchetTreeType.Delta).toBe(0x02); expect(RatchetTreeType.ByRef).toBe(0x03); @@ -128,15 +128,15 @@ test("can use pgs enums", async () => { await cc.createConversation(conversationId, window.CredentialType); - const { publicGroupState } = await cc.addClientsToConversation(conversationId, [ + const { groupInfo: groupInfo } = await cc.addClientsToConversation(conversationId, [ { id: encoder.encode(client2Config.clientId), kp }, ]); - return publicGroupState; + return groupInfo; }); expect(pgs.encryptionType).toBe(0x01); - expect(pgs.encryptionType).toBe(PublicGroupStateEncryptionType.Plaintext); + expect(pgs.encryptionType).toBe(GroupInfoEncryptionType.Plaintext); expect(pgs.ratchetTreeType).toBe(0x01); expect(pgs.ratchetTreeType).toBe(RatchetTreeType.Full); @@ -479,7 +479,7 @@ test("roundtrip message", async () => { welcome.welcome = Uint8Array.from(welcome.welcome); welcome.commit = Uint8Array.from(welcome.commit); - welcome.publicGroupState = Uint8Array.from(welcome.publicGroupState); + welcome.groupInfo = Uint8Array.from(welcome.groupInfo); message = Uint8Array.from(Object.values(message)); @@ -704,9 +704,9 @@ test("ext commits|proposals & callbacks", async () => { throw new Error("clientIsExistingGroupUser callback wasn't triggered"); } - const pgs = extProposalCommit.publicGroupState; + const gi = extProposalCommit.groupInfo; - const extCommit = await ccExternalCommit.joinByExternalCommit(pgs.payload, credentialType); + const extCommit = await ccExternalCommit.joinByExternalCommit(gi.payload, credentialType); // ! This should trigger the userAuthorize callback const somethingCommit = cc.decryptMessage(conversationId, extCommit.commit); @@ -1024,30 +1024,30 @@ test("end-to-end-identity", async () => { const certificateReq = enrollment.certificateRequest(previousNonce); const certificateResp = "-----BEGIN CERTIFICATE-----\n" + - "MIICLjCCAdSgAwIBAgIQIi6jHWSEF/LHAkiyoiSHbjAKBggqhkjOPQQDAjAuMQ0w\n" + + "MIICIjCCAcigAwIBAgIQKRapc1IDZvJc88zB+vlrNTAKBggqhkjOPQQDAjAuMQ0w\n" + "CwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3aXJlIEludGVybWVkaWF0ZSBDQTAeFw0y\n" + - "MzA0MDUwOTI2NThaFw0yMzA0MDUxMDI2NThaMCkxETAPBgNVBAoTCHdpcmUuY29t\n" + - "MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhAGzbFXHk2ngUGpBYzabE\n" + - "AtDJIefbX1/wDUSDJbEL/nJNo4IBBjCCAQIwDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\n" + - "JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUhifYTPG7M3pyQMrz\n" + - "HYmakvfDG80wHwYDVR0jBBgwFoAUHPSH1n7X87LAYJnc+cFG2a3ZAQ4wcgYDVR0R\n" + - "BGswaYZQaW06d2lyZWFwcD1OamhsTXpJeE9XRmpPRFJpTkRBd1lqazBaR0ZoWkRB\n" + - "Mk56RXhOVEV5TlRnLzZjMTg2NmY1Njc2MTZmMzFAd2lyZS5jb22GFWltOndpcmVh\n" + - "cHA9YWxpY2Vfd2lyZTAdBgwrBgEEAYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYI\n" + - "KoZIzj0EAwIDSAAwRQIhAKY0Zs8SYwS7mFFenPDoCDHPQbCbV9VdvYpBQncOFD5K\n" + - "AiAisX68Di4B0dN059YsVDXpM0drnkrVTRKHV+F+ipDjZQ==\n" + + "MzA2MDYxMjAzMDlaFw0zMzA2MDMxMjAzMDlaMCkxETAPBgNVBAoTCHdpcmUuY29t\n" + + "MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhACqExBb1vLgMNq8GkLgM\n" + + "R+W+dp0szvjYL2GybNkPKzoto4H7MIH4MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE\n" + + "DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUaPHUDloFLv5o4j4J4EmvoYToqHcwHwYD\n" + + "VR0jBBgwFoAUlbTj2u59dFDGs1LVj0GrGKJUK/gwcgYDVR0RBGswaYYVaW06d2ly\n" + + "ZWFwcD1hbGljZV93aXJlhlBpbTp3aXJlYXBwPVl6QXpZalZoT1dRMFpqSXdOR0k1\n" + + "T1Rrek9HRTRPREptT1RjeE0yWm1PR00vNDk1OWJjNmFiMTJmMjg0NkB3aXJlLmNv\n" + + "bTAdBgwrBgEEAYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYIKoZIzj0EAwIDSAAw\n" + + "RQIhAIRaoCuyIAXtpAsUhZvJb7Qb+2EKsc9iIzHtsBU5MtVMAiAz2Tm4ojAolq4J\n" + + "ZjWPVSDz4AN1gd200EpS50cS/mLDqw==\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + - "MIIBtzCCAV6gAwIBAgIQPbElEJQ58HlbQf7bqrJjXTAKBggqhkjOPQQDAjAmMQ0w\n" + - "CwYDVQQKEwR3aXJlMRUwEwYDVQQDEwx3aXJlIFJvb3QgQ0EwHhcNMjMwNDA1MDky\n" + - "NjUzWhcNMzMwNDAyMDkyNjUzWjAuMQ0wCwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3\n" + - "aXJlIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGbM\n" + - "rA1eqJE9xlGOwO+sYbexThtlU/to9jJj5SBoKPx7Q8QMBlmPTjqDVumXhUvSe+xY\n" + - "JE7M+lBXfVZCywzIIPWjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" + - "AQH/AgEAMB0GA1UdDgQWBBQc9IfWftfzssBgmdz5wUbZrdkBDjAfBgNVHSMEGDAW\n" + - "gBQY+1rDw64QLm/weFQC1mo9y29ddTAKBggqhkjOPQQDAgNHADBEAiARvd7RBuuv\n" + - "OhUy7ncjd/nzoN5Qs0p6D+ujdSLDqLlNIAIgfkwAAgsQMDF3ClqVM/p9cmS95B0g\n" + - "CAdIObqPoNL5MJo=\n" + + "MIIBuTCCAV6gAwIBAgIQYiSIW2ebbC32Iq5YO0AyLDAKBggqhkjOPQQDAjAmMQ0w\n" + + "CwYDVQQKEwR3aXJlMRUwEwYDVQQDEwx3aXJlIFJvb3QgQ0EwHhcNMjMwNjA2MTIw\n" + + "MzA2WhcNMzMwNjAzMTIwMzA2WjAuMQ0wCwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3\n" + + "aXJlIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEKu\n" + + "1Ekx95MKKr9FxUspwFtyErShqoPKZNlyfz8u8lmvi50FpwqUXem1EoOUOm7UHy5m\n" + + "HJO513uJY0Q/ecZUwAKjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\n" + + "AQH/AgEAMB0GA1UdDgQWBBSVtOPa7n10UMazUtWPQasYolQr+DAfBgNVHSMEGDAW\n" + + "gBSy9uS81ABjfHbkz42x/Gf160mt1jAKBggqhkjOPQQDAgNJADBGAiEAq/T83XSg\n" + + "7/GN+fUi79bzXI9oQdDuXqyhGnjIXtr2D8YCIQCuS1tZQm6lVcDZMWYQWLfv/b46\n" + + "GjWuPgx1fD4m+ar9Tw==\n" + "-----END CERTIFICATE-----"; await cc.e2eiMlsInit(enrollment, certificateResp); diff --git a/crypto-ffi/bindings/kt/main/com/wire/crypto/CoreCrypto.kt b/crypto-ffi/bindings/kt/main/com/wire/crypto/CoreCrypto.kt index a4d6359280..7f592d2bdb 100644 --- a/crypto-ffi/bindings/kt/main/com/wire/crypto/CoreCrypto.kt +++ b/crypto-ffi/bindings/kt/main/com/wire/crypto/CoreCrypto.kt @@ -44,7 +44,7 @@ open class RustBuffer : Structure() { companion object { internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_CoreCrypto_105c_rustbuffer_alloc(size, status).also { + _UniFFILib.INSTANCE.ffi_CoreCrypto_fbd8_rustbuffer_alloc(size, status).also { if(it.data == null) { throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") } @@ -52,7 +52,7 @@ open class RustBuffer : Structure() { } internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.ffi_CoreCrypto_105c_rustbuffer_free(buf, status) + _UniFFILib.INSTANCE.ffi_CoreCrypto_fbd8_rustbuffer_free(buf, status) } } @@ -264,347 +264,343 @@ internal interface _UniFFILib : Library { } } - fun ffi_CoreCrypto_105c_CoreCrypto_object_free(`ptr`: Pointer, + fun ffi_CoreCrypto_fbd8_CoreCrypto_object_free(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_new(`path`: RustBuffer.ByValue,`key`: RustBuffer.ByValue,`clientId`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_new(`path`: RustBuffer.ByValue,`key`: RustBuffer.ByValue,`clientId`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Pointer - fun CoreCrypto_105c_CoreCrypto_deferred_init(`path`: RustBuffer.ByValue,`key`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_deferred_init(`path`: RustBuffer.ByValue,`key`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Pointer - fun CoreCrypto_105c_CoreCrypto_mls_init(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_mls_init(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_mls_generate_keypairs(`ptr`: Pointer,`ciphersuites`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_mls_generate_keypairs(`ptr`: Pointer,`ciphersuites`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_mls_init_with_client_id(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`signaturePublicKeys`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_mls_init_with_client_id(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`signaturePublicKeys`: RustBuffer.ByValue,`ciphersuites`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_restore_from_disk(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_restore_from_disk(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_set_callbacks(`ptr`: Pointer,`callbacks`: Long, + fun CoreCrypto_fbd8_CoreCrypto_set_callbacks(`ptr`: Pointer,`callbacks`: Long, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_client_public_key(`ptr`: Pointer,`ciphersuite`: Short, + fun CoreCrypto_fbd8_CoreCrypto_client_public_key(`ptr`: Pointer,`ciphersuite`: Short, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_client_keypackages(`ptr`: Pointer,`ciphersuite`: Short,`amountRequested`: Int, + fun CoreCrypto_fbd8_CoreCrypto_client_keypackages(`ptr`: Pointer,`ciphersuite`: Short,`amountRequested`: Int, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_client_valid_keypackages_count(`ptr`: Pointer,`ciphersuite`: Short, + fun CoreCrypto_fbd8_CoreCrypto_client_valid_keypackages_count(`ptr`: Pointer,`ciphersuite`: Short, _uniffi_out_err: RustCallStatus ): Long - fun CoreCrypto_105c_CoreCrypto_create_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`creatorCredentialType`: RustBuffer.ByValue,`config`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_create_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`creatorCredentialType`: RustBuffer.ByValue,`config`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_conversation_epoch(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_conversation_epoch(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Long - fun CoreCrypto_105c_CoreCrypto_conversation_exists(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_conversation_exists(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Byte - fun CoreCrypto_105c_CoreCrypto_process_welcome_message(`ptr`: Pointer,`welcomeMessage`: RustBuffer.ByValue,`customConfiguration`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_process_welcome_message(`ptr`: Pointer,`welcomeMessage`: RustBuffer.ByValue,`customConfiguration`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_add_clients_to_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clients`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_add_clients_to_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clients`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_remove_clients_from_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clients`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_remove_clients_from_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clients`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_mark_conversation_as_child_of(`ptr`: Pointer,`childId`: RustBuffer.ByValue,`parentId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_mark_conversation_as_child_of(`ptr`: Pointer,`childId`: RustBuffer.ByValue,`parentId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_update_keying_material(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_update_keying_material(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_commit_pending_proposals(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_commit_pending_proposals(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_wipe_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_wipe_conversation(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_decrypt_message(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`payload`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_decrypt_message(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`payload`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_encrypt_message(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`message`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_encrypt_message(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`message`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_new_add_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`keyPackage`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_new_add_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`keyPackage`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_new_update_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_new_update_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_new_remove_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clientId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_new_remove_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`clientId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_new_external_add_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`epoch`: Long,`ciphersuite`: Short,`credentialType`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_new_external_add_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`epoch`: Long,`ciphersuite`: Short,`credentialType`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_new_external_remove_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`epoch`: Long,`keyPackageRef`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_join_by_external_commit(`ptr`: Pointer,`groupInfo`: RustBuffer.ByValue,`customConfiguration`: RustBuffer.ByValue,`credentialType`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_join_by_external_commit(`ptr`: Pointer,`publicGroupState`: RustBuffer.ByValue,`customConfiguration`: RustBuffer.ByValue,`credentialType`: RustBuffer.ByValue, - _uniffi_out_err: RustCallStatus - ): RustBuffer.ByValue - - fun CoreCrypto_105c_CoreCrypto_merge_pending_group_from_external_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_merge_pending_group_from_external_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_clear_pending_group_from_external_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_clear_pending_group_from_external_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_export_group_state(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_export_group_info(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_export_secret_key(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`keyLength`: Int, + fun CoreCrypto_fbd8_CoreCrypto_export_secret_key(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`keyLength`: Int, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_get_client_ids(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_get_client_ids(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_random_bytes(`ptr`: Pointer,`length`: Int, + fun CoreCrypto_fbd8_CoreCrypto_random_bytes(`ptr`: Pointer,`length`: Int, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_reseed_rng(`ptr`: Pointer,`seed`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_reseed_rng(`ptr`: Pointer,`seed`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_commit_accepted(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_commit_accepted(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_clear_pending_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`proposalRef`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_clear_pending_proposal(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue,`proposalRef`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_clear_pending_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_clear_pending_commit(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_init(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_init(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_session_from_prekey(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`prekey`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_session_from_prekey(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`prekey`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_session_from_message(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`envelope`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_session_from_message(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`envelope`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_session_save(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_session_save(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_session_delete(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_session_delete(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_session_exists(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_session_exists(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Byte - fun CoreCrypto_105c_CoreCrypto_proteus_decrypt(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`ciphertext`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_decrypt(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`ciphertext`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_encrypt(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`plaintext`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_encrypt(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`plaintext`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_encrypt_batched(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`plaintext`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_encrypt_batched(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue,`plaintext`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_new_prekey(`ptr`: Pointer,`prekeyId`: Short, + fun CoreCrypto_fbd8_CoreCrypto_proteus_new_prekey(`ptr`: Pointer,`prekeyId`: Short, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_new_prekey_auto(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_new_prekey_auto(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_last_resort_prekey(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_last_resort_prekey(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_last_resort_prekey_id(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_last_resort_prekey_id(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Short - fun CoreCrypto_105c_CoreCrypto_proteus_fingerprint(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_fingerprint_local(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_local(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_fingerprint_remote(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_remote(`ptr`: Pointer,`sessionId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_fingerprint_prekeybundle(`ptr`: Pointer,`prekey`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_prekeybundle(`ptr`: Pointer,`prekey`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_proteus_cryptobox_migrate(`ptr`: Pointer,`path`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_proteus_cryptobox_migrate(`ptr`: Pointer,`path`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_proteus_last_error_code(`ptr`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_proteus_last_error_code(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Int - fun CoreCrypto_105c_CoreCrypto_e2ei_new_enrollment(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`displayName`: RustBuffer.ByValue,`handle`: RustBuffer.ByValue,`expiryDays`: Int,`ciphersuite`: Short, + fun CoreCrypto_fbd8_CoreCrypto_e2ei_new_enrollment(`ptr`: Pointer,`clientId`: RustBuffer.ByValue,`displayName`: RustBuffer.ByValue,`handle`: RustBuffer.ByValue,`expiryDays`: Int,`ciphersuite`: Short, _uniffi_out_err: RustCallStatus ): Pointer - fun CoreCrypto_105c_CoreCrypto_e2ei_mls_init(`ptr`: Pointer,`enrollment`: Pointer,`certificateChain`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_e2ei_mls_init(`ptr`: Pointer,`enrollment`: Pointer,`certificateChain`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_CoreCrypto_e2ei_enrollment_stash(`ptr`: Pointer,`enrollment`: Pointer, + fun CoreCrypto_fbd8_CoreCrypto_e2ei_enrollment_stash(`ptr`: Pointer,`enrollment`: Pointer, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_CoreCrypto_e2ei_enrollment_stash_pop(`ptr`: Pointer,`handle`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_e2ei_enrollment_stash_pop(`ptr`: Pointer,`handle`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Pointer - fun CoreCrypto_105c_CoreCrypto_e2ei_is_degraded(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_CoreCrypto_e2ei_is_degraded(`ptr`: Pointer,`conversationId`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Byte - fun ffi_CoreCrypto_105c_WireE2eIdentity_object_free(`ptr`: Pointer, + fun ffi_CoreCrypto_fbd8_WireE2eIdentity_object_free(`ptr`: Pointer, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_WireE2eIdentity_directory_response(`ptr`: Pointer,`directory`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_directory_response(`ptr`: Pointer,`directory`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_account_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_account_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_account_response(`ptr`: Pointer,`account`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_account_response(`ptr`: Pointer,`account`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_WireE2eIdentity_new_order_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_order_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_order_response(`ptr`: Pointer,`order`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_order_response(`ptr`: Pointer,`order`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_authz_request(`ptr`: Pointer,`url`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_authz_request(`ptr`: Pointer,`url`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_authz_response(`ptr`: Pointer,`authz`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_authz_response(`ptr`: Pointer,`authz`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_create_dpop_token(`ptr`: Pointer,`expirySecs`: Int,`backendNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_create_dpop_token(`ptr`: Pointer,`expirySecs`: Int,`backendNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_dpop_challenge_request(`ptr`: Pointer,`accessToken`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_dpop_challenge_request(`ptr`: Pointer,`accessToken`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_oidc_challenge_request(`ptr`: Pointer,`idToken`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_oidc_challenge_request(`ptr`: Pointer,`idToken`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_new_challenge_response(`ptr`: Pointer,`challenge`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_new_challenge_response(`ptr`: Pointer,`challenge`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_WireE2eIdentity_check_order_request(`ptr`: Pointer,`orderUrl`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_check_order_request(`ptr`: Pointer,`orderUrl`: RustBuffer.ByValue,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_check_order_response(`ptr`: Pointer,`order`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_check_order_response(`ptr`: Pointer,`order`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_finalize_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_finalize_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_finalize_response(`ptr`: Pointer,`finalize`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_finalize_response(`ptr`: Pointer,`finalize`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun CoreCrypto_105c_WireE2eIdentity_certificate_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, + fun CoreCrypto_fbd8_WireE2eIdentity_certificate_request(`ptr`: Pointer,`previousNonce`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_CoreCrypto_105c_CoreCryptoCallbacks_init_callback(`callbackStub`: ForeignCallback, + fun ffi_CoreCrypto_fbd8_CoreCryptoCallbacks_init_callback(`callbackStub`: ForeignCallback, _uniffi_out_err: RustCallStatus ): Unit - fun CoreCrypto_105c_version( + fun CoreCrypto_fbd8_version( _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_CoreCrypto_105c_rustbuffer_alloc(`size`: Int, + fun ffi_CoreCrypto_fbd8_rustbuffer_alloc(`size`: Int, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_CoreCrypto_105c_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue, + fun ffi_CoreCrypto_fbd8_rustbuffer_from_bytes(`bytes`: ForeignBytes.ByValue, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue - fun ffi_CoreCrypto_105c_rustbuffer_free(`buf`: RustBuffer.ByValue, + fun ffi_CoreCrypto_fbd8_rustbuffer_free(`buf`: RustBuffer.ByValue, _uniffi_out_err: RustCallStatus ): Unit - fun ffi_CoreCrypto_105c_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int, + fun ffi_CoreCrypto_fbd8_rustbuffer_reserve(`buf`: RustBuffer.ByValue,`additional`: Int, _uniffi_out_err: RustCallStatus ): RustBuffer.ByValue @@ -967,10 +963,10 @@ public interface CoreCryptoInterface { fun `mlsInit`(`clientId`: ClientId, `ciphersuites`: Ciphersuites) @Throws(CryptoException::class) - fun `mlsGenerateKeypairs`(`ciphersuites`: Ciphersuites): List> + fun `mlsGenerateKeypairs`(`ciphersuites`: Ciphersuites): List @Throws(CryptoException::class) - fun `mlsInitWithClientId`(`clientId`: ClientId, `signaturePublicKeys`: List>, `ciphersuites`: Ciphersuites) + fun `mlsInitWithClientId`(`clientId`: ClientId, `signaturePublicKeys`: List, `ciphersuites`: Ciphersuites) @Throws(CryptoException::class) fun `restoreFromDisk`() @@ -1035,10 +1031,7 @@ public interface CoreCryptoInterface { fun `newExternalAddProposal`(`conversationId`: ConversationId, `epoch`: ULong, `ciphersuite`: Ciphersuite, `credentialType`: MlsCredentialType): List @Throws(CryptoException::class) - fun `newExternalRemoveProposal`(`conversationId`: ConversationId, `epoch`: ULong, `keyPackageRef`: List): List - - @Throws(CryptoException::class) - fun `joinByExternalCommit`(`publicGroupState`: List, `customConfiguration`: CustomConfiguration, `credentialType`: MlsCredentialType): ConversationInitBundle + fun `joinByExternalCommit`(`groupInfo`: List, `customConfiguration`: CustomConfiguration, `credentialType`: MlsCredentialType): ConversationInitBundle @Throws(CryptoException::class) fun `mergePendingGroupFromExternalCommit`(`conversationId`: ConversationId) @@ -1047,7 +1040,7 @@ public interface CoreCryptoInterface { fun `clearPendingGroupFromExternalCommit`(`conversationId`: ConversationId) @Throws(CryptoException::class) - fun `exportGroupState`(`conversationId`: ConversationId): List + fun `exportGroupInfo`(`conversationId`: ConversationId): List @Throws(CryptoException::class) fun `exportSecretKey`(`conversationId`: ConversationId, `keyLength`: UInt): List @@ -1149,7 +1142,7 @@ class CoreCrypto( constructor(`path`: String, `key`: String, `clientId`: ClientId, `ciphersuites`: Ciphersuites) : this( rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new(FfiConverterString.lower(`path`), FfiConverterString.lower(`key`), FfiConverterTypeClientId.lower(`clientId`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_new(FfiConverterString.lower(`path`), FfiConverterString.lower(`key`), FfiConverterTypeClientId.lower(`clientId`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) }) /** @@ -1162,7 +1155,7 @@ class CoreCrypto( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_CoreCrypto_105c_CoreCrypto_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_CoreCrypto_fbd8_CoreCrypto_object_free(this.pointer, status) } } @@ -1170,24 +1163,24 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `mlsInit`(`clientId`: ClientId, `ciphersuites`: Ciphersuites) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_mls_init(it, FfiConverterTypeClientId.lower(`clientId`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_mls_init(it, FfiConverterTypeClientId.lower(`clientId`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) } } - @Throws(CryptoException::class)override fun `mlsGenerateKeypairs`(`ciphersuites`: Ciphersuites): List> = + @Throws(CryptoException::class)override fun `mlsGenerateKeypairs`(`ciphersuites`: Ciphersuites): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_mls_generate_keypairs(it, FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_mls_generate_keypairs(it, FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) } }.let { - FfiConverterSequenceSequenceUByte.lift(it) + FfiConverterSequenceTypeClientId.lift(it) } - @Throws(CryptoException::class)override fun `mlsInitWithClientId`(`clientId`: ClientId, `signaturePublicKeys`: List>, `ciphersuites`: Ciphersuites) = + @Throws(CryptoException::class)override fun `mlsInitWithClientId`(`clientId`: ClientId, `signaturePublicKeys`: List, `ciphersuites`: Ciphersuites) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_mls_init_with_client_id(it, FfiConverterTypeClientId.lower(`clientId`), FfiConverterSequenceSequenceUByte.lower(`signaturePublicKeys`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_mls_init_with_client_id(it, FfiConverterTypeClientId.lower(`clientId`), FfiConverterSequenceTypeClientId.lower(`signaturePublicKeys`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) } } @@ -1195,7 +1188,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `restoreFromDisk`() = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_restore_from_disk(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_restore_from_disk(it, _status) } } @@ -1203,7 +1196,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `setCallbacks`(`callbacks`: CoreCryptoCallbacks) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_set_callbacks(it, FfiConverterTypeCoreCryptoCallbacks.lower(`callbacks`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_set_callbacks(it, FfiConverterTypeCoreCryptoCallbacks.lower(`callbacks`), _status) } } @@ -1211,7 +1204,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clientPublicKey`(`ciphersuite`: Ciphersuite): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_client_public_key(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_client_public_key(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1220,7 +1213,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clientKeypackages`(`ciphersuite`: Ciphersuite, `amountRequested`: UInt): List> = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_client_keypackages(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), FfiConverterUInt.lower(`amountRequested`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_client_keypackages(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), FfiConverterUInt.lower(`amountRequested`), _status) } }.let { FfiConverterSequenceSequenceUByte.lift(it) @@ -1229,7 +1222,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clientValidKeypackagesCount`(`ciphersuite`: Ciphersuite): ULong = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_client_valid_keypackages_count(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_client_valid_keypackages_count(it, FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) } }.let { FfiConverterULong.lift(it) @@ -1238,7 +1231,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `createConversation`(`conversationId`: ConversationId, `creatorCredentialType`: MlsCredentialType, `config`: ConversationConfiguration) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_create_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterTypeMlsCredentialType.lower(`creatorCredentialType`), FfiConverterTypeConversationConfiguration.lower(`config`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_create_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterTypeMlsCredentialType.lower(`creatorCredentialType`), FfiConverterTypeConversationConfiguration.lower(`config`), _status) } } @@ -1246,7 +1239,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `conversationEpoch`(`conversationId`: ConversationId): ULong = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_conversation_epoch(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_conversation_epoch(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterULong.lift(it) @@ -1254,7 +1247,7 @@ class CoreCrypto( override fun `conversationExists`(`conversationId`: ConversationId): Boolean = callWithPointer { rustCall() { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_conversation_exists(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_conversation_exists(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterBoolean.lift(it) @@ -1263,7 +1256,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `processWelcomeMessage`(`welcomeMessage`: List, `customConfiguration`: CustomConfiguration): ConversationId = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_process_welcome_message(it, FfiConverterSequenceUByte.lower(`welcomeMessage`), FfiConverterTypeCustomConfiguration.lower(`customConfiguration`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_process_welcome_message(it, FfiConverterSequenceUByte.lower(`welcomeMessage`), FfiConverterTypeCustomConfiguration.lower(`customConfiguration`), _status) } }.let { FfiConverterTypeConversationId.lift(it) @@ -1272,7 +1265,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `addClientsToConversation`(`conversationId`: ConversationId, `clients`: List): MemberAddedMessages = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_add_clients_to_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceTypeInvitee.lower(`clients`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_add_clients_to_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceTypeInvitee.lower(`clients`), _status) } }.let { FfiConverterTypeMemberAddedMessages.lift(it) @@ -1281,7 +1274,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `removeClientsFromConversation`(`conversationId`: ConversationId, `clients`: List): CommitBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_remove_clients_from_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceTypeClientId.lower(`clients`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_remove_clients_from_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceTypeClientId.lower(`clients`), _status) } }.let { FfiConverterTypeCommitBundle.lift(it) @@ -1290,7 +1283,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `markConversationAsChildOf`(`childId`: ConversationId, `parentId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_mark_conversation_as_child_of(it, FfiConverterTypeConversationId.lower(`childId`), FfiConverterTypeConversationId.lower(`parentId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_mark_conversation_as_child_of(it, FfiConverterTypeConversationId.lower(`childId`), FfiConverterTypeConversationId.lower(`parentId`), _status) } } @@ -1298,7 +1291,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `updateKeyingMaterial`(`conversationId`: ConversationId): CommitBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_update_keying_material(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_update_keying_material(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterTypeCommitBundle.lift(it) @@ -1307,7 +1300,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `commitPendingProposals`(`conversationId`: ConversationId): CommitBundle? = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_commit_pending_proposals(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_commit_pending_proposals(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterOptionalTypeCommitBundle.lift(it) @@ -1316,7 +1309,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `wipeConversation`(`conversationId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_wipe_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_wipe_conversation(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } } @@ -1324,7 +1317,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `decryptMessage`(`conversationId`: ConversationId, `payload`: List): DecryptedMessage = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_decrypt_message(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`payload`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_decrypt_message(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`payload`), _status) } }.let { FfiConverterTypeDecryptedMessage.lift(it) @@ -1333,7 +1326,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `encryptMessage`(`conversationId`: ConversationId, `message`: List): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_encrypt_message(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`message`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_encrypt_message(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`message`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1342,7 +1335,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `newAddProposal`(`conversationId`: ConversationId, `keyPackage`: List): ProposalBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new_add_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`keyPackage`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_new_add_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`keyPackage`), _status) } }.let { FfiConverterTypeProposalBundle.lift(it) @@ -1351,7 +1344,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `newUpdateProposal`(`conversationId`: ConversationId): ProposalBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new_update_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_new_update_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterTypeProposalBundle.lift(it) @@ -1360,7 +1353,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `newRemoveProposal`(`conversationId`: ConversationId, `clientId`: ClientId): ProposalBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new_remove_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterTypeClientId.lower(`clientId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_new_remove_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterTypeClientId.lower(`clientId`), _status) } }.let { FfiConverterTypeProposalBundle.lift(it) @@ -1369,25 +1362,16 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `newExternalAddProposal`(`conversationId`: ConversationId, `epoch`: ULong, `ciphersuite`: Ciphersuite, `credentialType`: MlsCredentialType): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new_external_add_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterULong.lower(`epoch`), FfiConverterTypeCiphersuite.lower(`ciphersuite`), FfiConverterTypeMlsCredentialType.lower(`credentialType`), _status) -} - }.let { - FfiConverterSequenceUByte.lift(it) - } - - @Throws(CryptoException::class)override fun `newExternalRemoveProposal`(`conversationId`: ConversationId, `epoch`: ULong, `keyPackageRef`: List): List = - callWithPointer { - rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_new_external_remove_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterULong.lower(`epoch`), FfiConverterSequenceUByte.lower(`keyPackageRef`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_new_external_add_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterULong.lower(`epoch`), FfiConverterTypeCiphersuite.lower(`ciphersuite`), FfiConverterTypeMlsCredentialType.lower(`credentialType`), _status) } }.let { FfiConverterSequenceUByte.lift(it) } - @Throws(CryptoException::class)override fun `joinByExternalCommit`(`publicGroupState`: List, `customConfiguration`: CustomConfiguration, `credentialType`: MlsCredentialType): ConversationInitBundle = + @Throws(CryptoException::class)override fun `joinByExternalCommit`(`groupInfo`: List, `customConfiguration`: CustomConfiguration, `credentialType`: MlsCredentialType): ConversationInitBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_join_by_external_commit(it, FfiConverterSequenceUByte.lower(`publicGroupState`), FfiConverterTypeCustomConfiguration.lower(`customConfiguration`), FfiConverterTypeMlsCredentialType.lower(`credentialType`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_join_by_external_commit(it, FfiConverterSequenceUByte.lower(`groupInfo`), FfiConverterTypeCustomConfiguration.lower(`customConfiguration`), FfiConverterTypeMlsCredentialType.lower(`credentialType`), _status) } }.let { FfiConverterTypeConversationInitBundle.lift(it) @@ -1396,7 +1380,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `mergePendingGroupFromExternalCommit`(`conversationId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_merge_pending_group_from_external_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_merge_pending_group_from_external_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } } @@ -1404,15 +1388,15 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clearPendingGroupFromExternalCommit`(`conversationId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_clear_pending_group_from_external_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_clear_pending_group_from_external_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } } - @Throws(CryptoException::class)override fun `exportGroupState`(`conversationId`: ConversationId): List = + @Throws(CryptoException::class)override fun `exportGroupInfo`(`conversationId`: ConversationId): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_export_group_state(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_export_group_info(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1421,7 +1405,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `exportSecretKey`(`conversationId`: ConversationId, `keyLength`: UInt): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_export_secret_key(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterUInt.lower(`keyLength`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_export_secret_key(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterUInt.lower(`keyLength`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1430,7 +1414,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `getClientIds`(`conversationId`: ConversationId): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_get_client_ids(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_get_client_ids(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterSequenceTypeClientId.lift(it) @@ -1439,7 +1423,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `randomBytes`(`length`: UInt): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_random_bytes(it, FfiConverterUInt.lower(`length`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_random_bytes(it, FfiConverterUInt.lower(`length`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1448,7 +1432,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `reseedRng`(`seed`: List) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_reseed_rng(it, FfiConverterSequenceUByte.lower(`seed`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_reseed_rng(it, FfiConverterSequenceUByte.lower(`seed`), _status) } } @@ -1456,7 +1440,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `commitAccepted`(`conversationId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_commit_accepted(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_commit_accepted(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } } @@ -1464,7 +1448,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clearPendingProposal`(`conversationId`: ConversationId, `proposalRef`: List) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_clear_pending_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`proposalRef`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_clear_pending_proposal(it, FfiConverterTypeConversationId.lower(`conversationId`), FfiConverterSequenceUByte.lower(`proposalRef`), _status) } } @@ -1472,7 +1456,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `clearPendingCommit`(`conversationId`: ConversationId) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_clear_pending_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_clear_pending_commit(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } } @@ -1480,7 +1464,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusInit`() = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_init(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_init(it, _status) } } @@ -1488,7 +1472,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusSessionFromPrekey`(`sessionId`: String, `prekey`: List) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_session_from_prekey(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`prekey`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_session_from_prekey(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`prekey`), _status) } } @@ -1496,7 +1480,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusSessionFromMessage`(`sessionId`: String, `envelope`: List): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_session_from_message(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`envelope`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_session_from_message(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`envelope`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1505,7 +1489,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusSessionSave`(`sessionId`: String) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_session_save(it, FfiConverterString.lower(`sessionId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_session_save(it, FfiConverterString.lower(`sessionId`), _status) } } @@ -1513,7 +1497,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusSessionDelete`(`sessionId`: String) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_session_delete(it, FfiConverterString.lower(`sessionId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_session_delete(it, FfiConverterString.lower(`sessionId`), _status) } } @@ -1521,7 +1505,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusSessionExists`(`sessionId`: String): Boolean = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_session_exists(it, FfiConverterString.lower(`sessionId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_session_exists(it, FfiConverterString.lower(`sessionId`), _status) } }.let { FfiConverterBoolean.lift(it) @@ -1530,7 +1514,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusDecrypt`(`sessionId`: String, `ciphertext`: List): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_decrypt(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`ciphertext`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_decrypt(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`ciphertext`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1539,7 +1523,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusEncrypt`(`sessionId`: String, `plaintext`: List): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_encrypt(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`plaintext`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_encrypt(it, FfiConverterString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`plaintext`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1548,7 +1532,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusEncryptBatched`(`sessionId`: List, `plaintext`: List): Map> = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_encrypt_batched(it, FfiConverterSequenceString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`plaintext`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_encrypt_batched(it, FfiConverterSequenceString.lower(`sessionId`), FfiConverterSequenceUByte.lower(`plaintext`), _status) } }.let { FfiConverterMapStringListUByte.lift(it) @@ -1557,7 +1541,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusNewPrekey`(`prekeyId`: UShort): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_new_prekey(it, FfiConverterUShort.lower(`prekeyId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_new_prekey(it, FfiConverterUShort.lower(`prekeyId`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1566,7 +1550,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusNewPrekeyAuto`(): ProteusAutoPrekeyBundle = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_new_prekey_auto(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_new_prekey_auto(it, _status) } }.let { FfiConverterTypeProteusAutoPrekeyBundle.lift(it) @@ -1575,7 +1559,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusLastResortPrekey`(): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_last_resort_prekey(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_last_resort_prekey(it, _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1584,7 +1568,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusLastResortPrekeyId`(): UShort = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_last_resort_prekey_id(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_last_resort_prekey_id(it, _status) } }.let { FfiConverterUShort.lift(it) @@ -1593,7 +1577,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusFingerprint`(): String = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_fingerprint(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint(it, _status) } }.let { FfiConverterString.lift(it) @@ -1602,7 +1586,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusFingerprintLocal`(`sessionId`: String): String = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_fingerprint_local(it, FfiConverterString.lower(`sessionId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_local(it, FfiConverterString.lower(`sessionId`), _status) } }.let { FfiConverterString.lift(it) @@ -1611,7 +1595,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusFingerprintRemote`(`sessionId`: String): String = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_fingerprint_remote(it, FfiConverterString.lower(`sessionId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_remote(it, FfiConverterString.lower(`sessionId`), _status) } }.let { FfiConverterString.lift(it) @@ -1620,7 +1604,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusFingerprintPrekeybundle`(`prekey`: List): String = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_fingerprint_prekeybundle(it, FfiConverterSequenceUByte.lower(`prekey`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_fingerprint_prekeybundle(it, FfiConverterSequenceUByte.lower(`prekey`), _status) } }.let { FfiConverterString.lift(it) @@ -1629,14 +1613,14 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `proteusCryptoboxMigrate`(`path`: String) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_cryptobox_migrate(it, FfiConverterString.lower(`path`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_cryptobox_migrate(it, FfiConverterString.lower(`path`), _status) } } override fun `proteusLastErrorCode`(): UInt = callWithPointer { rustCall() { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_proteus_last_error_code(it, _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_proteus_last_error_code(it, _status) } }.let { FfiConverterUInt.lift(it) @@ -1645,7 +1629,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `e2eiNewEnrollment`(`clientId`: String, `displayName`: String, `handle`: String, `expiryDays`: UInt, `ciphersuite`: Ciphersuite): WireE2eIdentity = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_e2ei_new_enrollment(it, FfiConverterString.lower(`clientId`), FfiConverterString.lower(`displayName`), FfiConverterString.lower(`handle`), FfiConverterUInt.lower(`expiryDays`), FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_e2ei_new_enrollment(it, FfiConverterString.lower(`clientId`), FfiConverterString.lower(`displayName`), FfiConverterString.lower(`handle`), FfiConverterUInt.lower(`expiryDays`), FfiConverterTypeCiphersuite.lower(`ciphersuite`), _status) } }.let { FfiConverterTypeWireE2eIdentity.lift(it) @@ -1654,7 +1638,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `e2eiMlsInit`(`enrollment`: WireE2eIdentity, `certificateChain`: String) = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_e2ei_mls_init(it, FfiConverterTypeWireE2eIdentity.lower(`enrollment`), FfiConverterString.lower(`certificateChain`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_e2ei_mls_init(it, FfiConverterTypeWireE2eIdentity.lower(`enrollment`), FfiConverterString.lower(`certificateChain`), _status) } } @@ -1662,7 +1646,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `e2eiEnrollmentStash`(`enrollment`: WireE2eIdentity): List = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_e2ei_enrollment_stash(it, FfiConverterTypeWireE2eIdentity.lower(`enrollment`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_e2ei_enrollment_stash(it, FfiConverterTypeWireE2eIdentity.lower(`enrollment`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1671,7 +1655,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `e2eiEnrollmentStashPop`(`handle`: List): WireE2eIdentity = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_e2ei_enrollment_stash_pop(it, FfiConverterSequenceUByte.lower(`handle`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_e2ei_enrollment_stash_pop(it, FfiConverterSequenceUByte.lower(`handle`), _status) } }.let { FfiConverterTypeWireE2eIdentity.lift(it) @@ -1680,7 +1664,7 @@ class CoreCrypto( @Throws(CryptoException::class)override fun `e2eiIsDegraded`(`conversationId`: ConversationId): Boolean = callWithPointer { rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_e2ei_is_degraded(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_e2ei_is_degraded(it, FfiConverterTypeConversationId.lower(`conversationId`), _status) } }.let { FfiConverterBoolean.lift(it) @@ -1691,7 +1675,7 @@ class CoreCrypto( fun `deferredInit`(`path`: String, `key`: String, `ciphersuites`: Ciphersuites): CoreCrypto = CoreCrypto( rustCallWithError(CryptoException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_CoreCrypto_deferred_init(FfiConverterString.lower(`path`), FfiConverterString.lower(`key`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_CoreCrypto_deferred_init(FfiConverterString.lower(`path`), FfiConverterString.lower(`key`), FfiConverterTypeCiphersuites.lower(`ciphersuites`), _status) }) } @@ -1789,7 +1773,7 @@ class WireE2eIdentity( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.ffi_CoreCrypto_105c_WireE2eIdentity_object_free(this.pointer, status) + _UniFFILib.INSTANCE.ffi_CoreCrypto_fbd8_WireE2eIdentity_object_free(this.pointer, status) } } @@ -1797,7 +1781,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `directoryResponse`(`directory`: List): AcmeDirectory = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_directory_response(it, FfiConverterSequenceUByte.lower(`directory`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_directory_response(it, FfiConverterSequenceUByte.lower(`directory`), _status) } }.let { FfiConverterTypeAcmeDirectory.lift(it) @@ -1806,7 +1790,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newAccountRequest`(`previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_account_request(it, FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_account_request(it, FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1815,7 +1799,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newAccountResponse`(`account`: List) = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_account_response(it, FfiConverterSequenceUByte.lower(`account`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_account_response(it, FfiConverterSequenceUByte.lower(`account`), _status) } } @@ -1823,7 +1807,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newOrderRequest`(`previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_order_request(it, FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_order_request(it, FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1832,7 +1816,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newOrderResponse`(`order`: List): NewAcmeOrder = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_order_response(it, FfiConverterSequenceUByte.lower(`order`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_order_response(it, FfiConverterSequenceUByte.lower(`order`), _status) } }.let { FfiConverterTypeNewAcmeOrder.lift(it) @@ -1841,7 +1825,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newAuthzRequest`(`url`: String, `previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_authz_request(it, FfiConverterString.lower(`url`), FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_authz_request(it, FfiConverterString.lower(`url`), FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1850,7 +1834,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newAuthzResponse`(`authz`: List): NewAcmeAuthz = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_authz_response(it, FfiConverterSequenceUByte.lower(`authz`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_authz_response(it, FfiConverterSequenceUByte.lower(`authz`), _status) } }.let { FfiConverterTypeNewAcmeAuthz.lift(it) @@ -1859,7 +1843,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `createDpopToken`(`expirySecs`: UInt, `backendNonce`: String): String = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_create_dpop_token(it, FfiConverterUInt.lower(`expirySecs`), FfiConverterString.lower(`backendNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_create_dpop_token(it, FfiConverterUInt.lower(`expirySecs`), FfiConverterString.lower(`backendNonce`), _status) } }.let { FfiConverterString.lift(it) @@ -1868,7 +1852,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newDpopChallengeRequest`(`accessToken`: String, `previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_dpop_challenge_request(it, FfiConverterString.lower(`accessToken`), FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_dpop_challenge_request(it, FfiConverterString.lower(`accessToken`), FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1877,7 +1861,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newOidcChallengeRequest`(`idToken`: String, `previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_oidc_challenge_request(it, FfiConverterString.lower(`idToken`), FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_oidc_challenge_request(it, FfiConverterString.lower(`idToken`), FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1886,7 +1870,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `newChallengeResponse`(`challenge`: List) = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_new_challenge_response(it, FfiConverterSequenceUByte.lower(`challenge`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_new_challenge_response(it, FfiConverterSequenceUByte.lower(`challenge`), _status) } } @@ -1894,7 +1878,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `checkOrderRequest`(`orderUrl`: String, `previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_check_order_request(it, FfiConverterString.lower(`orderUrl`), FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_check_order_request(it, FfiConverterString.lower(`orderUrl`), FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1903,7 +1887,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `checkOrderResponse`(`order`: List): String = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_check_order_response(it, FfiConverterSequenceUByte.lower(`order`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_check_order_response(it, FfiConverterSequenceUByte.lower(`order`), _status) } }.let { FfiConverterString.lift(it) @@ -1912,7 +1896,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `finalizeRequest`(`previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_finalize_request(it, FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_finalize_request(it, FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -1921,7 +1905,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `finalizeResponse`(`finalize`: List): String = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_finalize_response(it, FfiConverterSequenceUByte.lower(`finalize`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_finalize_response(it, FfiConverterSequenceUByte.lower(`finalize`), _status) } }.let { FfiConverterString.lift(it) @@ -1930,7 +1914,7 @@ class WireE2eIdentity( @Throws(E2eIdentityException::class)override fun `certificateRequest`(`previousNonce`: String): List = callWithPointer { rustCallWithError(E2eIdentityException) { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_WireE2eIdentity_certificate_request(it, FfiConverterString.lower(`previousNonce`), _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_WireE2eIdentity_certificate_request(it, FfiConverterString.lower(`previousNonce`), _status) } }.let { FfiConverterSequenceUByte.lift(it) @@ -2034,7 +2018,7 @@ public object FfiConverterTypeAcmeDirectory: FfiConverterRustBuffer?, var `commit`: List, - var `publicGroupState`: PublicGroupStateBundle + var `groupInfo`: GroupInfoBundle ) { } @@ -2044,20 +2028,20 @@ public object FfiConverterTypeCommitBundle: FfiConverterRustBuffer return CommitBundle( FfiConverterOptionalSequenceUByte.read(buf), FfiConverterSequenceUByte.read(buf), - FfiConverterTypePublicGroupStateBundle.read(buf), + FfiConverterTypeGroupInfoBundle.read(buf), ) } override fun allocationSize(value: CommitBundle) = ( FfiConverterOptionalSequenceUByte.allocationSize(value.`welcome`) + FfiConverterSequenceUByte.allocationSize(value.`commit`) + - FfiConverterTypePublicGroupStateBundle.allocationSize(value.`publicGroupState`) + FfiConverterTypeGroupInfoBundle.allocationSize(value.`groupInfo`) ) override fun write(value: CommitBundle, buf: ByteBuffer) { FfiConverterOptionalSequenceUByte.write(value.`welcome`, buf) FfiConverterSequenceUByte.write(value.`commit`, buf) - FfiConverterTypePublicGroupStateBundle.write(value.`publicGroupState`, buf) + FfiConverterTypeGroupInfoBundle.write(value.`groupInfo`, buf) } } @@ -2100,7 +2084,7 @@ public object FfiConverterTypeConversationConfiguration: FfiConverterRustBuffer< data class ConversationInitBundle ( var `conversationId`: List, var `commit`: List, - var `publicGroupState`: PublicGroupStateBundle + var `groupInfo`: GroupInfoBundle ) { } @@ -2110,20 +2094,20 @@ public object FfiConverterTypeConversationInitBundle: FfiConverterRustBuffer +) { + +} + +public object FfiConverterTypeGroupInfoBundle: FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): GroupInfoBundle { + return GroupInfoBundle( + FfiConverterTypeMlsGroupInfoEncryptionType.read(buf), + FfiConverterTypeMlsRatchetTreeType.read(buf), + FfiConverterSequenceUByte.read(buf), + ) + } + + override fun allocationSize(value: GroupInfoBundle) = ( + FfiConverterTypeMlsGroupInfoEncryptionType.allocationSize(value.`encryptionType`) + + FfiConverterTypeMlsRatchetTreeType.allocationSize(value.`ratchetTreeType`) + + FfiConverterSequenceUByte.allocationSize(value.`payload`) + ) + + override fun write(value: GroupInfoBundle, buf: ByteBuffer) { + FfiConverterTypeMlsGroupInfoEncryptionType.write(value.`encryptionType`, buf) + FfiConverterTypeMlsRatchetTreeType.write(value.`ratchetTreeType`, buf) + FfiConverterSequenceUByte.write(value.`payload`, buf) + } +} + + + + data class Invitee ( var `id`: ClientId, var `kp`: List @@ -2240,7 +2257,7 @@ public object FfiConverterTypeInvitee: FfiConverterRustBuffer { data class MemberAddedMessages ( var `commit`: List, var `welcome`: List, - var `publicGroupState`: PublicGroupStateBundle + var `groupInfo`: GroupInfoBundle ) { } @@ -2250,20 +2267,20 @@ public object FfiConverterTypeMemberAddedMessages: FfiConverterRustBuffer -) { - -} - -public object FfiConverterTypePublicGroupStateBundle: FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): PublicGroupStateBundle { - return PublicGroupStateBundle( - FfiConverterTypeMlsPublicGroupStateEncryptionType.read(buf), - FfiConverterTypeMlsRatchetTreeType.read(buf), - FfiConverterSequenceUByte.read(buf), - ) - } - - override fun allocationSize(value: PublicGroupStateBundle) = ( - FfiConverterTypeMlsPublicGroupStateEncryptionType.allocationSize(value.`encryptionType`) + - FfiConverterTypeMlsRatchetTreeType.allocationSize(value.`ratchetTreeType`) + - FfiConverterSequenceUByte.allocationSize(value.`payload`) - ) - - override fun write(value: PublicGroupStateBundle, buf: ByteBuffer) { - FfiConverterTypeMlsPublicGroupStateEncryptionType.write(value.`encryptionType`, buf) - FfiConverterTypeMlsRatchetTreeType.write(value.`ratchetTreeType`, buf) - FfiConverterSequenceUByte.write(value.`payload`, buf) - } -} - - - - data class WireIdentity ( var `clientId`: String, var `handle`: String, @@ -2506,20 +2490,20 @@ public object FfiConverterTypeMlsCredentialType: FfiConverterRustBuffer { +public object FfiConverterTypeMlsGroupInfoEncryptionType: FfiConverterRustBuffer { override fun read(buf: ByteBuffer) = try { - MlsPublicGroupStateEncryptionType.values()[buf.getInt() - 1] + MlsGroupInfoEncryptionType.values()[buf.getInt() - 1] } catch (e: IndexOutOfBoundsException) { throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: MlsPublicGroupStateEncryptionType) = 4 + override fun allocationSize(value: MlsGroupInfoEncryptionType) = 4 - override fun write(value: MlsPublicGroupStateEncryptionType, buf: ByteBuffer) { + override fun write(value: MlsGroupInfoEncryptionType, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) } } @@ -2585,7 +2569,6 @@ sealed class CryptoException(message: String): Exception(message) { class PendingCommitNotFound(message: String) : CryptoException(message) class MalformedIdentifier(message: String) : CryptoException(message) class ClientSignatureNotFound(message: String) : CryptoException(message) - class ClientSignatureMismatch(message: String) : CryptoException(message) class LockPoisonException(message: String) : CryptoException(message) class ImplementationException(message: String) : CryptoException(message) class OutOfKeyPackage(message: String) : CryptoException(message) @@ -2619,6 +2602,7 @@ sealed class CryptoException(message: String): Exception(message) { class ParentGroupNotFound(message: String) : CryptoException(message) class InvalidIdentity(message: String) : CryptoException(message) class IdentityInitializationException(message: String) : CryptoException(message) + class MessageEpochTooOld(message: String) : CryptoException(message) companion object ErrorHandler : CallStatusErrorHandler { @@ -2636,40 +2620,40 @@ public object FfiConverterTypeCryptoError : FfiConverterRustBuffer CryptoException.PendingCommitNotFound(FfiConverterString.read(buf)) 5 -> CryptoException.MalformedIdentifier(FfiConverterString.read(buf)) 6 -> CryptoException.ClientSignatureNotFound(FfiConverterString.read(buf)) - 7 -> CryptoException.ClientSignatureMismatch(FfiConverterString.read(buf)) - 8 -> CryptoException.LockPoisonException(FfiConverterString.read(buf)) - 9 -> CryptoException.ImplementationException(FfiConverterString.read(buf)) - 10 -> CryptoException.OutOfKeyPackage(FfiConverterString.read(buf)) - 11 -> CryptoException.MlsProviderException(FfiConverterString.read(buf)) - 12 -> CryptoException.KeyStoreException(FfiConverterString.read(buf)) - 13 -> CryptoException.MlsException(FfiConverterString.read(buf)) - 14 -> CryptoException.Utf8Exception(FfiConverterString.read(buf)) - 15 -> CryptoException.StringUtf8Exception(FfiConverterString.read(buf)) - 16 -> CryptoException.ParseIntException(FfiConverterString.read(buf)) - 17 -> CryptoException.ConvertIntException(FfiConverterString.read(buf)) - 18 -> CryptoException.InvalidByteArrayException(FfiConverterString.read(buf)) - 19 -> CryptoException.IoException(FfiConverterString.read(buf)) - 20 -> CryptoException.Unauthorized(FfiConverterString.read(buf)) - 21 -> CryptoException.CallbacksNotSet(FfiConverterString.read(buf)) - 22 -> CryptoException.UnauthorizedExternalAddProposal(FfiConverterString.read(buf)) - 23 -> CryptoException.UnauthorizedExternalCommit(FfiConverterString.read(buf)) - 24 -> CryptoException.InvalidHashReference(FfiConverterString.read(buf)) - 25 -> CryptoException.GenerationOutOfBound(FfiConverterString.read(buf)) - 26 -> CryptoException.WrongEpoch(FfiConverterString.read(buf)) - 27 -> CryptoException.DecryptionException(FfiConverterString.read(buf)) - 28 -> CryptoException.HexDecodeException(FfiConverterString.read(buf)) - 29 -> CryptoException.ProteusException(FfiConverterString.read(buf)) - 30 -> CryptoException.CryptoboxMigrationException(FfiConverterString.read(buf)) - 31 -> CryptoException.ProteusNotInitialized(FfiConverterString.read(buf)) - 32 -> CryptoException.ProteusSupportNotEnabled(FfiConverterString.read(buf)) - 33 -> CryptoException.MlsNotInitialized(FfiConverterString.read(buf)) - 34 -> CryptoException.InvalidKeyPackage(FfiConverterString.read(buf)) - 35 -> CryptoException.IdentityAlreadyPresent(FfiConverterString.read(buf)) - 36 -> CryptoException.NoProvisionalIdentityFound(FfiConverterString.read(buf)) - 37 -> CryptoException.TooManyIdentitiesPresent(FfiConverterString.read(buf)) - 38 -> CryptoException.ParentGroupNotFound(FfiConverterString.read(buf)) - 39 -> CryptoException.InvalidIdentity(FfiConverterString.read(buf)) - 40 -> CryptoException.IdentityInitializationException(FfiConverterString.read(buf)) + 7 -> CryptoException.LockPoisonException(FfiConverterString.read(buf)) + 8 -> CryptoException.ImplementationException(FfiConverterString.read(buf)) + 9 -> CryptoException.OutOfKeyPackage(FfiConverterString.read(buf)) + 10 -> CryptoException.MlsProviderException(FfiConverterString.read(buf)) + 11 -> CryptoException.KeyStoreException(FfiConverterString.read(buf)) + 12 -> CryptoException.MlsException(FfiConverterString.read(buf)) + 13 -> CryptoException.Utf8Exception(FfiConverterString.read(buf)) + 14 -> CryptoException.StringUtf8Exception(FfiConverterString.read(buf)) + 15 -> CryptoException.ParseIntException(FfiConverterString.read(buf)) + 16 -> CryptoException.ConvertIntException(FfiConverterString.read(buf)) + 17 -> CryptoException.InvalidByteArrayException(FfiConverterString.read(buf)) + 18 -> CryptoException.IoException(FfiConverterString.read(buf)) + 19 -> CryptoException.Unauthorized(FfiConverterString.read(buf)) + 20 -> CryptoException.CallbacksNotSet(FfiConverterString.read(buf)) + 21 -> CryptoException.UnauthorizedExternalAddProposal(FfiConverterString.read(buf)) + 22 -> CryptoException.UnauthorizedExternalCommit(FfiConverterString.read(buf)) + 23 -> CryptoException.InvalidHashReference(FfiConverterString.read(buf)) + 24 -> CryptoException.GenerationOutOfBound(FfiConverterString.read(buf)) + 25 -> CryptoException.WrongEpoch(FfiConverterString.read(buf)) + 26 -> CryptoException.DecryptionException(FfiConverterString.read(buf)) + 27 -> CryptoException.HexDecodeException(FfiConverterString.read(buf)) + 28 -> CryptoException.ProteusException(FfiConverterString.read(buf)) + 29 -> CryptoException.CryptoboxMigrationException(FfiConverterString.read(buf)) + 30 -> CryptoException.ProteusNotInitialized(FfiConverterString.read(buf)) + 31 -> CryptoException.ProteusSupportNotEnabled(FfiConverterString.read(buf)) + 32 -> CryptoException.MlsNotInitialized(FfiConverterString.read(buf)) + 33 -> CryptoException.InvalidKeyPackage(FfiConverterString.read(buf)) + 34 -> CryptoException.IdentityAlreadyPresent(FfiConverterString.read(buf)) + 35 -> CryptoException.NoProvisionalIdentityFound(FfiConverterString.read(buf)) + 36 -> CryptoException.TooManyIdentitiesPresent(FfiConverterString.read(buf)) + 37 -> CryptoException.ParentGroupNotFound(FfiConverterString.read(buf)) + 38 -> CryptoException.InvalidIdentity(FfiConverterString.read(buf)) + 39 -> CryptoException.IdentityInitializationException(FfiConverterString.read(buf)) + 40 -> CryptoException.MessageEpochTooOld(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -2705,139 +2689,139 @@ public object FfiConverterTypeCryptoError : FfiConverterRustBuffer { + is CryptoException.LockPoisonException -> { buf.putInt(7) Unit } - is CryptoException.LockPoisonException -> { + is CryptoException.ImplementationException -> { buf.putInt(8) Unit } - is CryptoException.ImplementationException -> { + is CryptoException.OutOfKeyPackage -> { buf.putInt(9) Unit } - is CryptoException.OutOfKeyPackage -> { + is CryptoException.MlsProviderException -> { buf.putInt(10) Unit } - is CryptoException.MlsProviderException -> { + is CryptoException.KeyStoreException -> { buf.putInt(11) Unit } - is CryptoException.KeyStoreException -> { + is CryptoException.MlsException -> { buf.putInt(12) Unit } - is CryptoException.MlsException -> { + is CryptoException.Utf8Exception -> { buf.putInt(13) Unit } - is CryptoException.Utf8Exception -> { + is CryptoException.StringUtf8Exception -> { buf.putInt(14) Unit } - is CryptoException.StringUtf8Exception -> { + is CryptoException.ParseIntException -> { buf.putInt(15) Unit } - is CryptoException.ParseIntException -> { + is CryptoException.ConvertIntException -> { buf.putInt(16) Unit } - is CryptoException.ConvertIntException -> { + is CryptoException.InvalidByteArrayException -> { buf.putInt(17) Unit } - is CryptoException.InvalidByteArrayException -> { + is CryptoException.IoException -> { buf.putInt(18) Unit } - is CryptoException.IoException -> { + is CryptoException.Unauthorized -> { buf.putInt(19) Unit } - is CryptoException.Unauthorized -> { + is CryptoException.CallbacksNotSet -> { buf.putInt(20) Unit } - is CryptoException.CallbacksNotSet -> { + is CryptoException.UnauthorizedExternalAddProposal -> { buf.putInt(21) Unit } - is CryptoException.UnauthorizedExternalAddProposal -> { + is CryptoException.UnauthorizedExternalCommit -> { buf.putInt(22) Unit } - is CryptoException.UnauthorizedExternalCommit -> { + is CryptoException.InvalidHashReference -> { buf.putInt(23) Unit } - is CryptoException.InvalidHashReference -> { + is CryptoException.GenerationOutOfBound -> { buf.putInt(24) Unit } - is CryptoException.GenerationOutOfBound -> { + is CryptoException.WrongEpoch -> { buf.putInt(25) Unit } - is CryptoException.WrongEpoch -> { + is CryptoException.DecryptionException -> { buf.putInt(26) Unit } - is CryptoException.DecryptionException -> { + is CryptoException.HexDecodeException -> { buf.putInt(27) Unit } - is CryptoException.HexDecodeException -> { + is CryptoException.ProteusException -> { buf.putInt(28) Unit } - is CryptoException.ProteusException -> { + is CryptoException.CryptoboxMigrationException -> { buf.putInt(29) Unit } - is CryptoException.CryptoboxMigrationException -> { + is CryptoException.ProteusNotInitialized -> { buf.putInt(30) Unit } - is CryptoException.ProteusNotInitialized -> { + is CryptoException.ProteusSupportNotEnabled -> { buf.putInt(31) Unit } - is CryptoException.ProteusSupportNotEnabled -> { + is CryptoException.MlsNotInitialized -> { buf.putInt(32) Unit } - is CryptoException.MlsNotInitialized -> { + is CryptoException.InvalidKeyPackage -> { buf.putInt(33) Unit } - is CryptoException.InvalidKeyPackage -> { + is CryptoException.IdentityAlreadyPresent -> { buf.putInt(34) Unit } - is CryptoException.IdentityAlreadyPresent -> { + is CryptoException.NoProvisionalIdentityFound -> { buf.putInt(35) Unit } - is CryptoException.NoProvisionalIdentityFound -> { + is CryptoException.TooManyIdentitiesPresent -> { buf.putInt(36) Unit } - is CryptoException.TooManyIdentitiesPresent -> { + is CryptoException.ParentGroupNotFound -> { buf.putInt(37) Unit } - is CryptoException.ParentGroupNotFound -> { + is CryptoException.InvalidIdentity -> { buf.putInt(38) Unit } - is CryptoException.InvalidIdentity -> { + is CryptoException.IdentityInitializationException -> { buf.putInt(39) Unit } - is CryptoException.IdentityInitializationException -> { + is CryptoException.MessageEpochTooOld -> { buf.putInt(40) Unit } @@ -3171,7 +3155,7 @@ public object FfiConverterTypeCoreCryptoCallbacks: FfiConverterCallbackInterface ) { override fun register(lib: _UniFFILib) { rustCall() { status -> - lib.ffi_CoreCrypto_105c_CoreCryptoCallbacks_init_callback(this.foreignCallback, status) + lib.ffi_CoreCrypto_fbd8_CoreCryptoCallbacks_init_callback(this.foreignCallback, status) } } } @@ -3701,7 +3685,7 @@ public typealias FfiConverterTypeMemberId = FfiConverterSequenceUByte fun `version`(): String { return FfiConverterString.lift( rustCall() { _status -> - _UniFFILib.INSTANCE.CoreCrypto_105c_version( _status) + _UniFFILib.INSTANCE.CoreCrypto_fbd8_version( _status) }) } diff --git a/crypto-ffi/bindings/kt/main/com/wire/crypto/client/MLSClient.kt b/crypto-ffi/bindings/kt/main/com/wire/crypto/client/MLSClient.kt index 33f38c06a0..77befce937 100644 --- a/crypto-ffi/bindings/kt/main/com/wire/crypto/client/MLSClient.kt +++ b/crypto-ffi/bindings/kt/main/com/wire/crypto/client/MLSClient.kt @@ -35,8 +35,8 @@ typealias ApplicationMessage = ByteArray typealias PlainMessage = ByteArray typealias MLSKeyPackage = ByteArray -open class PublicGroupStateBundle( - var encryptionType: MlsPublicGroupStateEncryptionType, +open class GroupInfoBundle( + var encryptionType: MlsGroupInfoEncryptionType, var ratchetTreeType: MlsRatchetTreeType, var payload: ByteArray ) @@ -44,7 +44,7 @@ open class PublicGroupStateBundle( open class CommitBundle( val commit: ByteArray, val welcome: ByteArray?, - val publicGroupStateBundle: PublicGroupStateBundle + val groupInfoBundle: GroupInfoBundle ) class DecryptedMessageBundle( @@ -77,7 +77,7 @@ interface MLSClient { credentialType: MlsCredentialType ): HandshakeMessage - fun joinByExternalCommit(publicGroupState: ByteArray, credentialType: MlsCredentialType): CommitBundle + fun joinByExternalCommit(groupInfo: ByteArray, credentialType: MlsCredentialType): CommitBundle fun mergePendingGroupFromExternalCommit(groupId: MLSGroupId) @@ -168,9 +168,9 @@ class MLSClientImpl( ).toByteArray() } - override fun joinByExternalCommit(publicGroupState: ByteArray, credentialType: MlsCredentialType): CommitBundle { + override fun joinByExternalCommit(groupInfo: ByteArray, credentialType: MlsCredentialType): CommitBundle { return cc.joinByExternalCommit( - publicGroupState.toUByteList(), + groupInfo.toUByteList(), defaultGroupConfiguration, credentialType ).toCommitBundle() @@ -274,22 +274,22 @@ class MLSClientImpl( fun MemberAddedMessages.toCommitBundle() = CommitBundle( commit.toByteArray(), welcome.toByteArray(), - publicGroupState.toPublicGroupStateBundle() + groupInfo.toGroupInfoBundle() ) fun com.wire.crypto.CommitBundle.toCommitBundle() = CommitBundle( commit.toByteArray(), welcome?.toByteArray(), - publicGroupState.toPublicGroupStateBundle() + groupInfo.toGroupInfoBundle() ) fun ConversationInitBundle.toCommitBundle() = CommitBundle( commit.toByteArray(), null, - publicGroupState.toPublicGroupStateBundle() + groupInfo.toGroupInfoBundle() ) - fun com.wire.crypto.PublicGroupStateBundle.toPublicGroupStateBundle() = PublicGroupStateBundle( + fun com.wire.crypto.GroupInfoBundle.toGroupInfoBundle() = GroupInfoBundle( encryptionType, ratchetTreeType, payload.toByteArray() diff --git a/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift b/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift index 1b02993e84..56627f8c80 100644 --- a/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift +++ b/crypto-ffi/bindings/swift/Sources/CoreCrypto/CoreCrypto.swift @@ -25,19 +25,19 @@ private protocol ConvertToInner { extension CoreCryptoSwift.CommitBundle { func convertTo() -> CommitBundle { - return CommitBundle(welcome: self.welcome, commit: self.commit, publicGroupState: self.publicGroupState.convertTo()) + return CommitBundle(welcome: self.welcome, commit: self.commit, groupInfo: self.groupInfo.convertTo()) } } extension CoreCryptoSwift.MemberAddedMessages { func convertTo() -> MemberAddedMessages { - return MemberAddedMessages(commit: self.commit, welcome: self.welcome, publicGroupState: self.publicGroupState.convertTo()) + return MemberAddedMessages(commit: self.commit, welcome: self.welcome, groupInfo: self.groupInfo.convertTo()) } } extension CoreCryptoSwift.ConversationInitBundle { func convertTo() -> ConversationInitBundle { - return ConversationInitBundle(conversationId: self.conversationId, commit: self.commit, publicGroupState: self.publicGroupState.convertTo()) + return ConversationInitBundle(conversationId: self.conversationId, commit: self.commit, groupInfo: self.groupInfo.convertTo()) } } @@ -61,17 +61,17 @@ extension CoreCryptoSwift.ProposalBundle { } } -extension CoreCryptoSwift.PublicGroupStateBundle { - func convertTo() -> PublicGroupStateBundle { - return PublicGroupStateBundle(encryptionType: self.encryptionType.convertTo(), ratchetTreeType: self.ratchetTreeType.convertTo(), payload: self.payload) +extension CoreCryptoSwift.GroupInfoBundle { + func convertTo() -> GroupInfoBundle { + return GroupInfoBundle(encryptionType: self.encryptionType.convertTo(), ratchetTreeType: self.ratchetTreeType.convertTo(), payload: self.payload) } } -extension CoreCryptoSwift.MlsPublicGroupStateEncryptionType { - func convertTo() -> PublicGroupStateEncryptionType { +extension CoreCryptoSwift.MlsGroupInfoEncryptionType { + func convertTo() -> GroupInfoEncryptionType { switch self { - case .jweEncrypted: return PublicGroupStateEncryptionType.JweEncrypted - case .plaintext: return PublicGroupStateEncryptionType.Plaintext + case .jweEncrypted: return GroupInfoEncryptionType.JweEncrypted + case .plaintext: return GroupInfoEncryptionType.Plaintext } } } @@ -255,16 +255,16 @@ public struct MemberAddedMessages: ConvertToInner { /// TLS-serialized MLS Commit that needs to be fanned out to other (existing) members of the conversation public var welcome: [UInt8] /// The current group state - public var publicGroupState: PublicGroupStateBundle + public var groupInfo: GroupInfoBundle - public init(commit: [UInt8], welcome: [UInt8], publicGroupState: PublicGroupStateBundle) { + public init(commit: [UInt8], welcome: [UInt8], groupInfo: GroupInfoBundle) { self.commit = commit self.welcome = welcome - self.publicGroupState = publicGroupState + self.groupInfo = groupInfo } func convert() -> Inner { - return CoreCryptoSwift.MemberAddedMessages(commit: self.commit, welcome: self.welcome, publicGroupState: self.publicGroupState.convert()) + return CoreCryptoSwift.MemberAddedMessages(commit: self.commit, welcome: self.welcome, groupInfo: self.groupInfo.convert()) } } @@ -360,19 +360,19 @@ public struct ConversationInitBundle: ConvertToInner { public var conversationId: ConversationId /// TLS-serialized MLS External Commit that needs to be fanned out public var commit: [UInt8] - /// TLS-serialized PublicGroupState (aka GroupInfo) which becomes valid when the external commit is accepted by the Delivery Service - public var publicGroupState: PublicGroupStateBundle + /// TLS-serialized GroupInfo (aka GroupInfo) which becomes valid when the external commit is accepted by the Delivery Service + public var groupInfo: GroupInfoBundle // Default memberwise initializers are never public by default, so we // declare one manually. - public init(conversationId: ConversationId, commit: [UInt8], publicGroupState: PublicGroupStateBundle) { + public init(conversationId: ConversationId, commit: [UInt8], groupInfo: GroupInfoBundle) { self.conversationId = conversationId self.commit = commit - self.publicGroupState = publicGroupState + self.groupInfo = groupInfo } func convert() -> Inner { - return CoreCryptoSwift.ConversationInitBundle(conversationId: self.conversationId, commit: self.commit, publicGroupState: self.publicGroupState.convert()) + return CoreCryptoSwift.ConversationInitBundle(conversationId: self.conversationId, commit: self.commit, groupInfo: self.groupInfo.convert()) } } @@ -383,63 +383,63 @@ public struct CommitBundle: ConvertToInner { /// TLS-serialized MLS Commit that needs to be fanned out to other (existing) members of the conversation public var commit: [UInt8] /// The current state of the group - public var publicGroupState: PublicGroupStateBundle + public var groupInfo: GroupInfoBundle // Default memberwise initializers are never public by default, so we // declare one manually. - public init(welcome: [UInt8]?, commit: [UInt8], publicGroupState: PublicGroupStateBundle) { + public init(welcome: [UInt8]?, commit: [UInt8], groupInfo: GroupInfoBundle) { self.welcome = welcome self.commit = commit - self.publicGroupState = publicGroupState + self.groupInfo = groupInfo } typealias Inner = CoreCryptoSwift.CommitBundle func convert() -> Inner { - return CoreCryptoSwift.CommitBundle(welcome: self.welcome, commit: self.commit, publicGroupState: self.publicGroupState.convert()) + return CoreCryptoSwift.CommitBundle(welcome: self.welcome, commit: self.commit, groupInfo: self.groupInfo.convert()) } } -/// A PublicGroupState with metadata -public struct PublicGroupStateBundle: ConvertToInner { +/// A GroupInfo with metadata +public struct GroupInfoBundle: ConvertToInner { /// Indicates if the payload is encrypted or not - public var encryptionType: PublicGroupStateEncryptionType - /// Indicates if the payload contains a full, partial or referenced PublicGroupState + public var encryptionType: GroupInfoEncryptionType + /// Indicates if the payload contains a full, partial or referenced GroupInfo public var ratchetTreeType: RatchetTreeType - /// TLS encoded PublicGroupState + /// TLS encoded GroupInfo public var payload: [UInt8] - public init(encryptionType: PublicGroupStateEncryptionType, ratchetTreeType: RatchetTreeType, payload: [UInt8]) { + public init(encryptionType: GroupInfoEncryptionType, ratchetTreeType: RatchetTreeType, payload: [UInt8]) { self.encryptionType = encryptionType self.ratchetTreeType = ratchetTreeType self.payload = payload } - typealias Inner = CoreCryptoSwift.PublicGroupStateBundle + typealias Inner = CoreCryptoSwift.GroupInfoBundle func convert() -> Inner { - return CoreCryptoSwift.PublicGroupStateBundle(encryptionType: self.encryptionType.convert(), ratchetTreeType: self.ratchetTreeType.convert(), payload: self.payload) + return CoreCryptoSwift.GroupInfoBundle(encryptionType: self.encryptionType.convert(), ratchetTreeType: self.ratchetTreeType.convert(), payload: self.payload) } } -/// In order to guarantee confidentiality of the PublicGroupState on the wire a domain can request it to be encrypted when sent to the Delivery Service. -public enum PublicGroupStateEncryptionType: ConvertToInner { - typealias Inner = CoreCryptoSwift.MlsPublicGroupStateEncryptionType +/// In order to guarantee confidentiality of the GroupInfo on the wire a domain can request it to be encrypted when sent to the Delivery Service. +public enum GroupInfoEncryptionType: ConvertToInner { + typealias Inner = CoreCryptoSwift.MlsGroupInfoEncryptionType case Plaintext case JweEncrypted } -private extension PublicGroupStateEncryptionType { +private extension GroupInfoEncryptionType { func convert() -> Inner { switch self { case .Plaintext: - return CoreCryptoSwift.MlsPublicGroupStateEncryptionType.plaintext + return CoreCryptoSwift.MlsGroupInfoEncryptionType.plaintext case .JweEncrypted: - return CoreCryptoSwift.MlsPublicGroupStateEncryptionType.jweEncrypted + return CoreCryptoSwift.MlsGroupInfoEncryptionType.jweEncrypted } } } -/// In order to spare some precious bytes, a PublicGroupState can have different representations. +/// In order to spare some precious bytes, a GroupInfo can have different representations. public enum RatchetTreeType: ConvertToInner { typealias Inner = CoreCryptoSwift.MlsRatchetTreeType @@ -579,7 +579,7 @@ public class CoreCryptoWrapper { /// /// The returned ``CommitBundle`` is a TLS struct that needs to be fanned out to Delivery Service in order to validate the commit. /// It also contains a Welcome message the Delivery Service will forward to invited clients and - /// an updated PublicGroupState required by clients willing to join the group by an external commit. + /// an updated GroupInfo required by clients willing to join the group by an external commit. /// /// **CAUTION**: ``CoreCryptoWrapper/commitAccepted`` **HAS TO** be called afterwards **ONLY IF** the Delivery Service responds /// '200 OK' to the ``CommitBundle`` upload. It will "merge" the commit locally i.e. increment the local group @@ -599,7 +599,7 @@ public class CoreCryptoWrapper { /// /// The returned ``CommitBundle`` is a TLS struct that needs to be fanned out to Delivery Service in order to validate the commit. /// It also contains a Welcome message the Delivery Service will forward to invited clients and - /// an updated PublicGroupState required by clients willing to join the group by an external commit. + /// an updated GroupInfo required by clients willing to join the group by an external commit. /// /// **CAUTION**: ``CoreCryptoWrapper/commitAccepted`` **HAS TO** be called afterwards **ONLY IF** the Delivery Service responds /// '200 OK' to the ``CommitBundle`` upload. It will "merge" the commit locally i.e. increment the local group @@ -625,7 +625,7 @@ public class CoreCryptoWrapper { /// /// The returned ``CommitBundle`` is a TLS struct that needs to be fanned out to Delivery Service in order to validate the commit. /// It also contains a Welcome message the Delivery Service will forward to invited clients and - /// an updated PublicGroupState required by clients willing to join the group by an external commit. + /// an updated GroupInfo required by clients willing to join the group by an external commit. /// /// **CAUTION**: ``CoreCryptoWrapper/commitAccepted`` **HAS TO** be called afterwards **ONLY IF** the Delivery Service responds /// '200 OK' to the ``CommitBundle`` upload. It will "merge" the commit locally i.e. increment the local group @@ -641,7 +641,7 @@ public class CoreCryptoWrapper { /// /// The returned ``CommitBundle`` is a TLS struct that needs to be fanned out to Delivery Service in order to validate the commit. /// It also contains a Welcome message the Delivery Service will forward to invited clients and - /// an updated PublicGroupState required by clients willing to join the group by an external commit. + /// an updated GroupInfo required by clients willing to join the group by an external commit. /// /// **CAUTION**: ``CoreCryptoWrapper/commitAccepted`` **HAS TO** be called afterwards **ONLY IF** the Delivery Service responds /// '200 OK' to the ``CommitBundle`` upload. It will "merge" the commit locally i.e. increment the local group @@ -715,17 +715,6 @@ public class CoreCryptoWrapper { return try self.coreCrypto.newExternalAddProposal(conversationId: conversationId, epoch: epoch, ciphersuite: ciphersuite, credentialType: credentialType.convert()) } - /// Crafts a new external Remove proposal. Enables a client outside a group to request removal - /// of a client within the group. - /// - /// - parameter conversationId: conversation identifier - /// - parameter epoch: the current epoch of the group - /// - parameter keyPackageRef: the `KeyPackageRef` of the client to be added to the group - /// - returns: a message with the proposal to be remove a client - public func newExternalRemoveProposal(conversationId: ConversationId, epoch: UInt64, keyPackageRef: [UInt8]) throws -> [UInt8] { - return try self.coreCrypto.newExternalRemoveProposal(conversationId: conversationId, epoch: epoch, keyPackageRef: keyPackageRef) - } - /// Issues an external commit and stores the group in a temporary table. This method is /// intended for example when a new client wants to join the user's existing groups. /// @@ -736,19 +725,19 @@ public class CoreCryptoWrapper { /// ``CoreCryptoWrapper/clearPendingGroupFromExternalCommit`` in order not to bloat the user's storage but nothing /// bad can happen if you forget to except some storage space wasted. /// - /// - parameter publicGroupState: a TLS encoded `PublicGroupState` fetched from the Delivery Service + /// - parameter groupInfo: a TLS encoded `GroupInfo` fetched from the Delivery Service /// - parameter config: - configuration of the MLS group /// - returns: an object of type `ConversationInitBundle` - public func joinByExternalCommit(publicGroupState: [UInt8], configuration: CustomConfiguration, credentialType: MlsCredentialType) throws -> ConversationInitBundle { - try self.coreCrypto.joinByExternalCommit(publicGroupState: publicGroupState, customConfiguration: configuration.convert(), credentialType: credentialType.convert()).convertTo() + public func joinByExternalCommit(groupInfo: [UInt8], configuration: CustomConfiguration, credentialType: MlsCredentialType) throws -> ConversationInitBundle { + try self.coreCrypto.joinByExternalCommit(groupInfo: groupInfo, customConfiguration: configuration.convert(), credentialType: credentialType.convert()).convertTo() } - /// Exports a TLS-serialized view of the current group state corresponding to the provided conversation ID. + /// Exports a TLS-serialized view of the current GroupInfo corresponding to the provided conversation ID. /// /// - parameter conversationId: conversation identifier /// - returns: a TLS serialized byte array of the conversation state - public func exportGroupState(conversationId: ConversationId) throws -> [UInt8] { - return try self.coreCrypto.exportGroupState(conversationId: conversationId) + public func exportGroupInfo(conversationId: ConversationId) throws -> [UInt8] { + return try self.coreCrypto.exportGroupInfo(conversationId: conversationId) } /// This merges the commit generated by ``CoreCryptoWrapper/joinByExternalCommit``, persists the group permanently and diff --git a/crypto-ffi/src/CoreCrypto.udl b/crypto-ffi/src/CoreCrypto.udl index ff2e5af369..32fb1c1138 100644 --- a/crypto-ffi/src/CoreCrypto.udl +++ b/crypto-ffi/src/CoreCrypto.udl @@ -10,13 +10,13 @@ typedef sequence ClientId; dictionary MemberAddedMessages { sequence commit; sequence welcome; - PublicGroupStateBundle public_group_state; + GroupInfoBundle group_info; }; dictionary CommitBundle { sequence? welcome; sequence commit; - PublicGroupStateBundle public_group_state; + GroupInfoBundle group_info; }; dictionary ProteusAutoPrekeyBundle { @@ -24,13 +24,13 @@ dictionary ProteusAutoPrekeyBundle { sequence pkb; }; -dictionary PublicGroupStateBundle { - MlsPublicGroupStateEncryptionType encryption_type; +dictionary GroupInfoBundle { + MlsGroupInfoEncryptionType encryption_type; MlsRatchetTreeType ratchet_tree_type; sequence payload; }; -enum MlsPublicGroupStateEncryptionType { +enum MlsGroupInfoEncryptionType { "Plaintext", "JweEncrypted", }; @@ -70,7 +70,6 @@ enum CryptoError { "PendingCommitNotFound", "MalformedIdentifier", "ClientSignatureNotFound", - "ClientSignatureMismatch", "LockPoisonError", "ImplementationError", "OutOfKeyPackage", @@ -104,6 +103,7 @@ enum CryptoError { "ParentGroupNotFound", "InvalidIdentity", "IdentityInitializationError", + "MessageEpochTooOld", }; dictionary DecryptedMessage { @@ -136,7 +136,7 @@ dictionary ProposalBundle { dictionary ConversationInitBundle { sequence conversation_id; sequence commit; - PublicGroupStateBundle public_group_state; + GroupInfoBundle group_info; }; dictionary ConversationConfiguration { @@ -172,10 +172,10 @@ interface CoreCrypto { void mls_init([ByRef] ClientId client_id, [ByRef] Ciphersuites ciphersuites); [Throws=CryptoError] - sequence> mls_generate_keypairs([ByRef] Ciphersuites ciphersuites); + sequence mls_generate_keypairs([ByRef] Ciphersuites ciphersuites); [Throws=CryptoError] - void mls_init_with_client_id([ByRef] ClientId client_id, sequence> signature_public_keys, [ByRef] Ciphersuites ciphersuites); + void mls_init_with_client_id([ByRef] ClientId client_id, sequence signature_public_keys, [ByRef] Ciphersuites ciphersuites); [Throws=CryptoError] void restore_from_disk(); @@ -240,10 +240,7 @@ interface CoreCrypto { sequence new_external_add_proposal(ConversationId conversation_id, u64 epoch, Ciphersuite ciphersuite, MlsCredentialType credential_type); [Throws=CryptoError] - sequence new_external_remove_proposal(ConversationId conversation_id, u64 epoch, sequence key_package_ref); - - [Throws=CryptoError] - ConversationInitBundle join_by_external_commit(sequence public_group_state, CustomConfiguration custom_configuration, MlsCredentialType credential_type); + ConversationInitBundle join_by_external_commit(sequence group_info, CustomConfiguration custom_configuration, MlsCredentialType credential_type); [Throws=CryptoError] void merge_pending_group_from_external_commit(ConversationId conversation_id); @@ -252,7 +249,7 @@ interface CoreCrypto { void clear_pending_group_from_external_commit(ConversationId conversation_id); [Throws=CryptoError] - sequence export_group_state(ConversationId conversation_id); + sequence export_group_info(ConversationId conversation_id); [Throws=CryptoError] sequence export_secret_key(ConversationId conversation_id, u32 key_length); diff --git a/crypto-ffi/src/generic.rs b/crypto-ffi/src/generic.rs index f677046d36..9ad591df91 100644 --- a/crypto-ffi/src/generic.rs +++ b/crypto-ffi/src/generic.rs @@ -15,15 +15,16 @@ // along with this program. If not, see http://www.gnu.org/licenses/. use std::collections::HashMap; +use tls_codec::Deserialize; +use tls_codec::Serialize; use futures_lite::future; use futures_util::TryFutureExt; use core_crypto::prelude::*; pub use core_crypto::prelude::{ - tls_codec::Serialize, CiphersuiteName, ClientId, ConversationId, CryptoError, E2eIdentityError, E2eIdentityResult, - MemberId, MlsCredentialType, MlsPublicGroupStateBundle, MlsPublicGroupStateEncryptionType, MlsRatchetTreeType, - MlsWirePolicy, PublicGroupStatePayload, + CiphersuiteName, ClientId, ConversationId, CryptoError, E2eIdentityError, E2eIdentityResult, MemberId, + MlsCredentialType, MlsGroupInfoBundle, MlsGroupInfoEncryptionType, MlsRatchetTreeType, MlsWirePolicy, }; cfg_if::cfg_if! { @@ -90,18 +91,18 @@ pub struct ProteusAutoPrekeyBundle { pub struct MemberAddedMessages { pub welcome: Vec, pub commit: Vec, - pub public_group_state: PublicGroupStateBundle, + pub group_info: GroupInfoBundle, } impl TryFrom for MemberAddedMessages { type Error = CryptoError; fn try_from(msg: MlsConversationCreationMessage) -> Result { - let (welcome, commit, pgs) = msg.to_bytes_triple()?; + let (welcome, commit, group_info) = msg.to_bytes_triple()?; Ok(Self { welcome, commit, - public_group_state: pgs.into(), + group_info: group_info.into(), }) } } @@ -110,36 +111,36 @@ impl TryFrom for MemberAddedMessages { pub struct CommitBundle { pub welcome: Option>, pub commit: Vec, - pub public_group_state: PublicGroupStateBundle, + pub group_info: GroupInfoBundle, } impl TryFrom for CommitBundle { type Error = CryptoError; fn try_from(msg: MlsCommitBundle) -> Result { - let (welcome, commit, pgs) = msg.to_bytes_triple()?; + let (welcome, commit, group_info) = msg.to_bytes_triple()?; Ok(Self { welcome, commit, - public_group_state: pgs.into(), + group_info: group_info.into(), }) } } #[derive(Debug, Clone)] #[allow(dead_code)] -pub struct PublicGroupStateBundle { - pub encryption_type: MlsPublicGroupStateEncryptionType, +pub struct GroupInfoBundle { + pub encryption_type: MlsGroupInfoEncryptionType, pub ratchet_tree_type: MlsRatchetTreeType, pub payload: Vec, } -impl From for PublicGroupStateBundle { - fn from(pgs: MlsPublicGroupStateBundle) -> Self { +impl From for GroupInfoBundle { + fn from(gi: MlsGroupInfoBundle) -> Self { Self { - encryption_type: pgs.encryption_type, - ratchet_tree_type: pgs.ratchet_tree_type, - payload: pgs.payload.bytes(), + encryption_type: gi.encryption_type, + ratchet_tree_type: gi.ratchet_tree_type, + payload: gi.payload.bytes(), } } } @@ -169,7 +170,7 @@ impl TryFrom for ProposalBundle { pub struct ConversationInitBundle { pub conversation_id: ConversationId, pub commit: Vec, - pub public_group_state: PublicGroupStateBundle, + pub group_info: GroupInfoBundle, } impl TryFrom for ConversationInitBundle { @@ -177,11 +178,11 @@ impl TryFrom for ConversationInitBundle { fn try_from(mut from: MlsConversationInitBundle) -> Result { let conversation_id = std::mem::take(&mut from.conversation_id); - let (commit, pgs) = from.to_bytes_pair()?; + let (commit, gi) = from.to_bytes_pair()?; Ok(Self { conversation_id, commit, - public_group_state: pgs.into(), + group_info: gi.into(), }) } } @@ -242,16 +243,19 @@ impl From for WireIdentity { impl Invitee { #[inline(always)] - fn group_to_conversation_member(clients: Vec) -> CryptoResult> { + fn group_to_conversation_member( + clients: Vec, + backend: &MlsCryptoProvider, + ) -> CryptoResult> { Ok(clients .into_iter() .try_fold( HashMap::new(), |mut acc, c| -> CryptoResult> { if let Some(member) = acc.get_mut(&c.id) { - member.add_keypackage(c.kp)?; + member.add_keypackage(c.kp, backend)?; } else { - acc.insert(c.id.clone(), ConversationMember::new_raw(c.id, c.kp)?); + acc.insert(c.id.clone(), ConversationMember::new_raw(c.id, c.kp, backend)?); } Ok(acc) }, @@ -261,14 +265,6 @@ impl Invitee { } } -impl TryInto for Invitee { - type Error = CryptoError; - - fn try_into(self) -> Result { - ConversationMember::new_raw(self.id, self.kp) - } -} - #[derive(Debug, Clone)] /// See [core_crypto::prelude::MlsConversationConfiguration] pub struct ConversationConfiguration { @@ -409,7 +405,7 @@ impl CoreCrypto<'_> { }) } - /// See [core_crypto::MlsCentral::mls_init] + /// See [core_crypto::mls::MlsCentral::mls_init] pub fn mls_init(&self, client_id: &ClientId, ciphersuites: &Ciphersuites) -> CryptoResult<()> { future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( @@ -422,7 +418,7 @@ impl CoreCrypto<'_> { } /// See [core_crypto::mls::MlsCentral::mls_generate_keypairs] - pub fn mls_generate_keypairs(&self, ciphersuites: &Ciphersuites) -> CryptoResult>> { + pub fn mls_generate_keypairs(&self, ciphersuites: &Ciphersuites) -> CryptoResult> { future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( self.central @@ -437,7 +433,7 @@ impl CoreCrypto<'_> { pub fn mls_init_with_client_id( &self, client_id: &ClientId, - signature_public_keys: Vec>, + tmp_client_ids: Vec, ciphersuites: &Ciphersuites, ) -> CryptoResult<()> { future::block_on( @@ -445,7 +441,7 @@ impl CoreCrypto<'_> { self.central .lock() .map_err(|_| CryptoError::LockPoisonError)? - .mls_init_with_client_id(client_id.clone(), signature_public_keys, ciphersuites.into()), + .mls_init_with_client_id(client_id.clone(), tmp_client_ids, ciphersuites.into()), ), ) } @@ -604,7 +600,13 @@ impl CoreCrypto<'_> { conversation_id: ConversationId, clients: Vec, ) -> CryptoResult { - let mut members = Invitee::group_to_conversation_member(clients)?; + let mut members = Invitee::group_to_conversation_member( + clients, + self.central + .lock() + .map_err(|_| CryptoError::LockPoisonError)? + .provider(), + )?; future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( @@ -740,13 +742,13 @@ impl CoreCrypto<'_> { conversation_id: ConversationId, keypackage: Vec, ) -> CryptoResult { - let kp = KeyPackage::try_from(&keypackage[..]).map_err(MlsError::from)?; + let kp = KeyPackageIn::tls_deserialize_bytes(keypackage).map_err(MlsError::from)?; future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( self.central .lock() .map_err(|_| CryptoError::LockPoisonError)? - .new_proposal(&conversation_id, MlsProposal::Add(kp)), + .new_proposal(&conversation_id, MlsProposal::Add(kp.into())), ), )? .try_into() @@ -802,39 +804,14 @@ impl CoreCrypto<'_> { .map_err(MlsError::from)?) } - /// See [core_crypto::mls::MlsCentral::new_external_remove_proposal] - pub fn new_external_remove_proposal( - &self, - conversation_id: ConversationId, - epoch: u64, - keypackage_ref: Vec, - ) -> CryptoResult> { - let value: [u8; 16] = keypackage_ref - .try_into() - .map_err(|_| CryptoError::InvalidByteArrayError(16))?; - let kpr = KeyPackageRef::from(value); - // TODO: we might not need this after all so let's not complicate things - let (cs, ct) = (MlsCiphersuite::default(), MlsCredentialType::default()); - Ok(future::block_on( - self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( - self.central - .lock() - .map_err(|_| CryptoError::LockPoisonError)? - .new_external_remove_proposal(conversation_id, epoch.into(), kpr, cs, ct), - ), - )? - .to_bytes() - .map_err(MlsError::from)?) - } - - /// See [core_crypto::mls::MlsCentral::export_public_group_state] - pub fn export_group_state(&self, conversation_id: ConversationId) -> CryptoResult> { + /// See [core_crypto::mls::MlsCentral::export_group_info] + pub fn export_group_info(&self, conversation_id: ConversationId) -> CryptoResult> { future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( self.central .lock() .map_err(|_| CryptoError::LockPoisonError)? - .export_public_group_state(&conversation_id), + .export_group_info(&conversation_id), ), ) } @@ -842,20 +819,17 @@ impl CoreCrypto<'_> { /// See [core_crypto::mls::MlsCentral::join_by_external_commit] pub fn join_by_external_commit( &self, - public_group_state: Vec, + group_info: Vec, custom_configuration: CustomConfiguration, credential_type: MlsCredentialType, ) -> CryptoResult { - use core_crypto::prelude::tls_codec::Deserialize as _; - - let group_state = - VerifiablePublicGroupState::tls_deserialize(&mut &public_group_state[..]).map_err(MlsError::from)?; + let group_info = MlsMessageIn::tls_deserialize_bytes(group_info).map_err(MlsError::from)?; future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( self.central .lock() .map_err(|_| CryptoError::LockPoisonError)? - .join_by_external_commit(group_state, custom_configuration.into(), credential_type), + .join_by_external_commit(group_info, custom_configuration.into(), credential_type), ), )? .try_into() @@ -897,7 +871,7 @@ impl CoreCrypto<'_> { .random_bytes(len.try_into()?) } - /// see [mls_crypto_provider::MlsCryptoProvider::reseed] + /// see [MlsCryptoProvider::reseed] pub fn reseed_rng(&self, seed: Vec) -> CryptoResult<()> { let seed = EntropySeed::try_from_slice(&seed)?; self.central @@ -999,7 +973,7 @@ impl CoreCrypto<'_> { .map_err(|_| CryptoError::ImplementationError) } - /// See [core_crypto::MlsCentral::e2ei_mls_init] + /// See [core_crypto::mls::MlsCentral::e2ei_mls_init] pub fn e2ei_mls_init( &self, enrollment: std::sync::Arc, @@ -1028,7 +1002,7 @@ impl CoreCrypto<'_> { ) } - /// See [core_crypto::MlsCentral::e2ei_enrollment_stash] + /// See [core_crypto::mls::MlsCentral::e2ei_enrollment_stash] pub fn e2ei_enrollment_stash(&self, enrollment: std::sync::Arc) -> CryptoResult> { let enrollment = std::sync::Arc::try_unwrap(enrollment).map_err(|_| CryptoError::LockPoisonError)?; let enrollment = std::sync::Arc::try_unwrap(enrollment.0).map_err(|_| CryptoError::LockPoisonError)?; @@ -1045,7 +1019,7 @@ impl CoreCrypto<'_> { ) } - /// See [core_crypto::MlsCentral::e2ei_enrollment_stash_pop] + /// See [core_crypto::mls::MlsCentral::e2ei_enrollment_stash_pop] pub fn e2ei_enrollment_stash_pop(&self, handle: Vec) -> CryptoResult> { let cc = self.central.lock().map_err(|_| CryptoError::LockPoisonError)?; let executor = self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?; @@ -1058,7 +1032,7 @@ impl CoreCrypto<'_> { .map_err(|_| CryptoError::ImplementationError) } - /// See [core_crypto::MlsCentral::e2ei_is_degraded] + /// See [core_crypto::mls::MlsCentral::e2ei_is_degraded] pub fn e2ei_is_degraded(&self, conversation_id: ConversationId) -> CryptoResult { let is_degraded = future::block_on( self.executor.lock().map_err(|_| CryptoError::LockPoisonError)?.run( diff --git a/crypto-ffi/src/wasm.rs b/crypto-ffi/src/wasm.rs index cd7356fdeb..abf34965d1 100644 --- a/crypto-ffi/src/wasm.rs +++ b/crypto-ffi/src/wasm.rs @@ -20,6 +20,7 @@ use core_crypto::prelude::*; use core_crypto::CryptoError; use futures_util::future::TryFutureExt; use js_sys::{Promise, Uint8Array}; +use tls_codec::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::future_to_promise; @@ -248,17 +249,17 @@ pub type FfiClientId = Box<[u8]>; pub struct MemberAddedMessages { welcome: Vec, commit: Vec, - public_group_state: PublicGroupStateBundle, + group_info: GroupInfoBundle, } #[wasm_bindgen] impl MemberAddedMessages { #[wasm_bindgen(constructor)] - pub fn new(welcome: Uint8Array, commit: Uint8Array, public_group_state: PublicGroupStateBundle) -> Self { + pub fn new(welcome: Uint8Array, commit: Uint8Array, group_info: GroupInfoBundle) -> Self { Self { welcome: welcome.to_vec(), commit: commit.to_vec(), - public_group_state, + group_info, } } @@ -273,8 +274,8 @@ impl MemberAddedMessages { } #[wasm_bindgen(getter)] - pub fn public_group_state(&self) -> PublicGroupStateBundle { - self.public_group_state.clone() + pub fn group_info(&self) -> GroupInfoBundle { + self.group_info.clone() } } @@ -290,7 +291,7 @@ impl TryFrom for MemberAddedMessages { Ok(Self { welcome, commit, - public_group_state: pgs.into(), + group_info: pgs.into(), }) } } @@ -308,7 +309,7 @@ pub struct ProteusAutoPrekeyBundle { pub struct CommitBundle { commit: Vec, welcome: Option>, - public_group_state: PublicGroupStateBundle, + group_info: GroupInfoBundle, } #[wasm_bindgen] @@ -324,8 +325,8 @@ impl CommitBundle { } #[wasm_bindgen(getter)] - pub fn public_group_state(&self) -> PublicGroupStateBundle { - self.public_group_state.clone() + pub fn group_info(&self) -> GroupInfoBundle { + self.group_info.clone() } } @@ -341,21 +342,21 @@ impl TryFrom for CommitBundle { Ok(Self { welcome, commit, - public_group_state: pgs.into(), + group_info: pgs.into(), }) } } #[wasm_bindgen] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PublicGroupStateBundle { +pub struct GroupInfoBundle { encryption_type: u8, ratchet_tree_type: u8, payload: Vec, } #[wasm_bindgen] -impl PublicGroupStateBundle { +impl GroupInfoBundle { #[wasm_bindgen(getter)] pub fn encryption_type(&self) -> u8 { self.encryption_type @@ -372,12 +373,12 @@ impl PublicGroupStateBundle { } } -impl From for PublicGroupStateBundle { - fn from(pgs: MlsPublicGroupStateBundle) -> Self { +impl From for GroupInfoBundle { + fn from(gi: MlsGroupInfoBundle) -> Self { Self { - encryption_type: pgs.encryption_type as u8, - ratchet_tree_type: pgs.ratchet_tree_type as u8, - payload: pgs.payload.bytes(), + encryption_type: gi.encryption_type as u8, + ratchet_tree_type: gi.ratchet_tree_type as u8, + payload: gi.payload.bytes(), } } } @@ -420,7 +421,7 @@ impl TryFrom for ProposalBundle { pub struct ConversationInitBundle { conversation_id: ConversationId, commit: Vec, - public_group_state: PublicGroupStateBundle, + group_info: GroupInfoBundle, } #[wasm_bindgen] @@ -436,8 +437,8 @@ impl ConversationInitBundle { } #[wasm_bindgen(getter)] - pub fn public_group_state(&self) -> PublicGroupStateBundle { - self.public_group_state.clone() + pub fn group_info(&self) -> GroupInfoBundle { + self.group_info.clone() } } @@ -454,7 +455,7 @@ impl TryFrom for ConversationInitBundle { Ok(Self { conversation_id, commit, - public_group_state: pgs.into(), + group_info: pgs.into(), }) } } @@ -636,7 +637,10 @@ impl Invitee { impl Invitee { #[inline(always)] - fn group_to_conversation_member(clients: Vec) -> WasmCryptoResult> { + fn group_to_conversation_member( + clients: Vec, + backend: &MlsCryptoProvider, + ) -> WasmCryptoResult> { Ok(clients .into_iter() .try_fold( @@ -644,11 +648,14 @@ impl Invitee { |mut acc, c| -> WasmCryptoResult> { let client_id: ClientId = c.id.into(); if let Some(member) = acc.get_mut(&client_id) { - member.add_keypackage(c.kp.to_vec()).map_err(CoreCryptoError::from)?; + member + .add_keypackage(c.kp.to_vec(), backend) + .map_err(CoreCryptoError::from)?; } else { acc.insert( client_id.clone(), - ConversationMember::new_raw(client_id, c.kp.to_vec()).map_err(CoreCryptoError::from)?, + ConversationMember::new_raw(client_id, c.kp.to_vec(), backend) + .map_err(CoreCryptoError::from)?, ); } Ok(acc) @@ -659,14 +666,6 @@ impl Invitee { } } -impl TryInto for Invitee { - type Error = CoreCryptoError; - - fn try_into(self) -> Result { - Ok(ConversationMember::new_raw(self.id.into(), self.kp.to_vec())?) - } -} - #[wasm_bindgen] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] /// see [core_crypto::prelude::MlsConversationConfiguration] @@ -1021,8 +1020,8 @@ impl CoreCrypto { let ciphersuites = lower_ciphersuites(&ciphersuites)?; let signature_public_keys = signature_public_keys .iter() - .map(|c| c.to_vec()) - .collect::>>(); + .map(|c| ClientId::from(c.to_vec())) + .collect(); let mut central = this.write().await; central @@ -1158,7 +1157,6 @@ impl CoreCrypto { let ciphersuite: CiphersuiteName = ciphersuite.into(); future_to_promise( async move { - use core_crypto::prelude::tls_codec::Serialize as _; let kps = this .write() .await @@ -1310,8 +1308,9 @@ impl CoreCrypto { .map(|js_client| Ok(serde_wasm_bindgen::from_value(js_client)?)) .collect::>>()?; - let mut members = Invitee::group_to_conversation_member(invitees)?; let mut central = this.write().await; + let backend = central.provider(); + let mut members = Invitee::group_to_conversation_member(invitees, backend)?; let conversation_id = conversation_id.into(); let commit = central .add_members_to_conversation(&conversation_id, &mut members) @@ -1484,7 +1483,7 @@ impl CoreCrypto { let this = self.inner.clone(); future_to_promise( async move { - let kp = KeyPackage::try_from(&keypackage[..]) + let kp = KeyPackageIn::tls_deserialize_bytes(keypackage) .map_err(MlsError::from) .map_err(CryptoError::from) .map_err(CoreCryptoError::from)?; @@ -1492,7 +1491,7 @@ impl CoreCrypto { let proposal: ProposalBundle = this .write() .await - .new_proposal(&conversation_id.to_vec(), MlsProposal::Add(kp)) + .new_proposal(&conversation_id.to_vec(), MlsProposal::Add(kp.into())) .await .map_err(CoreCryptoError::from)? .try_into()?; @@ -1583,55 +1582,15 @@ impl CoreCrypto { /// Returns: [`WasmCryptoResult`] /// - /// see [core_crypto::mls::MlsCentral::new_external_remove_proposal] - pub fn new_external_remove_proposal( - &self, - conversation_id: Box<[u8]>, - epoch: u32, - keypackage_ref: Box<[u8]>, - ) -> Promise { - let this = self.inner.clone(); - future_to_promise( - async move { - let kpr: Box<[u8; 16]> = keypackage_ref - .try_into() - .map_err(|_| CryptoError::InvalidByteArrayError(16)) - .map_err(CoreCryptoError::from)?; - let kpr = KeyPackageRef::from(*kpr); - // TODO: we might not need this after all so let's not complicate things - let (cs, ct) = ( - core_crypto::prelude::MlsCiphersuite::default(), - core_crypto::prelude::MlsCredentialType::default(), - ); - let proposal_bytes = this - .write() - .await - .new_external_remove_proposal(conversation_id.to_vec(), u64::from(epoch).into(), kpr, cs, ct) - .await - .map_err(CoreCryptoError::from)? - .to_bytes() - .map(|bytes| Uint8Array::from(bytes.as_slice())) - .map_err(MlsError::from) - .map_err(CryptoError::from) - .map_err(CoreCryptoError::from)?; - - WasmCryptoResult::Ok(proposal_bytes.into()) - } - .err_into(), - ) - } - - /// Returns: [`WasmCryptoResult`] - /// - /// see [core_crypto::mls::MlsCentral::export_public_group_state] - pub fn export_group_state(&self, conversation_id: Box<[u8]>) -> Promise { + /// see [core_crypto::mls::MlsCentral::export_group_info] + pub fn export_group_info(&self, conversation_id: Box<[u8]>) -> Promise { let this = self.inner.clone(); future_to_promise( async move { let state = this .write() .await - .export_public_group_state(&conversation_id.to_vec()) + .export_group_info(&conversation_id.to_vec()) .await .map_err(CoreCryptoError::from)?; WasmCryptoResult::Ok(Uint8Array::from(state.as_slice()).into()) @@ -1646,18 +1605,14 @@ impl CoreCrypto { /// see [core_crypto::mls::MlsCentral::join_by_external_commit] pub fn join_by_external_commit( &self, - public_group_state: Box<[u8]>, + group_info: Box<[u8]>, custom_configuration: CustomConfiguration, credential_type: CredentialType, ) -> Promise { - use core_crypto::prelude::tls_codec::Deserialize as _; - let this = self.inner.clone(); - let state = public_group_state.to_vec(); - future_to_promise( async move { - let group_state = VerifiablePublicGroupState::tls_deserialize(&mut &state[..]) + let group_info = MlsMessageIn::tls_deserialize_bytes(&group_info) .map_err(MlsError::from) .map_err(CryptoError::from) .map_err(CoreCryptoError::from)?; @@ -1665,7 +1620,7 @@ impl CoreCrypto { let result: ConversationInitBundle = this .write() .await - .join_by_external_commit(group_state, custom_configuration.into(), credential_type.into()) + .join_by_external_commit(group_info, custom_configuration.into(), credential_type.into()) .await .map_err(CoreCryptoError::from)? .try_into()?; diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 8915b78002..bf3bfa3e54 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -28,10 +28,12 @@ cfg-if = "1.0" hex = "0.4" futures-util = "0.3" -openmls = { version = "0.4", features = ["crypto-subtle"] } +openmls = { version = "0.20", features = ["crypto-subtle"] } +openmls_basic_credential = "0.1" +openmls_x509_credential = "0.1" openmls_traits = "0.1" # FIXME: This is being pulled only because of flaky error types from openmls -tls_codec = "0.2" +tls_codec = { workspace = true } serde = "1.0" serde_json = "1.0" url = "2.3" @@ -73,6 +75,7 @@ version = "^0.11.0" path = "../mls-provider" [dev-dependencies] +itertools = "0.10" uuid = { version = "1.0", features = ["v4", "v5"] } rand = "0.8" tempfile = "3.3" @@ -88,6 +91,7 @@ futures-lite = "1.12" proteus-traits = "2.0" async-trait = "0.1" wire-e2e-identity = { version = "0.4", features = ["identity-builder"] } +fluvio-wasm-timer = "0.2" [dev-dependencies.core-crypto-keystore] version = "^0.11.0" @@ -99,7 +103,7 @@ cryptobox = { git = "https://github.com/wireapp/cryptobox", tag = "v1.0.3" } proteus = { git = "https://github.com/wireapp//proteus", branch = "otak/fix-1.0.3" } [target.'cfg(not(target_family = "wasm"))'.dev-dependencies.criterion] -version = "0.3" +version = "0.4" features = ["async_futures", "html_reports"] [dev-dependencies.core-crypto-attributes] @@ -118,7 +122,7 @@ name = "proposal" harness = false [[bench]] -name = "public_group_state" +name = "group_info" harness = false [[bench]] diff --git a/crypto/benches/commit.rs b/crypto/benches/commit.rs index 6bc4dd012c..c137049d43 100644 --- a/crypto/benches/commit.rs +++ b/crypto/benches/commit.rs @@ -17,12 +17,13 @@ fn commit_add_bench(c: &mut Criterion) { || { let (mut central, id) = setup_mls(ciphersuite, &credential, in_memory); add_clients(&mut central, &id, ciphersuite, *i); - let member = rand_member(ciphersuite); + let member = block_on(async { rand_member(ciphersuite).await }); (central, id, member) }, |(mut central, id, member)| async move { black_box(central.add_members_to_conversation(&id, &mut [member]).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -41,7 +42,7 @@ fn commit_add_n_clients_bench(c: &mut Criterion) { || { let (central, id) = setup_mls(ciphersuite, &credential, in_memory); let members = (0..*i) - .map(|_| rand_member(ciphersuite)) + .map(|_| block_on(async { rand_member(ciphersuite).await })) .collect::>(); (central, id, members) }, @@ -52,7 +53,8 @@ fn commit_add_n_clients_bench(c: &mut Criterion) { .await .unwrap(), ); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -80,7 +82,8 @@ fn commit_remove_bench(c: &mut Criterion) { .await .unwrap(), ); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -109,7 +112,8 @@ fn commit_remove_n_clients_bench(c: &mut Criterion) { .await .unwrap(), ); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -132,7 +136,8 @@ fn commit_update_bench(c: &mut Criterion) { }, |(mut central, id)| async move { black_box(central.update_keying_material(&id).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -153,7 +158,7 @@ fn commit_pending_proposals_bench_var_n_proposals(c: &mut Criterion) { add_clients(&mut central, &id, ciphersuite, GROUP_MAX); block_on(async { for _ in 0..*i { - let (kp, ..) = rand_key_package(ciphersuite); + let (kp, ..) = rand_key_package(ciphersuite).await; central.new_proposal(&id, MlsProposal::Add(kp)).await.unwrap(); } }); @@ -161,7 +166,8 @@ fn commit_pending_proposals_bench_var_n_proposals(c: &mut Criterion) { }, |(mut central, id)| async move { black_box(central.commit_pending_proposals(&id).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -182,7 +188,7 @@ fn commit_pending_proposals_bench_var_group_size(c: &mut Criterion) { add_clients(&mut central, &id, ciphersuite, *i); block_on(async { for _ in 0..PENDING_MAX { - let (kp, ..) = rand_key_package(ciphersuite); + let (kp, ..) = rand_key_package(ciphersuite).await; central.new_proposal(&id, MlsProposal::Add(kp)).await.unwrap(); } }); @@ -190,7 +196,8 @@ fn commit_pending_proposals_bench_var_group_size(c: &mut Criterion) { }, |(mut central, id)| async move { black_box(central.commit_pending_proposals(&id).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) diff --git a/crypto/benches/create_group.rs b/crypto/benches/create_group.rs index a995c74349..0be284062b 100644 --- a/crypto/benches/create_group.rs +++ b/crypto/benches/create_group.rs @@ -2,7 +2,8 @@ use criterion::{ async_executor::FuturesExecutor, black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, }; use futures_lite::future::block_on; -use openmls::prelude::VerifiablePublicGroupState; + +use openmls::prelude::MlsMessageIn; use core_crypto::prelude::{ ConversationMember, MlsConversationConfiguration, MlsConversationInitBundle, MlsCredentialType, @@ -32,12 +33,11 @@ fn create_group_bench(c: &mut Criterion) { (central, id, cfg) }, |(mut central, id, cfg)| async move { - black_box( - central - .new_conversation(id, MlsCredentialType::Basic, cfg) - .await - .unwrap(), - ); + central + .new_conversation(id, MlsCredentialType::Basic, cfg) + .await + .unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -78,7 +78,7 @@ fn join_from_welcome_bench(c: &mut Criterion) { |(mut central, welcome)| async move { black_box( central - .process_welcome_message(welcome, MlsCustomConfiguration::default()) + .process_welcome_message(welcome.into(), MlsCustomConfiguration::default()) .await .unwrap(), ); @@ -91,7 +91,7 @@ fn join_from_welcome_bench(c: &mut Criterion) { group.finish(); } -fn join_from_public_group_state_bench(c: &mut Criterion) { +fn join_from_group_info_bench(c: &mut Criterion) { let mut group = c.benchmark_group("Join from external commit f(group size)"); for (case, ciphersuite, credential, in_memory) in MlsTestCase::values() { for i in (GROUP_RANGE).step_by(GROUP_STEP) { @@ -101,29 +101,27 @@ fn join_from_public_group_state_bench(c: &mut Criterion) { use openmls::prelude::TlsDeserializeTrait as _; let (mut alice_central, id) = setup_mls(ciphersuite, &credential, in_memory); add_clients(&mut alice_central, &id, ciphersuite, *i); - let pgs = block_on(async { alice_central.export_public_group_state(&id).await.unwrap() }); - let pgs: VerifiablePublicGroupState = - VerifiablePublicGroupState::tls_deserialize(&mut pgs.as_slice()).unwrap(); + let gi = block_on(async { alice_central.export_group_info(&id).await.unwrap() }); + let gi = MlsMessageIn::tls_deserialize(&mut gi.as_slice()).unwrap(); let (bob_central, ..) = new_central(ciphersuite, &credential, in_memory); - (bob_central, pgs) + (bob_central, gi) }, - |(mut central, pgs)| async move { + |(mut central, group_info)| async move { let MlsConversationInitBundle { conversation_id, .. } = black_box( central .join_by_external_commit( - pgs, + group_info, MlsCustomConfiguration::default(), MlsCredentialType::Basic, ) .await .unwrap(), ); - black_box( - central - .merge_pending_group_from_external_commit(&conversation_id) - .await - .unwrap(), - ); + central + .merge_pending_group_from_external_commit(&conversation_id) + .await + .unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -139,6 +137,6 @@ criterion_group!( targets = create_group_bench, join_from_welcome_bench, - join_from_public_group_state_bench, + join_from_group_info_bench, ); criterion_main!(create_group); diff --git a/crypto/benches/public_group_state.rs b/crypto/benches/group_info.rs similarity index 79% rename from crypto/benches/public_group_state.rs rename to crypto/benches/group_info.rs index 4c71c354a4..b2bf7655fa 100644 --- a/crypto/benches/public_group_state.rs +++ b/crypto/benches/group_info.rs @@ -6,18 +6,18 @@ use crate::utils::*; mod utils; fn export_pgs_bench(c: &mut Criterion) { - let mut group = c.benchmark_group("Export PublicGroupState"); + let mut group = c.benchmark_group("Export GroupInfo"); for (case, ciphersuite, credential, in_memory) in MlsTestCase::values() { for i in (GROUP_RANGE).step_by(GROUP_STEP) { group.bench_with_input(case.benchmark_id(i + 1, in_memory), &i, |b, i| { b.to_async(FuturesExecutor).iter_batched( || { - let (mut central, id) = setup_mls(ciphersuite, &&credential, in_memory); + let (mut central, id) = setup_mls(ciphersuite, &credential, in_memory); add_clients(&mut central, &id, ciphersuite, *i); (central, id) }, |(mut central, id)| async move { - black_box(central.export_public_group_state(&id).await.unwrap()); + black_box(central.export_group_info(&id).await.unwrap()); }, BatchSize::SmallInput, ) @@ -28,8 +28,8 @@ fn export_pgs_bench(c: &mut Criterion) { } criterion_group!( - name = public_group_state; + name = group_info; config = criterion(); targets = export_pgs_bench ); -criterion_main!(public_group_state); +criterion_main!(group_info); diff --git a/crypto/benches/mls_proteus.rs b/crypto/benches/mls_proteus.rs index a760b9c823..d035b41107 100644 --- a/crypto/benches/mls_proteus.rs +++ b/crypto/benches/mls_proteus.rs @@ -100,12 +100,13 @@ fn add_client_bench(c: &mut Criterion) { || { let (mut central, id) = setup_mls(ciphersuite, &credential, in_memory); add_clients(&mut central, &id, ciphersuite, *i); - let member = rand_member(ciphersuite); + let member = block_on(async { rand_member(ciphersuite).await }); (central, id, member) }, |(mut central, id, member)| async move { black_box(central.add_members_to_conversation(&id, &mut [member]).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -157,7 +158,8 @@ fn remove_client_bench(c: &mut Criterion) { .await .unwrap(), ); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -183,7 +185,8 @@ fn remove_client_bench(c: &mut Criterion) { }, |(mut central, keystore, session_material)| async move { for (session_id, _) in session_material { - black_box(central.session_delete(&keystore, &session_id).await.unwrap()); + central.session_delete(&keystore, &session_id).await.unwrap(); + black_box(()); } }, BatchSize::SmallInput, @@ -208,7 +211,8 @@ fn update_client_bench(c: &mut Criterion) { }, |(mut central, id)| async move { black_box(central.update_keying_material(&id).await.unwrap()); - black_box(central.commit_accepted(&id).await.unwrap()); + central.commit_accepted(&id).await.unwrap(); + black_box(()); }, BatchSize::SmallInput, ) @@ -243,7 +247,8 @@ fn update_client_bench(c: &mut Criterion) { |(mut central, keystore, new_pkb, session_material)| async move { for (session_id, _) in session_material { // replace existing session - black_box(central.session_delete(&keystore, &session_id).await.unwrap()); + central.session_delete(&keystore, &session_id).await.unwrap(); + black_box(()); black_box(central.session_from_prekey(&session_id, &new_pkb).await.unwrap()); } }, diff --git a/crypto/benches/proposal.rs b/crypto/benches/proposal.rs index 8d4a7646d9..26e298c7f2 100644 --- a/crypto/benches/proposal.rs +++ b/crypto/benches/proposal.rs @@ -1,3 +1,4 @@ +use async_std::task::block_on; use criterion::{async_executor::FuturesExecutor, black_box, criterion_group, criterion_main, BatchSize, Criterion}; use core_crypto::prelude::MlsProposal; @@ -16,7 +17,7 @@ fn proposal_add_bench(c: &mut Criterion) { || { let (mut central, id) = setup_mls(ciphersuite, &credential, in_memory); add_clients(&mut central, &id, ciphersuite, *i); - let (kp, ..) = rand_key_package(ciphersuite); + let (kp, ..) = block_on(async { rand_key_package(ciphersuite).await }); (central, id, kp) }, |(mut central, id, kp)| async move { diff --git a/crypto/benches/scripts/run.sh b/crypto/benches/scripts/run.sh index 93c01a41c3..90ecf89e6a 100644 --- a/crypto/benches/scripts/run.sh +++ b/crypto/benches/scripts/run.sh @@ -1,2 +1,2 @@ # Runs all benches in quick mode to prototype faster -cargo bench --bench commit --bench encryption --bench key_package --bench public_group_state --bench create_group --bench mls_proteus -- --quick +cargo bench --bench commit --bench encryption --bench key_package --bench group_info --bench create_group --bench mls_proteus -- --quick diff --git a/crypto/benches/scripts/test.sh b/crypto/benches/scripts/test.sh index d3f0f38ac1..c9b650d459 100644 --- a/crypto/benches/scripts/test.sh +++ b/crypto/benches/scripts/test.sh @@ -1,2 +1,2 @@ # Tests all benchmarks to make sure they are functional -cargo test --bench commit --bench encryption --bench key_package --bench public_group_state --bench create_group --bench mls_proteus +cargo test --bench commit --bench encryption --bench key_package --bench group_info --bench create_group --bench mls_proteus diff --git a/crypto/benches/utils/mls.rs b/crypto/benches/utils/mls.rs index a26dd3db14..9a4d155904 100644 --- a/crypto/benches/utils/mls.rs +++ b/crypto/benches/utils/mls.rs @@ -4,8 +4,11 @@ use std::fmt::{Display, Formatter}; use criterion::BenchmarkId; use futures_lite::future::block_on; -use openmls::prelude::{CredentialBundle, Extension, KeyPackage, KeyPackageBundle, LifetimeExtension}; +use openmls::prelude::{Credential, CredentialWithKey, CryptoConfig, KeyPackage, SignaturePublicKey}; +use openmls_basic_credential::SignatureKeyPair; +use openmls_traits::random::OpenMlsRand; use openmls_traits::types::Ciphersuite; +use openmls_traits::OpenMlsCryptoProvider; use core_crypto::prelude::MlsCredentialType; use core_crypto::{ @@ -45,7 +48,7 @@ impl MlsTestCase { None, ), MlsTestCase::Basic_Ciphersuite3 => ( - self.clone(), + *self, Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519.into(), None, ), @@ -132,7 +135,7 @@ pub fn setup_mls( id.clone(), MlsCredentialType::Basic, MlsConversationConfiguration { - ciphersuite: ciphersuite.clone(), + ciphersuite, ..Default::default() }, ) @@ -186,37 +189,50 @@ pub fn add_clients( let mut members = (0..nb_clients) .map(|_| { - let member = rand_member(ciphersuite); + let member = block_on(async { rand_member(ciphersuite).await }); client_ids.push(member.id().as_slice().into()); member }) .collect::>(); central - .add_members_to_conversation(&id, members.as_mut_slice()) + .add_members_to_conversation(id, members.as_mut_slice()) .await .unwrap(); - central.commit_accepted(&id).await.unwrap(); + central.commit_accepted(id).await.unwrap(); client_ids }) } -pub fn rand_key_package(ciphersuite: MlsCiphersuite) -> (KeyPackage, ClientId) { +pub async fn rand_key_package(ciphersuite: MlsCiphersuite) -> (KeyPackage, ClientId) { let client_id = Alphanumeric .sample_string(&mut rand::thread_rng(), 16) .as_bytes() .to_vec(); let backend = block_on(async { MlsCryptoProvider::try_new_in_memory("secret").await.unwrap() }); - let ciphersuite: Ciphersuite = ciphersuite.clone().into(); - let cred = CredentialBundle::new_basic(client_id.clone(), ciphersuite.signature_algorithm(), &backend).unwrap(); - let lifetime_extension = Extension::LifeTime(LifetimeExtension::new(3600)); - let kpb = KeyPackageBundle::new(&[ciphersuite], &cred, &backend, vec![lifetime_extension]).unwrap(); - (kpb.key_package().clone(), client_id.into()) + let cs: Ciphersuite = ciphersuite.into(); + + let mut rng = &mut *backend.rand().borrow_rand().unwrap(); + let signer = SignatureKeyPair::new(ciphersuite.signature_algorithm(), &mut rng).unwrap(); + + let cred = Credential::new_basic(client_id.clone()); + let signature_key = SignaturePublicKey::from(signer.public()); + let credential = CredentialWithKey { + credential: cred, + signature_key, + }; + + let cfg = CryptoConfig::with_default_version(cs); + let kp = KeyPackage::builder() + .build(cfg, &backend, &signer, credential) + .await + .unwrap(); + (kp, client_id.into()) } -pub fn rand_member(ciphersuite: MlsCiphersuite) -> ConversationMember { - let (kp, client_id) = rand_key_package(ciphersuite); +pub async fn rand_member(ciphersuite: MlsCiphersuite) -> ConversationMember { + let (kp, client_id) = rand_key_package(ciphersuite).await; ConversationMember::new(client_id, kp) } @@ -231,7 +247,7 @@ pub fn invite(from: &mut MlsCentral, other: &mut MlsCentral, id: &ConversationId .unwrap() .welcome; other - .process_welcome_message(welcome, MlsCustomConfiguration::default()) + .process_welcome_message(welcome.into(), MlsCustomConfiguration::default()) .await .unwrap(); from.commit_accepted(id).await.unwrap(); diff --git a/crypto/src/e2e_identity/crypto.rs b/crypto/src/e2e_identity/crypto.rs index 98bd7ce91c..541f1221d5 100644 --- a/crypto/src/e2e_identity/crypto.rs +++ b/crypto/src/e2e_identity/crypto.rs @@ -5,11 +5,25 @@ use openmls_traits::{crypto::OpenMlsCrypto, types::Ciphersuite, OpenMlsCryptoPro use wire_e2e_identity::prelude::JwsAlgorithm; impl super::WireE2eIdentity { + /// Length for all signature keys since there's not method to retrieve it from openmls + const SIGN_KEY_LENGTH: usize = 32; + pub(super) fn new_sign_key(ciphersuite: MlsCiphersuite, backend: &MlsCryptoProvider) -> E2eIdentityResult> { let crypto = backend.crypto(); let cs = openmls_traits::types::Ciphersuite::from(ciphersuite); - let (sk, _pk) = crypto.signature_key_gen(cs.signature_algorithm())?; - Ok(sk) + let (sk, pk) = crypto.signature_key_gen(cs.signature_algorithm())?; + Ok(Self::into_e2ei_sign_key(sk, pk)) + } + + fn into_e2ei_sign_key(sk: Vec, pk: Vec) -> Vec { + [sk, pk].concat() + } + + pub(super) fn get_sign_key_for_mls(&self) -> E2eIdentityResult> { + if self.sign_sk.len() != Self::SIGN_KEY_LENGTH * 2 { + return Err(E2eIdentityError::ImplementationError); + } + Ok(self.sign_sk[..Self::SIGN_KEY_LENGTH].to_vec()) } } diff --git a/crypto/src/e2e_identity/degraded.rs b/crypto/src/e2e_identity/degraded.rs index 8ebc26fe87..f4889bee71 100644 --- a/crypto/src/e2e_identity/degraded.rs +++ b/crypto/src/e2e_identity/degraded.rs @@ -13,9 +13,9 @@ impl MlsCentral { impl MlsConversation { fn e2ei_is_degraded(&self) -> bool { - self.group.members().iter().any(|kp| { - let is_basic = matches!(kp.credential().get_type(), MlsCredentialType::Basic); - let invalid_identity = kp.credential().extract_identity().is_none(); + self.group.members().any(|kp| { + let is_basic = matches!(kp.credential.get_type(), Ok(MlsCredentialType::Basic)); + let invalid_identity = Self::extract_identity(&kp.credential).is_err(); is_basic || invalid_identity }) } @@ -33,7 +33,7 @@ pub mod tests { // testing the case where both Bob & Alice have the same Credential type #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn uniform_conversation_should_be_degraded(case: TestCase) { + pub async fn uniform_conversation_should_be_degraded_when_basic(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob"], diff --git a/crypto/src/e2e_identity/identity.rs b/crypto/src/e2e_identity/identity.rs index b0cf689a35..2b813b619a 100644 --- a/crypto/src/e2e_identity/identity.rs +++ b/crypto/src/e2e_identity/identity.rs @@ -1,8 +1,7 @@ +use openmls::credentials::MlsCredentialType; + use crate::{prelude::MlsConversation, CryptoError, CryptoResult}; -use openmls::{ - credentials::MlsCredentialType, - prelude::{Credential, MlsCertificate}, -}; +use openmls::prelude::Credential; /// Represents the identity claims identifying a client /// Those claims are verifiable by any member in the group @@ -31,17 +30,17 @@ impl From for WireIdentity { impl MlsConversation { pub(crate) fn extract_identity(credential: &Credential) -> CryptoResult> { - match &credential.credential { - MlsCredentialType::X509(MlsCertificate { cert_chain, .. }) => { - let cert = cert_chain.get(0).ok_or(CryptoError::InvalidIdentity)?; + match credential.mls_credential() { + MlsCredentialType::X509(openmls::prelude::Certificate { cert_data, .. }) => { + let leaf = cert_data.get(0).ok_or(CryptoError::InvalidIdentity)?; use wire_e2e_identity::prelude::WireIdentityReader as _; - let identity = cert + let identity = leaf .as_slice() .extract_identity() .map_err(|_| CryptoError::InvalidIdentity)?; Ok(Some(identity.into())) } - MlsCredentialType::Basic(_) => Ok(None), + _ => Ok(None), } } } diff --git a/crypto/src/e2e_identity/mod.rs b/crypto/src/e2e_identity/mod.rs index fe5761d211..b98b3e90f0 100644 --- a/crypto/src/e2e_identity/mod.rs +++ b/crypto/src/e2e_identity/mod.rs @@ -1,12 +1,11 @@ -use openmls::prelude::SignaturePrivateKey; use std::collections::HashMap; use wire_e2e_identity::prelude::RustyE2eIdentity; use error::*; use mls_crypto_provider::MlsCryptoProvider; -use crate::prelude::identifier::ClientIdentifier; use crate::prelude::{id::ClientId, CertificateBundle, MlsCentral, MlsCiphersuite}; +use crate::{mls::credential::x509::CertificatePrivateKey, prelude::identifier::ClientIdentifier}; mod crypto; pub(crate) mod degraded; @@ -20,8 +19,6 @@ type Json = Vec; impl MlsCentral { /// Creates an enrollment instance with private key material you can use in order to fetch /// a new x509 certificate from the acme server. - /// Make sure to call [WireE2eIdentity::free] (not yet available) to dispose this instance and its associated - /// keying material. /// /// # Parameters /// * `client_id` - client identifier with user b64Url encoded & clientId hex encoded e.g. `NDUyMGUyMmY2YjA3NGU3NjkyZjE1NjJjZTAwMmQ2NTQ:6add501bacd1d90e@example.com` @@ -49,7 +46,21 @@ impl MlsCentral { /// Parses the ACME server response from the endpoint fetching x509 certificates and uses it /// to initialize the MLS client with a certificate pub async fn e2ei_mls_init(&mut self, e2ei: WireE2eIdentity, certificate_chain: String) -> E2eIdentityResult<()> { - e2ei.certificate_response(self, certificate_chain).await + let sk = e2ei.get_sign_key_for_mls()?; + let cs = e2ei.ciphersuite; + let certificate_chain = e2ei.certificate_response(certificate_chain).await?; + let private_key = CertificatePrivateKey { + value: sk, + signature_scheme: cs.signature_algorithm(), + }; + + let cert_bundle = CertificateBundle { + certificate_chain, + private_key, + }; + let identifier = ClientIdentifier::X509(HashMap::from([(cs, cert_bundle)])); + self.mls_init(identifier, vec![cs]).await?; + Ok(()) } } @@ -116,8 +127,8 @@ impl WireE2eIdentity { } /// Parses the response from `GET /acme/{provisioner-name}/directory`. - /// Use this [AcmeDirectory] in the next step to fetch the first nonce from the acme server. Use - /// [AcmeDirectory::new_nonce]. + /// Use this [types::E2eiAcmeDirectory] in the next step to fetch the first nonce from the acme server. Use + /// [types::E2eiAcmeDirectory.new_nonce]. /// /// See [RFC 8555 Section 7.1.1](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1) /// @@ -136,7 +147,7 @@ impl WireE2eIdentity { /// See [RFC 8555 Section 7.3](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3). /// /// # Parameters - /// * `directory` - you got from [Self::acme_directory_response] + /// * `directory` - you got from [Self::directory_response] /// * `previous_nonce` - you got from calling `HEAD {directory.new_nonce}` pub fn new_account_request(&self, previous_nonce: String) -> E2eIdentityResult { let directory = self.directory.as_ref().ok_or(E2eIdentityError::ImplementationError)?; @@ -196,8 +207,8 @@ impl WireE2eIdentity { /// See [RFC 8555 Section 7.5](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.5). /// /// # Parameters - /// * `url` - one of the URL in new order's authorizations (from [Self::acme_new_order_response]) - /// * `account` - you got from [Self::acme_new_account_response] + /// * `url` - one of the URL in new order's authorizations (from [Self::new_order_response]) + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/new-order` /// (or from the previous to this method if you are creating the second authorization) pub fn new_authz_request(&self, url: String, previous_nonce: String) -> E2eIdentityResult { @@ -250,8 +261,8 @@ impl WireE2eIdentity { /// /// # Parameters /// * `access_token` - returned by wire-server from [this endpoint](https://staging-nginz-https.zinfra.io/api/swagger-ui/#/default/post_clients__cid__access_token) - /// * `dpop_challenge` - you found after [Self::acme_new_authz_response] - /// * `account` - you got from [Self::acme_new_account_response] + /// * `dpop_challenge` - you found after [Self::new_authz_response] + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/authz/{authz-id}` pub fn new_dpop_challenge_request(&self, access_token: String, previous_nonce: String) -> E2eIdentityResult { let authz = self.authz.as_ref().ok_or(E2eIdentityError::ImplementationError)?; @@ -271,8 +282,8 @@ impl WireE2eIdentity { /// /// # Parameters /// * `id_token` - you get back from Identity Provider - /// * `oidc_challenge` - you found after [Self::acme_new_authz_response] - /// * `account` - you got from [Self::acme_new_account_response] + /// * `oidc_challenge` - you found after [Self::new_authz_response] + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/authz/{authz-id}` pub fn new_oidc_challenge_request(&self, id_token: String, previous_nonce: String) -> E2eIdentityResult { let authz = self.authz.as_ref().ok_or(E2eIdentityError::ImplementationError)?; @@ -302,8 +313,8 @@ impl WireE2eIdentity { /// See [RFC 8555 Section 7.4](https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4). /// /// # Parameters - /// * `order_url` - `location` header from http response you got from [Self::acme_new_order_response] - /// * `account` - you got from [Self::acme_new_account_response] + /// * `order_url` - `location` header from http response you got from [Self::new_order_response] + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/challenge/{challenge-id}` pub fn check_order_request(&self, order_url: String, previous_nonce: String) -> E2eIdentityResult { let account = self.account.as_ref().ok_or(E2eIdentityError::ImplementationError)?; @@ -335,8 +346,8 @@ impl WireE2eIdentity { /// /// # Parameters /// * `domains` - you want to generate a certificate for e.g. `["wire.com"]` - /// * `order` - you got from [Self::acme_check_order_response] - /// * `account` - you got from [Self::acme_new_account_response] + /// * `order` - you got from [Self::check_order_response] + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/order/{order-id}` pub fn finalize_request(&mut self, previous_nonce: String) -> E2eIdentityResult { let account = self.account.as_ref().ok_or(E2eIdentityError::ImplementationError)?; @@ -369,7 +380,7 @@ impl WireE2eIdentity { /// /// # Parameters /// * `finalize` - you got from [Self::finalize_response] - /// * `account` - you got from [Self::acme_new_account_response] + /// * `account` - you got from [Self::new_account_response] /// * `previous_nonce` - `replay-nonce` response header from `POST /acme/{provisioner-name}/order/{order-id}/finalize` pub fn certificate_request(&mut self, previous_nonce: String) -> E2eIdentityResult { let account = self.account.take().ok_or(E2eIdentityError::ImplementationError)?; @@ -379,25 +390,8 @@ impl WireE2eIdentity { Ok(certificate) } - async fn certificate_response( - self, - mls_central: &mut MlsCentral, - certificate_chain: String, - ) -> E2eIdentityResult<()> { - let certificate_chain = self.acme_x509_certificate_response(certificate_chain)?; - let private_key = SignaturePrivateKey { - value: self.sign_sk, - signature_scheme: self.ciphersuite.signature_algorithm(), - }; - - let cert_bundle = CertificateBundle { - certificate_chain, - private_key, - }; - // TODO - let identifier = ClientIdentifier::X509(HashMap::from([(self.ciphersuite, cert_bundle)])); - mls_central.mls_init(identifier, vec![self.ciphersuite]).await?; - Ok(()) + async fn certificate_response(self, certificate_chain: String) -> E2eIdentityResult>> { + Ok(self.acme_x509_certificate_response(certificate_chain)?) } } @@ -603,30 +597,30 @@ pub mod tests { let _certificate_req = enrollment.certificate_request(previous_nonce.to_string())?; let certificate_resp = r#"-----BEGIN CERTIFICATE----- -MIICLjCCAdSgAwIBAgIQIi6jHWSEF/LHAkiyoiSHbjAKBggqhkjOPQQDAjAuMQ0w +MIICIjCCAcigAwIBAgIQKRapc1IDZvJc88zB+vlrNTAKBggqhkjOPQQDAjAuMQ0w CwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3aXJlIEludGVybWVkaWF0ZSBDQTAeFw0y -MzA0MDUwOTI2NThaFw0yMzA0MDUxMDI2NThaMCkxETAPBgNVBAoTCHdpcmUuY29t -MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhAGzbFXHk2ngUGpBYzabE -AtDJIefbX1/wDUSDJbEL/nJNo4IBBjCCAQIwDgYDVR0PAQH/BAQDAgeAMB0GA1Ud -JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUhifYTPG7M3pyQMrz -HYmakvfDG80wHwYDVR0jBBgwFoAUHPSH1n7X87LAYJnc+cFG2a3ZAQ4wcgYDVR0R -BGswaYZQaW06d2lyZWFwcD1OamhsTXpJeE9XRmpPRFJpTkRBd1lqazBaR0ZoWkRB -Mk56RXhOVEV5TlRnLzZjMTg2NmY1Njc2MTZmMzFAd2lyZS5jb22GFWltOndpcmVh -cHA9YWxpY2Vfd2lyZTAdBgwrBgEEAYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYI -KoZIzj0EAwIDSAAwRQIhAKY0Zs8SYwS7mFFenPDoCDHPQbCbV9VdvYpBQncOFD5K -AiAisX68Di4B0dN059YsVDXpM0drnkrVTRKHV+F+ipDjZQ== +MzA2MDYxMjAzMDlaFw0zMzA2MDMxMjAzMDlaMCkxETAPBgNVBAoTCHdpcmUuY29t +MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhACqExBb1vLgMNq8GkLgM +R+W+dp0szvjYL2GybNkPKzoto4H7MIH4MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE +DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUaPHUDloFLv5o4j4J4EmvoYToqHcwHwYD +VR0jBBgwFoAUlbTj2u59dFDGs1LVj0GrGKJUK/gwcgYDVR0RBGswaYYVaW06d2ly +ZWFwcD1hbGljZV93aXJlhlBpbTp3aXJlYXBwPVl6QXpZalZoT1dRMFpqSXdOR0k1 +T1Rrek9HRTRPREptT1RjeE0yWm1PR00vNDk1OWJjNmFiMTJmMjg0NkB3aXJlLmNv +bTAdBgwrBgEEAYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYIKoZIzj0EAwIDSAAw +RQIhAIRaoCuyIAXtpAsUhZvJb7Qb+2EKsc9iIzHtsBU5MtVMAiAz2Tm4ojAolq4J +ZjWPVSDz4AN1gd200EpS50cS/mLDqw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIBtzCCAV6gAwIBAgIQPbElEJQ58HlbQf7bqrJjXTAKBggqhkjOPQQDAjAmMQ0w -CwYDVQQKEwR3aXJlMRUwEwYDVQQDEwx3aXJlIFJvb3QgQ0EwHhcNMjMwNDA1MDky -NjUzWhcNMzMwNDAyMDkyNjUzWjAuMQ0wCwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3 -aXJlIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGbM -rA1eqJE9xlGOwO+sYbexThtlU/to9jJj5SBoKPx7Q8QMBlmPTjqDVumXhUvSe+xY -JE7M+lBXfVZCywzIIPWjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG -AQH/AgEAMB0GA1UdDgQWBBQc9IfWftfzssBgmdz5wUbZrdkBDjAfBgNVHSMEGDAW -gBQY+1rDw64QLm/weFQC1mo9y29ddTAKBggqhkjOPQQDAgNHADBEAiARvd7RBuuv -OhUy7ncjd/nzoN5Qs0p6D+ujdSLDqLlNIAIgfkwAAgsQMDF3ClqVM/p9cmS95B0g -CAdIObqPoNL5MJo= +MIIBuTCCAV6gAwIBAgIQYiSIW2ebbC32Iq5YO0AyLDAKBggqhkjOPQQDAjAmMQ0w +CwYDVQQKEwR3aXJlMRUwEwYDVQQDEwx3aXJlIFJvb3QgQ0EwHhcNMjMwNjA2MTIw +MzA2WhcNMzMwNjAzMTIwMzA2WjAuMQ0wCwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3 +aXJlIEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEKu +1Ekx95MKKr9FxUspwFtyErShqoPKZNlyfz8u8lmvi50FpwqUXem1EoOUOm7UHy5m +HJO513uJY0Q/ecZUwAKjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG +AQH/AgEAMB0GA1UdDgQWBBSVtOPa7n10UMazUtWPQasYolQr+DAfBgNVHSMEGDAW +gBSy9uS81ABjfHbkz42x/Gf160mt1jAKBggqhkjOPQQDAgNJADBGAiEAq/T83XSg +7/GN+fUi79bzXI9oQdDuXqyhGnjIXtr2D8YCIQCuS1tZQm6lVcDZMWYQWLfv/b46 +GjWuPgx1fD4m+ar9Tw== -----END CERTIFICATE-----"#; cc.e2ei_mls_init(enrollment, certificate_resp.to_string()).await } diff --git a/crypto/src/error.rs b/crypto/src/error.rs index e70a39d76b..d419f27d41 100644 --- a/crypto/src/error.rs +++ b/crypto/src/error.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. +use crate::mls::conversation::config::MAX_PAST_EPOCHS; + /// CoreCrypto errors #[derive(Debug, thiserror::Error, strum::IntoStaticStr)] pub enum CryptoError { @@ -53,9 +55,6 @@ pub enum CryptoError { "Somehow CoreCrypto holds more than one MLS identity. Something might've gone very wrong with this client!" )] TooManyIdentitiesPresent, - /// The keystore has found the client, but the provided signature doesn't match against what is stored - #[error("The provided client signature doesn't match the keystore's")] - ClientSignatureMismatch, /// !!!! Something went very wrong and one of our locks has been poisoned by an in-thread panic !!!! #[error("One of the locks has been poisoned")] LockPoisonError, @@ -152,6 +151,9 @@ pub enum CryptoError { /// Parent group cannot be found #[error("The specified parent group has not been found in the keystore")] ParentGroupNotFound, + /// Message epoch is too old + #[error("The epoch in which message was encrypted is older than {MAX_PAST_EPOCHS}")] + MessageEpochTooOld, } /// A simpler definition for Result types that the Error is a [CryptoError] @@ -172,7 +174,7 @@ impl CryptoError { pub enum MlsError { /// Welcome error #[error(transparent)] - MlsWelcomeError(#[from] openmls::prelude::WelcomeError), + MlsWelcomeError(#[from] openmls::prelude::WelcomeError), /// Generic error type that indicates unrecoverable errors in the library. See [openmls::error::LibraryError] #[error(transparent)] MlsLibraryError(#[from] openmls::error::LibraryError), @@ -187,46 +189,44 @@ pub enum MlsError { MlsCredentialError(#[from] openmls::prelude::CredentialError), /// New group error #[error(transparent)] - MlsNewGroupError(#[from] openmls::prelude::NewGroupError), + MlsNewGroupError(#[from] openmls::prelude::NewGroupError), /// Add members error #[error(transparent)] - MlsAddMembersError(#[from] openmls::prelude::AddMembersError), + MlsAddMembersError(#[from] openmls::prelude::AddMembersError), /// Remove members error #[error(transparent)] - MlsRemoveMembersError(#[from] openmls::prelude::RemoveMembersError), - /// Unverified message error - #[error(transparent)] - MlsUnverifiedMessageError(#[from] openmls::prelude::UnverifiedMessageError), + MlsRemoveMembersError(#[from] openmls::prelude::RemoveMembersError), /// Parse message error #[error(transparent)] - MlsParseMessageError(#[from] openmls::prelude::ParseMessageError), + MlsMessageError(#[from] openmls::prelude::ProcessMessageError), /// [openmls::key_packages::KeyPackageBundle] new error #[error(transparent)] - MlsKeyPackageBundleNewError(#[from] openmls::prelude::KeyPackageBundleNewError), + MlsKeyPackageBundleNewError( + #[from] openmls::prelude::KeyPackageNewError, + ), /// Self update error #[error(transparent)] - MlsSelfUpdateError(#[from] openmls::prelude::SelfUpdateError), + MlsSelfUpdateError(#[from] openmls::prelude::SelfUpdateError), /// Group state error #[error(transparent)] MlsMlsGroupStateError(#[from] openmls::prelude::MlsGroupStateError), - /// MlsMessage error - #[error(transparent)] - MlsMessageError(#[from] openmls::framing::errors::MlsMessageError), /// Propose add members error #[error(transparent)] ProposeAddMemberError(#[from] openmls::prelude::ProposeAddMemberError), /// Propose self update error #[error(transparent)] - ProposeSelfUpdateError(#[from] openmls::prelude::ProposeSelfUpdateError), + ProposeSelfUpdateError(#[from] openmls::prelude::ProposeSelfUpdateError), /// Propose remove members error #[error(transparent)] ProposeRemoveMemberError(#[from] openmls::prelude::ProposeRemoveMemberError), /// Commit to pending proposals error #[error(transparent)] - MlsCommitToPendingProposalsError(#[from] openmls::prelude::CommitToPendingProposalsError), + MlsCommitToPendingProposalsError( + #[from] openmls::prelude::CommitToPendingProposalsError, + ), /// Export public group state error #[error(transparent)] - MlsExportPublicGroupStateError(#[from] openmls::prelude::ExportPublicGroupStateError), + MlsExportGroupInfoError(#[from] openmls::prelude::ExportGroupInfoError), /// Errors that are thrown by TLS serialization crate. #[error(transparent)] MlsTlsCodecError(#[from] tls_codec::Error), @@ -247,6 +247,20 @@ pub enum MlsError { /// OpenMls Export Secret error #[error(transparent)] MlsExportSecretError(#[from] openmls::prelude::ExportSecretError), + /// OpenMLS merge commit error + #[error(transparent)] + MlsMergeCommitError(#[from] openmls::prelude::MergeCommitError), + /// OpenMLS keypackage validation error + #[error(transparent)] + MlsKeyPackageValidationError(#[from] openmls::prelude::KeyPackageVerifyError), + /// OpenMLS Commit merge error + #[error(transparent)] + MlsMergePendingCommitError( + #[from] openmls::prelude::MergePendingCommitError, + ), + /// OpenMLS encrypt message error + #[error(transparent)] + MlsEncryptMessageError(#[from] openmls::framing::errors::MlsMessageError), } #[derive(Debug, thiserror::Error, strum::IntoStaticStr)] diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 0e49b9f1bc..c8cf4c275c 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -22,6 +22,8 @@ #![doc = include_str!("../../README.md")] #![deny(missing_docs)] #![allow(clippy::single_component_path_imports)] +// TODO: remove that and clean prelude when we got time +#![allow(ambiguous_glob_reexports)] #[cfg(test)] use rstest_reuse; @@ -52,17 +54,15 @@ mod group_store; /// Common imports that should be useful for most uses of the crate pub mod prelude { - pub use openmls::group::{MlsGroup, MlsGroupConfig}; - pub use openmls::prelude::Ciphersuite as CiphersuiteName; - pub use openmls::prelude::Credential; - pub use openmls::prelude::GroupEpoch; - pub use openmls::prelude::KeyPackage; - pub use openmls::prelude::KeyPackageRef; - pub use openmls::prelude::Node; - pub use openmls::prelude::VerifiablePublicGroupState; - pub use tls_codec; - - pub use mls_crypto_provider::{EntropySeed, RawEntropySeed}; + pub use openmls::{ + group::{MlsGroup, MlsGroupConfig}, + prelude::{ + group_info::VerifiableGroupInfo, Ciphersuite as CiphersuiteName, Credential, GroupEpoch, KeyPackage, + KeyPackageIn, KeyPackageRef, MlsMessageIn, Node, + }, + }; + + pub use mls_crypto_provider::{EntropySeed, MlsCryptoProvider, RawEntropySeed}; pub use crate::{ e2e_identity::{ @@ -80,16 +80,13 @@ pub mod prelude { conversation::{ config::{MlsConversationConfiguration, MlsCustomConfiguration, MlsWirePolicy}, decrypt::MlsConversationDecryptMessage, + group_info::{GroupInfoPayload, MlsGroupInfoBundle, MlsGroupInfoEncryptionType, MlsRatchetTreeType}, handshake::{MlsCommitBundle, MlsConversationCreationMessage, MlsProposalBundle}, - public_group_state::{ - MlsPublicGroupStateBundle, MlsPublicGroupStateEncryptionType, MlsRatchetTreeType, - PublicGroupStatePayload, - }, *, }, credential::{typ::MlsCredentialType, x509::CertificateBundle}, external_commit::MlsConversationInitBundle, - member::*, + member::{ConversationMember, MemberId}, proposal::{MlsProposal, MlsProposalRef}, MlsCentral, MlsCiphersuite, }, @@ -110,9 +107,9 @@ pub trait CoreCryptoCallbacks: std::fmt::Debug + Send + Sync { /// * `client_id` - id of the client to authorize async fn authorize(&self, conversation_id: prelude::ConversationId, client_id: prelude::ClientId) -> bool; /// Function responsible for authorizing an operation for a given user. - /// Use [external_client_id] & [existing_clients] to get all the 'client_id' belonging to the same user - /// as [external_client_id]. Then, given those client ids, verify that at least one has the right role - /// (is authorized) exactly like it's done in [authorize] + /// Use `external_client_id` & `existing_clients` to get all the 'client_id' belonging to the same user + /// as `external_client_id`. Then, given those client ids, verify that at least one has the right role + /// (is authorized) exactly like it's done in [Self::authorize] /// Returns `true` if the operation is authorized. /// /// # Arguments diff --git a/crypto/src/mls/client/identifier.rs b/crypto/src/mls/client/identifier.rs index 4cbe5b51f8..44b32ce592 100644 --- a/crypto/src/mls/client/identifier.rs +++ b/crypto/src/mls/client/identifier.rs @@ -1,6 +1,6 @@ +use super::CredentialBundle; use crate::prelude::{CertificateBundle, Client, ClientId, CryptoResult, MlsCiphersuite}; use mls_crypto_provider::MlsCryptoProvider; -use openmls::prelude::CredentialBundle; use std::collections::HashMap; /// Used by consumers to initializes a MLS client. Encompasses all the client types available. @@ -23,14 +23,15 @@ impl ClientIdentifier { // since ClientId has uniqueness constraints, it is the same for all certificates. // hence no need to compute it for every certificate then verify its uniqueness // that's not a getter's job - let cert = certs.values().find(|_| true).unwrap(); + let cert = certs.values().next().unwrap(); let id = cert.get_client_id()?; Ok(std::borrow::Cow::Owned(id)) } } } - /// TODO + /// Generate a new CredentialBundle (Credential + KeyPair) for each ciphersuite. + /// This method does not persist them in the keystore ! pub fn generate_credential_bundles( self, backend: &MlsCryptoProvider, @@ -46,10 +47,9 @@ impl ClientIdentifier { }, ), ClientIdentifier::X509(certs) => { - let size = certs.len(); certs .into_iter() - .try_fold(Vec::with_capacity(size), |mut acc, (cs, cert)| -> CryptoResult<_> { + .try_fold(vec![], |mut acc, (cs, cert)| -> CryptoResult<_> { let id = cert.get_client_id()?; let cb = Client::new_x509_credential_bundle(cert)?; acc.push((cs, id, cb)); diff --git a/crypto/src/mls/client/identities.rs b/crypto/src/mls/client/identities.rs index 7d01b38472..91f8e4e1e3 100644 --- a/crypto/src/mls/client/identities.rs +++ b/crypto/src/mls/client/identities.rs @@ -1,5 +1,5 @@ +use crate::mls::credential::CredentialBundle; use crate::{mls::credential::typ::MlsCredentialType, prelude::MlsCiphersuite, CryptoResult}; -use openmls::credentials::CredentialBundle; use std::collections::HashMap; use strum::EnumCount as _; @@ -24,9 +24,9 @@ impl ClientIdentities { ) -> Option<&CredentialBundle> { self.0.get(&cs)?.iter().find(|c| { matches!( - (ct, &c.credential().credential), - (MlsCredentialType::Basic, openmls::prelude::MlsCredentialType::Basic(_)) - | (MlsCredentialType::X509, openmls::prelude::MlsCredentialType::X509(_)) + (ct, &c.credential.credential_type()), + (MlsCredentialType::Basic, openmls::prelude::CredentialType::Basic) + | (MlsCredentialType::X509, openmls::prelude::CredentialType::X509) ) }) } diff --git a/crypto/src/mls/client/key_package.rs b/crypto/src/mls/client/key_package.rs index aab901fac9..0572e3b62c 100644 --- a/crypto/src/mls/client/key_package.rs +++ b/crypto/src/mls/client/key_package.rs @@ -14,31 +14,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use openmls::prelude::{CredentialBundle, KeyPackage}; -use openmls::{ - extensions::Extension, - prelude::{KeyPackageBundle, KeyPackageRef}, -}; -use openmls_traits::{ - key_store::{FromKeyStoreValue, OpenMlsKeyStore}, - OpenMlsCryptoProvider, -}; +use crate::mls::credential::CredentialBundle; +use openmls::prelude::{CredentialWithKey, CryptoConfig, KeyPackage, KeyPackageRef, Lifetime}; +use openmls_traits::OpenMlsCryptoProvider; use crate::{ mls::credential::typ::MlsCredentialType, prelude::{identities::ClientIdentities, Client, CryptoError, CryptoResult, MlsCiphersuite, MlsError}, }; -use core_crypto_keystore::{ - entities::{EntityFindParams, MlsKeypackage, StringEntityId}, - CryptoKeystoreError, -}; +use core_crypto_keystore::entities::{EntityFindParams, MlsKeyPackage, StringEntityId}; use mls_crypto_provider::MlsCryptoProvider; -/// +/// Default number of KeyPackages a client generates the first time it's created pub(crate) const INITIAL_KEYING_MATERIAL_COUNT: usize = 100; -/// -pub(crate) const KEYPACKAGE_DEFAULT_LIFETIME: std::time::Duration = std::time::Duration::from_secs(60 * 60 * 24 * 90); // 3 months +/// Default lifetime of all generated KeyPackages. Matches the limit defined in openmls +pub(crate) const KEYPACKAGE_DEFAULT_LIFETIME: std::time::Duration = + std::time::Duration::from_secs(60 * 60 * 24 * 28 * 3); // ~3 months impl Client { /// Generates a single new keypackage @@ -55,15 +47,25 @@ impl Client { .try_fold( Vec::with_capacity(ClientIdentities::MAX_DISTINCT_SIZE), |mut acc, (cs, cred)| async move { - let lifetime = Extension::LifeTime(openmls::prelude::LifetimeExtension::new( - self.keypackage_lifetime.as_secs(), - )); - let kpb = KeyPackageBundle::new(&[*cs], cred, backend, vec![lifetime]).map_err(MlsError::from)?; - - let href = kpb.key_package().hash_ref(backend.crypto()).map_err(MlsError::from)?; - backend.key_store().store(href.value(), &kpb).await?; + let keypackage = KeyPackage::builder() + .key_package_lifetime(Lifetime::new(self.keypackage_lifetime.as_secs())) + .build( + CryptoConfig { + ciphersuite: cs.into(), + version: openmls::versions::ProtocolVersion::default(), + }, + backend, + &cred.signature_key, + CredentialWithKey { + credential: cred.credential.clone(), + signature_key: cred.signature_key.public().into(), + }, + ) + .await + .map_err(MlsError::from)?; + + acc.push(keypackage); - acc.push(kpb.key_package().clone()); Ok(acc) }, ) @@ -100,14 +102,24 @@ impl Client { cb: &CredentialBundle, cs: MlsCiphersuite, ) -> CryptoResult { - let lifetime = Extension::LifeTime(openmls::prelude::LifetimeExtension::new( - self.keypackage_lifetime.as_secs(), - )); - let kpb = KeyPackageBundle::new(&[*cs], cb, backend, vec![lifetime]).map_err(MlsError::from)?; - - let href = kpb.key_package().hash_ref(backend.crypto()).map_err(MlsError::from)?; - backend.key_store().store(href.value(), &kpb).await?; - Ok(kpb.key_package().clone()) + let keypackage = KeyPackage::builder() + .key_package_lifetime(Lifetime::new(self.keypackage_lifetime.as_secs())) + .build( + CryptoConfig { + ciphersuite: cs.into(), + version: openmls::versions::ProtocolVersion::default(), + }, + backend, + &cb.signature_key, + CredentialWithKey { + credential: cb.credential.clone(), + signature_key: cb.signature_key.public().into(), + }, + ) + .await + .map_err(MlsError::from)?; + + Ok(keypackage) } /// Requests `count` keying material to be present and returns @@ -132,10 +144,9 @@ impl Client { let mut existing_kps = backend .key_store() - .mls_fetch_keypackage_bundles::(count as u32) + .mls_fetch_keypackages::(count as u32) .await? .into_iter() - .map(|kpb| kpb.key_package().clone()) // TODO: do this filtering in SQL when the schema is updated .filter(|kp| kp.ciphersuite() == ciphersuite.0) .collect::>(); @@ -174,19 +185,19 @@ impl Client { let keystore = backend.key_store(); let mut conn = keystore.borrow_conn().await?; - let kps = MlsKeypackage::find_all(&mut conn, EntityFindParams::default()).await?; + let kps = MlsKeyPackage::find_all(&mut conn, EntityFindParams::default()).await?; let valid_count = kps .into_iter() - .map(|kp| KeyPackageBundle::from_key_store_value(&kp.key).map_err(MlsError::from)) + .map(|kp| core_crypto_keystore::deser::(&kp.keypackage)) // TODO: do this filtering in SQL when the schema is updated - .filter(|kpb| { - kpb.as_ref() - .map(|b| b.key_package().ciphersuite() == ciphersuite.0) + .filter(|kp| { + kp.as_ref() + .map(|b| b.ciphersuite() == ciphersuite.0) .unwrap_or_default() }) - .try_fold(0usize, |mut valid_count, kpb| { - if !Self::is_mls_keypackage_expired(kpb?.key_package()) { + .try_fold(0usize, |mut valid_count, kp| { + if !Self::is_mls_keypackage_expired(&kp?) { valid_count += 1; } CryptoResult::Ok(valid_count) @@ -198,20 +209,11 @@ impl Client { /// Checks if a given OpenMLS [`KeyPackage`] is expired by looking through its extensions, /// finding a lifetime extension and checking if it's valid. fn is_mls_keypackage_expired(kp: &KeyPackage) -> bool { - kp.extensions() - .iter() - .find_map(|e| { - if let Extension::LifeTime(lifetime_ext) = e { - if !lifetime_ext.is_valid() { - Some(true) - } else { - None - } - } else { - None - } - }) - .unwrap_or_default() + let Some(lifetime) = kp.leaf_node().life_time() else { + return false; + }; + + !(lifetime.has_acceptable_range() && lifetime.is_valid()) } /// Prune the provided KeyPackageRefs from the keystore @@ -225,32 +227,25 @@ impl Client { let mut conn = keystore.borrow_conn().await?; - let kps = MlsKeypackage::find_all(&mut conn, EntityFindParams::default()).await?; + let kps = MlsKeyPackage::find_all(&mut conn, EntityFindParams::default()).await?; - let ids_to_delete = kps.into_iter().try_fold(Vec::new(), |mut acc, kp| { - let kpb = KeyPackageBundle::from_key_store_value(&kp.key).map_err(MlsError::from)?; - let mut is_expired = Self::is_mls_keypackage_expired(kpb.key_package()); + let ids_to_delete = kps.into_iter().try_fold(Vec::new(), |mut acc, store_kp| { + let kp = core_crypto_keystore::deser::(&store_kp.keypackage)?; + let mut is_expired = Self::is_mls_keypackage_expired(&kp); if !is_expired && !refs.is_empty() { - const HASH_REF_VALUE_LEN: usize = 16; - let href: [u8; HASH_REF_VALUE_LEN] = hex::decode(&kp.id) - .map_err(CryptoKeystoreError::from)? - .as_slice() - .try_into() - .map_err(CryptoKeystoreError::from)?; - let href = KeyPackageRef::from(href); - is_expired = refs.contains(&href); + is_expired = refs.iter().any(|r| r.as_slice() == store_kp.keypackage_ref); } if is_expired { - acc.push(kp.id.clone()); + acc.push(store_kp.keypackage_ref.clone()); } CryptoResult::Ok(acc) })?; - let entity_ids_to_delete: Vec = ids_to_delete.iter().map(|e| e.as_bytes().into()).collect(); + let entity_ids_to_delete: Vec = ids_to_delete.iter().map(|e| e.as_slice().into()).collect(); - MlsKeypackage::delete(&mut conn, &entity_ids_to_delete).await?; + MlsKeyPackage::delete(&mut conn, &entity_ids_to_delete).await?; Ok(()) } @@ -327,11 +322,9 @@ pub mod tests { } } - // #[apply(all_cred_cipher)] - // #[wasm_bindgen_test] - #[async_std::test] - pub async fn client_automatically_prunes_lifetime_expired_keypackages(/*case: TestCase*/) { - let case = TestCase::default(); + #[apply(all_cred_cipher)] + #[wasm_bindgen_test] + pub async fn client_automatically_prunes_lifetime_expired_keypackages(case: TestCase) { const UNEXPIRED_COUNT: usize = 125; const EXPIRED_COUNT: usize = 200; let backend = MlsCryptoProvider::try_new_in_memory("test").await.unwrap(); diff --git a/crypto/src/mls/client/mod.rs b/crypto/src/mls/client/mod.rs index 495649a3ea..72af12ad54 100644 --- a/crypto/src/mls/client/mod.rs +++ b/crypto/src/mls/client/mod.rs @@ -19,25 +19,33 @@ pub(crate) mod identifier; pub(crate) mod identities; pub(crate) mod key_package; -use openmls::credentials::CredentialBundle; -use openmls_traits::{ - key_store::{FromKeyStoreValue, OpenMlsKeyStore, ToKeyStoreValue}, - OpenMlsCryptoProvider, -}; - use crate::{ - mls::credential::ext::CredentialExt, + mls::credential::CredentialBundle, prelude::{ identifier::ClientIdentifier, key_package::{INITIAL_KEYING_MATERIAL_COUNT, KEYPACKAGE_DEFAULT_LIFETIME}, - ClientId, CryptoError, CryptoResult, MlsCiphersuite, MlsCredentialType, MlsError, + ClientId, CryptoError, CryptoResult, MlsCentral, MlsCiphersuite, MlsCredentialType, MlsError, }, }; -use core_crypto_keystore::entities::{EntityFindParams, MlsIdentity}; +use openmls::prelude::{Credential, CredentialType}; +use openmls_basic_credential::SignatureKeyPair; +use openmls_traits::{crypto::OpenMlsCrypto, OpenMlsCryptoProvider}; +use tls_codec::{Deserialize, Serialize}; + +use core_crypto_keystore::{ + entities::{EntityFindParams, MlsCredential, MlsSignatureKeyPair}, + CryptoKeystoreMls, +}; use futures_util::{StreamExt as _, TryStreamExt as _}; use identities::ClientIdentities; use mls_crypto_provider::MlsCryptoProvider; +impl MlsCentral { + pub(crate) fn mls_client(&self) -> CryptoResult<&Client> { + self.mls_client.as_ref().ok_or(CryptoError::MlsNotInitialized) + } +} + /// Represents a MLS client which in our case is the equivalent of a device. /// It can be the Android, iOS, web or desktop application which the authenticated user is using. /// A user has many client, a client has only one user. @@ -66,11 +74,10 @@ impl Client { ciphersuites: &[MlsCiphersuite], backend: &MlsCryptoProvider, ) -> CryptoResult { - use core_crypto_keystore::CryptoKeystoreMls as _; - let id = identifier.get_id()?; - let client = if let Some(signature) = backend.key_store().mls_load_identity_signature(&id.to_string()).await? { - match Self::load(id.as_ref(), &signature, ciphersuites, backend).await { + + let client = if let Some(credential) = backend.key_store().find::(id.as_slice()).await? { + match Self::load(id.as_ref(), &credential, ciphersuites, backend).await { Ok(client) => client, Err(CryptoError::ClientSignatureNotFound) => { Self::generate(identifier, backend, ciphersuites, true).await? @@ -96,38 +103,33 @@ impl Client { pub async fn generate_raw_keypairs( ciphersuites: &[MlsCiphersuite], backend: &MlsCryptoProvider, - ) -> CryptoResult>> { + ) -> CryptoResult> { const TEMP_KEY_SIZE: usize = 16; - //TODO: dehydrate & optimize - let identity_count = Self::fetch_basic_identities(backend).await?.len(); - if identity_count >= ciphersuites.len() { + let credentials = Self::find_all_basic_credentials(backend).await?; + if !credentials.is_empty() { return Err(CryptoError::IdentityAlreadyPresent); } - futures_util::stream::iter(ciphersuites) - .map(Ok::<_, CryptoError>) - .try_fold(Vec::with_capacity(ciphersuites.len()), |mut acc, &cs| async move { - use openmls_traits::random::OpenMlsRand as _; - // Here we generate a provisional, random, uuid-like random Client ID for no purpose other than database/store constraints - let provisional_client_id = backend.rand().random_vec(TEMP_KEY_SIZE)?.into(); - - let cb = Self::new_basic_credential_bundle(&provisional_client_id, cs, backend)?; - let signature = cb.keystore_key()?; - let identity = MlsIdentity { - id: provisional_client_id.to_string(), - ciphersuite: cs.into(), - credential_type: MlsCredentialType::Basic as u8, - signature: signature.clone(), - credential: cb.to_key_store_value().map_err(MlsError::from)?, - }; - backend.key_store().save(identity).await?; + use openmls_traits::random::OpenMlsRand as _; + // Here we generate a provisional, random, uuid-like random Client ID for no purpose other than database/store constraints + let mut tmp_client_ids = Vec::with_capacity(ciphersuites.len()); + for cs in ciphersuites { + let tmp_client_id: ClientId = backend.rand().random_vec(TEMP_KEY_SIZE)?.into(); + let cb = Self::new_basic_credential_bundle(&tmp_client_id, *cs, backend)?; + + let identity = MlsSignatureKeyPair { + signature_scheme: cs.signature_algorithm() as u16, + pk: cb.signature_key.to_public_vec(), + keypair: cb.signature_key.tls_serialize_detached().map_err(MlsError::from)?, + credential_id: tmp_client_id.clone().into(), + }; + backend.key_store().save(identity).await?; - acc.push(signature); + tmp_client_ids.push(tmp_client_id); + } - Ok(acc) - }) - .await + Ok(tmp_client_ids) } /// Finalizes initialization using a 2-step process of uploading first a public key and then associating a new Client ID to that keypair @@ -140,60 +142,68 @@ impl Client { /// **WARNING**: You have absolutely NO reason to call this if you didn't call [Client::generate_raw_keypairs] first. You have been warned! pub async fn init_with_external_client_id( client_id: ClientId, - signature_public_keys: Vec>, + tmp_ids: Vec, ciphersuites: &[MlsCiphersuite], backend: &MlsCryptoProvider, ) -> CryptoResult { - // Find all the identities, get the only one that exists (or bail), then insert the new one + delete the provisional one - let basic_store_identities = Self::fetch_basic_identities(backend).await?; + // Find all the keypairs, get the ones that exist (or bail), then insert new ones + delete the provisional ones + let stored_kp = backend + .key_store() + .find_all::(EntityFindParams::default()) + .await?; - match basic_store_identities.len() { - i if i < ciphersuites.len() => return Err(CryptoError::NoProvisionalIdentityFound), - i if i > ciphersuites.len() => return Err(CryptoError::TooManyIdentitiesPresent), - i if i != signature_public_keys.len() => return Err(CryptoError::ImplementationError), + match stored_kp.len() { + i if i < tmp_ids.len() => return Err(CryptoError::NoProvisionalIdentityFound), + i if i > tmp_ids.len() => return Err(CryptoError::TooManyIdentitiesPresent), _ => {} } - let prov_public_keys = ciphersuites.iter().zip(signature_public_keys.iter()); + // we verify that the supplied temporary ids are all present in the keypairs we have in store + let all_tmp_ids_exist = stored_kp + .iter() + .all(|kp| tmp_ids.contains(&kp.credential_id.as_slice().into())); + if !all_tmp_ids_exist { + return Err(CryptoError::NoProvisionalIdentityFound); + } - let identities = basic_store_identities.iter().zip(prov_public_keys); + let identities = stored_kp.iter().zip(ciphersuites); let client = Self { id: client_id.clone(), - identities: ClientIdentities::new(basic_store_identities.len()), + identities: ClientIdentities::new(stored_kp.len()), keypackage_lifetime: KEYPACKAGE_DEFAULT_LIFETIME, }; let id = &client_id; + // TODO: should we replace this with a for loop ? futures_util::stream::iter(identities) .map(Ok::<_, CryptoError>) - .try_fold(client, |mut acc, (provisional_identity, (&cs, prov_pk))| async move { - if prov_pk != &provisional_identity.signature { - return Err(CryptoError::ClientSignatureMismatch); - } - - // Now we restore the provisional credential from the store - let cb = - CredentialBundle::from_key_store_value(&provisional_identity.credential).map_err(MlsError::from)?; - - // Extract what's interesting from it - let (cred, sk) = cb.into_parts(); - let pk = cred.signature_key().as_slice(); - let kp = openmls::ciphersuite::signature::SignatureKeypair::from_bytes( - sk.signature_scheme, - sk.value, - pk.to_vec(), - ); + .try_fold(client, |mut acc, (tmp_kp, &cs)| async move { + let new_keypair = MlsSignatureKeyPair { + signature_scheme: tmp_kp.signature_scheme, + keypair: tmp_kp.keypair.clone(), + pk: tmp_kp.pk.clone(), + credential_id: id.clone().into(), + }; - // Then rebuild a proper credential with the new client ID - let cb = CredentialBundle::from_parts(id.0.clone(), kp); + let new_credential = MlsCredential { + id: id.clone().into(), + credential: tmp_kp.credential_id.clone(), + }; // Delete the old identity optimistically backend .key_store() - .delete::(provisional_identity.id.as_bytes()) + .remove::(&new_keypair.pk) .await?; + let signature_key = + SignatureKeyPair::tls_deserialize_bytes(&new_keypair.keypair).map_err(MlsError::from)?; + let cb = CredentialBundle { + credential: Credential::new_basic(new_credential.credential.clone()), + signature_key, + }; + // And now we save the new one Self::save_identity(backend, id, cs, &cb).await?; @@ -247,59 +257,66 @@ impl Client { /// Loads the client from the keystore. pub(crate) async fn load( id: &ClientId, - signature_public_key: &[u8], + credential: &MlsCredential, ciphersuites: &[MlsCiphersuite], backend: &MlsCryptoProvider, ) -> CryptoResult { - let identities = futures_util::stream::iter(ciphersuites) - .map(Ok::<_, CryptoError>) - .try_fold(ClientIdentities::new(ciphersuites.len()), |mut acc, &cs| async move { - // TODO: support many ciphersuites i.e. refactor the keystore - let identity = backend - .key_store() - .find::(id.to_string()) - .await? - .ok_or(CryptoError::ClientSignatureNotFound)?; - - if signature_public_key != identity.signature { - return Err(CryptoError::ClientSignatureMismatch); - } + let mls_credential = Credential::tls_deserialize_bytes(&credential.credential).map_err(MlsError::from)?; + let mut keypairs = ClientIdentities::new(ciphersuites.len()); + for cs in ciphersuites { + let keypair = if let Some(keypair) = backend + .key_store() + .mls_keypair_for_signature_scheme(&credential.id, cs.signature_algorithm()) + .await? + { + keypair + } else { + let (sk, pk) = backend + .crypto() + .signature_key_gen(cs.signature_algorithm()) + .map_err(MlsError::from)?; + let keypair = SignatureKeyPair::from_raw(cs.signature_algorithm(), sk, pk.clone()); + let store_keypair = MlsSignatureKeyPair { + signature_scheme: cs.signature_algorithm() as _, + keypair: keypair.tls_serialize_detached().map_err(MlsError::from)?, + pk, + credential_id: credential.id.clone(), + }; + backend.key_store().save(store_keypair.clone()).await?; + store_keypair + }; - let cb = CredentialBundle::from_key_store_value(&identity.credential).map_err(MlsError::from)?; + let raw_keypair = SignatureKeyPair::tls_deserialize_bytes(&keypair.keypair).map_err(MlsError::from)?; + let cb = CredentialBundle { + credential: mls_credential.clone(), + signature_key: raw_keypair, + }; - acc.push_credential_bundle(cs, cb)?; - Ok(acc) - }) - .await?; + keypairs.push_credential_bundle(*cs, cb)?; + } Ok(Self { id: id.clone(), - identities, + identities: keypairs, keypackage_lifetime: KEYPACKAGE_DEFAULT_LIFETIME, }) } - #[allow(dead_code)] - pub(crate) async fn load_credential_bundle( - &self, - signature_public_key: &[u8], - backend: &MlsCryptoProvider, - ) -> CryptoResult { - backend + async fn find_all_basic_credentials(backend: &MlsCryptoProvider) -> CryptoResult> { + let store_credentials = backend .key_store() - .read::(signature_public_key) - .await - .ok_or(CryptoError::ClientSignatureNotFound) - } + .find_all::(EntityFindParams::default()) + .await?; + let mut credentials = Vec::with_capacity(store_credentials.len()); + for store_credential in store_credentials.into_iter() { + let credential = Credential::tls_deserialize_bytes(&store_credential.credential).map_err(MlsError::from)?; + if !matches!(credential.credential_type(), CredentialType::Basic) { + continue; + } + credentials.push(credential); + } - async fn fetch_basic_identities(backend: &MlsCryptoProvider) -> CryptoResult> { - Ok(backend - .key_store() - .find_all::(EntityFindParams::default()) - .await? - .into_iter() - .filter(|i| i.credential_type == (MlsCredentialType::Basic as u8)) - .collect::>()) + Ok(credentials) } async fn save_identity( @@ -308,14 +325,26 @@ impl Client { cs: MlsCiphersuite, cb: &CredentialBundle, ) -> CryptoResult<()> { - let identity = MlsIdentity { - id: id.to_string(), - ciphersuite: cs.into(), - credential_type: cb.get_type() as u8, - signature: cb.keystore_key()?, - credential: cb.to_key_store_value().map_err(MlsError::from)?, - }; - Ok(backend.key_store().save(identity).await.map(|_| ())?) + if backend.key_store().find::(id.0.clone()).await?.is_none() { + backend + .key_store() + .save(MlsCredential { + id: id.clone().into(), + credential: cb.credential.tls_serialize_detached().map_err(MlsError::from)?, + }) + .await?; + } + + backend + .key_store() + .save(MlsSignatureKeyPair { + signature_scheme: cs.0.signature_algorithm() as _, + keypair: cb.signature_key.tls_serialize_detached().map_err(MlsError::from)?, + pk: cb.signature_key.to_public_vec(), + credential_id: id.clone().into(), + }) + .await?; + Ok(()) } /// Retrieves the client's client id. This is free-form and not inspected. @@ -345,15 +374,25 @@ impl Client { .find_credential_bundle(cs, MlsCredentialType::Basic) .is_none() { - let cb = Self::new_basic_credential_bundle(self.id(), cs, backend)?; - let identity = MlsIdentity { - id: self.id().to_string(), - ciphersuite: cs.into(), - credential_type: MlsCredentialType::Basic as u8, - signature: cb.keystore_key()?, - credential: cb.to_key_store_value().map_err(MlsError::from)?, - }; - backend.key_store().save(identity).await?; + let id = self.id(); + let cb = Self::new_basic_credential_bundle(id, cs, backend)?; + backend + .key_store() + .save(MlsCredential { + id: id.clone().into(), + credential: cb.credential.tls_serialize_detached().map_err(MlsError::from)?, + }) + .await?; + backend + .key_store() + .save(MlsSignatureKeyPair { + signature_scheme: cb.signature_key.signature_scheme() as _, + keypair: cb.signature_key.tls_serialize_detached().map_err(MlsError::from)?, + pk: cb.signature_key.to_public_vec(), + credential_id: id.clone().into(), + }) + .await?; + self.identities.push_credential_bundle(cs, cb)?; } Ok(()) @@ -406,23 +445,19 @@ impl Client { use core_crypto_keystore::CryptoKeystoreMls as _; let kps = backend .key_store() - .mls_fetch_keypackage_bundles::(u32::MAX) - .await? - .into_iter() - .try_fold(vec![], |mut acc, kpb| -> CryptoResult<_> { - acc.push(kpb.key_package().clone()); - Ok(acc) - })?; + .mls_fetch_keypackages::(u32::MAX) + .await?; Ok(kps) } } #[cfg(test)] pub mod tests { + use core_crypto_keystore::entities::{EntityFindParams, MlsSignatureKeyPair}; use wasm_bindgen_test::*; - use crate::prelude::MlsCredentialType; - use crate::{mls::credential::ext::CredentialExt, test_utils::*}; + use crate::prelude::{ClientId, MlsCredentialType}; + use crate::test_utils::*; use mls_crypto_provider::MlsCryptoProvider; use super::Client; @@ -444,13 +479,13 @@ pub mod tests { Box::pin(async move { let backend = MlsCryptoProvider::try_new(tmp_dir_argument, "test").await.unwrap(); // phase 1: generate standalone keypair - let keypair_sig_pk = Client::generate_raw_keypairs(&[case.ciphersuite()], &backend) + let handles = Client::generate_raw_keypairs(&[case.ciphersuite()], &backend) .await .unwrap(); - let mut identities: Vec = backend + let mut identities = backend .borrow_keystore() - .find_all(core_crypto_keystore::entities::EntityFindParams::default()) + .find_all::(EntityFindParams::default()) .await .unwrap(); @@ -460,13 +495,14 @@ pub mod tests { // Make sure we are actually returning the signature public key // TODO: test with multi-ciphersuite - assert_eq!(&prov_identity.signature, keypair_sig_pk.first().unwrap()); + let prov_client_id: ClientId = prov_identity.credential_id.as_slice().into(); + assert_eq!(&prov_client_id, handles.first().unwrap()); // phase 2: pretend we have a new client ID from the backend, and try to init the client this way - let client_id: super::ClientId = b"whatever:my:client:is@wire.com".to_vec().into(); + let client_id: ClientId = b"whatever:my:client:is@wire.com".to_vec().into(); let alice = Client::init_with_external_client_id( client_id.clone(), - keypair_sig_pk.clone(), + handles.clone(), &[case.ciphersuite()], &backend, ) @@ -475,13 +511,11 @@ pub mod tests { // Make sure both client id and PK are intact assert_eq!(alice.id(), &client_id); - let credentials = alice + let cb = alice .find_credential_bundle(case.ciphersuite(), case.credential_type) .unwrap(); - assert_eq!( - &credentials.keystore_key().unwrap().as_slice(), - keypair_sig_pk.first().unwrap() - ); + let client_id: ClientId = cb.credential().identity().into(); + assert_eq!(&client_id, handles.first().unwrap()); }) }) .await diff --git a/crypto/src/mls/conversation/commit_delay.rs b/crypto/src/mls/conversation/commit_delay.rs index 61a2ac683b..78564f0e19 100644 --- a/crypto/src/mls/conversation/commit_delay.rs +++ b/crypto/src/mls/conversation/commit_delay.rs @@ -1,7 +1,7 @@ -use openmls_traits::OpenMlsCryptoProvider; +use openmls::prelude::LeafNodeIndex; use super::MlsConversation; -use crate::{mls::MlsCryptoProvider, MlsError}; +use crate::MlsError; /// These constants intend to ramp up the delay and flatten the curve for later positions pub(self) const DELAY_RAMP_UP_MULTIPLIER: f32 = 120.0; @@ -15,11 +15,11 @@ impl MlsConversation { /// * `self_index` - ratchet tree index of self client /// * `epoch` - current group epoch /// * `nb_members` - number of clients in the group - pub fn compute_next_commit_delay(&self, backend: &MlsCryptoProvider) -> Option { - use openmls::{messages::proposals::Proposal, prelude::KeyPackageRef}; + pub fn compute_next_commit_delay(&self) -> Option { + use openmls::messages::proposals::Proposal; if self.group.pending_proposals().count() > 0 { - let removed_kprefs: Vec<&KeyPackageRef> = self + let removed_index = self .group .pending_proposals() .filter_map(|proposal| { @@ -29,16 +29,11 @@ impl MlsConversation { None } }) - .collect(); + .collect::>(); - let is_self_removed = if let Some(self_kpref) = self.group.key_package_ref() { - // Find a remove proposal that concerns us - removed_kprefs.iter().any(|kpref| *kpref == self_kpref) - } else { - // If we don't have a leaf node for the current client, MOST likely we've been removed from the group from a previous commit. - // So we shouldn't be committing anything - true - }; + let self_index = self.group.own_leaf_index(); + // Find a remove proposal that concerns us + let is_self_removed = removed_index.iter().any(|&i| i == self_index); // If our own client has been removed, don't commit if is_self_removed { @@ -46,16 +41,15 @@ impl MlsConversation { } let epoch = self.group.epoch().as_u64(); - let mut own_index = self.group.own_leaf_index() as u64; - let members = self.group.members(); + let mut own_index = self.group.own_leaf_index().u32() as u64; // Look for members that were removed at the left of our tree in order to shift our own leaf index (post-commit tree visualization) - let left_tree_diff = members - .iter() + let left_tree_diff = self + .group + .members() .take(own_index as usize) - .try_fold(0u32, |mut acc, keypackage| { - let hash_ref = keypackage.hash_ref(backend.crypto())?; - if removed_kprefs.contains(&&hash_ref) { + .try_fold(0u32, |mut acc, kp| { + if removed_index.contains(&kp.index) { acc += 1; } @@ -65,7 +59,7 @@ impl MlsConversation { .unwrap_or_default(); // Post-commit visualization of the number of members after remove proposals - let nb_members = (self.group.members().len() as u64).saturating_sub(removed_kprefs.len() as u64); + let nb_members = (self.group.members().count() as u64).saturating_sub(removed_index.len() as u64); // This shifts our own leaf index to the left (tree-wise) from as many as there was removed members that have a smaller leaf index than us (older members) own_index = own_index.saturating_sub(left_tree_diff as u64); @@ -195,7 +189,7 @@ pub mod tests { assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); bob_central - .process_welcome_message(bob_welcome.clone(), case.custom_cfg()) + .process_welcome_message(bob_welcome.clone().into(), case.custom_cfg()) .await .unwrap(); @@ -217,7 +211,7 @@ pub mod tests { .unwrap(); charlie_central - .process_welcome_message(charlie_welcome, case.custom_cfg()) + .process_welcome_message(charlie_welcome.into(), case.custom_cfg()) .await .unwrap(); diff --git a/crypto/src/mls/conversation/config.rs b/crypto/src/mls/conversation/config.rs index 9c9fa5c3c3..c9b2bb805e 100644 --- a/crypto/src/mls/conversation/config.rs +++ b/crypto/src/mls/conversation/config.rs @@ -20,13 +20,15 @@ //! when joining one by Welcome or external commit use openmls::prelude::{ - ExternalSender, SenderRatchetConfiguration, SignaturePublicKey, WireFormatPolicy, + Credential, ExternalSender, SenderRatchetConfiguration, SignaturePublicKey, WireFormatPolicy, PURE_CIPHERTEXT_WIRE_FORMAT_POLICY, PURE_PLAINTEXT_WIRE_FORMAT_POLICY, }; -use openmls_traits::types::SignatureScheme; use serde::{Deserialize, Serialize}; -use crate::{mls::MlsCiphersuite, CryptoError, CryptoResult, MlsError}; +use crate::{mls::MlsCiphersuite, CryptoResult}; + +/// Sets the config in OpenMls for the oldest possible epoch(past current) that a message can be decrypted +pub(crate) const MAX_PAST_EPOCHS: usize = 2; /// The configuration parameters for a group/conversation #[derive(Debug, Clone, Default)] @@ -49,9 +51,9 @@ impl MlsConversationConfiguration { pub fn as_openmls_default_configuration(&self) -> CryptoResult { Ok(openmls::group::MlsGroupConfig::builder() .wire_format_policy(self.custom.wire_policy.into()) - .max_past_epochs(3) + .max_past_epochs(MAX_PAST_EPOCHS) .padding_size(Self::PADDING_SIZE) - .number_of_resumtion_secrets(1) + .number_of_resumption_psks(1) .sender_ratchet_configuration(SenderRatchetConfiguration::new( self.custom.out_of_order_tolerance, self.custom.maximum_forward_distance, @@ -65,17 +67,15 @@ impl MlsConversationConfiguration { /// Note that this only works currently with Ed25519 keys and will have to be changed to accept /// other key schemes pub fn set_raw_external_senders(&mut self, external_senders: Vec>) { - let external_senders = external_senders - .iter() + self.external_senders = external_senders + .into_iter() .map(|key| { - SignaturePublicKey::new(key.clone(), SignatureScheme::ED25519) - .map_err(MlsError::from) - .map_err(CryptoError::from) + ExternalSender::new( + SignaturePublicKey::from(key), + Credential::new_basic(Self::WIRE_SERVER_IDENTITY.into()), + ) }) - .filter_map(|r: CryptoResult| r.ok()) - .map(|signature_key| ExternalSender::new_basic(Self::WIRE_SERVER_IDENTITY, signature_key)) .collect(); - self.external_senders = external_senders; } } diff --git a/crypto/src/mls/conversation/decrypt.rs b/crypto/src/mls/conversation/decrypt.rs index b9bd633df0..a1cdff991c 100644 --- a/crypto/src/mls/conversation/decrypt.rs +++ b/crypto/src/mls/conversation/decrypt.rs @@ -9,16 +9,19 @@ //! | 1+ pend. Proposal | ✅ | ✅ | use openmls::{ - framing::{errors::MessageDecryptionError, Sender}, - prelude::{MlsMessageIn, Node, ParseMessageError, ProcessedMessage, UnverifiedMessage, ValidationError}, + framing::errors::{MessageDecryptionError, SecretTreeError}, + prelude::{ + MlsMessageIn, MlsMessageInBody, ProcessMessageError, ProcessedMessage, ProcessedMessageContent, + ProtocolMessage, ValidationError, + }, }; -use openmls_traits::OpenMlsCryptoProvider; use mls_crypto_provider::MlsCryptoProvider; +use tls_codec::Deserialize; use crate::{ group_store::GroupStoreValue, - mls::{conversation::renew::Renew, ClientId, ConversationId, MlsCentral, MlsConversation}, + mls::{client::Client, conversation::renew::Renew, ClientId, ConversationId, MlsCentral, MlsConversation}, prelude::{MlsProposalBundle, WireIdentity}, CoreCryptoCallbacks, CryptoError, CryptoResult, MlsError, }; @@ -57,51 +60,27 @@ impl MlsConversation { &mut self, message: impl AsRef<[u8]>, parent_conversation: Option>, + client: &Client, backend: &MlsCryptoProvider, callbacks: Option<&dyn CoreCryptoCallbacks>, ) -> CryptoResult { - let msg_in = openmls::framing::MlsMessageIn::try_from_bytes(message.as_ref()).map_err(MlsError::from)?; + let msg_in = openmls::framing::MlsMessageIn::tls_deserialize_bytes(message.as_ref()).map_err(MlsError::from)?; - let parsed_message = self.parse_message(backend, msg_in)?; - let msg_epoch = parsed_message.epoch(); + let message = self.parse_message(backend, msg_in).await?; - let sender = match parsed_message.sender() { - Sender::Member(kpr) => Some(*kpr), - _ => None, - }; + let msg_epoch = message.epoch(); - let credential = parsed_message.credential().ok_or(CryptoError::ImplementationError)?; + let credential = message.credential(); let identity = Self::extract_identity(credential)?; let sender_client_id = credential.identity().into(); - let message = self - .group - .process_unverified_message(parsed_message, None, backend) - .await - .map_err(MlsError::from)?; - - let decrypted = match message { - ProcessedMessage::ApplicationMessage(app_msg) => { + let decrypted = match message.into_content() { + ProcessedMessageContent::ApplicationMessage(app_msg) => { if msg_epoch.as_u64() < self.group.epoch().as_u64() { return Err(CryptoError::WrongEpoch); } - // TODO: this should be guaranteed by openmls and removed when fixed on openmls side - if let Some(sender_kpr) = sender { - let is_valid = self.group.export_ratchet_tree().iter().any(|node| match node { - Some(Node::LeafNode(node)) => { - if node.key_package_ref() == Some(&sender_kpr) { - node.key_package().verify(backend).is_ok() - } else { - false - } - } - _ => false, - }); - if !is_valid { - return Err(CryptoError::InvalidKeyPackage); - } - } + MlsConversationDecryptMessage { app_msg: Some(app_msg.into_bytes()), proposals: vec![], @@ -112,60 +91,68 @@ impl MlsConversation { identity, } } - ProcessedMessage::ProposalMessage(proposal) => { - self.validate_external_proposal(&proposal, parent_conversation, callbacks, backend.crypto()) - .await?; + ProcessedMessageContent::ProposalMessage(proposal) => { self.group.store_pending_proposal(*proposal); - MlsConversationDecryptMessage { app_msg: None, proposals: vec![], is_active: true, - delay: self.compute_next_commit_delay(backend), + delay: self.compute_next_commit_delay(), sender_client_id: None, has_epoch_changed: false, identity, } } - ProcessedMessage::StagedCommitMessage(staged_commit) => { - let valid_commit = staged_commit.clone(); - self.validate_external_commit( - &valid_commit, - sender_client_id, - parent_conversation, - callbacks, - backend.crypto(), - ) - .await?; + ProcessedMessageContent::StagedCommitMessage(staged_commit) => { + self.validate_external_commit(&staged_commit, sender_client_id, parent_conversation, callbacks) + .await?; - let pending_commit = self.group.pending_commit().cloned(); #[allow(clippy::needless_collect)] // false positive let pending_proposals = self.self_pending_proposals().cloned().collect::>(); - let self_kpr = self.group.key_package_ref().cloned(); + // getting the pending has to be done before `merge_staged_commit` otherwise it's wiped out + let pending_commit = self.group.pending_commit().cloned(); - self.group.merge_staged_commit(*staged_commit).map_err(MlsError::from)?; + self.group + .merge_staged_commit(backend, *staged_commit.clone()) + .await + .map_err(MlsError::from)?; let (proposals, update_self) = Renew::renew( - self_kpr, + Some(self.group.own_leaf_index()), pending_proposals.into_iter(), pending_commit.as_ref(), - valid_commit.as_ref(), + staged_commit.as_ref(), ); let proposals = self - .renew_proposals_for_current_epoch(backend, proposals.into_iter(), update_self) + .renew_proposals_for_current_epoch(client, backend, proposals.into_iter(), update_self) .await?; MlsConversationDecryptMessage { app_msg: None, proposals, is_active: self.group.is_active(), - delay: self.compute_next_commit_delay(backend), + delay: self.compute_next_commit_delay(), sender_client_id: None, has_epoch_changed: true, identity, } } + ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => { + self.validate_external_proposal(&proposal, parent_conversation, callbacks) + .await?; + self.group.store_pending_proposal(*proposal); + + MlsConversationDecryptMessage { + app_msg: None, + proposals: vec![], + is_active: true, + delay: self.compute_next_commit_delay(), + sender_client_id: None, + has_epoch_changed: false, + identity, + } + } }; self.persist_group_when_changed(backend, false).await?; @@ -173,17 +160,36 @@ impl MlsConversation { Ok(decrypted) } - fn parse_message(&mut self, backend: &MlsCryptoProvider, msg_in: MlsMessageIn) -> CryptoResult { - self.group.parse_message(msg_in, backend).map_err(|e| match e { - ParseMessageError::ValidationError(ValidationError::UnableToDecrypt( - MessageDecryptionError::GenerationOutOfBound, - )) => CryptoError::GenerationOutOfBound, - ParseMessageError::ValidationError(ValidationError::WrongEpoch) => CryptoError::WrongEpoch, - ParseMessageError::ValidationError(ValidationError::UnableToDecrypt(MessageDecryptionError::AeadError)) => { - CryptoError::DecryptionError + async fn parse_message( + &mut self, + backend: &MlsCryptoProvider, + msg_in: MlsMessageIn, + ) -> CryptoResult { + let protocol_message = match msg_in.extract() { + MlsMessageInBody::PublicMessage(m) => ProtocolMessage::PublicMessage(m), + MlsMessageInBody::PrivateMessage(m) => ProtocolMessage::PrivateMessage(m), + _ => { + return Err(CryptoError::MlsError( + ProcessMessageError::IncompatibleWireFormat.into(), + )) } - _ => CryptoError::from(MlsError::from(e)), - }) + }; + self.group + .process_message(backend, protocol_message) + .await + .map_err(|e| match e { + ProcessMessageError::ValidationError(ValidationError::UnableToDecrypt( + MessageDecryptionError::GenerationOutOfBound, + )) => CryptoError::GenerationOutOfBound, + ProcessMessageError::ValidationError(ValidationError::WrongEpoch) => CryptoError::WrongEpoch, + ProcessMessageError::ValidationError(ValidationError::UnableToDecrypt( + MessageDecryptionError::AeadError, + )) => CryptoError::DecryptionError, + ProcessMessageError::ValidationError(ValidationError::UnableToDecrypt( + MessageDecryptionError::SecretTreeError(SecretTreeError::TooDistantInThePast), + )) => CryptoError::MessageEpochTooOld, + _ => CryptoError::from(MlsError::from(e)), + }) } } @@ -217,17 +223,14 @@ impl MlsCentral { .decrypt_message( message.as_ref(), parent_conversation, + self.mls_client()?, &self.mls_backend, self.callbacks.as_ref().map(|boxed| boxed.as_ref()), ) .await?; if !decrypt_message.is_active { - // ? Do we wipe conversations or do we just prune the group from the cache - subsequent accesses will make it look like the group doesn't exist self.wipe_conversation(conversation_id).await?; - // self.mls_groups - // .remove(conversation_id) - // .ok_or(CryptoError::ImplementationError)?; } Ok(decrypt_message) } @@ -237,10 +240,11 @@ impl MlsCentral { pub mod tests { use super::*; use crate::{ - prelude::{handshake::MlsCommitBundle, MlsProposal, MlsWirePolicy}, - test_utils::*, + prelude::{handshake::MlsCommitBundle, MemberId, MlsProposal, MlsWirePolicy}, + test_utils::{ValidationCallbacks, *}, + CryptoError, }; - use openmls::{framing::errors::MlsMessageError, prelude::KeyPackageRef}; + use openmls::prelude::KeyPackageRef; use std::time::Duration; use wasm_bindgen_test::*; @@ -315,7 +319,6 @@ pub mod tests { pub mod commit { use super::*; - use crate::prelude::MemberId; #[apply(all_cred_cipher)] #[wasm_bindgen_test] @@ -440,7 +443,7 @@ pub mod tests { let bob_commit = bob_central.update_keying_material(&id).await.unwrap().commit; bob_central.commit_accepted(&id).await.unwrap(); - let commit_epoch = bob_commit.epoch(); + // let commit_epoch = bob_commit.epoch(); // Alice propose to add Charlie alice_central @@ -468,10 +471,10 @@ pub mod tests { assert!(!proposals.is_empty()); assert_eq!(alice_central.pending_proposals(&id).await.len(), 1); assert!(alice_central.pending_commit(&id).await.is_none()); - assert_eq!( - commit_epoch.as_u64() + 1, - proposals.first().unwrap().proposal.epoch().as_u64() - ); + // assert_eq!( + // commit_epoch.as_u64() + 1, + // proposals.first().unwrap().proposal.epoch().as_u64() + // ); // Let's commit this proposal to see if it works for p in proposals { @@ -508,7 +511,7 @@ pub mod tests { // Charlie can join with the Welcome from renewed Add proposal let id = charlie_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); assert!(charlie_central.try_talk_to(&id, &mut alice_central).await.is_ok()); @@ -595,7 +598,7 @@ pub mod tests { // Then Alice will renew her proposal let bob_commit = bob_central.update_keying_material(&id).await.unwrap().commit; bob_central.commit_accepted(&id).await.unwrap(); - let commit_epoch = bob_commit.epoch(); + let commit_epoch = bob_commit.epoch().unwrap(); // Alice propose to add Charlie let charlie_kp = charlie_central.get_one_key_package(&case).await; @@ -624,7 +627,10 @@ pub mod tests { assert!(!proposals.is_empty()); assert_eq!(alice_central.pending_proposals(&id).await.len(), 1); let renewed_proposal = proposals.first().unwrap(); - assert_eq!(commit_epoch.as_u64() + 1, renewed_proposal.proposal.epoch().as_u64()); + assert_eq!( + commit_epoch.as_u64() + 1, + renewed_proposal.proposal.epoch().unwrap().as_u64() + ); // Let's use this proposal to see if it works bob_central @@ -746,14 +752,12 @@ pub mod tests { } } - pub mod decrypt_callback { - use crate::{test_utils::ValidationCallbacks, CryptoError}; - + pub mod external_proposal { use super::*; #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn can_decrypt_proposal(case: TestCase) { + pub async fn can_decrypt_external_proposal(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "alice2"], @@ -1076,9 +1080,9 @@ pub mod tests { // Now Bob will rejoin the group and try to decrypt Alice's message // in epoch 2 which should fail - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; bob_central - .join_by_external_commit(pgs, case.custom_cfg(), case.credential_type) + .join_by_external_commit(gi.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); bob_central.merge_pending_group_from_external_commit(&id).await.unwrap(); @@ -1092,46 +1096,6 @@ pub mod tests { .await } - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - pub async fn cannot_decrypt_app_message_after_epoch_change(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let id = conversation_id(); - alice_central - .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) - .await - .unwrap(); - alice_central - .invite(&id, &mut bob_central, case.custom_cfg()) - .await - .unwrap(); - - // encrypt a message in epoch 1 - let msg = b"Hello bob"; - let encrypted = alice_central.encrypt_message(&id, msg).await.unwrap(); - - // Now Bob will rejoin the group and try to decrypt Alice's message - // in epoch 2 which should fail - let commit = alice_central.update_keying_material(&id).await.unwrap().commit; - alice_central.commit_accepted(&id).await.unwrap(); - bob_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - - // fails because of Forward Secrecy - let decrypt = bob_central.decrypt_message(&id, &encrypted).await; - assert!(matches!(decrypt.unwrap_err(), CryptoError::WrongEpoch)); - }) - }, - ) - .await - } - #[apply(all_cred_cipher)] #[wasm_bindgen_test] pub async fn cannot_decrypt_app_message_from_future_epoch(case: TestCase) { @@ -1261,8 +1225,62 @@ pub mod tests { } pub mod epoch_sync { + use crate::mls::conversation::config::MAX_PAST_EPOCHS; + use super::*; + #[apply(all_cred_cipher)] + #[wasm_bindgen_test] + pub async fn should_throw_specialized_error_when_epoch_too_old(mut case: TestCase) { + case.cfg.custom.out_of_order_tolerance = 0; + run_test_with_client_ids( + case.clone(), + ["alice", "bob"], + move |[mut alice_central, mut bob_central]| { + Box::pin(async move { + let id = conversation_id(); + alice_central + .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) + .await + .unwrap(); + alice_central + .invite(&id, &mut bob_central, case.custom_cfg()) + .await + .unwrap(); + + // Alice encrypts a message to Bob + let bob_message1 = alice_central.encrypt_message(&id, b"Hello Bob").await.unwrap(); + let bob_message2 = alice_central.encrypt_message(&id, b"Hello again Bob").await.unwrap(); + + // Move group's epoch forward by self updating + for _ in 0..MAX_PAST_EPOCHS { + let commit = alice_central.update_keying_material(&id).await.unwrap().commit; + alice_central.commit_accepted(&id).await.unwrap(); + bob_central + .decrypt_message(&id, commit.to_bytes().unwrap()) + .await + .unwrap(); + } + // Decrypt should work + let decrypt = bob_central.decrypt_message(&id, &bob_message1).await.unwrap(); + assert_eq!(decrypt.app_msg.unwrap(), b"Hello Bob"); + + // Moving the epochs once more should cause an error + let commit = alice_central.update_keying_material(&id).await.unwrap().commit; + alice_central.commit_accepted(&id).await.unwrap(); + bob_central + .decrypt_message(&id, commit.to_bytes().unwrap()) + .await + .unwrap(); + + let decrypt = bob_central.decrypt_message(&id, &bob_message2).await; + assert!(matches!(decrypt.unwrap_err(), CryptoError::MessageEpochTooOld)); + }) + }, + ) + .await + } + #[apply(all_cred_cipher)] #[wasm_bindgen_test] pub async fn should_throw_specialized_error_when_epoch_desynchronized(mut case: TestCase) { @@ -1283,8 +1301,6 @@ pub mod tests { .unwrap(); // Alice generates a bunch of soon to be outdated messages - let msg = b"Hello bob"; - let old_app_msg = alice_central.encrypt_message(&id, msg).await.unwrap(); let old_proposal = alice_central .new_proposal(&id, MlsProposal::Update) .await @@ -1305,7 +1321,7 @@ pub mod tests { .to_bytes() .unwrap(); alice_central.clear_pending_commit(&id).await.unwrap(); - let outdated_messages = vec![old_app_msg, old_proposal, old_commit]; + let outdated_messages = vec![old_proposal, old_commit]; // Now let's jump to next epoch let commit = alice_central.update_keying_material(&id).await.unwrap().commit; @@ -1328,108 +1344,117 @@ pub mod tests { } pub mod expired { + use crate::prelude::MlsCredentialType; + use openmls::prelude::ProcessMessageError; + use openmls_traits::OpenMlsCryptoProvider; + use super::*; #[apply(all_cred_cipher)] #[wasm_bindgen_test] + #[ignore] pub async fn should_fail_when_message_signed_by_expired_key_package(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["alice", "bob"], - move |[mut alice_central, mut bob_central]| { - Box::pin(async move { - let id = conversation_id(); - alice_central - .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) - .await - .unwrap(); + if matches!(case.credential_type, MlsCredentialType::X509) { + run_test_with_client_ids( + case.clone(), + ["alice", "bob"], + move |[mut alice_central, mut bob_central]| { + Box::pin(async move { + let id = conversation_id(); + alice_central + .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) + .await + .unwrap(); - // Bob will have generated a bunch of long to expire KeyPackage, no suitable for a test. - // So we will prune all his KeyPackages and replace them by shorter ones - let bob_client = bob_central.mls_client.as_mut().unwrap(); - let bob_kps = bob_client.find_keypackages(&bob_central.mls_backend).await.unwrap(); - let bob_kp_refs = bob_kps - .iter() - .map(|k| k.hash_ref(bob_central.mls_backend.crypto()).unwrap()) - .collect::>(); - bob_client - .prune_keypackages(&bob_kp_refs, &bob_central.mls_backend) - .await - .unwrap(); - let bob_nb_kps = bob_client - .valid_keypackages_count(&bob_central.mls_backend, case.ciphersuite()) - .await - .unwrap(); - // Alright Bob does not have any KeyPackage - assert_eq!(bob_nb_kps, 0); + // Bob will have generated a bunch of long to expire KeyPackage, no suitable for a test. + // So we will prune all his KeyPackages and replace them by shorter ones + let bob_client = bob_central.mls_client.as_mut().unwrap(); + let bob_kps = bob_client.find_keypackages(&bob_central.mls_backend).await.unwrap(); + let bob_kp_refs = bob_kps + .iter() + .map(|k| k.hash_ref(bob_central.mls_backend.crypto()).unwrap()) + .collect::>(); + bob_client + .prune_keypackages(&bob_kp_refs, &bob_central.mls_backend) + .await + .unwrap(); + let bob_nb_kps = bob_client + .valid_keypackages_count(&bob_central.mls_backend, case.ciphersuite()) + .await + .unwrap(); + // Alright Bob does not have any KeyPackage + assert_eq!(bob_nb_kps, 0); - bob_client.set_keypackage_lifetime(Duration::from_secs(2)); + bob_client.set_keypackage_lifetime(Duration::from_secs(2)); - // Now Bob will have shorter KeyPackages. Let's add Bob to the group before those expire - alice_central - .invite(&id, &mut bob_central, case.custom_cfg()) - .await - .unwrap(); + // Now Bob will have shorter KeyPackages. Let's add Bob to the group before those expire + alice_central + .invite(&id, &mut bob_central, case.custom_cfg()) + .await + .unwrap(); - // Now Bob will generate AND SIGN some messages with a signature key - // in his soon to expire KeyPackage - let msg = b"Hello alice"; - let expired_app_msg = bob_central.encrypt_message(&id, msg).await.unwrap(); - let expired_proposal = bob_central - .new_proposal(&id, MlsProposal::Update) - .await - .unwrap() - .proposal - .to_bytes() - .unwrap(); - bob_central - .get_conversation_unchecked(&id) - .await - .group - .clear_pending_proposals(); - let expired_commit = bob_central - .update_keying_material(&id) - .await - .unwrap() - .commit - .to_bytes() - .unwrap(); - bob_central.clear_pending_commit(&id).await.unwrap(); - let expired_handshakes = vec![expired_proposal, expired_commit]; - - // Sleep to trigger the expiration - async_std::task::sleep(Duration::from_secs(5)).await; - - // Expired handshake messages should fail - for expired_handshake in expired_handshakes { - let decrypted = alice_central.decrypt_message(&id, expired_handshake).await; - if case.custom_cfg().wire_policy == MlsWirePolicy::Ciphertext { - // Cannot return a precise error here this this could fail for so many reasons - assert!(matches!( - decrypted.unwrap_err(), - CryptoError::MlsError(MlsError::MlsParseMessageError( - ParseMessageError::ValidationError(ValidationError::UnableToDecrypt( - MessageDecryptionError::MalformedContent + // Now Bob will generate AND SIGN some messages with a signature key + // in his soon to expire KeyPackage + let msg = b"Hello alice"; + let expired_app_msg = bob_central.encrypt_message(&id, msg).await.unwrap(); + let expired_proposal = bob_central + .new_proposal(&id, MlsProposal::Update) + .await + .unwrap() + .proposal + .to_bytes() + .unwrap(); + bob_central + .get_conversation_unchecked(&id) + .await + .group + .clear_pending_proposals(); + let expired_commit = bob_central + .update_keying_material(&id) + .await + .unwrap() + .commit + .to_bytes() + .unwrap(); + bob_central.clear_pending_commit(&id).await.unwrap(); + let expired_handshakes = vec![expired_proposal, expired_commit]; + + // Sleep to trigger the expiration + async_std::task::sleep(Duration::from_secs(5)).await; + + // Expired handshake messages should fail + for expired_handshake in expired_handshakes { + let decrypted = alice_central.decrypt_message(&id, expired_handshake).await; + if case.custom_cfg().wire_policy == MlsWirePolicy::Ciphertext { + // Cannot return a precise error here this this could fail for so many reasons + assert!(matches!( + decrypted.unwrap_err(), + CryptoError::MlsError(MlsError::MlsMessageError( + ProcessMessageError::ValidationError(ValidationError::UnableToDecrypt( + MessageDecryptionError::MalformedContent + )) )) - )) - )); - } else { - // Unfortunately this errors cannot be pattern matched because KeyPackage - // expiry validation happens when TLS decoding - assert!(matches!( - decrypted.unwrap_err(), - CryptoError::MlsError(MlsError::MlsMessageError(MlsMessageError::UnableToDecode)) - )); + )); + } else { + // Unfortunately this errors cannot be pattern matched because KeyPackage + // expiry validation happens when TLS decoding + assert!(matches!( + decrypted.unwrap_err(), + CryptoError::MlsError(MlsError::MlsMessageError( + ProcessMessageError::ValidationError(ValidationError::UnableToDecrypt(_)) + )) + )); + } } - } - // So is expired application message - let decrypted = alice_central.decrypt_message(&id, expired_app_msg).await; - assert!(matches!(decrypted.unwrap_err(), CryptoError::InvalidKeyPackage)); - }) - }, - ) - .await + // So is expired application message + let decrypted = alice_central.decrypt_message(&id, expired_app_msg).await; + assert!(matches!(decrypted.unwrap_err(), CryptoError::InvalidKeyPackage)); + }) + }, + ) + .await + } } } } diff --git a/crypto/src/mls/conversation/encrypt.rs b/crypto/src/mls/conversation/encrypt.rs index ad6cb2e0a4..abe0da1967 100644 --- a/crypto/src/mls/conversation/encrypt.rs +++ b/crypto/src/mls/conversation/encrypt.rs @@ -9,6 +9,7 @@ use mls_crypto_provider::MlsCryptoProvider; +use crate::prelude::Client; use crate::{mls::ConversationId, mls::MlsCentral, CryptoResult, MlsError}; use super::MlsConversation; @@ -20,13 +21,16 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub async fn encrypt_message( &mut self, + client: &Client, message: impl AsRef<[u8]>, backend: &MlsCryptoProvider, ) -> CryptoResult> { + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; let encrypted = self .group - .create_message(backend, message.as_ref()) - .await + .create_message(backend, signer, message.as_ref()) .map_err(MlsError::from) .and_then(|m| m.to_bytes().map_err(MlsError::from))?; self.persist_group_when_changed(backend, false).await?; @@ -56,7 +60,7 @@ impl MlsCentral { .await? .write() .await - .encrypt_message(message, &self.mls_backend) + .encrypt_message(self.mls_client()?, message, &self.mls_backend) .await } } diff --git a/crypto/src/mls/conversation/export.rs b/crypto/src/mls/conversation/export.rs index aaf6f00e65..493d7bd96e 100644 --- a/crypto/src/mls/conversation/export.rs +++ b/crypto/src/mls/conversation/export.rs @@ -39,8 +39,7 @@ impl MlsConversation { pub fn get_client_ids(&self) -> Vec { self.group .members() - .iter() - .map(|kp| ClientId::from(kp.credential().identity())) + .map(|kp| ClientId::from(kp.credential.identity())) .collect() } } diff --git a/crypto/src/mls/conversation/group_info.rs b/crypto/src/mls/conversation/group_info.rs new file mode 100644 index 0000000000..4436cb7a59 --- /dev/null +++ b/crypto/src/mls/conversation/group_info.rs @@ -0,0 +1,105 @@ +use openmls::prelude::{group_info::GroupInfo, MlsMessageOut}; +use serde::{Deserialize, Serialize}; + +use crate::{CryptoResult, MlsError}; + +/// A [GroupInfo] with metadata +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MlsGroupInfoBundle { + /// Indicates if the [payload] is encrypted or not + pub encryption_type: MlsGroupInfoEncryptionType, + /// Indicates if the [payload] contains a full, partial or referenced [GroupInfo] + pub ratchet_tree_type: MlsRatchetTreeType, + /// The [GroupInfo] + pub payload: GroupInfoPayload, +} + +impl MlsGroupInfoBundle { + /// Creates a new [GroupInfoBundle] with complete and unencrypted [GroupInfo] + pub(crate) fn try_new_full_plaintext(gi: GroupInfo) -> CryptoResult { + use tls_codec::Serialize as _; + + let payload = MlsMessageOut::from(gi); + let payload = payload.tls_serialize_detached().map_err(MlsError::from)?; + Ok(Self { + encryption_type: MlsGroupInfoEncryptionType::Plaintext, + ratchet_tree_type: MlsRatchetTreeType::Full, + payload: GroupInfoPayload::Plaintext(payload), + }) + } +} + +#[cfg(test)] +impl MlsGroupInfoBundle { + pub fn get_payload(mut self) -> openmls::prelude::MlsMessageIn { + use tls_codec::Deserialize as _; + match &mut self.payload { + GroupInfoPayload::Plaintext(gi) => { + openmls::prelude::MlsMessageIn::tls_deserialize_bytes(gi.as_slice()).unwrap() + } + } + } +} + +/// # GroupInfoEncryptionType +/// +/// In order to guarantee confidentiality of the [GroupInfo] on the wire a domain can +/// request it to be encrypted when sent to the Delivery Service. +/// +/// ```text +/// enum { +/// plaintext(1), +/// jwe_encrypted(2), +/// (255) +/// } GroupInfoEncryptionType; +/// ``` +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[repr(u8)] +pub enum MlsGroupInfoEncryptionType { + /// Unencrypted [GroupInfo] + Plaintext = 1, + /// [GroupInfo] encrypted in a JWE + JweEncrypted = 2, +} + +/// # RatchetTreeType +/// +/// In order to spare some precious bytes, a [GroupInfo] can have different representations. +/// +/// ```text +/// enum { +/// full(1), +/// delta(2), +/// by_ref(3), +/// (255) +/// } RatchetTreeType; +/// ``` +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[repr(u8)] +pub enum MlsRatchetTreeType { + /// Plain old and complete [GroupInfo] + Full = 1, + /// Contains [GroupInfo] changes since previous epoch (not yet implemented) + /// (see [draft](https://github.com/rohan-wire/ietf-drafts/blob/main/mahy-mls-ratchet-tree-delta/draft-mahy-mls-ratchet-tree-delta.md)) + Delta = 2, + /// TODO: to define + ByRef = 3, +} + +/// Represents the byte array in [GroupInfoBundle] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum GroupInfoPayload { + /// Unencrypted [GroupInfo] + Plaintext(Vec), + // TODO: expose when fully implemented + // Encrypted(Vec), +} + +impl GroupInfoPayload { + /// Returns the internal byte array + pub fn bytes(self) -> Vec { + match self { + GroupInfoPayload::Plaintext(gi) => gi, + } + } +} diff --git a/crypto/src/mls/conversation/handshake.rs b/crypto/src/mls/conversation/handshake.rs index ae430d769a..c70b993dfb 100644 --- a/crypto/src/mls/conversation/handshake.rs +++ b/crypto/src/mls/conversation/handshake.rs @@ -8,16 +8,13 @@ //! | 0 pend. Proposal | ✅ | ❌ | //! | 1+ pend. Proposal | ✅ | ❌ | -use openmls::prelude::{KeyPackage, KeyPackageRef, MlsMessageOut, Welcome}; -use openmls_traits::OpenMlsCryptoProvider; +use openmls::prelude::{KeyPackage, LeafNodeIndex, MlsMessageOut}; use mls_crypto_provider::MlsCryptoProvider; -use crate::mls::conversation::public_group_state::MlsPublicGroupStateBundle; -use crate::prelude::MlsProposalRef; -use crate::{ - mls::member::ConversationMember, mls::ClientId, mls::ConversationId, mls::MlsCentral, CryptoError, CryptoResult, - MlsError, +use crate::prelude::{ + Client, ClientId, ConversationId, ConversationMember, CryptoError, CryptoResult, MlsCentral, MlsError, + MlsGroupInfoBundle, MlsProposalRef, }; use super::MlsConversation; @@ -46,7 +43,7 @@ impl MlsProposalBundle { /// 1 -> proposal reference pub fn to_bytes_pair(&self) -> CryptoResult<(Vec, Vec)> { use openmls::prelude::TlsSerializeTrait as _; - let proposal = self.proposal.to_bytes().map_err(MlsError::from)?; + let proposal = self.proposal.tls_serialize_detached().map_err(MlsError::from)?; let proposal_ref = self.proposal_ref.tls_serialize_detached().map_err(MlsError::from)?; Ok((proposal, proposal_ref)) @@ -59,13 +56,16 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub async fn propose_add_member( &mut self, + client: &Client, backend: &MlsCryptoProvider, key_package: &KeyPackage, ) -> CryptoResult { + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; let proposal = self .group - .propose_add_member(backend, key_package) - .await + .propose_add_member(backend, signer, key_package) .map_err(MlsError::from) .map(MlsProposalBundle::from)?; self.persist_group_when_changed(backend, false).await?; @@ -74,10 +74,17 @@ impl MlsConversation { /// see [openmls::group::MlsGroup::propose_self_update] #[cfg_attr(test, crate::durable)] - pub async fn propose_self_update(&mut self, backend: &MlsCryptoProvider) -> CryptoResult { + pub async fn propose_self_update( + &mut self, + client: &Client, + backend: &MlsCryptoProvider, + ) -> CryptoResult { + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; let proposal = self .group - .propose_self_update(backend, None) + .propose_self_update(backend, signer, None) .await .map_err(MlsError::from) .map(MlsProposalBundle::from)?; @@ -89,13 +96,16 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub async fn propose_remove_member( &mut self, + client: &Client, backend: &MlsCryptoProvider, - member: &KeyPackageRef, + member: LeafNodeIndex, ) -> CryptoResult { + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; let proposal = self .group - .propose_remove_member(backend, member) - .await + .propose_remove_member(backend, signer, member) .map_err(MlsError::from) .map_err(CryptoError::from) .map(MlsProposalBundle::from)?; @@ -109,23 +119,23 @@ impl MlsConversation { #[derive(Debug)] pub struct MlsConversationCreationMessage { /// A welcome message for new members to join the group - pub welcome: Welcome, + pub welcome: MlsMessageOut, /// Commit message adding members to the group pub commit: MlsMessageOut, - /// [`PublicGroupState`] (aka GroupInfo) if the commit is merged - pub public_group_state: MlsPublicGroupStateBundle, + /// [`GroupInfo`] (aka GroupInfo) if the commit is merged + pub group_info: MlsGroupInfoBundle, } impl MlsConversationCreationMessage { /// Serializes both wrapped objects into TLS and return them as a tuple of byte arrays. /// 0 -> welcome /// 1 -> commit - /// 2 -> public_group_state - pub fn to_bytes_triple(self) -> CryptoResult<(Vec, Vec, MlsPublicGroupStateBundle)> { + /// 2 -> group_info + pub fn to_bytes_triple(self) -> CryptoResult<(Vec, Vec, MlsGroupInfoBundle)> { use openmls::prelude::TlsSerializeTrait as _; let welcome = self.welcome.tls_serialize_detached().map_err(MlsError::from)?; - let msg = self.commit.to_bytes().map_err(MlsError::from)?; - Ok((welcome, msg, self.public_group_state)) + let msg = self.commit.tls_serialize_detached().map_err(MlsError::from)?; + Ok((welcome, msg, self.group_info)) } } @@ -133,11 +143,11 @@ impl MlsConversationCreationMessage { #[derive(Debug)] pub struct MlsCommitBundle { /// A welcome message if there are pending Add proposals - pub welcome: Option, + pub welcome: Option, /// The commit message pub commit: MlsMessageOut, - /// [`PublicGroupState`] (aka GroupInfo) if the commit is merged - pub public_group_state: MlsPublicGroupStateBundle, + /// [`GroupInfo`] (aka GroupInfo) if the commit is merged + pub group_info: MlsGroupInfoBundle, } impl MlsCommitBundle { @@ -146,15 +156,15 @@ impl MlsCommitBundle { /// 1 -> message /// 2 -> public group state #[allow(clippy::type_complexity)] - pub fn to_bytes_triple(self) -> CryptoResult<(Option>, Vec, MlsPublicGroupStateBundle)> { + pub fn to_bytes_triple(self) -> CryptoResult<(Option>, Vec, MlsGroupInfoBundle)> { use openmls::prelude::TlsSerializeTrait as _; let welcome = self .welcome .as_ref() .map(|w| w.tls_serialize_detached().map_err(MlsError::from)) .transpose()?; - let commit = self.commit.to_bytes().map_err(MlsError::from)?; - Ok((welcome, commit, self.public_group_state)) + let commit = self.commit.tls_serialize_detached().map_err(MlsError::from)?; + Ok((welcome, commit, self.group_info)) } } @@ -165,6 +175,7 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub(crate) async fn add_members( &mut self, + client: &Client, members: &mut [ConversationMember], backend: &MlsCryptoProvider, ) -> CryptoResult { @@ -174,19 +185,26 @@ impl MlsConversation { .filter_map(|(_, kps)| kps) .collect::>(); - let (commit, welcome, pgs) = self + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; + + let (commit, welcome, gi) = self .group - .add_members(backend, &keypackages) + .add_members(backend, signer, keypackages.as_slice()) .await .map_err(MlsError::from)?; - let public_group_state = MlsPublicGroupStateBundle::try_new_full_plaintext(pgs)?; + + // SAFETY: This should be safe as adding members always generates a new commit + let gi = gi.ok_or(CryptoError::ImplementationError)?; + let group_info = MlsGroupInfoBundle::try_new_full_plaintext(gi)?; self.persist_group_when_changed(backend, false).await?; Ok(MlsConversationCreationMessage { welcome, commit, - public_group_state, + group_info, }) } @@ -195,38 +213,43 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub(crate) async fn remove_members( &mut self, + client: &Client, clients: &[ClientId], backend: &MlsCryptoProvider, ) -> CryptoResult { - let crypto = backend.crypto(); - let member_kps = self .group .members() - .into_iter() .filter(|kp| { clients .iter() - .any(move |client_id| client_id.as_slice() == kp.credential().identity()) + .any(move |client_id| client_id.as_slice() == kp.credential.identity()) }) - .try_fold(Vec::new(), |mut acc, kp| -> CryptoResult> { - acc.push(kp.hash_ref(crypto).map_err(MlsError::from)?); + .try_fold(vec![], |mut acc, kp| -> CryptoResult> { + acc.push(kp.index); Ok(acc) })?; - let (commit, welcome, pgs) = self + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; + + let (commit, welcome, gi) = self .group - .remove_members(backend, &member_kps) + .remove_members(backend, signer, &member_kps) .await .map_err(MlsError::from)?; - let public_group_state = MlsPublicGroupStateBundle::try_new_full_plaintext(pgs)?; + + // SAFETY: This should be safe as removing members always generates a new commit + let gi = gi.ok_or(CryptoError::ImplementationError)?; + let group_info = MlsGroupInfoBundle::try_new_full_plaintext(gi)?; self.persist_group_when_changed(backend, false).await?; Ok(MlsCommitBundle { commit, welcome, - public_group_state, + group_info, }) } @@ -234,17 +257,24 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub(crate) async fn update_keying_material( &mut self, + client: &Client, backend: &MlsCryptoProvider, ) -> CryptoResult { - let (commit, welcome, pgs) = self.group.self_update(backend, None).await.map_err(MlsError::from)?; - let public_group_state = MlsPublicGroupStateBundle::try_new_full_plaintext(pgs)?; + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; + let (commit, welcome, group_info) = self.group.self_update(backend, signer).await.map_err(MlsError::from)?; + + // We should always have ratchet tree extension turned on hence GroupInfo should always be present + let group_info = group_info.ok_or(CryptoError::ImplementationError)?; + let group_info = MlsGroupInfoBundle::try_new_full_plaintext(group_info)?; self.persist_group_when_changed(backend, false).await?; Ok(MlsCommitBundle { welcome, commit, - public_group_state, + group_info, }) } @@ -252,22 +282,27 @@ impl MlsConversation { #[cfg_attr(test, crate::durable)] pub(crate) async fn commit_pending_proposals( &mut self, + client: &Client, backend: &MlsCryptoProvider, ) -> CryptoResult> { if self.group.pending_proposals().count() > 0 { - let (commit, welcome, pgs) = self + let cs = self.ciphersuite(); + let ct = self.own_credential_type()?; + let signer = &client.find_credential_bundle(cs, ct)?.signature_key; + + let (commit, welcome, gi) = self .group - .commit_to_pending_proposals(backend) + .commit_to_pending_proposals(backend, signer) .await .map_err(MlsError::from)?; - let public_group_state = MlsPublicGroupStateBundle::try_new_full_plaintext(pgs)?; + let group_info = MlsGroupInfoBundle::try_new_full_plaintext(gi.unwrap())?; self.persist_group_when_changed(backend, false).await?; Ok(Some(MlsCommitBundle { welcome, commit, - public_group_state, + group_info, })) } else { Ok(None) @@ -296,21 +331,17 @@ impl MlsCentral { members: &mut [ConversationMember], ) -> CryptoResult { if let Some(callbacks) = self.callbacks.as_ref() { - let client_id = self - .mls_client - .as_ref() - .ok_or(CryptoError::MlsNotInitialized)? - .id() - .clone(); + let client_id = self.mls_client()?.id().clone(); if !callbacks.authorize(id.clone(), client_id).await { return Err(CryptoError::Unauthorized); } } + self.get_conversation(id) .await? .write() .await - .add_members(members, &self.mls_backend) + .add_members(self.mls_client()?, members, &self.mls_backend) .await } @@ -333,12 +364,7 @@ impl MlsCentral { clients: &[ClientId], ) -> CryptoResult { if let Some(callbacks) = self.callbacks.as_ref() { - let client_id = self - .mls_client - .as_ref() - .ok_or(CryptoError::MlsNotInitialized)? - .id() - .clone(); + let client_id = self.mls_client()?.id().clone(); if !callbacks.authorize(id.clone(), client_id).await { return Err(CryptoError::Unauthorized); } @@ -347,7 +373,7 @@ impl MlsCentral { .await? .write() .await - .remove_members(clients, &self.mls_backend) + .remove_members(self.mls_client()?, clients, &self.mls_backend) .await } @@ -368,7 +394,7 @@ impl MlsCentral { .await? .write() .await - .update_keying_material(&self.mls_backend) + .update_keying_material(self.mls_client()?, &self.mls_backend) .await } @@ -387,7 +413,7 @@ impl MlsCentral { .await? .write() .await - .commit_pending_proposals(&self.mls_backend) + .commit_pending_proposals(self.mls_client()?, &self.mls_backend) .await } } @@ -397,6 +423,8 @@ pub mod tests { use wasm_bindgen_test::*; use crate::{mls::proposal::MlsProposal, test_utils::*}; + use itertools::Itertools; + use openmls::prelude::SignaturePublicKey; use super::*; @@ -441,7 +469,7 @@ pub mod tests { assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); bob_central - .process_welcome_message(welcome, case.custom_cfg()) + .process_welcome_message(welcome.into(), case.custom_cfg()) .await .unwrap(); assert_eq!( @@ -478,7 +506,7 @@ pub mod tests { alice_central.commit_accepted(&id).await.unwrap(); bob_central - .process_welcome_message(welcome, case.custom_cfg()) + .process_welcome_message(welcome.into(), case.custom_cfg()) .await .unwrap(); assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_ok()); @@ -490,7 +518,7 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn should_return_valid_public_group_state(case: TestCase) { + pub async fn should_return_valid_group_info(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "guest"], @@ -502,20 +530,15 @@ pub mod tests { .await .unwrap(); - let public_group_state = alice_central + let group_info = alice_central .add_members_to_conversation(&id, &mut [bob_central.rand_member().await]) .await .unwrap() - .public_group_state; + .group_info; alice_central.commit_accepted(&id).await.unwrap(); assert!(guest_central - .try_join_from_public_group_state( - &case, - &id, - public_group_state.get_pgs(), - vec![&mut alice_central] - ) + .try_join_from_group_info(&case, &id, group_info.get_payload(), vec![&mut alice_central]) .await .is_ok()); }) @@ -574,7 +597,7 @@ pub mod tests { charlie_central .try_join_from_welcome( &id, - welcome.unwrap(), + welcome.unwrap().into(), case.custom_cfg(), vec![&mut alice_central, &mut bob_central], ) @@ -674,7 +697,12 @@ pub mod tests { alice_central.commit_accepted(&id).await.unwrap(); assert!(guest_central - .try_join_from_welcome(&id, welcome.unwrap(), case.custom_cfg(), vec![&mut alice_central]) + .try_join_from_welcome( + &id, + welcome.unwrap().into(), + case.custom_cfg(), + vec![&mut alice_central] + ) .await .is_ok()); // because Bob has been removed from the group @@ -687,7 +715,7 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn should_return_valid_public_group_state(case: TestCase) { + pub async fn should_return_valid_group_info(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "guest"], @@ -704,20 +732,15 @@ pub mod tests { .await .unwrap(); - let public_group_state = alice_central + let group_info = alice_central .remove_members_from_conversation(&id, &[bob_central.read_client_id()]) .await .unwrap() - .public_group_state; + .group_info; alice_central.commit_accepted(&id).await.unwrap(); assert!(guest_central - .try_join_from_public_group_state( - &case, - &id, - public_group_state.get_pgs(), - vec![&mut alice_central] - ) + .try_join_from_group_info(&case, &id, group_info.get_payload(), vec![&mut alice_central]) .await .is_ok()); // because Bob has been removed from the group @@ -749,14 +772,9 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); @@ -810,25 +828,21 @@ pub mod tests { .await .unwrap(); - let bob_keys: Vec = bob_central + let bob_keys = bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); - let alice_keys: Vec = alice_central + .encryption_keys() + .collect::>>(); + let alice_keys = alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .encryption_keys() + .collect::>>(); assert!(alice_keys.iter().all(|a_key| bob_keys.contains(a_key))); - let alice_key = alice_central.key_package_of(&id, alice_central.read_client_id()).await; + let alice_key = alice_central + .encryption_key_of(&id, alice_central.read_client_id()) + .await; // proposing the key update for alice let MlsCommitBundle { commit, welcome, .. } = @@ -839,18 +853,22 @@ pub mod tests { assert!(alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); + alice_central.commit_accepted(&id).await.unwrap(); - let alice_new_keys: Vec = alice_central + + assert!(!alice_central + .get_conversation_unchecked(&id) + .await + .encryption_keys() + .contains(&alice_key)); + + let alice_new_keys = alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .encryption_keys() + .collect::>(); assert!(!alice_new_keys.contains(&alice_key)); // receiving the commit on bob's side (updating key from alice) @@ -859,14 +877,11 @@ pub mod tests { .await .unwrap(); - let bob_new_keys: Vec = bob_central + let bob_new_keys = bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .encryption_keys() + .collect::>(); assert!(alice_new_keys.iter().all(|a_key| bob_new_keys.contains(a_key))); // ensuring both can encrypt messages @@ -895,27 +910,23 @@ pub mod tests { .await .unwrap(); - let bob_keys: Vec = bob_central + let bob_keys = bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); - let alice_keys: Vec = alice_central + .signature_keys() + .collect::>(); + let alice_keys = alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .signature_keys() + .collect::>(); // checking that the members on both sides are the same assert!(alice_keys.iter().all(|a_key| bob_keys.contains(a_key))); - let alice_key = alice_central.key_package_of(&id, alice_central.read_client_id()).await; + let alice_key = alice_central + .encryption_key_of(&id, alice_central.read_client_id()) + .await; // proposing adding charlie let charlie_kp = charlie_central.get_one_key_package(&case).await; @@ -937,20 +948,18 @@ pub mod tests { assert!(alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); alice_central.commit_accepted(&id).await.unwrap(); assert!(!alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); // create the group on charlie's side charlie_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); @@ -959,14 +968,11 @@ pub mod tests { // bob still didn't receive the message with the updated key and charlie's addition assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 2); - let alice_new_keys: Vec = alice_central + let alice_new_keys = alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .encryption_keys() + .collect::>>(); assert!(!alice_new_keys.contains(&alice_key)); // receiving the key update and the charlie's addition to the group @@ -976,14 +982,11 @@ pub mod tests { .unwrap(); assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 3); - let bob_new_keys: Vec = bob_central + let bob_new_keys = bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .encryption_keys() + .collect::>>(); assert!(alice_new_keys.iter().all(|a_key| bob_new_keys.contains(a_key))); // ensure all parties can encrypt messages @@ -1036,7 +1039,7 @@ pub mod tests { assert!(guest_central .try_join_from_welcome( &id, - welcome.unwrap(), + welcome.unwrap().into(), case.custom_cfg(), vec![&mut alice_central, &mut bob_central] ) @@ -1050,7 +1053,7 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn should_return_valid_public_group_state(case: TestCase) { + pub async fn should_return_valid_group_info(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "guest"], @@ -1066,20 +1069,11 @@ pub mod tests { .await .unwrap(); - let public_group_state = alice_central - .update_keying_material(&id) - .await - .unwrap() - .public_group_state; + let group_info = alice_central.update_keying_material(&id).await.unwrap().group_info; alice_central.commit_accepted(&id).await.unwrap(); assert!(guest_central - .try_join_from_public_group_state( - &case, - &id, - public_group_state.get_pgs(), - vec![&mut alice_central] - ) + .try_join_from_group_info(&case, &id, group_info.get_payload(), vec![&mut alice_central]) .await .is_ok()); }) @@ -1110,24 +1104,20 @@ pub mod tests { .await .unwrap(); - let bob_keys: Vec = bob_central + let bob_keys = bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); - let alice_keys: Vec<_> = alice_central + .signature_keys() + .collect::>(); + let alice_keys = alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .into_iter() - .cloned() - .collect(); + .signature_keys() + .collect::>(); assert!(alice_keys.iter().all(|a_key| bob_keys.contains(a_key))); - let alice_key = alice_central.key_package_of(&id, alice_central.read_client_id()).await; + let alice_key = alice_central + .encryption_key_of(&id, alice_central.read_client_id()) + .await; let proposal = alice_central .new_proposal(&id, MlsProposal::Update) @@ -1144,23 +1134,20 @@ pub mod tests { assert!(bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); bob_central.commit_accepted(&id).await.unwrap(); assert!(!bob_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); assert!(alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); // if 'new_proposal' wasn't durable this would fail because proposal would // not be referenced in commit alice_central @@ -1170,9 +1157,8 @@ pub mod tests { assert!(!alice_central .get_conversation_unchecked(&id) .await - .group - .members() - .contains(&&alice_key)); + .encryption_keys() + .contains(&alice_key)); // ensuring both can encrypt messages assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_ok()); @@ -1211,7 +1197,7 @@ pub mod tests { assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); bob_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_ok()); @@ -1305,7 +1291,7 @@ pub mod tests { alice_central.commit_accepted(&id).await.unwrap(); bob_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_ok()); @@ -1317,7 +1303,7 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn should_return_valid_public_group_state(case: TestCase) { + pub async fn should_return_valid_group_info(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "guest"], @@ -1332,17 +1318,12 @@ pub mod tests { .new_proposal(&id, MlsProposal::Add(bob_central.get_one_key_package(&case).await)) .await .unwrap(); - let MlsCommitBundle { public_group_state, .. } = + let MlsCommitBundle { group_info, .. } = alice_central.commit_pending_proposals(&id).await.unwrap().unwrap(); alice_central.commit_accepted(&id).await.unwrap(); assert!(guest_central - .try_join_from_public_group_state( - &case, - &id, - public_group_state.get_pgs(), - vec![&mut alice_central] - ) + .try_join_from_group_info(&case, &id, group_info.get_payload(), vec![&mut alice_central]) .await .is_ok()); }) diff --git a/crypto/src/mls/conversation/merge.rs b/crypto/src/mls/conversation/merge.rs index 2c524a4400..86ae3af352 100644 --- a/crypto/src/mls/conversation/merge.rs +++ b/crypto/src/mls/conversation/merge.rs @@ -26,7 +26,7 @@ impl MlsConversation { /// see [MlsCentral::commit_accepted] #[cfg_attr(test, crate::durable)] pub async fn commit_accepted(&mut self, backend: &MlsCryptoProvider) -> CryptoResult<()> { - self.group.merge_pending_commit().map_err(MlsError::from)?; + self.group.merge_pending_commit(backend).await.map_err(MlsError::from)?; self.persist_group_when_changed(backend, false).await } @@ -37,10 +37,12 @@ impl MlsConversation { proposal_ref: MlsProposalRef, backend: &MlsCryptoProvider, ) -> CryptoResult<()> { - self.group.clear_pending_proposal(*proposal_ref).map_err(|e| match e { - MlsGroupStateError::PendingProposalNotFound => CryptoError::PendingProposalNotFound(proposal_ref), - _ => CryptoError::from(MlsError::from(e)), - })?; + self.group + .remove_pending_proposal(proposal_ref.clone().into_inner()) + .map_err(|e| match e { + MlsGroupStateError::PendingProposalNotFound => CryptoError::PendingProposalNotFound(proposal_ref), + _ => CryptoError::from(MlsError::from(e)), + })?; self.persist_group_when_changed(backend, true).await?; Ok(()) } @@ -107,7 +109,7 @@ impl MlsCentral { .await? .write() .await - .clear_pending_proposal(proposal_ref.try_into()?, &self.mls_backend) + .clear_pending_proposal(proposal_ref.into(), &self.mls_backend) .await } @@ -292,7 +294,9 @@ pub mod tests { run_test_with_client_ids(case.clone(), ["alice"], move |[mut alice_central]| { Box::pin(async move { let id = conversation_id(); - let simple_ref = MlsProposalRef::try_from(vec![0; 16]).unwrap().into(); + let simple_ref = MlsProposalRef::try_from(vec![0; case.ciphersuite().hash_length()]) + .unwrap() + .into(); let clear = alice_central.clear_pending_proposal(&id, simple_ref).await; assert!(matches!(clear.unwrap_err(), CryptoError::ConversationNotFound(conv_id) if conv_id == id)) }) @@ -311,8 +315,8 @@ pub mod tests { .await .unwrap(); assert!(alice_central.pending_proposals(&id).await.is_empty()); - let any_ref = MlsProposalRef::try_from(vec![0; 16]).unwrap(); - let clear = alice_central.clear_pending_proposal(&id, any_ref.into()).await; + let any_ref = MlsProposalRef::try_from(vec![0; case.ciphersuite().hash_length()]).unwrap(); + let clear = alice_central.clear_pending_proposal(&id, any_ref.clone().into()).await; assert!(matches!(clear.unwrap_err(), CryptoError::PendingProposalNotFound(prop_ref) if prop_ref == any_ref)) }) }) diff --git a/crypto/src/mls/conversation/mod.rs b/crypto/src/mls/conversation/mod.rs index 0fd65b9a75..27aa4db06e 100644 --- a/crypto/src/mls/conversation/mod.rs +++ b/crypto/src/mls/conversation/mod.rs @@ -37,9 +37,9 @@ use mls_crypto_provider::MlsCryptoProvider; use config::MlsConversationConfiguration; -use crate::prelude::MlsCredentialType; use crate::{ - mls::{client::Client, member::MemberId, ClientId, MlsCentral}, + mls::{client::Client, member::MemberId, ClientId, MlsCentral, MlsCiphersuite}, + prelude::MlsCredentialType, CryptoError, CryptoResult, MlsError, }; @@ -50,9 +50,9 @@ pub mod decrypt; mod durability; pub mod encrypt; pub mod export; +pub(crate) mod group_info; pub mod handshake; pub mod merge; -pub(crate) mod public_group_state; mod renew; /// A unique identifier for a group/conversation. The identifier must be unique within a client. @@ -89,16 +89,15 @@ impl MlsConversation { configuration: MlsConversationConfiguration, backend: &MlsCryptoProvider, ) -> CryptoResult { - let kp = author_client - .generate_keypackage(backend, configuration.ciphersuite, creator_credential_type) - .await?; - let kp_hash = kp.hash_ref(backend.crypto()).map_err(MlsError::from)?; + let (cs, ct) = (configuration.ciphersuite, creator_credential_type); + let cb = author_client.get_or_create_credential_bundle(backend, cs, ct).await?; - let group = MlsGroup::new( + let group = MlsGroup::new_with_group_id( backend, + &cb.signature_key, &configuration.as_openmls_default_configuration()?, - openmls::group::GroupId::from_slice(&id), - kp_hash.value(), + openmls::prelude::GroupId::from_slice(id.as_slice()), + cb.to_mls_credential_with_key(), ) .await .map_err(MlsError::from)?; @@ -159,7 +158,7 @@ impl MlsConversation { /// Internal API: restore the conversation from a persistence-saved serialized Group State. pub(crate) fn from_serialized_state(buf: Vec, parent_id: Option) -> CryptoResult { - let group = MlsGroup::load(&mut &buf[..])?; + let group: MlsGroup = core_crypto_keystore::deser(&buf)?; let id = ConversationId::from(group.group_id().as_slice()); let configuration = MlsConversationConfiguration { ciphersuite: group.ciphersuite().into(), @@ -181,11 +180,11 @@ impl MlsConversation { /// Returns all members credentials from the group/conversation pub fn members(&self) -> HashMap> { - self.group.members().iter().fold(HashMap::new(), |mut acc, kp| { - let credential = kp.credential(); + self.group.members().fold(HashMap::new(), |mut acc, kp| { + let credential = kp.credential; let client_id: ClientId = credential.identity().into(); let member_id: MemberId = client_id.to_vec(); - acc.entry(member_id).or_insert_with(Vec::new).push(credential.clone()); + acc.entry(member_id).or_insert_with(Vec::new).push(credential); acc }) } @@ -196,17 +195,20 @@ impl MlsConversation { force: bool, ) -> CryptoResult<()> { if force || self.group.state_changed() == openmls::group::InnerState::Changed { - let mut buf = vec![]; - self.group.save(&mut buf)?; - use core_crypto_keystore::CryptoKeystoreMls as _; - Ok(backend + backend .key_store() - .mls_group_persist(&self.id, &buf, self.parent_id.as_deref()) - .await?) - } else { - Ok(()) + .mls_group_persist( + &self.id, + &core_crypto_keystore::ser(&self.group)?, + self.parent_id.as_deref(), + ) + .await?; + + self.group.set_state(openmls::group::InnerState::Persisted); } + + Ok(()) } /// Marks this conversation as child of another. @@ -224,6 +226,20 @@ impl MlsConversation { Err(CryptoError::ParentGroupNotFound) } } + + pub(crate) fn own_credential_type(&self) -> CryptoResult { + Ok(self + .group + .own_leaf_node() + .ok_or(CryptoError::ImplementationError)? + .credential() + .credential_type() + .into()) + } + + pub(crate) fn ciphersuite(&self) -> MlsCiphersuite { + self.configuration.ciphersuite + } } impl MlsCentral { @@ -366,7 +382,7 @@ pub mod tests { assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); bob_central - .process_welcome_message(welcome, case.custom_cfg()) + .process_welcome_message(welcome.into(), case.custom_cfg()) .await .unwrap(); @@ -442,7 +458,7 @@ pub mod tests { let mut bob_and_friends_groups = Vec::with_capacity(bob_and_friends.len()); // TODO: Do things in parallel, this is waaaaay too slow (takes around 5 minutes) for mut c in bob_and_friends { - c.process_welcome_message(welcome.clone(), case.custom_cfg()) + c.process_welcome_message(welcome.clone().into(), case.custom_cfg()) .await .unwrap(); assert!(c.try_talk_to(&id, &mut alice_central).await.is_ok()); @@ -489,7 +505,7 @@ pub mod tests { // Bob accepts the welcome message, and as such, it should prune the used keypackage from the store bob_central - .process_welcome_message(welcome, case.custom_cfg()) + .process_welcome_message(welcome.into(), case.custom_cfg()) .await .unwrap(); diff --git a/crypto/src/mls/conversation/public_group_state.rs b/crypto/src/mls/conversation/public_group_state.rs deleted file mode 100644 index 7b2e1e38b4..0000000000 --- a/crypto/src/mls/conversation/public_group_state.rs +++ /dev/null @@ -1,103 +0,0 @@ -use openmls::prelude::PublicGroupState; -use serde::{Deserialize, Serialize}; - -use crate::{CryptoResult, MlsError}; - -/// A [PublicGroupState] with metadata -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MlsPublicGroupStateBundle { - /// Indicates if the [payload] is encrypted or not - pub encryption_type: MlsPublicGroupStateEncryptionType, - /// Indicates if the [payload] contains a full, partial or referenced [PublicGroupState] - pub ratchet_tree_type: MlsRatchetTreeType, - /// The [PublicGroupState] - pub payload: PublicGroupStatePayload, -} - -impl MlsPublicGroupStateBundle { - /// Creates a new [PublicGroupStateBundle] with complete and unencrypted [PublicGroupState] - pub(crate) fn try_new_full_plaintext(pgs: PublicGroupState) -> CryptoResult { - use tls_codec::Serialize as _; - let payload = pgs.tls_serialize_detached().map_err(MlsError::from)?; - Ok(Self { - encryption_type: MlsPublicGroupStateEncryptionType::Plaintext, - ratchet_tree_type: MlsRatchetTreeType::Full, - payload: PublicGroupStatePayload::Plaintext(payload), - }) - } -} - -#[cfg(test)] -impl MlsPublicGroupStateBundle { - pub fn get_pgs(mut self) -> openmls::prelude::VerifiablePublicGroupState { - use tls_codec::Deserialize as _; - match &mut self.payload { - PublicGroupStatePayload::Plaintext(pgs) => { - openmls::prelude::VerifiablePublicGroupState::tls_deserialize(&mut pgs.as_slice()).unwrap() - } - } - } -} - -/// # PublicGroupStateEncryptionType -/// -/// In order to guarantee confidentiality of the [PublicGroupState] on the wire a domain can -/// request it to be encrypted when sent to the Delivery Service. -/// -/// ```text -/// enum { -/// plaintext(1), -/// jwe_encrypted(2), -/// (255) -/// } PublicGroupStateEncryptionType; -/// ``` -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] -#[repr(u8)] -pub enum MlsPublicGroupStateEncryptionType { - /// Unencrypted [PublicGroupState] - Plaintext = 1, - /// [PublicGroupState] encrypted in a JWE - JweEncrypted = 2, -} - -/// # RatchetTreeType -/// -/// In order to spare some precious bytes, a [PublicGroupState] can have different representations. -/// -/// ```text -/// enum { -/// full(1), -/// delta(2), -/// by_ref(3), -/// (255) -/// } RatchetTreeType; -/// ``` -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] -#[repr(u8)] -pub enum MlsRatchetTreeType { - /// Plain old and complete [PublicGroupState] - Full = 1, - /// Contains [PublicGroupState] changes since previous epoch (not yet implemented) - /// (see [draft](https://github.com/rohan-wire/ietf-drafts/blob/main/mahy-mls-ratchet-tree-delta/draft-mahy-mls-ratchet-tree-delta.md)) - Delta = 2, - /// TODO: to define - ByRef = 3, -} - -/// Represents the byte array in [PublicGroupStateBundle] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PublicGroupStatePayload { - /// Unencrypted [PublicGroupState] - Plaintext(Vec), - // TODO: expose when fully implemented - // Encrypted(Vec), -} - -impl PublicGroupStatePayload { - /// Returns the internal byte array - pub fn bytes(self) -> Vec { - match self { - PublicGroupStatePayload::Plaintext(pgs) => pgs, - } - } -} diff --git a/crypto/src/mls/conversation/renew.rs b/crypto/src/mls/conversation/renew.rs index 8aa4f74259..3e0edfaa0d 100644 --- a/crypto/src/mls/conversation/renew.rs +++ b/crypto/src/mls/conversation/renew.rs @@ -1,8 +1,9 @@ -use openmls::prelude::{KeyPackageRef, Proposal, QueuedProposal, Sender, StagedCommit}; +use openmls::prelude::{LeafNodeIndex, Proposal, QueuedProposal, Sender, StagedCommit}; use mls_crypto_provider::MlsCryptoProvider; use crate::prelude::handshake::MlsProposalBundle; +use crate::prelude::Client; use crate::{mls::MlsConversation, CryptoError, CryptoResult}; /// Marker struct holding methods responsible for restoring (renewing) proposals (or pending commit) @@ -22,7 +23,7 @@ impl Renew { /// * `pending_commit` - local pending commit which is now invalid /// * `valid_commit` - commit accepted by the backend which will now supersede our local pending commit pub(crate) fn renew<'a>( - self_kpr: Option, + self_kpr: Option, pending_proposals: impl Iterator + 'a, pending_commit: Option<&'a StagedCommit>, valid_commit: &'a StagedCommit, @@ -34,7 +35,7 @@ impl Renew { let renewed_pending_proposals = if let Some(pending_commit) = pending_commit { // present in pending commit but not in valid commit - let commit_proposals = pending_commit.staged_proposal_queue().cloned().collect::>(); + let commit_proposals = pending_commit.queued_proposals().cloned().collect::>(); // if our own pending commit is empty it means we were attempting to update let empty_commit = commit_proposals.is_empty(); @@ -65,14 +66,16 @@ impl Renew { if let Some(commit) = commit { let in_commit = match proposal.proposal() { Proposal::Add(ref add) => commit.add_proposals().any(|p| { - p.add_proposal().key_package().credential().identity() == add.key_package().credential().identity() + let commits_identity = p.add_proposal().key_package().leaf_node().credential().identity(); + let proposal_identity = add.key_package().leaf_node().credential().identity(); + commits_identity == proposal_identity }), Proposal::Remove(ref remove) => commit .remove_proposals() .any(|p| p.remove_proposal().removed() == remove.removed()), Proposal::Update(ref update) => commit .update_proposals() - .any(|p| p.update_proposal().key_package() == update.key_package()), + .any(|p| p.update_proposal().leaf_node() == update.leaf_node()), _ => true, }; if in_commit { @@ -92,31 +95,32 @@ impl MlsConversation { /// This will also add them to the local proposal store pub(crate) async fn renew_proposals_for_current_epoch( &mut self, + client: &Client, backend: &MlsCryptoProvider, proposals: impl Iterator, update_self: bool, ) -> CryptoResult> { let mut result = vec![]; - let is_external = |p: &QueuedProposal| matches!(p.sender(), Sender::External(_) | Sender::NewMember); + let is_external = |p: &QueuedProposal| matches!(p.sender(), Sender::External(_) | Sender::NewMemberProposal); let proposals = proposals.filter(|p| !is_external(p)); for proposal in proposals { let msg = match proposal.proposal() { - Proposal::Add(add) => self.propose_add_member(backend, add.key_package()).await?, - Proposal::Remove(remove) => self.propose_remove_member(backend, remove.removed()).await?, - Proposal::Update(_) => self.propose_self_update(backend).await?, + Proposal::Add(add) => self.propose_add_member(client, backend, add.key_package()).await?, + Proposal::Remove(remove) => self.propose_remove_member(client, backend, remove.removed()).await?, + Proposal::Update(_) => self.propose_self_update(client, backend).await?, _ => return Err(CryptoError::ImplementationError), }; result.push(msg); } if update_self { - result.push(self.propose_self_update(backend).await?); + result.push(self.propose_self_update(client, backend).await?); } Ok(result) } pub(crate) fn self_pending_proposals(&self) -> impl Iterator { self.group.pending_proposals().filter(|&p| match p.sender() { - Sender::Member(sender_kpr) => self.group.key_package_ref() == Some(sender_kpr), + Sender::Member(sender_kpr) => self.group.own_leaf_index() == *sender_kpr, _ => false, }) } @@ -313,14 +317,9 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); @@ -467,14 +466,9 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); @@ -567,7 +561,7 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn renews_pending_commit_when_valid_commit_doesnt_adds_same(case: TestCase) { + pub async fn renews_pending_commit_when_valid_commit_doesnt_add_same(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "charlie"], @@ -627,14 +621,9 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); @@ -681,14 +670,9 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); @@ -736,22 +720,17 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; debbie_central - .try_join_from_public_group_state( + .try_join_from_group_info( &case, &id, - pgs, + gi.into(), vec![&mut alice_central, &mut bob_central, &mut charlie_central], ) .await @@ -802,22 +781,17 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; debbie_central - .try_join_from_public_group_state( + .try_join_from_group_info( &case, &id, - pgs, + gi.into(), vec![&mut alice_central, &mut bob_central, &mut charlie_central], ) .await @@ -867,22 +841,17 @@ pub mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; charlie_central - .try_join_from_public_group_state( - &case, - &id, - pgs, - vec![&mut alice_central, &mut bob_central], - ) + .try_join_from_group_info(&case, &id, gi.into(), vec![&mut alice_central, &mut bob_central]) .await .unwrap(); - let pgs = alice_central.verifiable_public_group_state(&id).await; + let gi = alice_central.get_group_info(&id).await; debbie_central - .try_join_from_public_group_state( + .try_join_from_group_info( &case, &id, - pgs, + gi.into(), vec![&mut alice_central, &mut bob_central, &mut charlie_central], ) .await diff --git a/crypto/src/mls/credential/ext.rs b/crypto/src/mls/credential/ext.rs index 7b1ab63a52..1dc196b83e 100644 --- a/crypto/src/mls/credential/ext.rs +++ b/crypto/src/mls/credential/ext.rs @@ -1,48 +1,16 @@ -use crate::prelude::{CryptoResult, MlsCredentialType, MlsError, WireIdentity}; -use openmls::prelude::{Credential, CredentialBundle}; -use tls_codec::Serialize; -use wire_e2e_identity::prelude::WireIdentityReader; +use crate::prelude::{CryptoError, CryptoResult, MlsCredentialType}; +use openmls::prelude::{Credential, CredentialType}; pub(crate) trait CredentialExt { - fn keystore_key(&self) -> CryptoResult>; - fn get_type(&self) -> MlsCredentialType; - fn extract_identity(&self) -> Option; -} - -impl CredentialExt for CredentialBundle { - #[inline(always)] - fn keystore_key(&self) -> CryptoResult> { - self.credential().keystore_key() - } - - fn get_type(&self) -> MlsCredentialType { - self.credential().get_type() - } - - fn extract_identity(&self) -> Option { - self.credential().extract_identity() - } + fn get_type(&self) -> CryptoResult; } impl CredentialExt for Credential { - fn keystore_key(&self) -> CryptoResult> { - Ok(self.signature_key().tls_serialize_detached().map_err(MlsError::from)?) - } - - fn get_type(&self) -> MlsCredentialType { - match self.credential { - openmls::prelude::MlsCredentialType::Basic(_) => MlsCredentialType::Basic, - openmls::prelude::MlsCredentialType::X509(_) => MlsCredentialType::X509, - } - } - - fn extract_identity(&self) -> Option { - match &self.credential { - openmls::prelude::MlsCredentialType::X509(c) => { - let leaf = c.cert_chain.get(0)?; - Some(leaf.as_slice().extract_identity().ok()?.into()) - } - _ => None, + fn get_type(&self) -> CryptoResult { + match self.credential_type() { + CredentialType::Basic => Ok(MlsCredentialType::Basic), + CredentialType::X509 => Ok(MlsCredentialType::X509), + CredentialType::Unknown(_) => Err(CryptoError::ImplementationError), } } } diff --git a/crypto/src/mls/credential/mod.rs b/crypto/src/mls/credential/mod.rs index 950313e0c6..61de556d59 100644 --- a/crypto/src/mls/credential/mod.rs +++ b/crypto/src/mls/credential/mod.rs @@ -1,13 +1,59 @@ +use openmls::prelude::{CredentialWithKey, OpenMlsCrypto}; +use openmls_traits::OpenMlsCryptoProvider; pub(crate) mod ext; pub(crate) mod typ; pub(crate) mod x509; -use openmls::prelude::CredentialBundle; +use openmls::prelude::Credential; +use openmls_basic_credential::SignatureKeyPair; +use openmls_x509_credential::CertificateKeyPair; use mls_crypto_provider::MlsCryptoProvider; use crate::prelude::{CertificateBundle, Client, ClientId, CryptoResult, MlsCiphersuite, MlsError}; +#[derive(Debug)] +pub struct CredentialBundle { + pub(crate) credential: Credential, + pub(crate) signature_key: SignatureKeyPair, +} + +impl CredentialBundle { + pub fn credential(&self) -> &Credential { + &self.credential + } + + pub fn to_mls_credential_with_key(&self) -> CredentialWithKey { + CredentialWithKey { + credential: self.credential.clone(), + signature_key: self.signature_key.to_public_vec().into(), + } + } +} + +#[cfg(test)] +impl From for CredentialWithKey { + fn from(cb: CredentialBundle) -> Self { + Self { + credential: cb.credential, + signature_key: cb.signature_key.public().into(), + } + } +} + +impl Clone for CredentialBundle { + fn clone(&self) -> Self { + Self { + credential: self.credential.clone(), + signature_key: SignatureKeyPair::from_raw( + self.signature_key.signature_scheme(), + self.signature_key.private().to_vec(), + self.signature_key.to_public_vec(), + ), + } + } +} + impl Client { pub(crate) fn new_basic_credential_bundle( id: &ClientId, @@ -15,14 +61,34 @@ impl Client { backend: &MlsCryptoProvider, ) -> CryptoResult { let signature_scheme = ciphersuite.signature_algorithm(); - let cb = CredentialBundle::new_basic(id.to_vec(), signature_scheme, backend).map_err(MlsError::from)?; + let (sk, pk) = backend + .crypto() + .signature_key_gen(signature_scheme) + .map_err(MlsError::from)?; + + let signature_key = SignatureKeyPair::from_raw(signature_scheme, sk, pk); + let credential = Credential::new_basic(id.to_vec()); + let cb = CredentialBundle { + credential, + signature_key, + }; + Ok(cb) } pub(crate) fn new_x509_credential_bundle(cert: CertificateBundle) -> CryptoResult { let client_id = cert.get_client_id()?; - let cb = CredentialBundle::new_x509(client_id.into(), cert.certificate_chain, cert.private_key) - .map_err(MlsError::from)?; + let (sk, ..) = cert.private_key.into_parts(); + let chain = cert.certificate_chain; + + let kp = CertificateKeyPair::new(sk, chain.clone()).map_err(MlsError::from)?; + + let credential = Credential::new_x509(client_id.into(), chain).map_err(MlsError::from)?; + + let cb = CredentialBundle { + credential, + signature_key: kp.0, + }; Ok(cb) } } @@ -31,16 +97,20 @@ impl Client { // Requires more than 1 ciphersuite supported at the moment. #[cfg(test)] pub mod tests { - use openmls::prelude::{CredentialError, SignaturePrivateKey, WelcomeError}; - use openmls_traits::types::Ciphersuite; + use openmls::{ + prelude::{CreationFromExternalError, WelcomeError}, + treesync::{errors::TreeSyncFromNodesError, RatchetTreeError}, + }; use std::collections::HashMap; use wasm_bindgen_test::*; use wire_e2e_identity::prelude::WireIdentityBuilder; - use crate::prelude::{ClientIdentifier, MlsCredentialType}; use crate::{ error::CryptoError, - mls::{MlsCentral, MlsCentralConfiguration, MlsConversationConfiguration}, + mls::{ + credential::x509::CertificatePrivateKey, MlsCentral, MlsCentralConfiguration, MlsConversationConfiguration, + }, + prelude::{ClientIdentifier, ConversationId, MlsCredentialType}, test_utils::*, }; @@ -76,14 +146,9 @@ pub mod tests { } } - // #[apply(all_cred_cipher)] - // #[wasm_bindgen_test] - #[async_std::test] - async fn heterogeneous_clients_can_send_messages(/*case: TestCase*/) { - let case = TestCase::new( - MlsCredentialType::X509, - Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, - ); + #[apply(all_cred_cipher)] + #[wasm_bindgen_test] + async fn heterogeneous_clients_can_send_messages(case: TestCase) { // check that both credentials can initiate/join a group { let alice_identifier = ClientIdentifier::Basic("alice".into()); @@ -145,21 +210,24 @@ pub mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] async fn should_fail_when_certificate_is_orphan(case: TestCase) { - // remove root_ca from the chain - let mut certs = CertificateBundle::rand(case.ciphersuite(), "alice".into()); - let leaf = certs.certificate_chain.first().unwrap().to_owned(); - certs.certificate_chain = vec![leaf]; - let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), certs)])); + if matches!(case.credential_type, MlsCredentialType::X509) { + // remove root_ca from the chain + let mut certs = CertificateBundle::rand(case.ciphersuite(), "alice".into()); + let leaf = certs.certificate_chain.first().unwrap().to_owned(); + certs.certificate_chain = vec![leaf]; + let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), certs)])); - let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); - assert!(matches!( - try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) - .await - .unwrap_err(), - CryptoError::MlsError(MlsError::MlsCredentialError( - CredentialError::IncompleteCertificateChain - )) - )); + let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); + + assert!(matches!( + try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) + .await + .unwrap_err(), + CryptoError::MlsError(MlsError::MlsCryptoError( + openmls::prelude::CryptoError::IncompleteCertificateChain + )) + )); + } } #[apply(all_cred_cipher)] @@ -192,11 +260,14 @@ pub mod tests { let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), certs)])); let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); + assert!(matches!( - try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) + try_talk(alice_identifier, bob_identifier, case.credential_type, case.cfg) .await .unwrap_err(), - CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::InvalidGroupInfoSignature)) + CryptoError::MlsError(MlsError::MlsMessageError( + openmls::prelude::ProcessMessageError::CryptoError(openmls::prelude::CryptoError::InvalidSignature) + )) )); } } @@ -207,7 +278,7 @@ pub mod tests { if matches!(case.credential_type, MlsCredentialType::X509) { let certs = CertificateBundle::rand(case.ciphersuite(), "alice".into()); let (_, sign_key) = WireIdentityBuilder::default().new_key_pair(); - let eve_key = SignaturePrivateKey { + let eve_key = CertificatePrivateKey { value: sign_key, signature_scheme: case.ciphersuite().signature_algorithm(), }; @@ -218,11 +289,16 @@ pub mod tests { let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), cb)])); let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); + assert!(matches!( - try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) + try_talk(alice_identifier, bob_identifier, case.credential_type, case.cfg) .await .unwrap_err(), - CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::InvalidGroupInfoSignature)) + CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::PublicGroupError( + CreationFromExternalError::TreeSyncError(TreeSyncFromNodesError::RatchetTreeError( + RatchetTreeError::InvalidNodeSignature + )) + ))) )); } } @@ -231,17 +307,16 @@ pub mod tests { #[wasm_bindgen_test] async fn should_fail_when_certificate_expired(case: TestCase) { if matches!(case.credential_type, MlsCredentialType::X509) { - let yesterday = - wire_e2e_identity::prelude::OffsetDateTime::now_utc() - core::time::Duration::from_secs(3600 * 24); + let in_2_secs = now() + core::time::Duration::from_secs(2); let (certificate_chain, sign_key) = WireIdentityBuilder { - not_after: yesterday, + not_after: in_2_secs, ..Default::default() } .build_x509_der(); let cb = CertificateBundle { certificate_chain, - private_key: SignaturePrivateKey { + private_key: CertificatePrivateKey { value: sign_key, signature_scheme: case.ciphersuite().signature_algorithm(), }, @@ -249,11 +324,22 @@ pub mod tests { let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), cb)])); let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); - assert!(matches!( - try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) + // this should work since the certificate is not yet expired + let (mut alice_central, mut bob_central, id) = + try_talk(alice_identifier, bob_identifier, case.credential_type, case.cfg) .await - .unwrap_err(), - CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::InvalidGroupInfoSignature)) + .unwrap(); + + // Give time to the certificate to expire + async_std::task::sleep(core::time::Duration::from_secs(4)).await; + + assert!(matches!( + alice_central.try_talk_to(&id, &mut bob_central).await.unwrap_err(), + CryptoError::MlsError(MlsError::MlsMessageError( + openmls::prelude::ProcessMessageError::CryptoError( + openmls::prelude::CryptoError::InvalidCertificate + ) + )) )); } } @@ -262,8 +348,7 @@ pub mod tests { #[wasm_bindgen_test] async fn should_fail_when_certificate_not_valid_yet(case: TestCase) { if matches!(case.credential_type, MlsCredentialType::X509) { - let tomorrow = - wire_e2e_identity::prelude::OffsetDateTime::now_utc() + core::time::Duration::from_secs(3600 * 24); + let tomorrow = now() + core::time::Duration::from_secs(3600 * 24); let (certificate_chain, sign_key) = WireIdentityBuilder { not_before: tomorrow, ..Default::default() @@ -272,7 +357,7 @@ pub mod tests { let cb = CertificateBundle { certificate_chain, - private_key: SignaturePrivateKey { + private_key: CertificatePrivateKey { value: sign_key, signature_scheme: case.ciphersuite().signature_algorithm(), }, @@ -280,21 +365,31 @@ pub mod tests { let alice_identifier = ClientIdentifier::X509(HashMap::from([(case.ciphersuite(), cb)])); let bob_identifier = CertificateBundle::rand_identifier(&[case.ciphersuite()], "bob".into()); + assert!(matches!( - try_talk(alice_identifier, bob_identifier, MlsCredentialType::X509, case.cfg) + try_talk(alice_identifier, bob_identifier, case.credential_type, case.cfg) .await .unwrap_err(), - CryptoError::MlsError(MlsError::MlsWelcomeError(WelcomeError::InvalidGroupInfoSignature)) + CryptoError::MlsError(MlsError::MlsCryptoError( + openmls::prelude::CryptoError::InvalidCertificate + )) )); } } + /// In order to be WASM-compatible + fn now() -> wire_e2e_identity::prelude::OffsetDateTime { + let now = fluvio_wasm_timer::SystemTime::now(); + let now_since_epoch = now.duration_since(fluvio_wasm_timer::UNIX_EPOCH).unwrap().as_secs() as i64; + wire_e2e_identity::prelude::OffsetDateTime::from_unix_timestamp(now_since_epoch).unwrap() + } + async fn try_talk( alice_identifier: ClientIdentifier, bob_identifier: ClientIdentifier, creator_ct: MlsCredentialType, cfg: MlsConversationConfiguration, - ) -> CryptoResult<()> { + ) -> CryptoResult<(MlsCentral, MlsCentral, ConversationId)> { let id = conversation_id(); let ciphersuites = vec![cfg.ciphersuite]; let alice_path = tmp_db_file(); @@ -315,6 +410,7 @@ pub mod tests { .new_conversation(id.clone(), creator_ct, cfg.clone()) .await?; alice_central.invite(&id, &mut bob_central, cfg.custom).await?; - alice_central.try_talk_to(&id, &mut bob_central).await + alice_central.try_talk_to(&id, &mut bob_central).await?; + Ok((alice_central, bob_central, id)) } } diff --git a/crypto/src/mls/credential/typ.rs b/crypto/src/mls/credential/typ.rs index ef663155ca..d643cdc1ca 100644 --- a/crypto/src/mls/credential/typ.rs +++ b/crypto/src/mls/credential/typ.rs @@ -1,3 +1,7 @@ +use std::unreachable; + +use openmls::prelude::CredentialType; + /// Lists all the supported Credential types. Could list in the future some types not supported by /// openmls such as Verifiable Presentation #[derive(Default, Debug, Clone, Copy, strum::EnumCount)] @@ -9,3 +13,13 @@ pub enum MlsCredentialType { /// A x509 certificate generally obtained through e2e identity enrollment process X509 = 0x02, } + +impl From for MlsCredentialType { + fn from(value: CredentialType) -> Self { + match value { + CredentialType::Basic => MlsCredentialType::Basic, + CredentialType::X509 => MlsCredentialType::X509, + _ => unreachable!("Unknown credential type"), + } + } +} diff --git a/crypto/src/mls/credential/x509.rs b/crypto/src/mls/credential/x509.rs index 0e8e692cf4..7d1028cbf5 100644 --- a/crypto/src/mls/credential/x509.rs +++ b/crypto/src/mls/credential/x509.rs @@ -1,8 +1,24 @@ -use openmls::prelude::SignaturePrivateKey; +use openmls_traits::types::SignatureScheme; use wire_e2e_identity::prelude::WireIdentityReader; use crate::prelude::{ClientId, CryptoError, CryptoResult}; +use zeroize::Zeroize; + +#[derive(Debug, Clone, Zeroize)] +#[zeroize(drop)] +pub struct CertificatePrivateKey { + pub(crate) value: Vec, + #[zeroize(skip)] + pub(crate) signature_scheme: SignatureScheme, +} + +impl CertificatePrivateKey { + pub(crate) fn into_parts(mut self) -> (Vec, SignatureScheme) { + (std::mem::take(&mut self.value), self.signature_scheme) + } +} + /// Represents a x509 certificate chain supplied by the client /// It can fetch it after an end-to-end identity process where it can get back a certificate /// from the Authentication Service @@ -12,7 +28,7 @@ pub struct CertificateBundle { /// First entry is the leaf certificate and each subsequent is its issuer pub certificate_chain: Vec>, /// Leaf certificate private key - pub private_key: SignaturePrivateKey, + pub private_key: CertificatePrivateKey, } impl CertificateBundle { @@ -42,7 +58,7 @@ impl CertificateBundle { .build_x509_der(); Self { certificate_chain, - private_key: SignaturePrivateKey { + private_key: CertificatePrivateKey { value: sign_key, signature_scheme: cs.signature_algorithm(), }, diff --git a/crypto/src/mls/external_commit.rs b/crypto/src/mls/external_commit.rs index 526901a17a..9d699d9e84 100644 --- a/crypto/src/mls/external_commit.rs +++ b/crypto/src/mls/external_commit.rs @@ -14,16 +14,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use openmls::prelude::{MlsGroup, MlsMessageOut, Proposal, Sender, StagedCommit, VerifiablePublicGroupState}; -use openmls_traits::{crypto::OpenMlsCrypto, OpenMlsCryptoProvider}; +use openmls::framing::{MlsMessageIn, MlsMessageInBody}; +use openmls::prelude::{MlsGroup, MlsMessageOut, Proposal, Sender, StagedCommit}; +use openmls_traits::OpenMlsCryptoProvider; use core_crypto_keystore::CryptoKeystoreMls; +use tls_codec::Serialize; use crate::{ group_store::GroupStoreValue, prelude::{ id::ClientId, ConversationId, CoreCryptoCallbacks, CryptoError, CryptoResult, MlsCentral, MlsConversation, - MlsConversationConfiguration, MlsCredentialType, MlsCustomConfiguration, MlsError, MlsPublicGroupStateBundle, + MlsConversationConfiguration, MlsCredentialType, MlsCustomConfiguration, MlsError, MlsGroupInfoBundle, }, }; @@ -34,8 +36,8 @@ pub struct MlsConversationInitBundle { pub conversation_id: ConversationId, /// The external commit message pub commit: MlsMessageOut, - /// [`PublicGroupState`] (aka GroupInfo) which becomes valid when the external commit is accepted by the Delivery Service - pub public_group_state: MlsPublicGroupStateBundle, + /// [`GroupInfo`] (aka GroupInfo) which becomes valid when the external commit is accepted by the Delivery Service + pub group_info: MlsGroupInfoBundle, } impl MlsConversationInitBundle { @@ -43,9 +45,9 @@ impl MlsConversationInitBundle { /// 0 -> external commit /// 1 -> public group state #[allow(clippy::type_complexity)] - pub fn to_bytes_pair(self) -> CryptoResult<(Vec, MlsPublicGroupStateBundle)> { - let commit = self.commit.to_bytes().map_err(MlsError::from)?; - Ok((commit, self.public_group_state)) + pub fn to_bytes_pair(self) -> CryptoResult<(Vec, MlsGroupInfoBundle)> { + let commit = self.commit.tls_serialize_detached().map_err(MlsError::from)?; + Ok((commit, self.group_info)) } } @@ -63,8 +65,8 @@ impl MlsCentral { /// bad can happen if you forget to except some storage space wasted. /// /// # Arguments - /// * `group_state` - a verifiable public group state. it can be obtained by deserializing a TLS - /// serialized `PublicGroupState` object + /// * `group_info` - a GroupInfo wrapped in a MLS message. it can be obtained by deserializing a TLS + /// serialized `GroupInfo` object /// * `custom_cfg` - configuration of the MLS conversation fetched from the Delivery Service /// * `credential_type` - kind of [openmls::prelude::Credential] to use for joining this group. /// If [MlsCredentialType::Basic] is chosen and no Credential has been created yet for it, @@ -79,13 +81,19 @@ impl MlsCentral { /// Errors resulting from OpenMls, the KeyStore calls and serialization pub async fn join_by_external_commit( &mut self, - public_group_state: VerifiablePublicGroupState, + group_info: MlsMessageIn, custom_cfg: MlsCustomConfiguration, credential_type: MlsCredentialType, ) -> CryptoResult { let mls_client = self.mls_client.as_mut().ok_or(CryptoError::MlsNotInitialized)?; - let cs = public_group_state.ciphersuite().into(); + let group_info = match group_info.extract() { + MlsMessageInBody::GroupInfo(gi) => gi, + // TODO: proper error handling + _ => return Err(CryptoError::ImplementationError), + }; + + let cs = group_info.ciphersuite().into(); let cb = mls_client .get_or_create_credential_bundle(&self.mls_backend, cs, credential_type) .await?; @@ -96,28 +104,36 @@ impl MlsCentral { custom: custom_cfg, ..Default::default() }; - let (mut group, commit, pgs) = MlsGroup::join_by_external_commit( + + let (group, commit, group_info) = MlsGroup::join_by_external_commit( &self.mls_backend, + &cb.signature_key, None, - public_group_state, + group_info, &configuration.as_openmls_default_configuration()?, &[], - cb, + cb.to_mls_credential_with_key(), ) .await .map_err(MlsError::from)?; - let mut group_serialized = vec![]; - group.save(&mut group_serialized)?; - self.mls_backend .key_store() - .mls_pending_groups_save(group.group_id().as_slice(), &group_serialized, &serialized_cfg, None) + .mls_pending_groups_save( + group.group_id().as_slice(), + &core_crypto_keystore::ser(&group)?, + &serialized_cfg, + None, + ) .await?; + + // We should always have ratchet tree extension turned on hence GroupInfo should always be present + let group_info = group_info.ok_or(CryptoError::ImplementationError)?; + Ok(MlsConversationInitBundle { conversation_id: group.group_id().to_vec(), commit, - public_group_state: MlsPublicGroupStateBundle::try_new_full_plaintext(pgs)?, + group_info: MlsGroupInfoBundle::try_new_full_plaintext(group_info)?, }) } @@ -133,10 +149,17 @@ impl MlsCentral { // Retrieve the pending MLS group from the keystore let keystore = self.mls_backend.key_store(); let (group, cfg) = keystore.mls_pending_groups_load(id).await?; - let mut mls_group = MlsGroup::load(&mut &group[..])?; + + let mut mls_group = core_crypto_keystore::deser::(&group)?; + + // let group_id = GroupId::from_slice(&id); + // let mut mls_group = MlsGroup::load(&group_id, &self.mls_backend).await.ok_or(CryptoError::ImplementationError)?; // Merge it aka bring the MLS group to life and make it usable - mls_group.merge_pending_commit().map_err(MlsError::from)?; + mls_group + .merge_pending_commit(&self.mls_backend) + .await + .map_err(MlsError::from)?; // Restore the custom configuration and build a conversation from it let custom_cfg = serde_json::from_slice(&cfg).map_err(MlsError::MlsKeystoreSerializationError)?; @@ -176,17 +199,16 @@ impl MlsConversation { sender: ClientId, parent_conversation: Option>, callbacks: Option<&dyn CoreCryptoCallbacks>, - backend: &impl OpenMlsCrypto, ) -> CryptoResult<()> { // i.e. has this commit been created by [MlsCentral::join_by_external_commit] ? - let is_external_init = commit - .staged_proposal_queue() - .any(|p| matches!(p.sender(), Sender::NewMember) && matches!(p.proposal(), Proposal::ExternalInit(_))); + let is_external_init = commit.queued_proposals().any(|p| { + matches!(p.sender(), Sender::NewMemberCommit) && matches!(p.proposal(), Proposal::ExternalInit(_)) + }); if is_external_init { let callbacks = callbacks.ok_or(CryptoError::CallbacksNotSet)?; // first let's verify the sender belongs to an user already in the MLS group - let existing_clients = self.members_in_next_epoch(backend); + let existing_clients = self.members_in_next_epoch(); let parent_clients = if let Some(parent_conv) = parent_conversation { Some( parent_conv @@ -194,8 +216,7 @@ impl MlsConversation { .await .group .members() - .iter() - .map(|kp| kp.credential().identity().to_vec().into()) + .map(|kp| kp.credential.identity().to_vec().into()) .collect(), ) } else { @@ -251,7 +272,7 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group let MlsConversationInitBundle { @@ -259,7 +280,7 @@ mod tests { commit: external_commit, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); assert_eq!(group_id.as_slice(), &id); @@ -314,15 +335,15 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group bob_central - .join_by_external_commit(public_group_state.clone(), case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.clone().into(), case.custom_cfg(), case.credential_type) .await .unwrap(); // BUT for some reason the Delivery Service will reject this external commit - // e.g. another commit arrived meanwhile and the [PublicGroupState] is no longer valid + // e.g. another commit arrived meanwhile and the [GroupInfo] is no longer valid // Retrying let MlsConversationInitBundle { @@ -330,7 +351,7 @@ mod tests { commit: external_commit, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); assert_eq!(conversation_id.as_slice(), &id); @@ -368,13 +389,13 @@ mod tests { .await .unwrap(); - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // try to make an external join into Alice's group let MlsConversationInitBundle { commit: external_commit, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); @@ -411,15 +432,15 @@ mod tests { .invite(&id, &mut bob_central, case.custom_cfg()) .await .unwrap(); - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Alice can rejoin by external commit let alice_join = alice_central - .join_by_external_commit(public_group_state.clone(), case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.clone().into(), case.custom_cfg(), case.credential_type) .await; assert!(alice_join.is_ok()); // So can Bob let bob_join = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await; assert!(bob_join.is_ok()); }) @@ -450,7 +471,7 @@ mod tests { #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn join_by_external_commit_should_return_valid_public_group_state(case: TestCase) { + pub async fn join_by_external_commit_should_return_valid_group_info(case: TestCase) { run_test_with_client_ids( case.clone(), ["alice", "bob", "charlie"], @@ -463,15 +484,15 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group let MlsConversationInitBundle { commit: bob_external_commit, - public_group_state, + group_info, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); @@ -488,13 +509,13 @@ mod tests { assert_eq!(bob_central.get_conversation_unchecked(&id).await.members().len(), 2); assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_ok()); - // Now charlie wants to join with the [PublicGroupState] from Bob's external commit - let bob_pgs = public_group_state.get_pgs(); + // Now charlie wants to join with the [GroupInfo] from Bob's external commit + let bob_gi = group_info.get_payload(); let MlsConversationInitBundle { commit: charlie_external_commit, .. } = charlie_central - .join_by_external_commit(bob_pgs, case.custom_cfg(), case.credential_type) + .join_by_external_commit(bob_gi, case.custom_cfg(), case.credential_type) .await .unwrap(); @@ -546,11 +567,11 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group let MlsConversationInitBundle { commit, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); let alice_accepts_ext_commit = @@ -586,11 +607,11 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group let MlsConversationInitBundle { commit, .. } = bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); let alice_accepts_ext_commit = @@ -620,11 +641,11 @@ mod tests { .unwrap(); // export Alice group info - let public_group_state = alice_central.verifiable_public_group_state(&id).await; + let group_info = alice_central.get_group_info(&id).await; // Bob tries to join Alice's group bob_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info.into(), case.custom_cfg(), case.credential_type) .await .unwrap(); diff --git a/crypto/src/mls/external_proposal.rs b/crypto/src/mls/external_proposal.rs index 9e1c3a76be..e05d0950b5 100644 --- a/crypto/src/mls/external_proposal.rs +++ b/crypto/src/mls/external_proposal.rs @@ -1,8 +1,9 @@ use std::collections::HashSet; +use openmls::prelude::{JoinProposal, LeafNodeIndex}; use openmls::{ group::QueuedProposal, - prelude::{ExternalProposal, GroupEpoch, GroupId, KeyPackageRef, MlsMessageOut, OpenMlsCrypto, Proposal, Sender}, + prelude::{GroupEpoch, GroupId, MlsMessageOut, Proposal, Sender}, }; use crate::{ @@ -19,14 +20,13 @@ impl MlsConversation { proposal: &QueuedProposal, parent_conversation: Option>, callbacks: Option<&dyn CoreCryptoCallbacks>, - backend: &impl OpenMlsCrypto, ) -> CryptoResult<()> { - let is_external_proposal = matches!(proposal.sender(), Sender::External(_) | Sender::NewMember); + let is_external_proposal = matches!(proposal.sender(), Sender::External(_) | Sender::NewMemberProposal); if is_external_proposal { if let Proposal::Add(add_proposal) = proposal.proposal() { let callbacks = callbacks.ok_or(CryptoError::CallbacksNotSet)?; - let existing_clients = self.members_in_next_epoch(backend); - let self_identity = add_proposal.key_package().credential().identity(); + let existing_clients = self.members_in_next_epoch(); + let self_identity = add_proposal.key_package().leaf_node().credential().identity(); let parent_clients = if let Some(parent_conv) = parent_conversation { Some( parent_conv @@ -34,8 +34,7 @@ impl MlsConversation { .await .group .members() - .iter() - .map(|kp| kp.credential().identity().to_vec().into()) + .map(|kp| kp.credential.identity().to_vec().into()) .collect(), ) } else { @@ -58,15 +57,14 @@ impl MlsConversation { } /// Get actual group members and subtract pending remove proposals - pub fn members_in_next_epoch(&self, backend: &impl OpenMlsCrypto) -> Vec { + pub fn members_in_next_epoch(&self) -> Vec { let pending_removals = self.pending_removals(); let existing_clients = self .group .members() - .into_iter() .filter_map(|kp| { - if !pending_removals.contains(&&kp.hash_ref(backend).ok()?) { - Some(kp.credential().identity().into()) + if !pending_removals.contains(&kp.index) { + Some(kp.credential.identity().into()) } else { None } @@ -76,7 +74,7 @@ impl MlsConversation { } /// Gather pending remove proposals - fn pending_removals(&self) -> Vec<&KeyPackageRef> { + fn pending_removals(&self) -> Vec { self.group .pending_proposals() .filter_map(|proposal| match proposal.proposal() { @@ -102,7 +100,7 @@ impl MlsCentral { /// /// # Errors /// Errors resulting from the creation of the proposal within OpenMls. - /// Fails when [credential_type] is [MlsCredentialType::X509] and no Credential has been created + /// Fails when `credential_type` is [MlsCredentialType::X509] and no Credential has been created /// for it beforehand with [MlsCentral::e2ei_mls_init] or variants. pub async fn new_external_add_proposal( &self, @@ -111,7 +109,7 @@ impl MlsCentral { ciphersuite: MlsCiphersuite, credential_type: MlsCredentialType, ) -> CryptoResult { - let mls_client = self.mls_client.as_ref().ok_or(CryptoError::MlsNotInitialized)?; + let mls_client = self.mls_client()?; let group_id = GroupId::from_slice(&conversation_id[..]); @@ -121,60 +119,16 @@ impl MlsCentral { .generate_keypackage_from_credential_bundle(&self.mls_backend, cb, ciphersuite) .await?; - let ext_proposal = - ExternalProposal::new_add(kp, group_id, epoch, cb, &self.mls_backend).map_err(MlsError::from)?; - Ok(ext_proposal) - } - - /// Crafts a new external Remove proposal. Enables a client outside a group to request removal - /// of a client within the group. - /// - /// # Arguments - /// * `conversation_id` - the group/conversation id - /// * `epoch` - the current epoch of the group. See [openmls::group::GroupEpoch][GroupEpoch] - /// * `key_package_ref` - the `KeyPackageRef` of the client to be added to the group - /// - /// # Return type - /// Returns a message with the proposal to be remove a client - /// - /// # Errors - /// Errors resulting from the creation of the proposal within OpenMls - // TODO: we might not need this after all so let's not complicate things - pub async fn new_external_remove_proposal( - &self, - conversation_id: ConversationId, - epoch: GroupEpoch, - key_package_ref: KeyPackageRef, - ciphersuite: MlsCiphersuite, - credential_type: MlsCredentialType, - ) -> CryptoResult { - let mls_client = self.mls_client.as_ref().ok_or(CryptoError::MlsNotInitialized)?; - - let cb = mls_client.find_credential_bundle(ciphersuite, credential_type)?; - // TODO: maybe we should delete this CredentialBundle when this External Remove Proposal is merged. - - let sender_index = 0; - let group_id = GroupId::from_slice(&conversation_id[..]); - let ext_proposal = - ExternalProposal::new_remove(key_package_ref, group_id, epoch, cb, sender_index, &self.mls_backend) - .map_err(MlsError::from)?; + let ext_proposal = JoinProposal::new(kp, group_id, epoch, &cb.signature_key).map_err(MlsError::from)?; Ok(ext_proposal) } } #[cfg(test)] mod tests { - use openmls::prelude::{OpenMlsCryptoProvider, SignaturePublicKey, UnverifiedMessageError}; use wasm_bindgen_test::*; - use crate::{ - prelude::{ - handshake::MlsCommitBundle, CryptoError, MlsConversationCreationMessage, MlsConversationInitBundle, - MlsError, - }, - test_utils::*, - }; - use tls_codec::Serialize; + use crate::{prelude::handshake::MlsCommitBundle, test_utils::*}; wasm_bindgen_test_configure!(run_in_browser); @@ -221,7 +175,7 @@ mod tests { assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 2); guest_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); assert_eq!(guest_central.get_conversation_unchecked(&id).await.members().len(), 2); @@ -233,425 +187,4 @@ mod tests { .await } } - - mod remove { - use super::*; - use openmls_traits::types::SignatureScheme; - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - async fn ds_should_remove_guest_from_conversation(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["owner", "guest", "ds"], - move |[mut owner_central, mut guest_central, ds]| { - Box::pin(async move { - // TODO since wire-server only sends ed25519 removal keys without metadata we cannot currently support other signature schemes - if case.ciphersuite().0.signature_algorithm() == SignatureScheme::ED25519 { - let id = conversation_id(); - - let remove_key = ds.client_signature_key(&case).as_slice().to_vec(); - let mut cfg = case.cfg.clone(); - cfg.set_raw_external_senders(vec![remove_key]); - owner_central - .new_conversation(id.clone(), case.credential_type, cfg) - .await - .unwrap(); - - owner_central - .invite(&id, &mut guest_central, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 2); - - // now, as e.g. a Delivery Service, let's create an external remove proposal - // and kick guest out of the conversation - let guest_kp = guest_central.key_package_of(&id, guest_central.read_client_id()).await; - let guest_kp_ref = guest_kp.hash_ref(guest_central.mls_backend.crypto()).unwrap(); - let ext_remove_proposal = ds - .new_external_remove_proposal( - id.clone(), - owner_central.get_conversation_unchecked(&id).await.group.epoch(), - guest_kp_ref, - case.ciphersuite(), - case.credential_type, - ) - .await - .unwrap(); - - owner_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - guest_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - let MlsCommitBundle { commit, .. } = - owner_central.commit_pending_proposals(&id).await.unwrap().unwrap(); - // before merging, commit is not applied - assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 2); - owner_central.commit_accepted(&id).await.unwrap(); - assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 1); - - // guest can no longer participate - guest_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - assert!(guest_central.get_conversation(&id).await.is_err()); - assert!(guest_central.try_talk_to(&id, &mut owner_central).await.is_err()); - } - }) - }, - ) - .await - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - async fn should_fail_when_invalid_external_sender(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["owner", "guest", "ds", "attacker"], - move |[mut owner_central, mut guest_central, ds, attacker]| { - Box::pin(async move { - let id = conversation_id(); - // Delivery service key is used in the group.. - let remove_key = ds.client_signature_key(&case).as_slice().to_vec(); - let mut cfg = case.cfg.clone(); - cfg.set_raw_external_senders(vec![remove_key]); - owner_central - .new_conversation(id.clone(), case.credential_type, cfg) - .await - .unwrap(); - - owner_central - .invite(&id, &mut guest_central, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 2); - - // now, attacker will try to remove guest from the group, and should fail - let guest_kp = guest_central.key_package_of(&id, guest_central.read_client_id()).await; - let guest_kp_ref = guest_kp.hash_ref(guest_central.mls_backend.crypto()).unwrap(); - let ext_remove_proposal = attacker - .new_external_remove_proposal( - id.clone(), - owner_central.get_conversation_unchecked(&id).await.group.epoch(), - guest_kp_ref, - case.ciphersuite(), - case.credential_type, - ) - .await - .unwrap(); - - let owner_decrypt = owner_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(matches!( - owner_decrypt.unwrap_err(), - CryptoError::MlsError(MlsError::MlsUnverifiedMessageError( - UnverifiedMessageError::InvalidSignature - )) - )); - - let guest_decrypt = guest_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(matches!( - guest_decrypt.unwrap_err(), - CryptoError::MlsError(MlsError::MlsUnverifiedMessageError( - UnverifiedMessageError::InvalidSignature - )) - )); - }) - }, - ) - .await - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - async fn should_fail_when_invalid_remove_key(case: TestCase) { - run_test_with_client_ids( - case.clone(), - ["owner", "guest", "ds"], - move |[mut owner_central, mut guest_central, ds]| { - Box::pin(async move { - let id = conversation_id(); - - let remove_key = ds.client_signature_key(&case); - let short_remove_key = - SignaturePublicKey::new(remove_key.as_slice()[1..].to_vec(), remove_key.signature_scheme()) - .unwrap(); - let short_remove_key = short_remove_key.tls_serialize_detached().unwrap(); - let mut cfg = case.cfg.clone(); - cfg.set_raw_external_senders(vec![short_remove_key.as_slice().to_vec()]); - owner_central - .new_conversation(id.clone(), case.credential_type, cfg) - .await - .unwrap(); - - owner_central - .invite(&id, &mut guest_central, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(owner_central.get_conversation_unchecked(&id).await.members().len(), 2); - - // now, as e.g. a Delivery Service, let's create an external remove proposal - // and kick guest out of the conversation - let guest_kp = guest_central.key_package_of(&id, guest_central.read_client_id()).await; - let guest_kp_ref = guest_kp.hash_ref(guest_central.mls_backend.crypto()).unwrap(); - let ext_remove_proposal = ds - .new_external_remove_proposal( - id.clone(), - owner_central.get_conversation_unchecked(&id).await.group.epoch(), - guest_kp_ref, - case.ciphersuite(), - case.credential_type, - ) - .await - .unwrap(); - - let owner_decrypt = owner_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(matches!( - owner_decrypt.unwrap_err(), - CryptoError::MlsError(MlsError::MlsUnverifiedMessageError( - UnverifiedMessageError::InvalidSignature - )) - )); - - let guest_decrypt = guest_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(matches!( - guest_decrypt.unwrap_err(), - CryptoError::MlsError(MlsError::MlsUnverifiedMessageError( - UnverifiedMessageError::InvalidSignature - )) - )); - }) - }, - ) - .await - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - async fn joiners_from_welcome_can_accept_external_remove_proposals(case: TestCase) { - // TODO since wire-server only sends ed25519 removal keys without metadata we cannot currently support other signature schemes - if case.ciphersuite().0.signature_algorithm() == SignatureScheme::ED25519 { - run_test_with_client_ids( - case.clone(), - ["alice", "bob", "charlie", "ds"], - move |[mut alice_central, mut bob_central, mut charlie_central, ds]| { - Box::pin(async move { - let id = conversation_id(); - - let remove_key = ds.client_signature_key(&case).as_slice().to_vec(); - let mut cfg = case.cfg.clone(); - cfg.set_raw_external_senders(vec![remove_key]); - alice_central - .new_conversation(id.clone(), case.credential_type, cfg) - .await - .unwrap(); - - alice_central - .invite(&id, &mut bob_central, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); - - // Charlie joins through a Welcome and should get external_senders from Welcome - // message and not from configuration - let charlie = charlie_central.rand_member().await; - let MlsConversationCreationMessage { welcome, commit, .. } = alice_central - .add_members_to_conversation(&id, &mut [charlie]) - .await - .unwrap(); - alice_central.commit_accepted(&id).await.unwrap(); - bob_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - // Purposely have a configuration without `external_senders` - charlie_central - .process_welcome_message(welcome, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(charlie_central.get_conversation_unchecked(&id).await.members().len(), 3); - assert!(charlie_central.try_talk_to(&id, &mut alice_central).await.is_ok()); - assert!(charlie_central.try_talk_to(&id, &mut bob_central).await.is_ok()); - - // now, as e.g. a Delivery Service, let's create an external remove proposal - // and kick Bob out of the conversation - let bob_kp = bob_central.key_package_of(&id, bob_central.read_client_id()).await; - let bob_kp_ref = bob_kp.hash_ref(bob_central.mls_backend.crypto()).unwrap(); - let ext_remove_proposal = ds - .new_external_remove_proposal( - id.clone(), - alice_central.get_conversation_unchecked(&id).await.group.epoch(), - bob_kp_ref, - case.ciphersuite(), - case.credential_type, - ) - .await - .unwrap(); - - // joiner from Welcome should be able to verify the external remove proposal since - // it has fetched back the external_sender from Welcome - let charlie_can_verify_ext_proposal = charlie_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(charlie_can_verify_ext_proposal.is_ok()); - - alice_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - bob_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - - let commit = charlie_central - .commit_pending_proposals(&id) - .await - .unwrap() - .unwrap() - .commit; - charlie_central.commit_accepted(&id).await.unwrap(); - assert_eq!(charlie_central.get_conversation_unchecked(&id).await.members().len(), 2); - - alice_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); - bob_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - assert!(alice_central.try_talk_to(&id, &mut charlie_central).await.is_ok()); - assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_err()); - }) - }, - ) - .await - } - } - - #[apply(all_cred_cipher)] - #[wasm_bindgen_test] - async fn joiners_from_external_commit_can_accept_external_remove_proposals(case: TestCase) { - // TODO since wire-server only sends ed25519 removal keys without metadata we cannot currently support other signature schemes - if case.ciphersuite().0.signature_algorithm() == SignatureScheme::ED25519 { - run_test_with_client_ids( - case.clone(), - ["alice", "bob", "charlie", "ds"], - move |[mut alice_central, mut bob_central, mut charlie_central, ds]| { - Box::pin(async move { - let id = conversation_id(); - - let remove_key = ds.client_signature_key(&case).as_slice().to_vec(); - let mut cfg = case.cfg.clone(); - cfg.set_raw_external_senders(vec![remove_key]); - alice_central - .new_conversation(id.clone(), case.credential_type, cfg) - .await - .unwrap(); - - alice_central - .invite(&id, &mut bob_central, case.custom_cfg()) - .await - .unwrap(); - assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); - - // Charlie joins through an external commit and should get external_senders - // from PGS and not from configuration - let public_group_state = alice_central.verifiable_public_group_state(&id).await; - let MlsConversationInitBundle { commit, .. } = charlie_central - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) - .await - .unwrap(); - // Purposely have a configuration without `external_senders` - charlie_central - .merge_pending_group_from_external_commit(&id) - .await - .unwrap(); - - alice_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - bob_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - - assert_eq!(charlie_central.get_conversation_unchecked(&id).await.members().len(), 3); - assert!(charlie_central.try_talk_to(&id, &mut alice_central).await.is_ok()); - assert!(charlie_central.try_talk_to(&id, &mut bob_central).await.is_ok()); - - // now, as e.g. a Delivery Service, let's create an external remove proposal - // and kick Bob out of the conversation - let bob_kp = bob_central.key_package_of(&id, bob_central.read_client_id()).await; - let bob_kp_ref = bob_kp.hash_ref(bob_central.mls_backend.crypto()).unwrap(); - let ext_remove_proposal = ds - .new_external_remove_proposal( - id.clone(), - alice_central.get_conversation_unchecked(&id).await.group.epoch(), - bob_kp_ref, - case.ciphersuite(), - case.credential_type, - ) - .await - .unwrap(); - - // joiner from external commit should be able to verify the external remove proposal - // since it has fetched back the external_sender from external commit - let charlie_can_verify_ext_proposal = charlie_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await; - assert!(charlie_can_verify_ext_proposal.is_ok()); - - alice_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - bob_central - .decrypt_message(&id, ext_remove_proposal.to_bytes().unwrap()) - .await - .unwrap(); - - let commit = charlie_central - .commit_pending_proposals(&id) - .await - .unwrap() - .unwrap() - .commit; - charlie_central.commit_accepted(&id).await.unwrap(); - assert_eq!(charlie_central.get_conversation_unchecked(&id).await.members().len(), 2); - - alice_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); - bob_central - .decrypt_message(&id, commit.to_bytes().unwrap()) - .await - .unwrap(); - assert!(alice_central.try_talk_to(&id, &mut charlie_central).await.is_ok()); - assert!(alice_central.try_talk_to(&id, &mut bob_central).await.is_err()); - }) - }, - ) - .await - } - } - } } diff --git a/crypto/src/mls/member.rs b/crypto/src/mls/member.rs index 52ba1c6674..2a13eedf9c 100644 --- a/crypto/src/mls/member.rs +++ b/crypto/src/mls/member.rs @@ -14,9 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. +use openmls_traits::OpenMlsCryptoProvider; use std::collections::HashMap; -use openmls::prelude::KeyPackage; +use mls_crypto_provider::MlsCryptoProvider; +use openmls::prelude::{KeyPackage, KeyPackageIn}; use tls_codec::Deserialize; use crate::{ @@ -41,9 +43,11 @@ impl ConversationMember { /// /// # Errors /// Deserialization errors - pub fn new_raw(client_id: ClientId, kp_ser: Vec) -> CryptoResult { - use openmls::prelude::TlsDeserializeTrait as _; - let kp = KeyPackage::tls_deserialize(&mut &kp_ser[..]).map_err(MlsError::from)?; + pub fn new_raw(client_id: ClientId, kp_ser: Vec, backend: &MlsCryptoProvider) -> CryptoResult { + let kp = KeyPackageIn::tls_deserialize_bytes(kp_ser).map_err(MlsError::from)?; + let kp = kp + .validate(backend.crypto(), openmls::versions::ProtocolVersion::Mls10) + .map_err(MlsError::from)?; Ok(Self { id: client_id.to_vec(), @@ -87,9 +91,12 @@ impl ConversationMember { /// /// # Errors /// Deserialization errors - pub fn add_keypackage(&mut self, kp: Vec) -> CryptoResult<()> { - let kp = KeyPackage::tls_deserialize(&mut &kp[..]).map_err(MlsError::from)?; - let cid = ClientId::from(kp.credential().identity()); + pub fn add_keypackage(&mut self, kp: Vec, backend: &MlsCryptoProvider) -> CryptoResult<()> { + let kp = KeyPackageIn::tls_deserialize_bytes(kp).map_err(MlsError::from)?; + let kp = kp + .validate(backend.crypto(), openmls::versions::ProtocolVersion::Mls10) + .map_err(MlsError::from)?; + let cid = ClientId::from(kp.leaf_node().credential().identity()); self.clients.entry(cid).or_insert_with(Vec::new).push(kp); Ok(()) } @@ -128,7 +135,7 @@ impl ConversationMember { pub fn random_generate_clientless( case: &crate::test_utils::TestCase, backend: &mls_crypto_provider::MlsCryptoProvider, - ) -> CryptoResult<(Self, openmls::prelude::CredentialBundle)> { + ) -> CryptoResult<(Self, crate::mls::credential::CredentialBundle)> { let (credential, client_id) = match case.credential_type { crate::prelude::MlsCredentialType::Basic => { let user_uuid = uuid::Uuid::new_v4(); @@ -166,13 +173,15 @@ impl ConversationMember { #[cfg(test)] pub mod tests { - use openmls::prelude::*; use wasm_bindgen_test::wasm_bindgen_test; use mls_crypto_provider::MlsCryptoProvider; - use crate::prelude::key_package::INITIAL_KEYING_MATERIAL_COUNT; - use crate::{mls::ClientId, test_utils::*}; + use crate::{ + prelude::{key_package::INITIAL_KEYING_MATERIAL_COUNT, ClientId}, + test_utils::*, + }; + use openmls::prelude::{CryptoConfig, KeyPackage}; use super::ConversationMember; @@ -206,18 +215,20 @@ pub mod tests { #[wasm_bindgen_test] pub async fn add_valid_keypackage_should_add_it_to_client(case: TestCase) { let backend = MlsCryptoProvider::try_new_in_memory("test").await.unwrap(); - let (mut member, credential) = ConversationMember::random_generate_clientless(&case, &backend).unwrap(); + let (mut member, cb) = ConversationMember::random_generate_clientless(&case, &backend).unwrap(); let cid = ClientId::from(member.id.as_slice()); - let (kp, _) = KeyPackageBundle::new(&[case.ciphersuite().0], &credential, &backend, vec![]) - .unwrap() - .into_parts(); + let cfg = CryptoConfig::with_default_version(case.ciphersuite().into()); + let kp = KeyPackage::builder() + .build(cfg, &backend, &cb.signature_key.clone(), cb.into()) + .await + .unwrap(); let mut kp_raw = vec![]; use tls_codec::Serialize as _; kp.tls_serialize(&mut kp_raw).unwrap(); assert!(member.clients.get(&cid).is_none()); - assert!(member.add_keypackage(kp_raw).is_ok()); + assert!(member.add_keypackage(kp_raw, &backend).is_ok()); assert!(member.clients.get(&cid).is_some()); } @@ -227,7 +238,7 @@ pub mod tests { let backend = MlsCryptoProvider::try_new_in_memory("test").await.unwrap(); let (mut member, _) = ConversationMember::random_generate_clientless(&case, &backend).unwrap(); let previous_clients = member.clients.clone(); - assert!(member.add_keypackage(b"invalid-keypackage".to_vec()).is_err()); + assert!(member.add_keypackage(b"invalid-keypackage".to_vec(), &backend).is_err()); // ensure clients are not altered in the process assert_eq!(member.clients, previous_clients); } diff --git a/crypto/src/mls/mod.rs b/crypto/src/mls/mod.rs index ee8103632e..2be2d5923d 100644 --- a/crypto/src/mls/mod.rs +++ b/crypto/src/mls/mod.rs @@ -1,14 +1,14 @@ -use openmls::prelude::{Ciphersuite, KeyPackage, Welcome}; +use crate::prelude::CiphersuiteName; +use openmls::prelude::{Ciphersuite, KeyPackage, MlsMessageIn, MlsMessageInBody}; use openmls_traits::OpenMlsCryptoProvider; use tls_codec::{Deserialize, Serialize}; use mls_crypto_provider::{MlsCryptoProvider, MlsCryptoProviderConfiguration}; use crate::prelude::{ - config::{MlsConversationConfiguration, MlsCustomConfiguration}, - identifier::ClientIdentifier, - CiphersuiteName, Client, ClientId, ConversationId, CoreCryptoCallbacks, CryptoError, CryptoResult, - MlsCentralConfiguration, MlsConversation, MlsCredentialType, MlsError, + identifier::ClientIdentifier, Client, ClientId, ConversationId, CoreCryptoCallbacks, CryptoError, CryptoResult, + MlsCentralConfiguration, MlsConversation, MlsConversationConfiguration, MlsCredentialType, MlsCustomConfiguration, + MlsError, }; pub(crate) mod client; @@ -239,7 +239,7 @@ impl MlsCentral { }) } - /// Same as the [crate::MlsCentral::try_new] but instead, it uses an in memory KeyStore. Although required, the `store_path` parameter from the `MlsCentralConfiguration` won't be used here. + /// Same as the [MlsCentral::try_new] but instead, it uses an in memory KeyStore. Although required, the `store_path` parameter from the `MlsCentralConfiguration` won't be used here. pub async fn try_new_in_memory(configuration: MlsCentralConfiguration) -> CryptoResult { let mls_backend = MlsCryptoProvider::try_new_with_configuration(MlsCryptoProviderConfiguration { db_path: &configuration.store_path, @@ -270,8 +270,8 @@ impl MlsCentral { }) } - /// Initializes the MLS client if [CoreCrypto] has previously been initialized with - /// [CoreCrypto::deferred_init] instead of [CoreCrypto::new]. + /// Initializes the MLS client if [super::CoreCrypto] has previously been initialized with + /// `CoreCrypto::deferred_init` instead of `CoreCrypto::new`. /// This should stay as long as proteus is supported. Then it should be removed. pub async fn mls_init( &mut self, @@ -291,7 +291,7 @@ impl MlsCentral { /// This method is designed to be used in conjunction with [MlsCentral::mls_init_with_client_id] and represents the first step in this process. /// /// This returns the TLS-serialized identity keys (i.e. the signature keypair's public key) - pub async fn mls_generate_keypairs(&self, ciphersuites: Vec) -> CryptoResult>> { + pub async fn mls_generate_keypairs(&self, ciphersuites: Vec) -> CryptoResult> { if self.mls_client.is_some() { // prevents wrong usage of the method instead of silently hiding the mistake return Err(CryptoError::ImplementationError); @@ -306,7 +306,7 @@ impl MlsCentral { pub async fn mls_init_with_client_id( &mut self, client_id: ClientId, - signature_public_keys: Vec>, + tmp_client_ids: Vec, ciphersuites: Vec, ) -> CryptoResult<()> { if self.mls_client.is_some() { @@ -315,8 +315,7 @@ impl MlsCentral { } let mls_client = - Client::init_with_external_client_id(client_id, signature_public_keys, &ciphersuites, &self.mls_backend) - .await?; + Client::init_with_external_client_id(client_id, tmp_client_ids, &ciphersuites, &self.mls_backend).await?; self.mls_client = Some(mls_client); Ok(()) @@ -372,22 +371,17 @@ impl MlsCentral { /// # Arguments /// * `ciphersuite` - a callback to be called to perform authorization pub fn client_public_key(&self, ciphersuite: MlsCiphersuite) -> CryptoResult> { - let mls_client = self.mls_client.as_ref().ok_or(CryptoError::MlsNotInitialized)?; + let mls_client = self.mls_client()?; let cb = mls_client.find_credential_bundle(ciphersuite, MlsCredentialType::Basic)?; - Ok(cb.credential().signature_key().as_slice().to_vec()) + Ok(cb.signature_key.to_public_vec()) } /// Returns the client's id as a buffer pub fn client_id(&self) -> CryptoResult { - Ok(self - .mls_client - .as_ref() - .ok_or(CryptoError::MlsNotInitialized)? - .id() - .clone()) + Ok(self.mls_client()?.id().clone()) } - /// Returns `amount_requested` OpenMLS [`KeyPackageBundle`]s. + /// Returns `amount_requested` OpenMLS [openmls::key_packages::KeyPackage]s. /// Will always return the requested amount as it will generate the necessary (lacking) amount on-the-fly /// /// Note: Keypackage pruning is performed as a first step @@ -405,17 +399,14 @@ impl MlsCentral { ciphersuite: MlsCiphersuite, amount_requested: usize, ) -> CryptoResult> { - let mls_client = self.mls_client.as_ref().ok_or(CryptoError::MlsNotInitialized)?; - mls_client + self.mls_client()? .request_key_packages(amount_requested, ciphersuite, &self.mls_backend) .await } /// Returns the count of valid, non-expired, unclaimed keypackages in store for the given [MlsCiphersuite] pub async fn client_valid_key_packages_count(&self, ciphersuite: MlsCiphersuite) -> CryptoResult { - self.mls_client - .as_ref() - .ok_or(CryptoError::MlsNotInitialized)? + self.mls_client()? .valid_keypackages_count(&self.mls_backend, ciphersuite) .await } @@ -429,7 +420,7 @@ impl MlsCentral { /// * `config` - configuration of the group/conversation /// /// # Errors - /// Errors can happen from the KeyStore or from OpenMls for ex if no [KeyPackageBundle] can + /// Errors can happen from the KeyStore or from OpenMls for ex if no [openmls::key_packages::KeyPackage] can /// be found in the KeyStore pub async fn new_conversation( &mut self, @@ -490,17 +481,21 @@ impl MlsCentral { /// /// # Errors /// Errors can be originating from the KeyStore of from OpenMls: - /// * if no [KeyPackageBundle] can be read from the KeyStore + /// * if no [openmls::key_packages::KeyPackage] can be read from the KeyStore /// * if the message can't be decrypted pub async fn process_welcome_message( &mut self, - welcome: Welcome, + welcome: MlsMessageIn, custom_cfg: MlsCustomConfiguration, ) -> CryptoResult { let configuration = MlsConversationConfiguration { custom: custom_cfg, ..Default::default() }; + let welcome = match welcome.extract() { + MlsMessageInBody::Welcome(welcome) => welcome, + _ => return Err(CryptoError::ImplementationError), + }; let conversation = MlsConversation::from_welcome_message(welcome, configuration, &self.mls_backend).await?; let conversation_id = conversation.id.clone(); self.mls_groups.insert(conversation_id.clone(), conversation); @@ -525,7 +520,7 @@ impl MlsCentral { custom_cfg: MlsCustomConfiguration, ) -> CryptoResult { let mut cursor = std::io::Cursor::new(welcome); - let welcome = Welcome::tls_deserialize(&mut cursor).map_err(MlsError::from)?; + let welcome = MlsMessageIn::tls_deserialize(&mut cursor).map_err(MlsError::from)?; self.process_welcome_message(welcome, custom_cfg).await } @@ -536,19 +531,28 @@ impl MlsCentral { /// * `message` - the encrypted message as a byte array /// /// # Return type - /// A TLS serialized byte array of the `PublicGroupState` + /// A TLS serialized byte array of the `GroupInfo` /// /// # Errors /// If the conversation can't be found, an error will be returned. Other errors are originating /// from OpenMls and serialization - pub async fn export_public_group_state(&mut self, conversation_id: &ConversationId) -> CryptoResult> { + pub async fn export_group_info(&mut self, conversation_id: &ConversationId) -> CryptoResult> { let conversation = self.get_conversation(conversation_id).await?; + let conversation_lock = conversation.read().await; + let group = &conversation_lock.group; + let cs = group.ciphersuite(); + // TODO: proper error + let ct = group + .own_leaf() + .ok_or(CryptoError::ImplementationError)? + .credential() + .credential_type(); + let cb = self.mls_client()?.find_credential_bundle(cs.into(), ct.into())?; let state = conversation .read() .await .group - .export_public_group_state(&self.mls_backend) - .await + .export_group_info(&self.mls_backend, &cb.signature_key, true) .map_err(MlsError::from)?; Ok(state.tls_serialize_detached().map_err(MlsError::from)?) @@ -774,7 +778,7 @@ pub mod tests { .unwrap(); let mut central = MlsCentral::try_new(configuration.clone()).await.unwrap(); - central.mls_init(cid, vec![case.ciphersuite()]).await.unwrap(); + central.mls_init(cid.clone(), vec![case.ciphersuite()]).await.unwrap(); let id = conversation_id(); let _ = central .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) @@ -782,6 +786,7 @@ pub mod tests { central.close().await.unwrap(); let mut central = MlsCentral::try_new(configuration).await.unwrap(); + central.mls_init(cid, vec![case.ciphersuite()]).await.unwrap(); let _ = central.encrypt_message(&id, b"Test").await.unwrap(); central.mls_backend.destroy_and_reset().await.unwrap(); @@ -820,7 +825,7 @@ pub mod tests { .unwrap(); let mut alice_central = MlsCentral::try_new(alice_cfg.clone()).await.unwrap(); alice_central - .mls_init(alice_cid, vec![case.ciphersuite()]) + .mls_init(alice_cid.clone(), vec![case.ciphersuite()]) .await .unwrap(); @@ -846,6 +851,10 @@ pub mod tests { // Create another central which will be desynchronized at some point let mut alice_central_mirror = MlsCentral::try_new(alice_cfg).await.unwrap(); + alice_central_mirror + .mls_init(alice_cid, vec![case.ciphersuite()]) + .await + .unwrap(); assert!(alice_central_mirror.try_talk_to(&id, &mut bob_central).await.is_ok()); // alice original instance will update its key without synchronizing with its mirror diff --git a/crypto/src/mls/proposal.rs b/crypto/src/mls/proposal.rs index 0f26b8bcdf..94ba525430 100644 --- a/crypto/src/mls/proposal.rs +++ b/crypto/src/mls/proposal.rs @@ -14,27 +14,29 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use openmls::prelude::KeyPackage; -use openmls_traits::OpenMlsCryptoProvider; +use openmls::prelude::{hash_ref::ProposalRef, KeyPackage}; use mls_crypto_provider::MlsCryptoProvider; use crate::{ mls::{ClientId, ConversationId, MlsCentral, MlsConversation}, - prelude::handshake::MlsProposalBundle, - CryptoError, CryptoResult, MlsError, + prelude::{handshake::MlsProposalBundle, Client, CryptoError, CryptoResult}, }; /// Abstraction over a [openmls::prelude::hash_ref::ProposalRef] to deal with conversions -#[derive(Debug, Copy, Clone, Eq, PartialEq, derive_more::From, derive_more::Deref, derive_more::Display)] -pub struct MlsProposalRef(openmls::prelude::hash_ref::ProposalRef); +#[derive(Debug, Clone, Eq, PartialEq, derive_more::From, derive_more::Deref, derive_more::Display)] +pub struct MlsProposalRef(ProposalRef); -impl TryFrom> for MlsProposalRef { - type Error = CryptoError; +impl From> for MlsProposalRef { + fn from(value: Vec) -> Self { + Self(ProposalRef::from_slice(value.as_slice())) + } +} - fn try_from(value: Vec) -> Result { - let value: [u8; 16] = value.try_into().map_err(|_| Self::Error::InvalidHashReference)?; - Ok(Self(value.into())) +impl MlsProposalRef { + /// Duh + pub fn into_inner(self) -> ProposalRef { + self.0 } } @@ -60,21 +62,21 @@ impl MlsProposal { /// Creates a new proposal within the specified `MlsGroup` async fn create( self, + client: &Client, backend: &MlsCryptoProvider, mut conversation: impl std::ops::DerefMut, ) -> CryptoResult { let proposal = match self { - MlsProposal::Add(key_package) => (*conversation).propose_add_member(backend, &key_package).await, - MlsProposal::Update => (*conversation).propose_self_update(backend).await, + MlsProposal::Add(key_package) => (*conversation).propose_add_member(client, backend, &key_package).await, + MlsProposal::Update => (*conversation).propose_self_update(client, backend).await, MlsProposal::Remove(client_id) => { - let href = conversation + let index = conversation .group .members() - .into_iter() - .find(|kp| kp.credential().identity() == client_id.as_slice()) + .find(|kp| kp.credential.identity() == client_id.as_slice()) .ok_or(CryptoError::ClientNotFound(client_id)) - .and_then(|kp| Ok(kp.hash_ref(backend.crypto()).map_err(MlsError::from)?))?; - (*conversation).propose_remove_member(backend, &href).await + .map(|kp| kp.index)?; + (*conversation).propose_remove_member(client, backend, index).await } }?; Ok(proposal) @@ -89,7 +91,7 @@ impl MlsCentral { /// * `proposal` - the proposal do be added in the group /// /// # Return type - /// A [ProposalBundle] with the proposal in a Mls message and a reference to that proposal in order to rollback it if required + /// A [MlsProposalBundle] with the proposal in a Mls message and a reference to that proposal in order to rollback it if required /// /// # Errors /// If the conversation is not found, an error will be returned. Errors from OpenMls can be @@ -100,7 +102,10 @@ impl MlsCentral { proposal: MlsProposal, ) -> CryptoResult { let conversation = self.get_conversation(conversation).await?; - proposal.create(&self.mls_backend, conversation.write().await).await + let client = self.mls_client()?; + proposal + .create(client, &self.mls_backend, conversation.write().await) + .await } } @@ -137,7 +142,7 @@ pub mod proposal_tests { alice_central.commit_accepted(&id).await.unwrap(); assert_eq!(alice_central.get_conversation_unchecked(&id).await.members().len(), 2); let new_id = bob_central - .process_welcome_message(welcome.unwrap(), case.custom_cfg()) + .process_welcome_message(welcome.unwrap().into(), case.custom_cfg()) .await .unwrap(); assert_eq!(id, new_id); @@ -151,10 +156,11 @@ pub mod proposal_tests { pub mod update { use super::*; + use itertools::Itertools; #[apply(all_cred_cipher)] #[wasm_bindgen_test] - pub async fn should_update_key_package(case: TestCase) { + pub async fn should_update_hpke_key(case: TestCase) { run_test_with_central(case.clone(), move |[mut central]| { Box::pin(async move { let id = conversation_id(); @@ -162,25 +168,21 @@ pub mod proposal_tests { .new_conversation(id.clone(), case.credential_type, case.cfg.clone()) .await .unwrap(); - let before = &(*central + let before = central .get_conversation_unchecked(&id) .await - .group - .members() - .first() - .unwrap()) - .clone(); + .encryption_keys() + .find_or_first(|_| true) + .unwrap(); central.new_proposal(&id, MlsProposal::Update).await.unwrap(); central.commit_pending_proposals(&id).await.unwrap(); central.commit_accepted(&id).await.unwrap(); - let after = &(*central + let after = central .get_conversation_unchecked(&id) .await - .group - .members() - .first() - .unwrap()) - .clone(); + .encryption_keys() + .find_or_first(|_| true) + .unwrap(); assert_ne!(before, after) }) }) diff --git a/crypto/src/proteus.rs b/crypto/src/proteus.rs index 1e931d53f0..f62c7f6e2f 100644 --- a/crypto/src/proteus.rs +++ b/crypto/src/proteus.rs @@ -290,7 +290,7 @@ impl CoreCrypto { /// Proteus counterpart of [crate::mls::MlsCentral] /// The big difference is that [ProteusCentral] doesn't *own* its own keystore but must borrow it from the outside. -/// Whether it's exclusively for this struct's purposes or it's shared with our main struct, [MlsCentral] +/// Whether it's exclusively for this struct's purposes or it's shared with our main struct, [crate::mls::MlsCentral] #[derive(Debug)] pub struct ProteusCentral { proteus_identity: Arc, diff --git a/crypto/src/test_utils/central.rs b/crypto/src/test_utils/central.rs index 1da5b8e64c..9a905e9677 100644 --- a/crypto/src/test_utils/central.rs +++ b/crypto/src/test_utils/central.rs @@ -14,22 +14,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use crate::mls::credential::ext::CredentialExt; -use crate::prelude::{CertificateBundle, MlsCiphersuite, MlsCredentialType}; use crate::{ prelude::{ - Client, ClientId, ConversationId, ConversationMember, CryptoError, CryptoResult, MlsCentral, MlsConversation, + ClientId, ConversationId, ConversationMember, CryptoError, CryptoResult, MlsCentral, MlsConversation, MlsConversationDecryptMessage, MlsConversationInitBundle, MlsCustomConfiguration, MlsError, }, test_utils::TestCase, }; -use core_crypto_keystore::entities::MlsIdentity; + +use crate::mls::MlsCiphersuite; +use crate::prelude::{CertificateBundle, Client, MlsCredentialType}; +use core_crypto_keystore::entities::{MlsCredential, MlsSignatureKeyPair}; use mls_crypto_provider::MlsCryptoProvider; use openmls::prelude::{ - KeyPackage, PublicGroupState, QueuedProposal, SignaturePublicKey, StagedCommit, VerifiablePublicGroupState, Welcome, + KeyPackage, LeafNodeIndex, MlsMessageIn, MlsMessageOut, QueuedProposal, SignaturePublicKey, StagedCommit, }; -use openmls_traits::key_store::ToKeyStoreValue; use openmls_traits::OpenMlsCryptoProvider; +use tls_codec::Serialize; use wire_e2e_identity::prelude::WireIdentityReader; impl MlsCentral { @@ -105,7 +106,7 @@ impl MlsCentral { .add_members_to_conversation(id, &mut [other.rand_member().await]) .await? .welcome; - other.process_welcome_message(welcome, custom_cfg).await?; + other.process_welcome_message(welcome.into(), custom_cfg).await?; self.commit_accepted(id).await?; assert_eq!( self.get_conversation_unchecked(id).await.members().len(), @@ -119,28 +120,26 @@ impl MlsCentral { Ok(()) } - pub async fn try_join_from_public_group_state( + pub async fn try_join_from_group_info( &mut self, - case: &crate::test_utils::TestCase, + case: &TestCase, id: &ConversationId, - public_group_state: VerifiablePublicGroupState, + group_info: MlsMessageIn, others: Vec<&mut Self>, ) -> CryptoResult<()> { - use tls_codec::{Deserialize as _, Serialize as _}; - let public_group_state = public_group_state.tls_serialize_detached().map_err(MlsError::from)?; - let public_group_state = - VerifiablePublicGroupState::tls_deserialize(&mut public_group_state.as_slice()).map_err(MlsError::from)?; + use tls_codec::Serialize as _; + let MlsConversationInitBundle { conversation_id, commit, .. } = self - .join_by_external_commit(public_group_state, case.custom_cfg(), case.credential_type) + .join_by_external_commit(group_info, case.custom_cfg(), case.credential_type) .await?; self.merge_pending_group_from_external_commit(&conversation_id).await?; assert_eq!(conversation_id.as_slice(), id.as_slice()); for other in others { - let commit = commit.to_bytes().map_err(MlsError::from)?; + let commit = commit.tls_serialize_detached().map_err(MlsError::from)?; other.decrypt_message(id, commit).await?; self.try_talk_to(id, other).await?; } @@ -150,7 +149,7 @@ impl MlsCentral { pub async fn try_join_from_welcome( &mut self, id: &ConversationId, - welcome: Welcome, + welcome: MlsMessageIn, custom_cfg: MlsCustomConfiguration, others: Vec<&mut Self>, ) -> CryptoResult<()> { @@ -161,26 +160,61 @@ impl MlsCentral { Ok(()) } - pub async fn verifiable_public_group_state(&mut self, id: &ConversationId) -> VerifiablePublicGroupState { - use tls_codec::{Deserialize as _, Serialize as _}; - let public_group_state = self.public_group_state(id).await.tls_serialize_detached().unwrap(); - VerifiablePublicGroupState::tls_deserialize(&mut public_group_state.as_slice()).unwrap() + pub async fn get_group_info(&mut self, id: &ConversationId) -> MlsMessageOut { + let conversation_arc = self.get_conversation(id).await.unwrap(); + let mut conversation = conversation_arc.write().await; + let group = &mut conversation.group; + let ct = group.credential().unwrap().credential_type(); + let cs = group.ciphersuite(); + let cb = self + .mls_client + .as_ref() + .unwrap() + .find_credential_bundle(cs.into(), ct.into()) + .unwrap(); + + group + .export_group_info(&self.mls_backend, &cb.signature_key, true) + .unwrap() } - pub async fn public_group_state(&mut self, id: &ConversationId) -> PublicGroupState { - self.get_conversation(id) + /// Finds the [SignaturePublicKey] of a [Client] within a [MlsGroup] + pub async fn signature_key_of(&mut self, conv_id: &ConversationId, client_id: ClientId) -> SignaturePublicKey { + let sign_key = self + .mls_groups + .get_fetch(conv_id, self.mls_backend.borrow_keystore_mut(), None) .await .unwrap() - .write() + .unwrap() + .read() .await .group - .export_public_group_state(&self.mls_backend) + .members() + .find(|k| k.credential.identity() == client_id.0.as_slice()) + .unwrap() + .signature_key; + + SignaturePublicKey::from(sign_key) + } + + /// Finds the HPKE Public key of a [Client] within a [MlsGroup] + pub async fn encryption_key_of(&mut self, conv_id: &ConversationId, client_id: ClientId) -> Vec { + self.mls_groups + .get_fetch(conv_id, self.mls_backend.borrow_keystore_mut(), None) + .await + .unwrap() + .unwrap() + .read() .await + .group + .members() + .find(|k| k.credential.identity() == client_id.0.as_slice()) .unwrap() + .encryption_key } - /// Finds the [KeyPackage] of a [Client] within a [MlsGroup] - pub async fn key_package_of(&mut self, conv_id: &ConversationId, client_id: ClientId) -> KeyPackage { + /// Finds the [LeafNodeIndex] of a [Client] within a [MlsGroup] + pub async fn index_of(&mut self, conv_id: &ConversationId, client_id: ClientId) -> LeafNodeIndex { self.mls_groups .get_fetch(conv_id, self.mls_backend.borrow_keystore_mut(), None) .await @@ -190,17 +224,16 @@ impl MlsCentral { .await .group .members() - .into_iter() - .find(|k| k.credential().identity() == client_id.0.as_slice()) + .find(|k| k.credential.identity() == client_id.as_slice()) .unwrap() - .clone() + .index } - pub fn client_signature_key(&self, case: &TestCase) -> &SignaturePublicKey { + pub fn client_signature_key(&self, case: &TestCase) -> SignaturePublicKey { let mls_client = self.mls_client.as_ref().unwrap(); let (cs, ct) = (case.ciphersuite(), case.credential_type); let cb = mls_client.find_credential_bundle(cs, ct).unwrap(); - cb.credential().signature_key() + SignaturePublicKey::from(cb.signature_key.public()) } pub async fn get_conversation_unchecked( @@ -221,13 +254,13 @@ impl MlsCentral { let cb = mls_client.find_credential_bundle(cs, ct).unwrap(); let sender_credential = cb.credential(); - if let openmls::prelude::MlsCredentialType::X509(openmls::prelude::MlsCertificate { + if let openmls::prelude::MlsCredentialType::X509(openmls::prelude::Certificate { identity: dup_client_id, - cert_chain, - }) = &sender_credential.credential + cert_data: cert_chain, + }) = &sender_credential.mls_credential() { - let leaf = cert_chain.get(0).map(|c| c.clone().into_vec()).unwrap(); - let identity = leaf.extract_identity().unwrap(); + let leaf: Vec = cert_chain.get(0).map(|c| c.clone().into()).unwrap(); + let identity = leaf.as_slice().extract_identity().unwrap(); let decr_identity = decrypted.identity.as_ref().unwrap(); assert_eq!(decr_identity.client_id, identity.client_id); assert_eq!(decr_identity.client_id.as_bytes(), dup_client_id.as_slice()); @@ -238,6 +271,19 @@ impl MlsCentral { } } +impl MlsConversation { + pub fn signature_keys(&self) -> impl Iterator + '_ { + self.group + .members() + .map(|m| m.signature_key) + .map(|mpk| SignaturePublicKey::from(mpk.as_slice())) + } + + pub fn encryption_keys(&self) -> impl Iterator> + '_ { + self.group.members().map(|m| m.encryption_key) + } +} + impl Client { #[cfg(test)] pub async fn init_x509_credential_bundle_if_missing( @@ -251,15 +297,26 @@ impl Client { .find_credential_bundle(cs, MlsCredentialType::X509) .is_none() { + let id = cb.get_client_id()?; let cb = Self::new_x509_credential_bundle(cb)?; - let identity = MlsIdentity { - id: self.id().to_string(), - ciphersuite: cs.into(), - credential_type: MlsCredentialType::X509 as u8, - signature: cb.keystore_key()?, - credential: cb.to_key_store_value().map_err(MlsError::from)?, - }; - backend.key_store().save(identity).await?; + + backend + .key_store() + .save(MlsCredential { + id: id.clone().into(), + credential: cb.credential.tls_serialize_detached().map_err(MlsError::from)?, + }) + .await?; + backend + .key_store() + .save(MlsSignatureKeyPair { + signature_scheme: cb.signature_key.signature_scheme() as _, + keypair: cb.signature_key.tls_serialize_detached().map_err(MlsError::from)?, + pk: cb.signature_key.to_public_vec(), + credential_id: id.clone().into(), + }) + .await?; + self.identities.push_credential_bundle(cs, cb)?; } Ok(()) diff --git a/crypto/src/test_utils/fixtures.rs b/crypto/src/test_utils/fixtures.rs index bcc3e1f3b1..90b8acf305 100644 --- a/crypto/src/test_utils/fixtures.rs +++ b/crypto/src/test_utils/fixtures.rs @@ -48,11 +48,11 @@ pub use rstest_reuse::{self, *}; crate::prelude::MlsCredentialType::Basic, openmls::prelude::Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 )), - #[cfg(feature = "test-all-cipher")] - case::cert_cs3(TestCase::new( - crate::prelude::MlsCredentialType::X509, - openmls::prelude::Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 - )), + // #[cfg(feature = "test-all-cipher")] + // case::cert_cs3(TestCase::new( + // crate::prelude::MlsCredentialType::X509, + // openmls::prelude::Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 + // )), #[cfg(feature = "test-all-cipher")] case::basic_cs7(TestCase::new( crate::prelude::MlsCredentialType::Basic, @@ -107,6 +107,13 @@ impl TestCase { pub fn custom_cfg(&self) -> MlsCustomConfiguration { self.cfg.custom.clone() } + + pub fn default_x509() -> Self { + Self { + credential_type: MlsCredentialType::X509, + cfg: MlsConversationConfiguration::default(), + } + } } impl Default for TestCase { diff --git a/crypto/src/test_utils/message.rs b/crypto/src/test_utils/message.rs new file mode 100644 index 0000000000..d881302da0 --- /dev/null +++ b/crypto/src/test_utils/message.rs @@ -0,0 +1,17 @@ +use openmls::framing::MlsMessageInBody; +use openmls::prelude::{GroupEpoch, MlsMessageIn, MlsMessageOut}; + +pub trait MessageExt { + fn epoch(&self) -> Option; +} + +impl MessageExt for MlsMessageOut { + fn epoch(&self) -> Option { + let msg_in = MlsMessageIn::from(self.clone()); + match msg_in.extract() { + MlsMessageInBody::PublicMessage(m) => Some(m.epoch()), + MlsMessageInBody::PrivateMessage(m) => Some(m.epoch()), + _ => None, + } + } +} diff --git a/crypto/src/test_utils/mod.rs b/crypto/src/test_utils/mod.rs index 907eee094d..ce45ca4d1a 100644 --- a/crypto/src/test_utils/mod.rs +++ b/crypto/src/test_utils/mod.rs @@ -26,6 +26,7 @@ use crate::{ pub mod central; pub mod fixtures; +pub mod message; // Cannot name it `proteus` because then it conflicts with proteus the crate :( #[cfg(feature = "proteus")] pub mod proteus_utils; @@ -34,6 +35,7 @@ use crate::prelude::{ClientIdentifier, MlsCredentialType}; pub use central::*; pub use fixtures::TestCase; pub use fixtures::*; +pub use message::*; // FIXME: This takes around 10 minutes on WASM // #[cfg(debug_assertions)] diff --git a/crypto/tests/sizes/group_info.rs b/crypto/tests/sizes/group_info.rs deleted file mode 100644 index 2f922ddec0..0000000000 --- a/crypto/tests/sizes/group_info.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -#[cfg(test)] -mod tests { - use core_crypto::{ - prelude::{ConversationMember, MlsCentralConfiguration, MlsConversationConfiguration}, - MlsCentral, - }; - - const CLIENT_LIMIT: usize = 64; - - async fn generate_client() -> MlsCentral { - let user_uuid = uuid::Uuid::new_v4().hyphenated(); - let client_id = format!("{user_uuid}:1234@members.wire.com"); - let mut tmp_dir = tempfile::tempdir().unwrap().into_path(); - tmp_dir.push("stub.edb"); - - let config = MlsCentralConfiguration::try_new(tmp_dir.to_str().unwrap(), "test1234", &client_id).unwrap(); - - MlsCentral::try_new_in_memory(config).await.unwrap() - } - - #[cfg_attr(not(target_family = "wasm"), async_std::test)] - async fn group_info_size() { - let mut root_client = generate_client().await; - - let mut clients = vec![]; - for _ in 0..=CLIENT_LIMIT { - clients.push(generate_client().await); - } - - let conversation_id = uuid::Uuid::new_v4(); - let cid_bytes = conversation_id.as_bytes().to_vec(); - - root_client - .new_conversation(cid_bytes.clone(), MlsConversationConfiguration::default()) - .await - .unwrap(); - - let mut lengths = Vec::with_capacity(CLIENT_LIMIT + 1); - let state = root_client.export_public_group_state(&cid_bytes).await.unwrap(); - lengths.push(state.len()); - - for client in clients.iter() { - let kp = client.client_keypackages(1).await.unwrap(); - let members_to_add = - ConversationMember::new(client.client_id().unwrap().into(), kp[0].key_package().clone()); - root_client - .add_members_to_conversation(&cid_bytes, &mut [members_to_add]) - .await - .unwrap(); - let state = root_client.export_public_group_state(&cid_bytes).await.unwrap(); - lengths.push(state.len()); - } - - lengths.into_iter().enumerate().for_each(|(i, len)| { - println!("group.len[{i}] => state.len[{len}]"); - }); - } -} diff --git a/crypto/tests/sizes/increment_message.rs b/crypto/tests/sizes/increment_message.rs deleted file mode 100644 index dc6ba6b195..0000000000 --- a/crypto/tests/sizes/increment_message.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -#[cfg(test)] -mod tests { - use core_crypto::{ - prelude::{MlsCentralConfiguration, MlsConversationConfiguration}, - MlsCentral, - }; - - const MSG: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"; - - #[cfg_attr(not(target_family = "wasm"), async_std::test)] - fn increment_message() { - let user_uuid = uuid::Uuid::new_v4().hyphenated(); - let client_id = format!("{user_uuid}:1234@members.wire.com"); - let config = MlsCentralConfiguration::try_new("increment_message.edb", "test1234", &client_id).unwrap(); - - let mut central = MlsCentral::try_new(config).await.unwrap(); - let _ = central.client_keypackages(100).await.unwrap(); - - let conversation_id = uuid::Uuid::new_v4(); - let cid_bytes = conversation_id.as_bytes().to_vec(); - - central - .new_conversation(cid_bytes.clone(), MlsConversationConfiguration::default()) - .await - .unwrap(); - - let mut lengths = Vec::with_capacity(MSG.len()); - - for i in 0..MSG.len() { - lengths.push( - central - .encrypt_message(cid_bytes.clone(), &MSG[0..=i]) - .await - .unwrap() - .len(), - ); - } - - lengths.into_iter().enumerate().for_each(|(i, len)| { - println!("msg.len[{i}] => enc.len[{len}]"); - }); - - central.wipe().await.unwrap(); - } -} diff --git a/deny.toml b/deny.toml index 7fe8374d51..d66706ba14 100644 --- a/deny.toml +++ b/deny.toml @@ -72,8 +72,6 @@ allow-git = [ "https://github.com/briansmith/ring", ] private = [ - # for criterion (test only) - "https://github.com/bheisler", # TODO: remove when no longer required "https://github.com/otak", ] diff --git a/extras/anycrypto/Cargo.toml b/extras/anycrypto/Cargo.toml deleted file mode 100644 index 58cdab9a77..0000000000 --- a/extras/anycrypto/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "core-anycrypto" -version = "0.0.0" -edition = "2021" -license = "GPL-3.0-only" -license-file = "LICENSE" - -[features] -default = [] - -[dependencies] -eyre = "0.6" -thiserror = "1.0" -uuid = "0.8" -openmls = "0.4" -openmls_rust_crypto = "0.1" -openmls_traits = "0.1" -proteus = "1.0" - -[dependencies.core-crypto-keystore] -version = "0.2" -path = "../keystore" - -[dependencies.mls-crypto-provider] -version = "0.2" -path = "../mls-provider" diff --git a/extras/anycrypto/LICENSE b/extras/anycrypto/LICENSE deleted file mode 100644 index ebd853e83a..0000000000 --- a/extras/anycrypto/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - -{one line to give the program's name and a brief idea of what it does.} -Copyright (C) {year} {name of author} - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -{project} Copyright (C) {year} {fullname} -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - -The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/extras/anycrypto/README.md b/extras/anycrypto/README.md deleted file mode 100644 index 0a333c4515..0000000000 --- a/extras/anycrypto/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Wire CoreCrypto - -**Probably abandoned** - -Project that supports and abstracts both MLS and proteus under the CoreCrypto umbrella. diff --git a/extras/anycrypto/src/central/config.rs b/extras/anycrypto/src/central/config.rs deleted file mode 100644 index 39373f60a5..0000000000 --- a/extras/anycrypto/src/central/config.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - - -#[repr(C)] -#[derive(Debug)] -pub struct MlsConversationConfiguration { - pub(crate) init_keys: Vec>, - pub(crate) admins: Vec, - // FIXME: No way to configure ciphersuites. - // FIXME: Can maybe only check it against the supported ciphersuites in the group afterwards? - pub(crate) ciphersuite: (), - // FIXME: openmls::group::config::UpdatePolicy is NOT configurable at the moment. - // FIXME: None of the fields are available and there are no way to build it/mutate it - pub(crate) key_rotation_span: (), -} - -#[repr(C)] -#[derive(Debug)] -pub struct ProteusConversationConfiguration { - pub(crate) identity: proteus::keys::IdentityKeyPair, - pub(crate) prekeys: proteus::keys::PreKeyBundle, -} - -#[repr(C)] -#[derive(Debug)] -pub enum ConversationConfiguration { - Mls(Box), - Proteus(Box), -} - -impl TryInto for ConversationConfiguration { - type Error = crate::CryptoError; - - fn try_into(self) -> Result { - match self { - ConversationConfiguration::Mls(mls_config) => Ok(*mls_config), - _ => Err(crate::CryptoError::ConfigurationMismatch( - crate::Protocol::Proteus, - )), - } - } -} - -impl TryInto for ConversationConfiguration { - type Error = crate::CryptoError; - - fn try_into(self) -> Result { - match self { - ConversationConfiguration::Proteus(proteus_config) => Ok(*proteus_config), - _ => Err(crate::CryptoError::ConfigurationMismatch( - crate::Protocol::Mls, - )), - } - } -} - -impl TryInto for ConversationConfiguration { - type Error = crate::CryptoError; - - fn try_into(self) -> Result { - let mls_config: MlsConversationConfiguration = self.try_into()?; - let mls_group_config = openmls::group::MlsGroupConfig::builder() - .wire_format(openmls::framing::WireFormat::MlsCiphertext) - // .update_policy() FIXME: Unsupported - // .padding_size(0) TODO: Understand what it does and define a safe value - // .max_past_epochs(5) TODO: Understand what it does and define a safe value - // .number_of_resumtion_secrets(0) TODO: Understand what it does and define a safe value - // .use_ratchet_tree_extension(false) TODO: Understand what it does and define a safe value - .build(); - - Ok(mls_group_config) - } -} diff --git a/extras/anycrypto/src/central/mod.rs b/extras/anycrypto/src/central/mod.rs deleted file mode 100644 index 0ffb23aa6a..0000000000 --- a/extras/anycrypto/src/central/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -mod config; -use openmls::prelude::{TlsSerializeTrait, TlsSizeTrait}; - -pub use self::config::*; - -use std::collections::HashMap; - -pub type ConversationId = uuid::Uuid; - -// #[derive(Debug)] -// pub enum ClientIdentity { -// Mls(openmls::key_packages::KeyPackageBundle), -// Proteus(proteus::keys::IdentityKeyPair), -// } - -// #[derive(Debug)] -// pub struct Client { -// protocol_in_use: crate::Protocol, -// keys: ClientIdentity, -// } - -/// Central acts as an abstraction over both MLS Groups and Proteus Sessions. -/// The goal being to create a superset API for both that makes functionally similar operations behave the same. -/// For instance: creating a new conversation for example creates a new `Group` in MLS, and sum(users' devices) `Session`s in Proteus -#[derive(Debug)] -pub struct Central { - mls_backend: mls_crypto_provider::MlsCryptoProvider, - mls_groups: HashMap, - proteus: - HashMap>>, -} - -impl Central { - pub fn try_new>( - store_path: S, - identity_key: S, - ) -> crate::error::CryptoResult { - let mls_backend = - mls_crypto_provider::MlsCryptoProvider::try_new(store_path, identity_key)?; - - Ok(Self { - mls_backend, - mls_groups: Default::default(), - proteus: Default::default(), - }) - } - - /// Create a new (empty) conversation - pub fn new_conversation( - &mut self, - protocol: crate::Protocol, - id: ConversationId, - config: ConversationConfiguration, - ) -> crate::error::CryptoResult<()> { - match protocol { - crate::Protocol::Mls => { - let mls_config = config.try_into()?; - let group = openmls::group::MlsGroup::new( - &self.mls_backend, - &mls_config, - openmls::group::GroupId::from_slice(id.as_bytes()), - &[], - ).map_err(crate::MlsError::from)?; - self.mls_groups.insert(id, group); - } - crate::Protocol::Proteus => { - let proteus_config: ProteusConversationConfiguration = config.try_into()?; - let session = proteus::session::Session::init_from_prekey( - proteus_config.identity, - proteus_config.prekeys, - ).map_err(crate::ProteusError::from)?; - // TODO: Should we create N sessions for each device? or we do that later? - self.proteus.insert(id, vec![session]); - } - } - - Ok(()) - } - - pub fn encrypt_message>( - &mut self, - protocol: crate::Protocol, - conversation: ConversationId, - message: M, - ) -> crate::error::CryptoResult> { - match protocol { - crate::Protocol::Mls => { - let group = self.mls_groups - .get_mut(&conversation) - .ok_or(crate::error::CryptoError::ConversationNotFound { - protocol, - conversation - })?; - - let message = group.create_message(&self.mls_backend, message.as_ref()).map_err(crate::MlsError::from)?; - let mut buf = Vec::with_capacity(message.tls_serialized_len()); - // FIXME: Support error - // TODO: Define serialization format? Probably won't be the TLS thingy? - message.tls_serialize(&mut buf).unwrap(); - Ok(buf) - }, - crate::Protocol::Proteus => { - let sessions = self.proteus - .get_mut(&conversation) - .ok_or(crate::error::CryptoError::ConversationNotFound { - protocol, - conversation - })?; - - let envelopes = sessions.iter_mut() - .try_fold( - std::collections::HashMap::new(), - |mut acc, session| -> crate::CryptoResult { - let identity = session.remote_identity().fingerprint(); - let message = session.encrypt(message.as_ref()).map_err(crate::ProteusError::from)?; - acc.insert(identity, message); - Ok(acc) - } - )?; - - // TODO: ser BatchedMessage to CBOR? Addendum to proteus protocol? - todo!() - } - } - } - - pub fn decrypt_message>( - &mut self, - protocol: crate::Protocol, - conversation: ConversationId, - message: M, - ) -> crate::CryptoResult> { - match protocol { - crate::Protocol::Mls => { - let group = self.mls_groups - .get_mut(&conversation) - .ok_or(crate::error::CryptoError::ConversationNotFound { - protocol, - conversation - })?; - - //let - - let parsed_message = group.parse_message(message.as_ref().into(), &self.mls_backend)?; - let message = group.process_unverified_message(parsed_message, None, &self.mls_backend)?; - - todo!() - }, - crate::Protocol::Proteus => todo!(), - } - } -} - -type BatchedMessage<'a> = std::collections::HashMap>; - -#[cfg(test)] -mod tests {} diff --git a/extras/anycrypto/src/error.rs b/extras/anycrypto/src/error.rs deleted file mode 100644 index 9c68acc25b..0000000000 --- a/extras/anycrypto/src/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -#[derive(Debug, thiserror::Error)] -pub enum MlsError { - #[error(transparent)] - MlsGroupError(#[from] openmls::group::MlsGroupError), - #[error(transparent)] - MlsErrorString(#[from] openmls::error::ErrorString), -} - -#[derive(Debug, thiserror::Error)] -pub enum ProteusError { - #[error(transparent)] - ProteusSessionError(#[from] proteus::session::Error>), - #[error(transparent)] - ProteusDecodeError(#[from] proteus::DecodeError), - #[error(transparent)] - ProteusEncodeError(#[from] proteus::EncodeError), -} - -#[derive(Debug, thiserror::Error)] -pub enum CryptoError { - #[error("Couldn't find {protocol} conversation with id {conversation}")] - ConversationNotFound { - protocol: crate::Protocol, - conversation: crate::central::ConversationId, - }, - #[error(transparent)] - KeyStoreError(#[from] core_crypto_keystore::CryptoKeystoreError), - #[error(transparent)] - MlsError(#[from] MlsError), - #[error(transparent)] - ProteusError(#[from] ProteusError), - #[error("The requested ({0}) configuration is not contained in this package")] - ConfigurationMismatch(crate::Protocol), - #[error(transparent)] - Other(#[from] eyre::Report), -} - -pub type CryptoResult = Result; diff --git a/extras/anycrypto/src/ffi.rs b/extras/anycrypto/src/ffi.rs deleted file mode 100644 index 7ffbbedd96..0000000000 --- a/extras/anycrypto/src/ffi.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -use std::sync::{ONCE_INIT, Once}; - -static LIB_HAS_INIT: Once = ONCE_INIT; -static CALLBACK_HANDLER_HAS_INIT: Once = ONCE_INIT; -static mut CALLBACK_HANDLER: extern "C" fn(*const message::Message) = std::ptr::null_mut(); - -#[repr(C)] -pub union ConversationConfigurationUnion { - mls: MlsConversationConfiguration, - proteus: ProteusConversationConfiguration, -} - -#[repr(C, packed)] -#[derive(Debug)] -pub struct ConversationConfiguration { - t: Protocol, - c: ConversationConfigurationUnion, -} - - -#[no_mangle] -pub extern "C" fn init() { - LIB_HAS_INIT.call_once(|| { - - }); -} - -#[no_mangle] -pub extern "C" fn init_and_listen_with(callback: extern "C" fn(*const message::Message)) { - CALLBACK_HANDLER_INIT.call_once(move || { - init(); - unsafe { CALLBACK_HANDLER = callback; } - }); -} diff --git a/extras/anycrypto/src/lib.rs b/extras/anycrypto/src/lib.rs deleted file mode 100644 index 3b5f25d6a5..0000000000 --- a/extras/anycrypto/src/lib.rs +++ /dev/null @@ -1,128 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -#![allow(dead_code, unused_variables)] - -mod error; -mod message; -pub use self::error::*; -mod central; - -#[repr(u8)] -#[derive(Debug)] -pub enum Protocol { - Mls, - Proteus, -} - -impl std::fmt::Display for Protocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Protocol::Mls => "MLS", - Protocol::Proteus => "Proteus", - } - ) - } -} - -///// -// Sending -// -// -// To avoid possible decryption errors, CoreLogic will not send a message for encryption with MLS -// until the backend is up and there are no incoming messages to process for that conversation. -// -// This is a blocking, synchronous call from CoreLogic to CoreCrypto. If successful, it returns an encrypted -// MLSApplicationMessage. -// pub fun encryptMlsMessage( -// qualifiedConversationId: String, -// messageId: String, // UUID4? -// genericMessage: Vec // CoreCrypto borrows genericMessage -// ) -> Result, Error> // CoreLogic needs to copy the MLSApplicationMessage - -// -// Receiving -// - -// pub fun decryptMlsApplicationMessage( -// mlsApplicationMessage: Vec // CoreCrypto borrows the MLSApplicationMessage -// ) -> Result<(Vec, SavedStateDelta), Error> // CoreLogic needs to copy the decrypted GenericMessage and state - -// These are the events that could occur to an MLS group -// enum GroupAction { -// none, // ex: Proposal -// welcomedToGroup, -// modifiedGroupMembers, -// deletedGroup, -// rekeyedGroup // does CoreLogic care about this? -// } - -// struct MlsGroupChangeEvent { -// groupChangeEvent: GroupAction, -// qualifiedConversationId: Option, -// addedClientList: Option>, // only relevant for newGroup and modifyGroupMembers -// removedClientList: Option> // only relevant for modifyGroupMembers -// } - -// pub fun processMlsControlMessage( -// mlsControlMessage: Vec, // CoreCrypto borrows the MLSControlMessage -// // We can include the callback here or once at initialization time -// wecomeCallback: Option)> -// ) -> Result<(MlsGroupChangeEvent, SavedStateDelta), Error> -// CoreLogic needs to deep copy the MlsGroupChangeEvent -// C version would be like: unsafe extern "C" fn(*mut u8, usize) - -// -// Group Management -// - -// pub fun newMlsConversation( -// qualifiedConversationId: String, -// initKeyList: Vec, -// // MlsConfiguration includes among other fun things: -// // list of admins -// // ciphersuite -// // amount of time before key rotation (ex: 1 week, 1 day, 1 hour) -// groupConfig: mut MlsConfiguration -// ) -> Result<(Vec, SavedStateDelta), Error> // The MLSControlMessage - -// pub fun deleteConversation( -// qualifiedConversationId: String -// ) -> Result<(Vec, SavedStateDelta), Error> - -// pub fun modifyParticipants( -// qualifiedConversationId: String, -// addedClientList: Option>, -// removedClientList: Option> -// ) -> Result<(Vec, SavedStateDelta), Error> - -// pub fun modifyAuthorization( -// qualifiedConversationId: String, -// adminList: Vec, // list of UUIDs who can add/remove new users -// guestList: Vec // list of guest client IDs? -// ) -> Result<(), Error> - -// // *** -// // We got an unsolicited Welcome message. Someone invited us to join a -// // conversation (could also be a 1:1 "connection") -// // Is that OK CoreLogic? -// fun onWelcomeCallback( -// inviter: String, // userID of inviter -// participants: Vec<(String, String)> // list of tuples of client IDs with each's wire handle -// ) -> Result // Kotlin, return conversation ID if we should create diff --git a/extras/anycrypto/src/message.rs b/extras/anycrypto/src/message.rs deleted file mode 100644 index 235b7bec57..0000000000 --- a/extras/anycrypto/src/message.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -use crate::error::CryptoResult; - -#[repr(C)] -pub enum Message<'a> { - Proteus(Box>), - Mls(Box), -} - -impl std::fmt::Debug for Message<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Proteus(arg0) => f.debug_tuple("Proteus").field(&"[REDACTED]").finish(), - Self::Mls(arg0) => f.debug_tuple("Mls").field(arg0).finish(), - } - } -} - -impl Message<'_> { - pub fn to_vec(&self) -> CryptoResult> { - match self { - Message::Proteus(boxed_p_msg) => match boxed_p_msg.as_ref() { - proteus::message::Message::Plain(p_msg) => Ok(p_msg.cipher_text.clone()), - _ => Err(eyre::eyre!("Message is still ciphered!").into()), - }, - Message::Mls(mls_msg) => Ok(mls_msg.message().into()), - } - } - - pub fn as_slice(&self) -> CryptoResult<&[u8]> { - match self { - Message::Proteus(boxed_p_msg) => match boxed_p_msg.as_ref() { - proteus::message::Message::Plain(p_msg) => Ok(p_msg.cipher_text.as_slice()), - _ => Err(eyre::eyre!("Message is still ciphered!").into()), - }, - Message::Mls(mls_msg) => Ok(mls_msg.message()), - } - } -} diff --git a/interop/Cargo.toml b/interop/Cargo.toml index 6be2435c7c..73e5d351a8 100644 --- a/interop/Cargo.toml +++ b/interop/Cargo.toml @@ -21,6 +21,7 @@ log = "0.4" femme = "2.2" dirs = "5.0" core-crypto = { path = "../crypto" } +tls_codec = { workspace = true } # core-crypto-ffi = { path = "../crypto-ffi" } # Utils diff --git a/interop/src/clients/corecrypto/native.rs b/interop/src/clients/corecrypto/native.rs index 8fea9d06b2..8b22f05709 100644 --- a/interop/src/clients/corecrypto/native.rs +++ b/interop/src/clients/corecrypto/native.rs @@ -16,8 +16,8 @@ use color_eyre::eyre::Result; use serde_json::json; +use tls_codec::Serialize; -use core_crypto::prelude::tls_codec::Serialize; use core_crypto::prelude::*; use crate::clients::{EmulatedClient, EmulatedClientProtocol, EmulatedClientType, EmulatedMlsClient}; @@ -101,7 +101,7 @@ impl EmulatedMlsClient for CoreCryptoNativeClient { .await?; } - let member = ConversationMember::new_raw(client_id.to_vec().into(), kp.to_vec())?; + let member = ConversationMember::new_raw(client_id.to_vec().into(), kp.to_vec(), self.cc.provider())?; let welcome = self .cc .add_members_to_conversation(&conversation_id.to_vec(), &mut [member]) diff --git a/interop/src/main.rs b/interop/src/main.rs index 6a6b8c1987..c67dce0a2c 100644 --- a/interop/src/main.rs +++ b/interop/src/main.rs @@ -17,6 +17,7 @@ #![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))] use color_eyre::eyre::{eyre, Result}; +use tls_codec::Serialize; #[cfg(not(target_family = "wasm"))] mod build; @@ -115,7 +116,7 @@ fn run_test() -> Result<()> { #[cfg(not(target_family = "wasm"))] async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> { - use core_crypto::prelude::{tls_codec::Serialize, *}; + use core_crypto::prelude::*; let spinner = util::RunningProcess::new("[MLS] Step 0: Initializing clients & env...", true); @@ -150,7 +151,11 @@ async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> { let mut members = vec![]; for c in clients.iter_mut() { let kp = c.get_keypackage().await?; - members.push(ConversationMember::new_raw(c.client_id().into(), kp)?); + members.push(ConversationMember::new_raw( + c.client_id().into(), + kp, + master_client.provider(), + )?); } spinner.success("[MLS] Step 1: KeyPackages [OK]"); @@ -374,8 +379,6 @@ async fn run_proteus_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<( // - This whole EmulatedE2eIdentityClient thing makes little sense #[allow(dead_code)] async fn run_e2e_identity_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> { - use core_crypto::prelude::*; - let spinner = util::RunningProcess::new("[E2EI] Step 0: Perform ACME enrollment", true); let mut clients: Vec> = vec![]; @@ -389,9 +392,11 @@ async fn run_e2e_identity_test(chrome_driver_addr: &std::net::SocketAddr) -> Res clients::corecrypto::web::CoreCryptoWebClient::new_deferred(chrome_driver_addr).await?, )); + /* + use core_crypto::prelude::*; for c in clients.iter_mut() { c.e2ei_new_enrollment(MlsCiphersuite::default()).await?; - } + }*/ spinner.success("[E2EI] Step 0: Perform ACME enrollment [OK]"); Ok(()) diff --git a/keystore/Cargo.toml b/keystore/Cargo.toml index 0e954f5544..25191b8234 100644 --- a/keystore/Cargo.toml +++ b/keystore/Cargo.toml @@ -21,10 +21,11 @@ harness = false [features] default = ["mls-keystore", "proteus-keystore"] -mls-keystore = ["dep:openmls_traits"] +mls-keystore = ["dep:openmls_traits", "dep:openmls_basic_credential", "dep:openmls_x509_credential"] proteus-keystore = ["dep:proteus-traits"] ios-wal-compat = ["dep:security-framework", "dep:security-framework-sys", "dep:core-foundation"] idb-regression-test = [] +log-queries = ["dep:log", "rusqlite/trace"] dummy-entity = ["dep:serde"] [dependencies] @@ -34,7 +35,7 @@ hex = "0.4" zeroize = { version = "1.5", features = ["zeroize_derive"] } async-trait = "0.1" async-lock = "2.5" -serde_json = "1.0" +postcard = { version = "1.0", default-features = false, features = ["use-std"] } sha2 = "0.10" # iOS specific things @@ -42,8 +43,11 @@ security-framework = { version = "2.8", optional = true } security-framework-sys = { version = "2.8", optional = true } core-foundation = { version = "0.9", optional = true } -openmls_traits = { version = "0.1", optional = true, features = ["single-threaded"] } +openmls_traits = { version = "0.1", optional = true } +openmls_basic_credential = { version = "0.1", optional = true } +openmls_x509_credential = { version = "0.1", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } +log = { version = "0.4", optional = true } [dependencies.proteus-traits] optional = true @@ -95,13 +99,14 @@ wasm-bindgen-test = "0.3" uuid = { version = "1.0", features = ["v4", "js"] } rand = { version = "0.8", features = ["getrandom"] } getrandom = { version = "0.2", features = ["js"] } -openmls = { version = "0.4", default-features = false, features = ["crypto-subtle"] } +openmls = { version = "0.20", default-features = false, features = ["crypto-subtle"] } mls-crypto-provider = { path = "../mls-provider" } rstest = "0.17" rstest_reuse = "0.5" async-std = { version = "1.12", features = ["attributes"] } futures-lite = "1.12" -core-crypto-keystore = { path = ".", features = ["idb-regression-test"] } +core-crypto-keystore = { path = ".", features = ["idb-regression-test", "log-queries"] } +pretty_env_logger = "0.4" [dev-dependencies.proteus-wasm] version = "2.0" diff --git a/keystore/benches/read.rs b/keystore/benches/read.rs index 274f064e52..749e138f70 100644 --- a/keystore/benches/read.rs +++ b/keystore/benches/read.rs @@ -19,17 +19,13 @@ use criterion::{ async_executor::FuturesExecutor, black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput, }; -use openmls::{ - credentials::CredentialBundle, - extensions::{Extension, ExternalKeyIdExtension}, - key_packages::KeyPackageBundle, - prelude::Ciphersuite, -}; +use openmls::prelude::Ciphersuite; use openmls_traits::{key_store::OpenMlsKeyStore, random::OpenMlsRand, OpenMlsCryptoProvider}; use core_crypto_keystore::Connection as CryptoKeystore; use futures_lite::future::block_on; use mls_crypto_provider::MlsCryptoProvider; +use openmls_basic_credential::SignatureKeyPair; #[cfg(feature = "proteus-keystore")] struct ProteusReadParams { @@ -114,40 +110,14 @@ fn benchmark_reads_mls(c: &mut Criterion) { let backend = block_on(async { MlsCryptoProvider::try_new("mls-read.edb", "secret").await.unwrap() }); - let uuid: [u8; 16] = backend.rand().random_array().unwrap(); let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519; - - let key_id = uuid::Uuid::from_bytes(uuid); - - let credentials = CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); - - let keypackage_bundle = KeyPackageBundle::new( - &[ciphersuite], - &credentials, - &backend, - vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], - ) - .unwrap(); - - keypackage_bundle.key_package().verify(&backend).unwrap(); - - let key = { - let id = keypackage_bundle - .key_package() - .extensions() - .iter() - .find(|e| e.as_external_key_id_extension().is_ok()) - .unwrap() - .as_external_key_id_extension() - .unwrap() - .as_slice(); - - uuid::Uuid::from_slice(id).unwrap() - }; + let key = uuid::Uuid::new_v4(); + let mut rng = &mut *backend.rand().borrow_rand().unwrap(); + let kp = SignatureKeyPair::new(ciphersuite.signature_algorithm(), &mut rng).unwrap(); block_on(async { - store_cached.store(key.as_bytes(), &keypackage_bundle).await.unwrap(); - store_uncached.store(key.as_bytes(), &keypackage_bundle).await.unwrap(); + store_cached.store(key.as_bytes(), &kp).await.unwrap(); + store_uncached.store(key.as_bytes(), &kp).await.unwrap(); }); let mut group = c.benchmark_group("MLS Reads"); @@ -155,15 +125,15 @@ fn benchmark_reads_mls(c: &mut Criterion) { group.bench_with_input(BenchmarkId::new("Reads", "cached"), &key, |b, key| { b.to_async(FuturesExecutor).iter(|| async { - let bundle: KeyPackageBundle = store_cached.read(key.as_bytes()).await.unwrap(); - black_box(bundle); + let skp: SignatureKeyPair = store_cached.read(key.as_bytes()).await.unwrap(); + black_box(skp); }) }); group.bench_with_input(BenchmarkId::new("Reads", "uncached"), &key, |b, key| { b.to_async(FuturesExecutor).iter(|| async { - let bundle: KeyPackageBundle = store_uncached.read(key.as_bytes()).await.unwrap(); - black_box(bundle); + let skp: SignatureKeyPair = store_uncached.read(key.as_bytes()).await.unwrap(); + black_box(skp); }) }); diff --git a/keystore/benches/write.rs b/keystore/benches/write.rs index 70363b4823..08f7a19108 100644 --- a/keystore/benches/write.rs +++ b/keystore/benches/write.rs @@ -19,17 +19,13 @@ use criterion::{ async_executor::FuturesExecutor, black_box, criterion_group, criterion_main, BatchSize, Criterion, Throughput, }; -use openmls::{ - credentials::CredentialBundle, - extensions::{Extension, ExternalKeyIdExtension}, - key_packages::KeyPackageBundle, - prelude::Ciphersuite, -}; +use openmls::prelude::Ciphersuite; use openmls_traits::{key_store::OpenMlsKeyStore, random::OpenMlsRand, OpenMlsCryptoProvider}; use core_crypto_keystore::Connection as CryptoKeystore; use futures_lite::future::block_on; use mls_crypto_provider::MlsCryptoProvider; +use openmls_basic_credential::SignatureKeyPair; #[cfg(feature = "proteus-keystore")] fn benchmark_writes_proteus(c: &mut Criterion) { @@ -71,41 +67,14 @@ fn benchmark_writes_mls(c: &mut Criterion) { group.bench_with_input("Writes", backend.borrow_keystore(), |b, store| { b.to_async(FuturesExecutor).iter_batched( || { - let uuid: [u8; 16] = backend.rand().random_array().unwrap(); let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519; + let key = uuid::Uuid::new_v4(); + let mut rng = &mut *backend.rand().borrow_rand().unwrap(); + let kp = SignatureKeyPair::new(ciphersuite.signature_algorithm(), &mut rng).unwrap(); - let key_id = uuid::Uuid::from_bytes(uuid); - - let credentials = - CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); - - let keypackage_bundle = KeyPackageBundle::new( - &[ciphersuite], - &credentials, - &backend, - vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], - ) - .unwrap(); - - keypackage_bundle.key_package().verify(&backend).unwrap(); - - let key = { - let id = keypackage_bundle - .key_package() - .extensions() - .iter() - .find(|e| e.as_external_key_id_extension().is_ok()) - .unwrap() - .as_external_key_id_extension() - .unwrap() - .as_slice(); - - uuid::Uuid::from_slice(id).unwrap() - }; - - (key, keypackage_bundle) + (key, kp) }, - |(key, bundle)| async move { black_box(store.store(key.as_bytes(), &bundle).await) }, + |(key, skp)| async move { black_box(store.store(key.as_bytes(), &skp).await) }, BatchSize::SmallInput, ) }); diff --git a/keystore/src/connection/platform/generic/migrations/V1__schema.sql b/keystore/src/connection/platform/generic/migrations/V1__schema.sql index a4eabb2f79..313bdb18e1 100644 --- a/keystore/src/connection/platform/generic/migrations/V1__schema.sql +++ b/keystore/src/connection/platform/generic/migrations/V1__schema.sql @@ -1,17 +1,46 @@ -CREATE TABLE mls_keys ( - id VARCHAR(255) UNIQUE, - key BLOB +CREATE TABLE mls_credentials ( + id BLOB, + credential BLOB ); -CREATE TABLE mls_identities ( - id VARCHAR(255) UNIQUE, - signature BLOB, - credential BLOB +CREATE TABLE mls_signature_keypairs ( + signature_scheme INT, + keypair BLOB, + pk BLOB, + credential_id BLOB +); + +CREATE TABLE mls_hpke_private_keys ( + pk BLOB, + sk BLOB +); + +CREATE TABLE mls_encryption_keypairs ( + pk BLOB, + sk BLOB +); + +CREATE TABLE mls_psk_bundles ( + psk_id BLOB, + psk BLOB +); + +CREATE TABLE mls_keypackages ( + keypackage_ref BLOB, + keypackage BLOB ); CREATE TABLE mls_groups ( id BLOB, - state BLOB + state BLOB, + parent_id BLOB +); + +CREATE TABLE mls_pending_groups ( + id BLOB, + state BLOB, + cfg BLOB, + parent_id BLOB ); CREATE TABLE proteus_prekeys ( @@ -19,8 +48,17 @@ CREATE TABLE proteus_prekeys ( key BLOB ); -CREATE TABLE mls_pending_groups ( - id BLOB, - state BLOB, - cfg BLOB +CREATE TABLE proteus_identities ( + pk BLOB, + sk BLOB +); + +CREATE TABLE proteus_sessions ( + id VARCHAR(255) UNIQUE, + session BLOB +); + +CREATE TABLE e2ei_enrollment ( + id VARCHAR(255) UNIQUE, + content BLOB ); diff --git a/keystore/src/connection/platform/generic/migrations/V2__proteus.sql b/keystore/src/connection/platform/generic/migrations/V2__proteus.sql deleted file mode 100644 index 22bd80524e..0000000000 --- a/keystore/src/connection/platform/generic/migrations/V2__proteus.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE proteus_identities ( - sk BLOB, - pk BLOB -); - -CREATE TABLE proteus_sessions ( - id VARCHAR(255) UNIQUE, - session BLOB -); diff --git a/keystore/src/connection/platform/generic/migrations/V3__parentgroups.sql b/keystore/src/connection/platform/generic/migrations/V3__parentgroups.sql deleted file mode 100644 index b7399d99fd..0000000000 --- a/keystore/src/connection/platform/generic/migrations/V3__parentgroups.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE mls_groups ADD COLUMN parent_id BLOB; -ALTER TABLE mls_pending_groups ADD COLUMN parent_id BLOB; diff --git a/keystore/src/connection/platform/generic/migrations/V4__e2ei.sql b/keystore/src/connection/platform/generic/migrations/V4__e2ei.sql deleted file mode 100644 index 0bb20a54b5..0000000000 --- a/keystore/src/connection/platform/generic/migrations/V4__e2ei.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE e2ei_enrollment ( - id VARCHAR(255) UNIQUE, - content BLOB -); diff --git a/keystore/src/connection/platform/generic/mod.rs b/keystore/src/connection/platform/generic/mod.rs index fffaed30d6..1a992b963e 100644 --- a/keystore/src/connection/platform/generic/mod.rs +++ b/keystore/src/connection/platform/generic/mod.rs @@ -44,7 +44,18 @@ impl std::ops::DerefMut for SqlCipherConnection { } impl SqlCipherConnection { - fn init_with_connection(conn: rusqlite::Connection, path: &str, key: &str) -> CryptoKeystoreResult { + #[allow(unused_mut)] + fn init_with_connection(mut conn: rusqlite::Connection, path: &str, key: &str) -> CryptoKeystoreResult { + cfg_if::cfg_if! { + if #[cfg(feature = "log-queries")] { + fn log_query(q: &str) { + log::info!("{}", q); + } + + conn.trace(Some(log_query)); + } + } + conn.pragma_update(None, "key", key)?; // ? iOS WAL journaling fix; see details here: https://github.com/sqlcipher/sqlcipher/issues/255 @@ -54,6 +65,9 @@ impl SqlCipherConnection { // Enable WAL journaling mode conn.pragma_update(None, "journal_mode", "wal")?; + // Disable FOREIGN KEYs - The 2 step blob writing process invalidates foreign key checks unfortunately + conn.pragma_update(None, "foreign_keys", "OFF")?; + let mut conn = Self { path: path.into(), conn, @@ -195,13 +209,16 @@ impl SqlCipherConnection { const CIPHER_PLAINTEXT_BYTES: u32 = 32; conn.pragma_update(None, "cipher_plaintext_header_size", CIPHER_PLAINTEXT_BYTES)?; - conn.pragma_update(None, "user_version", 1u32)?; + conn.pragma_update(None, "user_version", 2u32)?; Ok(()) } fn run_migrations(&mut self) -> CryptoKeystoreResult<()> { - migrations::runner().run(&mut self.conn).map_err(Box::new)?; + let report = migrations::runner().run(&mut self.conn).map_err(Box::new)?; + if let Some(version) = report.applied_migrations().iter().map(|m| m.version()).max() { + self.conn.pragma_update(None, "schema_version", version)?; + } Ok(()) } diff --git a/keystore/src/connection/platform/wasm/mod.rs b/keystore/src/connection/platform/wasm/mod.rs index 5b8f766974..85ef0cb6a5 100644 --- a/keystore/src/connection/platform/wasm/mod.rs +++ b/keystore/src/connection/platform/wasm/mod.rs @@ -51,18 +51,68 @@ impl DatabaseConnection for WasmConnection { let rexie_builder = rexie::Rexie::builder(&name) .version(version) - .add_object_store(ObjectStore::new("mls_keys").auto_increment(false)) .add_object_store( - ObjectStore::new("mls_identities") + ObjectStore::new("mls_credentials") .auto_increment(false) - .add_index(Index::new("signature", "signature").unique(true)), + .add_index(Index::new("id", "id").unique(true)), ) - .add_object_store(ObjectStore::new("mls_groups").auto_increment(false)) - .add_object_store(ObjectStore::new("e2ei_enrollment").auto_increment(false)) - .add_object_store(ObjectStore::new("mls_pending_groups").auto_increment(false)) - .add_object_store(ObjectStore::new("proteus_prekeys").auto_increment(false)) - .add_object_store(ObjectStore::new("proteus_identities").auto_increment(false)) - .add_object_store(ObjectStore::new("proteus_sessions").auto_increment(false)); + .add_object_store( + ObjectStore::new("mls_signature_keypairs") + .auto_increment(false) + .add_index(Index::new("mls_id", "mls_id").unique(true)) + .add_index(Index::new("signature_scheme", "signature_scheme")) + .add_index(Index::new("pk", "pk").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_hpke_private_keys") + .auto_increment(false) + .add_index(Index::new("pk", "pk").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_encryption_keypairs") + .auto_increment(false) + .add_index(Index::new("pk", "pk").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_psk_bundles") + .auto_increment(false) + .add_index(Index::new("psk_id", "psk_id").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_keypackages") + .auto_increment(false) + .add_index(Index::new("keypackage_ref", "keypackage_ref").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_groups") + .auto_increment(false) + .add_index(Index::new("id", "id").unique(true)), + ) + .add_object_store( + ObjectStore::new("mls_pending_groups") + .auto_increment(false) + .add_index(Index::new("id", "id").unique(true)), + ) + .add_object_store( + ObjectStore::new("e2ei_enrollment") + .auto_increment(false) + .add_index(Index::new("id", "id").unique(true)), + ) + .add_object_store( + ObjectStore::new("proteus_prekeys") + .auto_increment(false) + .add_index(Index::new("id", "id").unique(true)), + ) + .add_object_store( + ObjectStore::new("proteus_identities") + .auto_increment(false) + .add_index(Index::new("pk", "pk").unique(true)), + ) + .add_object_store( + ObjectStore::new("proteus_sessions") + .auto_increment(false) + .add_index(Index::new("id", "id").unique(true)), + ); #[cfg(feature = "idb-regression-test")] let rexie_builder = rexie_builder.add_object_store(ObjectStore::new("regression_check").auto_increment(false)); diff --git a/keystore/src/connection/platform/wasm/storage.rs b/keystore/src/connection/platform/wasm/storage.rs index 26c48b038e..cba709467d 100644 --- a/keystore/src/connection/platform/wasm/storage.rs +++ b/keystore/src/connection/platform/wasm/storage.rs @@ -132,50 +132,26 @@ impl WasmEncryptedStorage { } } + /// Note: get_indexed only supports unencrypted indexes pub async fn get_indexed + 'static>( &self, collection: impl AsRef, index: impl AsRef, - id: impl AsRef<[u8]>, + id: &wasm_bindgen::JsValue, ) -> CryptoKeystoreResult> { match &self.storage { crate::connection::storage::WasmStorageWrapper::Persistent(rexie) => { let transaction = rexie.transaction(&[collection.as_ref()], rexie::TransactionMode::ReadOnly)?; let store = transaction.store(collection.as_ref())?; let store_index = store.index(index.as_ref())?; - let id = id.as_ref(); - let js_key = js_sys::Uint8Array::from(id); - // Optimistic case where the targeted index isn't encrypted - if let Some(entity_raw) = store_index.get(&js_key).await? { - let mut entity: R = serde_wasm_bindgen::from_value(entity_raw)?; - entity.decrypt(&self.cipher)?; + let Some(entity_raw) = store_index.get(id).await? else { + return Ok(None); + }; + let mut entity: R = serde_wasm_bindgen::from_value(entity_raw)?; + entity.decrypt(&self.cipher)?; - Ok(Some(entity)) - } else { - // Extra work... - let records_iter = store_index - .get_all(None, None, None, None) - .await? - .into_iter() - .map(|(_, value)| value); - - for store_value in records_iter { - let prop_bytes = js_sys::Reflect::get(&store_value, &index.as_ref().into()) - .map(|prop| Uint8Array::from(prop).to_vec())?; - - let mut entity: R = serde_wasm_bindgen::from_value(store_value)?; - entity.decrypt(&self.cipher)?; - let entity_id = entity.id_raw(); - - let decrypted_id = R::decrypt_data(&self.cipher, &prop_bytes, entity_id)?; - if decrypted_id == id { - return Ok(Some(entity)); - } - } - - Ok(None) - } + Ok(Some(entity)) } crate::connection::storage::WasmStorageWrapper::InMemory(map) => { if let Some(store) = map.get(collection.as_ref()) { @@ -184,28 +160,14 @@ impl WasmEncryptedStorage { return None; } - let prop_bytes = js_sys::Reflect::get(v, &index.as_ref().into()) - .map(|prop| Uint8Array::from(prop).to_vec()) - .ok()?; + let entity_id = js_sys::Reflect::get(v, &index.as_ref().into()).ok()?; + if id != &entity_id { + return None; + } let mut entity: R = serde_wasm_bindgen::from_value(v.clone()).ok()?; entity.decrypt(&self.cipher).ok()?; - let entity_id = entity.id_raw(); - let clear_prop_bytes = R::decrypt_data(&self.cipher, &prop_bytes, entity_id).ok()?; - if clear_prop_bytes == id.as_ref() { - if let Some(mut entity) = serde_wasm_bindgen::from_value::>(v.clone()) - .ok() - .flatten() - .take() - { - entity.decrypt(&self.cipher).ok()?; - Some(entity) - } else { - None - } - } else { - None - } + Some(entity) })) } else { Ok(None) diff --git a/keystore/src/entities/mls.rs b/keystore/src/entities/mls.rs index 95f7bf553d..b802764550 100644 --- a/keystore/src/entities/mls.rs +++ b/keystore/src/entities/mls.rs @@ -16,6 +16,7 @@ use super::Entity; use crate::CryptoKeystoreResult; +use openmls_traits::types::SignatureScheme; use zeroize::Zeroize; /// Entity representing a persisted `MlsGroup` @@ -24,13 +25,14 @@ use zeroize::Zeroize; #[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] pub struct PersistedMlsGroup { pub id: Vec, - pub parent_id: Option>, pub state: Vec, + pub parent_id: Option>, } #[async_trait::async_trait(?Send)] pub trait PersistedMlsGroupExt: Entity { fn parent_id(&self) -> Option<&[u8]>; + async fn parent_group( &self, conn: &mut ::ConnectionType, @@ -57,7 +59,7 @@ pub trait PersistedMlsGroupExt: Entity { } } -/// Entity representing a temporary persisted `MlsGroup` +/// Entity representing a temporarily persisted `MlsGroup` #[derive(Debug, Clone, PartialEq, Eq, Zeroize)] #[zeroize(drop)] #[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] @@ -68,32 +70,69 @@ pub struct PersistedMlsPendingGroup { pub custom_configuration: Vec, } -/// Entity representing a persisted `MlsIdentity` +/// Entity representing a persisted `Credential` #[derive(Debug, Clone, PartialEq, Eq, Zeroize)] #[zeroize(drop)] #[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] -pub struct MlsIdentity { - pub id: String, - pub ciphersuite: u16, - pub credential_type: u8, - pub signature: Vec, +pub struct MlsCredential { + pub id: Vec, pub credential: Vec, } +/// Entity representing a persisted `SignatureKeyPair` +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] +pub struct MlsSignatureKeyPair { + pub signature_scheme: u16, + pub keypair: Vec, + pub pk: Vec, + pub credential_id: Vec, +} + #[async_trait::async_trait(?Send)] -pub trait MlsIdentityExt: Entity { - async fn find_by_signature(conn: &mut Self::ConnectionType, signature: &[u8]) - -> CryptoKeystoreResult>; - async fn delete_by_signature(conn: &mut Self::ConnectionType, signature: &[u8]) -> CryptoKeystoreResult<()>; +pub trait MlsSignatureKeyPairExt: Entity { + async fn keypair_for_signature_scheme( + conn: &mut Self::ConnectionType, + credential_id: &[u8], + signature_scheme: SignatureScheme, + ) -> CryptoKeystoreResult>; +} + +/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of) +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] +pub struct MlsHpkePrivateKey { + pub sk: Vec, + pub pk: Vec, +} + +/// Entity representing a persisted `HpkePrivateKey` (related to LeafNode Private keys that the client is aware of) +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] +pub struct MlsEncryptionKeyPair { + pub sk: Vec, + pub pk: Vec, +} + +/// Entity representing a persisted `SignatureKeyPair` +#[derive(Debug, Clone, PartialEq, Eq, Zeroize)] +#[zeroize(drop)] +#[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] +pub struct MlsPskBundle { + pub psk_id: Vec, + pub psk: Vec, } /// Entity representing a persisted `KeyPackage` #[derive(Debug, Clone, PartialEq, Eq, Zeroize)] #[zeroize(drop)] #[cfg_attr(target_family = "wasm", derive(serde::Serialize, serde::Deserialize))] -pub struct MlsKeypackage { - pub id: String, - pub key: Vec, +pub struct MlsKeyPackage { + pub keypackage_ref: Vec, + pub keypackage: Vec, } /// Entity representing an enrollment instance used to fetch a x509 certificate and persisted when diff --git a/keystore/src/entities/mod.rs b/keystore/src/entities/mod.rs index f90c06e811..0216a74efd 100644 --- a/keystore/src/entities/mod.rs +++ b/keystore/src/entities/mod.rs @@ -59,11 +59,11 @@ impl<'a> StringEntityId<'a> { hex::encode(self.0) } - pub fn into_bytes(&self) -> Vec { + pub fn to_bytes(&self) -> Vec { self.0.into() } - pub fn as_bytes(&self) -> &[u8] { + pub fn as_slice(&self) -> &[u8] { self.0 } @@ -110,10 +110,9 @@ impl EntityFindParams { if let Some(offset) = self.offset { let _ = write!(query, " OFFSET {offset}"); } + let _ = write!(query, " ORDER BY rowid"); if self.reverse { - let _ = write!(query, " ORDER BY rowid DESC"); - } else { - let _ = write!(query, " ORDER BY rowid"); + let _ = write!(query, " DESC"); } query @@ -146,7 +145,7 @@ pub trait EntityBase: Send + Sized + Clone + PartialEq + Eq + std::fmt::Debug { cfg_if::cfg_if! { if #[cfg(target_family = "wasm")] { - const AES_CBC_256_NONCE_SIZE: usize = 12; + const AES_GCM_256_NONCE_SIZE: usize = 12; pub trait Entity: EntityBase + serde::Serialize + serde::de::DeserializeOwned { fn id(&self) -> CryptoKeystoreResult { @@ -183,12 +182,12 @@ cfg_if::cfg_if! { } fn encrypt_data(cipher: &aes_gcm::Aes256Gcm, data: &[u8], aad: &[u8]) -> CryptoKeystoreResult> { - let nonce_bytes: [u8; AES_CBC_256_NONCE_SIZE] = rand::random(); + let nonce_bytes: [u8; AES_GCM_256_NONCE_SIZE] = rand::random(); Self::encrypt_with_nonce_and_aad(cipher, data, &nonce_bytes, aad) } fn reencrypt_data(cipher: &aes_gcm::Aes256Gcm, encrypted: &[u8], clear: &[u8], aad: &[u8]) -> CryptoKeystoreResult> { - let nonce_bytes = &encrypted[..AES_CBC_256_NONCE_SIZE]; + let nonce_bytes = &encrypted[..AES_GCM_256_NONCE_SIZE]; Self::encrypt_with_nonce_and_aad(cipher, clear, nonce_bytes, aad) } @@ -196,9 +195,16 @@ cfg_if::cfg_if! { fn decrypt_data(cipher: &aes_gcm::Aes256Gcm, data: &[u8], aad: &[u8]) -> CryptoKeystoreResult> { use aes_gcm::aead::Aead as _; - let nonce_bytes = &data[..AES_CBC_256_NONCE_SIZE]; + if data.is_empty() { + return Err(CryptoKeystoreError::MissingKeyInStore(Self::to_missing_key_err_kind())); + } + if data.len() < AES_GCM_256_NONCE_SIZE { + return Err(CryptoKeystoreError::AesGcmError); + } + + let nonce_bytes = &data[..AES_GCM_256_NONCE_SIZE]; let nonce = aes_gcm::Nonce::from_slice(nonce_bytes); - let msg = &data[AES_CBC_256_NONCE_SIZE..]; + let msg = &data[AES_GCM_256_NONCE_SIZE..]; let payload = aes_gcm::aead::Payload { msg, aad, diff --git a/keystore/src/entities/platform/generic/mls/credential.rs b/keystore/src/entities/platform/generic/mls/credential.rs new file mode 100644 index 0000000000..8bf9d50072 --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/credential.rs @@ -0,0 +1,185 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsCredential, StringEntityId}, + MissingKeyErrorKind, +}; +use std::io::{Read, Write}; + +impl Entity for MlsCredential { + fn id_raw(&self) -> &[u8] { + self.id.as_slice() + } +} + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsCredential { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsCredential + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query: String = format!("SELECT rowid FROM mls_credentials {}", params.to_sql()); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| r.get(0))?; + let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { + use std::io::Read as _; + let rowid = rowid_result?; + + let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "mls_credentials", "id", rowid, true)?; + + let mut id = vec![]; + blob.read_to_end(&mut id)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_credentials", + "credential", + rowid, + true, + )?; + + let mut credential = vec![]; + blob.read_to_end(&mut credential)?; + blob.close()?; + + acc.push(Self { id, credential }); + + crate::CryptoKeystoreResult::Ok(acc) + })?; + + Ok(entities) + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + use rusqlite::OptionalExtension as _; + + Self::ConnectionType::check_buffer_size(self.credential.len())?; + + let zb_cred = rusqlite::blob::ZeroBlob(self.credential.len() as i32); + + let transaction = conn.transaction()?; + let mut existing_rowid = transaction + .query_row("SELECT rowid FROM mls_credentials WHERE id = ?", [&self.id], |r| { + r.get::<_, i64>(0) + }) + .optional()?; + + let row_id = if let Some(rowid) = existing_rowid.take() { + use rusqlite::ToSql as _; + transaction.execute( + "UPDATE mls_credentials SET credential = ? WHERE rowid = ?", + [&zb_cred.to_sql()?, &rowid.to_sql()?], + )?; + rowid + } else { + use rusqlite::ToSql as _; + Self::ConnectionType::check_buffer_size(self.id.len())?; + let id_zb = rusqlite::blob::ZeroBlob(self.id.len() as i32); + + let params: [rusqlite::types::ToSqlOutput; 2] = [id_zb.to_sql()?, zb_cred.to_sql()?]; + + transaction.execute("INSERT INTO mls_credentials (id, credential) VALUES (?, ?)", params)?; + let rowid = transaction.last_insert_rowid(); + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_credentials", "id", rowid, false)?; + blob.write_all(&self.id)?; + blob.close()?; + + rowid + }; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_credentials", + "credential", + row_id, + false, + )?; + + blob.write_all(&self.credential)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + let maybe_rowid = transaction + .query_row("SELECT rowid FROM mls_credentials WHERE id = ?", [id.as_slice()], |r| { + r.get::<_, i64>(0) + }) + .optional()?; + + if let Some(rowid) = maybe_rowid { + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_credentials", + "credential", + rowid, + true, + )?; + + let mut credential = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut credential)?; + blob.close()?; + + Ok(Some(Self { + id: id.to_bytes(), + credential, + })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row("SELECT COUNT(*) FROM mls_credentials", [], |r| r.get(0))?) + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let transaction = conn.transaction()?; + let len = ids.len(); + let mut updated = 0; + for id in ids { + updated += transaction.execute("DELETE FROM mls_credentials WHERE id = ?", [id.as_slice()])?; + } + + if updated == len { + transaction.commit()?; + Ok(()) + } else { + transaction.rollback()?; + Err(Self::to_missing_key_err_kind().into()) + } + } +} diff --git a/keystore/src/entities/platform/generic/mls/encryption_keypair.rs b/keystore/src/entities/platform/generic/mls/encryption_keypair.rs new file mode 100644 index 0000000000..5cc29f3660 --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/encryption_keypair.rs @@ -0,0 +1,208 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsEncryptionKeyPair, StringEntityId}, + MissingKeyErrorKind, +}; +use std::io::{Read, Write}; + +impl Entity for MlsEncryptionKeyPair { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } +} + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsEncryptionKeyPair { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsEncryptionKeyPair + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query: String = format!("SELECT rowid FROM mls_encryption_keypairs {}", params.to_sql()); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| r.get(0))?; + let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { + use std::io::Read as _; + let rowid = rowid_result?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "sk", + rowid, + true, + )?; + + let mut sk = vec![]; + blob.read_to_end(&mut sk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "pk", + rowid, + true, + )?; + + let mut pk = vec![]; + blob.read_to_end(&mut pk)?; + blob.close()?; + + acc.push(Self { sk, pk }); + + crate::CryptoKeystoreResult::Ok(acc) + })?; + + Ok(entities) + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + use rusqlite::OptionalExtension as _; + + Self::ConnectionType::check_buffer_size(self.sk.len())?; + Self::ConnectionType::check_buffer_size(self.pk.len())?; + + let zb_pk = rusqlite::blob::ZeroBlob(self.pk.len() as i32); + let zb_sk = rusqlite::blob::ZeroBlob(self.sk.len() as i32); + + let transaction = conn.transaction()?; + let mut existing_rowid = transaction + .query_row( + "SELECT rowid FROM mls_encryption_keypairs WHERE pk = ?", + [&self.pk], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + let row_id = if let Some(rowid) = existing_rowid.take() { + use rusqlite::ToSql as _; + transaction.execute( + "UPDATE mls_encryption_keypairs SET pk = ?, sk = ? WHERE rowid = ?", + [&zb_pk.to_sql()?, &zb_sk.to_sql()?, &rowid.to_sql()?], + )?; + rowid + } else { + use rusqlite::ToSql as _; + let params: [rusqlite::types::ToSqlOutput; 2] = [zb_pk.to_sql()?, zb_sk.to_sql()?]; + + transaction.execute("INSERT INTO mls_encryption_keypairs (pk, sk) VALUES (?, ?)", params)?; + transaction.last_insert_rowid() + }; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "pk", + row_id, + false, + )?; + + blob.write_all(&self.pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "sk", + row_id, + false, + )?; + + blob.write_all(&self.sk)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + let maybe_rowid = transaction + .query_row( + "SELECT rowid FROM mls_encryption_keypairs WHERE pk = ?", + [id.as_slice()], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + if let Some(rowid) = maybe_rowid { + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "pk", + rowid, + true, + )?; + + let mut pk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_encryption_keypairs", + "sk", + rowid, + true, + )?; + + let mut sk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut sk)?; + blob.close()?; + + Ok(Some(Self { pk, sk })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row("SELECT COUNT(*) FROM mls_encryption_keypairs", [], |r| r.get(0))?) + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let transaction = conn.transaction()?; + let len = ids.len(); + let mut updated = 0; + for id in ids { + updated += transaction.execute("DELETE FROM mls_encryption_keypairs WHERE pk = ?", [id.as_slice()])?; + } + + if updated == len { + transaction.commit()?; + Ok(()) + } else { + transaction.rollback()?; + Err(Self::to_missing_key_err_kind().into()) + } + } +} diff --git a/keystore/src/entities/platform/generic/mls/enrollment.rs b/keystore/src/entities/platform/generic/mls/enrollment.rs index 4b340e59fd..9d480a063e 100644 --- a/keystore/src/entities/platform/generic/mls/enrollment.rs +++ b/keystore/src/entities/platform/generic/mls/enrollment.rs @@ -87,7 +87,7 @@ impl EntityBase for E2eiEnrollment { let transaction = conn.transaction()?; use rusqlite::OptionalExtension as _; let mut row_id = transaction - .query_row("SELECT rowid FROM e2ei_enrollment WHERE id = ?", [id.as_bytes()], |r| { + .query_row("SELECT rowid FROM e2ei_enrollment WHERE id = ?", [id.as_slice()], |r| { r.get::<_, i64>(0) }) .optional()?; @@ -103,7 +103,7 @@ impl EntityBase for E2eiEnrollment { transaction.commit()?; Ok(Some(Self { - id: id.into_bytes(), + id: id.to_bytes(), content: buf, })) } else { @@ -120,7 +120,7 @@ impl EntityBase for E2eiEnrollment { let len = ids.len(); let mut updated = 0; for id in ids { - updated += transaction.execute("DELETE FROM e2ei_enrollment WHERE id = ?", [id.as_bytes()])?; + updated += transaction.execute("DELETE FROM e2ei_enrollment WHERE id = ?", [id.as_slice()])?; } if updated == len { diff --git a/keystore/src/entities/platform/generic/mls/group.rs b/keystore/src/entities/platform/generic/mls/group.rs index a5c3edf1c1..825e9d112b 100644 --- a/keystore/src/entities/platform/generic/mls/group.rs +++ b/keystore/src/entities/platform/generic/mls/group.rs @@ -22,7 +22,7 @@ use crate::{ impl Entity for PersistedMlsGroup { fn id_raw(&self) -> &[u8] { - &self.id + self.id.as_slice() } } @@ -85,7 +85,7 @@ impl EntityBase for PersistedMlsGroup { let parent_id = self.parent_id.as_ref(); let transaction = conn.transaction()?; - let id_bytes = &self.id; + let id_bytes = self.id.as_slice(); Self::ConnectionType::check_buffer_size(state.len())?; Self::ConnectionType::check_buffer_size(id_bytes.len())?; @@ -145,7 +145,7 @@ impl EntityBase for PersistedMlsGroup { use rusqlite::OptionalExtension as _; let transaction = conn.transaction()?; let mut rowid: Option = transaction - .query_row("SELECT rowid FROM mls_groups WHERE id = ?", [id.into_bytes()], |r| { + .query_row("SELECT rowid FROM mls_groups WHERE id = ?", [id.as_slice()], |r| { r.get::<_, i64>(0) }) .optional()?; @@ -243,7 +243,7 @@ impl EntityBase for PersistedMlsGroup { let len = ids.len(); let mut updated = 0; for id in ids { - updated += transaction.execute("DELETE FROM mls_groups WHERE id = ?", [id.into_bytes()])?; + updated += transaction.execute("DELETE FROM mls_groups WHERE id = ?", [id.as_slice()])?; } if updated == len { diff --git a/keystore/src/entities/platform/generic/mls/hpke_private_key.rs b/keystore/src/entities/platform/generic/mls/hpke_private_key.rs new file mode 100644 index 0000000000..7383bf705c --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/hpke_private_key.rs @@ -0,0 +1,188 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsHpkePrivateKey, StringEntityId}, + MissingKeyErrorKind, +}; +use std::io::{Read, Write}; + +impl Entity for MlsHpkePrivateKey { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } +} + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsHpkePrivateKey { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsHpkePrivateKey + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query: String = format!("SELECT rowid FROM mls_hpke_private_keys {}", params.to_sql()); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| r.get(0))?; + let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { + use std::io::Read as _; + let rowid = rowid_result?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_hpke_private_keys", "sk", rowid, true)?; + + let mut sk = vec![]; + blob.read_to_end(&mut sk)?; + blob.close()?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_hpke_private_keys", "pk", rowid, true)?; + + let mut pk = vec![]; + blob.read_to_end(&mut pk)?; + blob.close()?; + + acc.push(Self { sk, pk }); + + crate::CryptoKeystoreResult::Ok(acc) + })?; + + Ok(entities) + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + use rusqlite::OptionalExtension as _; + + Self::ConnectionType::check_buffer_size(self.sk.len())?; + Self::ConnectionType::check_buffer_size(self.pk.len())?; + + let zb_pk = rusqlite::blob::ZeroBlob(self.pk.len() as i32); + let zb_sk = rusqlite::blob::ZeroBlob(self.sk.len() as i32); + + let transaction = conn.transaction()?; + let mut existing_rowid = transaction + .query_row( + "SELECT rowid FROM mls_hpke_private_keys WHERE pk = ?", + [&self.pk], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + let row_id = if let Some(rowid) = existing_rowid.take() { + use rusqlite::ToSql as _; + transaction.execute( + "UPDATE mls_hpke_private_keys SET pk = ?, sk = ? WHERE rowid = ?", + [&zb_pk.to_sql()?, &zb_sk.to_sql()?, &rowid.to_sql()?], + )?; + rowid + } else { + use rusqlite::ToSql as _; + let params: [rusqlite::types::ToSqlOutput; 2] = [zb_pk.to_sql()?, zb_sk.to_sql()?]; + + transaction.execute("INSERT INTO mls_hpke_private_keys (pk, sk) VALUES (?, ?)", params)?; + transaction.last_insert_rowid() + }; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_hpke_private_keys", + "pk", + row_id, + false, + )?; + + blob.write_all(&self.pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_hpke_private_keys", + "sk", + row_id, + false, + )?; + + blob.write_all(&self.sk)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + let maybe_rowid = transaction + .query_row( + "SELECT rowid FROM mls_hpke_private_keys WHERE pk = ?", + [id.as_slice()], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + if let Some(rowid) = maybe_rowid { + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_hpke_private_keys", "pk", rowid, true)?; + + let mut pk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut pk)?; + blob.close()?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_hpke_private_keys", "sk", rowid, true)?; + + let mut sk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut sk)?; + blob.close()?; + + Ok(Some(Self { pk, sk })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row("SELECT COUNT(*) FROM mls_hpke_private_keys", [], |r| r.get(0))?) + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let transaction = conn.transaction()?; + let len = ids.len(); + let mut updated = 0; + for id in ids { + updated += transaction.execute("DELETE FROM mls_hpke_private_keys WHERE pk = ?", [id.as_slice()])?; + } + + if updated == len { + transaction.commit()?; + Ok(()) + } else { + transaction.rollback()?; + Err(Self::to_missing_key_err_kind().into()) + } + } +} diff --git a/keystore/src/entities/platform/generic/mls/identity.rs b/keystore/src/entities/platform/generic/mls/identity.rs deleted file mode 100644 index 614bcb6908..0000000000 --- a/keystore/src/entities/platform/generic/mls/identity.rs +++ /dev/null @@ -1,285 +0,0 @@ -// Wire -// Copyright (C) 2022 Wire Swiss GmbH - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. - -use crate::{ - connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::{Entity, EntityBase, EntityFindParams, MlsIdentity, MlsIdentityExt, StringEntityId}, - CryptoKeystoreResult, MissingKeyErrorKind, -}; - -impl Entity for MlsIdentity { - fn id_raw(&self) -> &[u8] { - self.id.as_bytes() - } -} - -#[async_trait::async_trait(?Send)] -impl EntityBase for MlsIdentity { - type ConnectionType = KeystoreDatabaseConnection; - - fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::MlsIdentityBundle - } - - async fn find_all( - conn: &mut Self::ConnectionType, - params: EntityFindParams, - ) -> crate::CryptoKeystoreResult> { - let transaction = conn.transaction()?; - let query: String = format!("SELECT rowid, id FROM mls_identities {}", params.to_sql()); - - let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; - let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { - use std::io::Read as _; - let (rowid, id) = rowid_result?; - - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "mls_identities", "signature", rowid, true)?; - - let mut signature = vec![]; - blob.read_to_end(&mut signature)?; - blob.close()?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "mls_identities", - "credential", - rowid, - true, - )?; - - let mut credential = vec![]; - blob.read_to_end(&mut credential)?; - blob.close()?; - - acc.push(Self { - id, - // TODO: ciphersuite in MlsIdentity - ciphersuite: 1, - credential_type: 1, - signature, - credential, - }); - - crate::CryptoKeystoreResult::Ok(acc) - })?; - - Ok(entities) - } - - async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { - use rusqlite::OptionalExtension as _; - - let signature = &self.signature; - let credential = &self.credential; - - Self::ConnectionType::check_buffer_size(signature.len())?; - Self::ConnectionType::check_buffer_size(credential.len())?; - - let zb_sig = rusqlite::blob::ZeroBlob(signature.len() as i32); - let zb_cred = rusqlite::blob::ZeroBlob(credential.len() as i32); - - let transaction = conn.transaction()?; - let mut existing_rowid = transaction - .query_row("SELECT rowid FROM mls_identities WHERE id = ?", [&self.id], |r| { - r.get::<_, i64>(0) - }) - .optional()?; - - let row_id = if let Some(rowid) = existing_rowid.take() { - let sig_zb = rusqlite::blob::ZeroBlob(self.signature.len() as i32); - let cred_zb = rusqlite::blob::ZeroBlob(self.credential.len() as i32); - - use rusqlite::ToSql as _; - transaction.execute( - "UPDATE mls_identities SET signature = ?, credential = ? WHERE id = ?", - [&sig_zb.to_sql()?, &cred_zb.to_sql()?, &self.id.to_sql()?], - )?; - rowid - } else { - use rusqlite::ToSql as _; - let params: [rusqlite::types::ToSqlOutput; 3] = [self.id.to_sql()?, zb_sig.to_sql()?, zb_cred.to_sql()?]; - - transaction.execute( - "INSERT INTO mls_identities (id, signature, credential) VALUES (?, ?, ?)", - params, - )?; - transaction.last_insert_rowid() - }; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "mls_identities", - "signature", - row_id, - false, - )?; - - use std::io::Write as _; - blob.write_all(signature)?; - blob.close()?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "mls_identities", - "credential", - row_id, - false, - )?; - - blob.write_all(credential)?; - blob.close()?; - - transaction.commit()?; - - Ok(()) - } - - async fn find_one( - conn: &mut Self::ConnectionType, - id: &StringEntityId, - ) -> crate::CryptoKeystoreResult> { - let id: String = id.try_into()?; - let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - let maybe_rowid = transaction - .query_row("SELECT rowid FROM mls_identities WHERE id = ?", [&id], |r| { - r.get::<_, i64>(0) - }) - .optional()?; - - if let Some(rowid) = maybe_rowid { - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "mls_identities", "signature", rowid, true)?; - - use std::io::Read as _; - let mut signature = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut signature)?; - blob.close()?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "mls_identities", - "credential", - rowid, - true, - )?; - - let mut credential = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut credential)?; - blob.close()?; - - Ok(Some(Self { - id, - // TODO: ciphersuite in MlsIdentity - ciphersuite: 1, - credential_type: 1, - signature, - credential, - })) - } else { - Ok(None) - } - } - - // async fn find_many( - // conn: &mut Self::ConnectionType, - // ids: &[StringEntityId], - // ) -> crate::CryptoKeystoreResult> { - // let mut stmt = conn.prepare_cached("SELECT id FROM mls_identities ORDER BY rowid")?; - - // unimplemented!("There is only one identity within a keystore, so this won't be implemented") - // } - - async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - Ok(conn.query_row("SELECT COUNT(*) FROM mls_identities", [], |r| r.get(0))?) - } - - async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { - let transaction = conn.transaction()?; - let len = ids.len(); - let mut updated = 0; - for id in ids { - let id: String = id.try_into()?; - updated += transaction.execute("DELETE FROM mls_identities WHERE id = ?", [id])?; - } - - if updated == len { - transaction.commit()?; - Ok(()) - } else { - transaction.rollback()?; - Err(Self::to_missing_key_err_kind().into()) - } - } -} - -#[async_trait::async_trait(?Send)] -impl MlsIdentityExt for MlsIdentity { - async fn find_by_signature( - conn: &mut Self::ConnectionType, - signature: &[u8], - ) -> CryptoKeystoreResult> { - let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - let maybe_rowid = transaction - .query_row( - "SELECT rowid, id FROM mls_identities WHERE signature = ?", - [&signature], - |r| Ok((r.get::<_, i64>(0)?, r.get::<_, String>(1)?)), - ) - .optional()?; - - if let Some((rowid, id)) = maybe_rowid { - let mut blob = - transaction.blob_open(rusqlite::DatabaseName::Main, "mls_identities", "signature", rowid, true)?; - - use std::io::Read as _; - let mut signature = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut signature)?; - blob.close()?; - - let mut blob = transaction.blob_open( - rusqlite::DatabaseName::Main, - "mls_identities", - "credential", - rowid, - true, - )?; - - let mut credential = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut credential)?; - blob.close()?; - - Ok(Some(Self { - id, - // TODO: ciphersuite in MlsIdentity - ciphersuite: 1, - credential_type: 1, - signature, - credential, - })) - } else { - Ok(None) - } - } - - async fn delete_by_signature(conn: &mut Self::ConnectionType, signature: &[u8]) -> CryptoKeystoreResult<()> { - let _ = conn.execute("DELETE FROM mls_identities WHERE signature = ?", [&signature])?; - Ok(()) - } -} diff --git a/keystore/src/entities/platform/generic/mls/keypackage.rs b/keystore/src/entities/platform/generic/mls/keypackage.rs index 1e2946f986..fd46410ed1 100644 --- a/keystore/src/entities/platform/generic/mls/keypackage.rs +++ b/keystore/src/entities/platform/generic/mls/keypackage.rs @@ -14,24 +14,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use crate::connection::DatabaseConnection; -use crate::entities::EntityFindParams; -use crate::entities::MlsKeypackage; -use crate::entities::StringEntityId; use crate::{ - connection::KeystoreDatabaseConnection, - entities::{Entity, EntityBase}, + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsKeyPackage, StringEntityId}, MissingKeyErrorKind, }; +use std::io::Read; -impl Entity for MlsKeypackage { +impl Entity for MlsKeyPackage { fn id_raw(&self) -> &[u8] { - self.id.as_bytes() + self.keypackage_ref.as_slice() } } #[async_trait::async_trait(?Send)] -impl EntityBase for MlsKeypackage { +impl EntityBase for MlsKeyPackage { type ConnectionType = KeystoreDatabaseConnection; fn to_missing_key_err_kind() -> MissingKeyErrorKind { @@ -43,20 +40,40 @@ impl EntityBase for MlsKeypackage { params: EntityFindParams, ) -> crate::CryptoKeystoreResult> { let transaction = conn.transaction()?; - let query: String = format!("SELECT rowid, id FROM mls_keys {}", params.to_sql()); + let query: String = format!("SELECT rowid FROM mls_keypackages {}", params.to_sql()); let mut stmt = transaction.prepare_cached(&query)?; - let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; + let mut rows = stmt.query_map([], |r| r.get(0))?; let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { use std::io::Read as _; - let (rowid, id) = rowid_result?; + let rowid = rowid_result?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage_ref", + rowid, + true, + )?; + let mut keypackage_ref = vec![]; + blob.read_to_end(&mut keypackage_ref)?; + blob.close()?; - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "mls_keys", "key", rowid, true)?; - let mut buf = vec![]; - blob.read_to_end(&mut buf)?; + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage", + rowid, + true, + )?; + let mut keypackage = vec![]; + blob.read_to_end(&mut keypackage)?; blob.close()?; - acc.push(Self { id, key: buf }); + acc.push(Self { + keypackage_ref, + keypackage, + }); crate::CryptoKeystoreResult::Ok(acc) })?; @@ -68,34 +85,60 @@ impl EntityBase for MlsKeypackage { use rusqlite::OptionalExtension as _; use rusqlite::ToSql as _; - Self::ConnectionType::check_buffer_size(self.key.len())?; + Self::ConnectionType::check_buffer_size(self.keypackage_ref.len())?; + Self::ConnectionType::check_buffer_size(self.keypackage.len())?; let transaction = conn.transaction()?; let mut existing_rowid = transaction - .query_row("SELECT rowid FROM mls_keys WHERE id = ?", [&self.id], |r| { - r.get::<_, i64>(0) - }) + .query_row( + "SELECT rowid FROM mls_keypackages WHERE keypackage_ref = ?", + [&self.keypackage_ref], + |r| r.get::<_, i64>(0), + ) .optional()?; + let kp_zb = rusqlite::blob::ZeroBlob(self.keypackage.len() as i32); + let row_id = if let Some(rowid) = existing_rowid.take() { - let zb = rusqlite::blob::ZeroBlob(self.key.len() as i32); transaction.execute( - "UPDATE mls_keys SET key = ? WHERE rowid = ?", - [zb.to_sql()?, rowid.to_sql()?], + "UPDATE mls_keypackages SET keypackage = ? WHERE rowid = ?", + [kp_zb.to_sql()?, rowid.to_sql()?], )?; rowid } else { - let zb = rusqlite::blob::ZeroBlob(self.key.len() as i32); - let params: [rusqlite::types::ToSqlOutput; 2] = [self.id.to_sql()?, zb.to_sql()?]; - transaction.execute("INSERT INTO mls_keys (id, key) VALUES (?, ?)", params)?; - transaction.last_insert_rowid() + let kp_ref_zb = rusqlite::blob::ZeroBlob(self.keypackage_ref.len() as i32); + let params: [rusqlite::types::ToSqlOutput; 2] = [kp_ref_zb.to_sql()?, kp_zb.to_sql()?]; + transaction.execute( + "INSERT INTO mls_keypackages (keypackage_ref, keypackage) VALUES (?, ?)", + params, + )?; + let row_id = transaction.last_insert_rowid(); + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage_ref", + row_id, + false, + )?; + + use std::io::Write as _; + blob.write_all(&self.keypackage_ref)?; + blob.close()?; + + row_id }; - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "mls_keys", "key", row_id, false)?; + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage", + row_id, + false, + )?; use std::io::Write as _; - blob.write_all(&self.key)?; + blob.write_all(&self.keypackage)?; blob.close()?; transaction.commit()?; @@ -107,31 +150,54 @@ impl EntityBase for MlsKeypackage { conn: &mut Self::ConnectionType, id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { - let id = String::from_utf8(id.into_bytes())?; - let transaction = conn.transaction()?; use rusqlite::OptionalExtension as _; let mut row_id = transaction - .query_row("SELECT rowid FROM mls_keys WHERE id = ?", [&id], |r| r.get::<_, i64>(0)) + .query_row( + "SELECT rowid FROM mls_keypackages WHERE keypackage_ref = ?", + [id.as_slice()], + |r| r.get::<_, i64>(0), + ) .optional()?; if let Some(rowid) = row_id.take() { - let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "mls_keys", "key", rowid, true)?; - use std::io::Read as _; - let mut buf = Vec::with_capacity(blob.len()); - blob.read_to_end(&mut buf)?; + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage_ref", + rowid, + true, + )?; + + let mut keypackage_ref = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut keypackage_ref)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage", + rowid, + true, + )?; + + let mut keypackage = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut keypackage)?; blob.close()?; transaction.commit()?; - Ok(Some(Self { id, key: buf })) + Ok(Some(Self { + keypackage_ref, + keypackage, + })) } else { Ok(None) } } async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - let count: usize = conn.query_row("SELECT COUNT(*) FROM mls_keys", [], |r| r.get(0))?; + let count: usize = conn.query_row("SELECT COUNT(*) FROM mls_keypackages", [], |r| r.get(0))?; Ok(count) } @@ -140,8 +206,7 @@ impl EntityBase for MlsKeypackage { let len = ids.len(); let mut updated = 0; for id in ids { - let id = String::from_utf8(id.into_bytes())?; - updated += transaction.execute("DELETE FROM mls_keys WHERE id = ?", [id])?; + updated += transaction.execute("DELETE FROM mls_keypackages WHERE keypackage_ref = ?", [id.as_slice()])?; } if updated == len { diff --git a/keystore/src/entities/platform/generic/mls/mod.rs b/keystore/src/entities/platform/generic/mls/mod.rs index e2b93c5d3b..5d79644dfd 100644 --- a/keystore/src/entities/platform/generic/mls/mod.rs +++ b/keystore/src/entities/platform/generic/mls/mod.rs @@ -14,8 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. +pub mod credential; +pub mod encryption_keypair; pub mod enrollment; pub mod group; -pub mod identity; +pub mod hpke_private_key; pub mod keypackage; pub mod pending_group; +pub mod psk_bundle; +pub mod signature_keypair; diff --git a/keystore/src/entities/platform/generic/mls/pending_group.rs b/keystore/src/entities/platform/generic/mls/pending_group.rs index 3ac966103e..7e7edf482a 100644 --- a/keystore/src/entities/platform/generic/mls/pending_group.rs +++ b/keystore/src/entities/platform/generic/mls/pending_group.rs @@ -23,7 +23,7 @@ use crate::{ impl Entity for PersistedMlsPendingGroup { fn id_raw(&self) -> &[u8] { - &self.id + self.id.as_slice() } } @@ -32,35 +32,32 @@ impl EntityBase for PersistedMlsPendingGroup { type ConnectionType = KeystoreDatabaseConnection; fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::MlsGroup + MissingKeyErrorKind::MlsPendingGroup } async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { - let state = &self.state; - let id_bytes = &self.id; let parent_id = self.parent_id.as_ref(); let transaction = conn.transaction()?; - use rusqlite::OptionalExtension as _; - Self::ConnectionType::check_buffer_size(state.len())?; - Self::ConnectionType::check_buffer_size(id_bytes.len())?; + Self::ConnectionType::check_buffer_size(self.state.len())?; + Self::ConnectionType::check_buffer_size(self.id.len())?; Self::ConnectionType::check_buffer_size(parent_id.map(Vec::len).unwrap_or_default())?; let zcfg = rusqlite::blob::ZeroBlob(self.custom_configuration.len() as i32); let zpid = rusqlite::blob::ZeroBlob(parent_id.map(Vec::len).unwrap_or_default() as i32); - let zb = rusqlite::blob::ZeroBlob(state.len() as i32); - let zid = rusqlite::blob::ZeroBlob(id_bytes.len() as i32); + let zb = rusqlite::blob::ZeroBlob(self.state.len() as i32); + let zid = rusqlite::blob::ZeroBlob(self.id.len() as i32); let rowid: i64 = if let Some(rowid) = transaction - .query_row("SELECT rowid FROM mls_pending_groups WHERE id = ?", [&self.id], |r| { - r.get(0) - }) + .query_row( + "SELECT rowid FROM mls_pending_groups WHERE id = ?", + [self.id.as_slice()], + |r| r.get(0), + ) .optional()? { - // we have to update the size of the blob, otherwise we can have trash in the data - let zb = rusqlite::blob::ZeroBlob(state.len() as i32); use rusqlite::ToSql as _; transaction.execute( "UPDATE mls_pending_groups SET state = ?, parent_id = ?, cfg = ? WHERE id = ?", @@ -94,7 +91,7 @@ impl EntityBase for PersistedMlsPendingGroup { false, )?; use std::io::Write as _; - blob.write_all(state)?; + blob.write_all(&self.state)?; blob.close()?; let mut blob = @@ -130,7 +127,7 @@ impl EntityBase for PersistedMlsPendingGroup { let rowid: Option = transaction .query_row( "SELECT rowid FROM mls_pending_groups WHERE id = ?", - [&id.into_bytes()], + [&id.as_slice()], |r| r.get(0), ) .optional()?; @@ -316,7 +313,7 @@ impl EntityBase for PersistedMlsPendingGroup { let len = ids.len(); let mut updated = 0; for id in ids { - updated += transaction.execute("DELETE FROM mls_pending_groups WHERE id = ?", [id.into_bytes()])?; + updated += transaction.execute("DELETE FROM mls_pending_groups WHERE id = ?", [id.as_slice()])?; } if updated == len { diff --git a/keystore/src/entities/platform/generic/mls/psk_bundle.rs b/keystore/src/entities/platform/generic/mls/psk_bundle.rs new file mode 100644 index 0000000000..9da2382c8e --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/psk_bundle.rs @@ -0,0 +1,177 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsPskBundle, StringEntityId}, + MissingKeyErrorKind, +}; +use std::io::{Read, Write}; + +impl Entity for MlsPskBundle { + fn id_raw(&self) -> &[u8] { + self.psk_id.as_slice() + } +} + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsPskBundle { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsPskBundle + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query: String = format!("SELECT rowid FROM mls_psk_bundles {}", params.to_sql()); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| r.get(0))?; + let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { + use std::io::Read as _; + let rowid = rowid_result?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk_id", rowid, true)?; + + let mut psk_id = vec![]; + blob.read_to_end(&mut psk_id)?; + blob.close()?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk", rowid, true)?; + + let mut psk = vec![]; + blob.read_to_end(&mut psk)?; + blob.close()?; + + acc.push(Self { psk_id, psk }); + + crate::CryptoKeystoreResult::Ok(acc) + })?; + + Ok(entities) + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + use rusqlite::OptionalExtension as _; + + Self::ConnectionType::check_buffer_size(self.psk_id.len())?; + Self::ConnectionType::check_buffer_size(self.psk.len())?; + + let zb_psk = rusqlite::blob::ZeroBlob(self.psk.len() as i32); + + let transaction = conn.transaction()?; + let mut existing_rowid = transaction + .query_row( + "SELECT rowid FROM mls_psk_bundles WHERE psk_id = ?", + [&self.psk_id], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + let row_id = if let Some(rowid) = existing_rowid.take() { + use rusqlite::ToSql as _; + transaction.execute( + "UPDATE mls_psk_bundles SET psk = ? WHERE rowid = ?", + [&zb_psk.to_sql()?, &rowid.to_sql()?], + )?; + rowid + } else { + let zb_psk_id = rusqlite::blob::ZeroBlob(self.psk_id.len() as i32); + use rusqlite::ToSql as _; + let params: [rusqlite::types::ToSqlOutput; 2] = [zb_psk_id.to_sql()?, zb_psk.to_sql()?]; + + transaction.execute("INSERT INTO mls_psk_bundles (psk_id, psk) VALUES (?, ?)", params)?; + let row_id = transaction.last_insert_rowid(); + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk_id", row_id, false)?; + + blob.write_all(&self.psk_id)?; + blob.close()?; + + row_id + }; + + let mut blob = transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk", row_id, false)?; + blob.write_all(&self.psk)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + let maybe_rowid = transaction + .query_row( + "SELECT rowid FROM mls_psk_bundles WHERE psk_id = ?", + [id.as_slice()], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + if let Some(rowid) = maybe_rowid { + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk_id", rowid, true)?; + + let mut psk_id = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut psk_id)?; + blob.close()?; + + let mut blob = + transaction.blob_open(rusqlite::DatabaseName::Main, "mls_psk_bundles", "psk", rowid, true)?; + + let mut psk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut psk)?; + blob.close()?; + + Ok(Some(Self { psk_id, psk })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row("SELECT COUNT(*) FROM mls_psk_bundles", [], |r| r.get(0))?) + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let transaction = conn.transaction()?; + let len = ids.len(); + let mut updated = 0; + for id in ids { + updated += transaction.execute("DELETE FROM mls_psk_bundles WHERE psk_id = ?", [id.as_slice()])?; + } + + if updated == len { + transaction.commit()?; + Ok(()) + } else { + transaction.rollback()?; + Err(Self::to_missing_key_err_kind().into()) + } + } +} diff --git a/keystore/src/entities/platform/generic/mls/signature_keypair.rs b/keystore/src/entities/platform/generic/mls/signature_keypair.rs new file mode 100644 index 0000000000..c3f52e61ec --- /dev/null +++ b/keystore/src/entities/platform/generic/mls/signature_keypair.rs @@ -0,0 +1,342 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsSignatureKeyPair, MlsSignatureKeyPairExt, StringEntityId}, + CryptoKeystoreResult, MissingKeyErrorKind, +}; +use openmls_traits::types::SignatureScheme; +use std::io::{Read, Write}; + +impl Entity for MlsSignatureKeyPair { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } +} + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsSignatureKeyPair { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsSignatureKeyPair + } + + async fn find_all( + conn: &mut Self::ConnectionType, + params: EntityFindParams, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + let query: String = format!( + "SELECT rowid, signature_scheme FROM mls_signature_keypairs {}", + params.to_sql() + ); + + let mut stmt = transaction.prepare_cached(&query)?; + let mut rows = stmt.query_map([], |r| Ok((r.get(0)?, r.get(1)?)))?; + let entities = rows.try_fold(Vec::new(), |mut acc, rowid_result| { + use std::io::Read as _; + let (rowid, signature_scheme) = rowid_result?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "keypair", + rowid, + true, + )?; + + let mut keypair = vec![]; + blob.read_to_end(&mut keypair)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "pk", + rowid, + true, + )?; + + let mut pk = vec![]; + blob.read_to_end(&mut pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "credential_id", + rowid, + true, + )?; + + let mut credential_id = vec![]; + blob.read_to_end(&mut credential_id)?; + blob.close()?; + + acc.push(Self { + signature_scheme, + keypair, + pk, + credential_id, + }); + + crate::CryptoKeystoreResult::Ok(acc) + })?; + + Ok(entities) + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + use rusqlite::OptionalExtension as _; + + Self::ConnectionType::check_buffer_size(self.keypair.len())?; + Self::ConnectionType::check_buffer_size(self.pk.len())?; + Self::ConnectionType::check_buffer_size(self.credential_id.len())?; + + let zb_pk = rusqlite::blob::ZeroBlob(self.pk.len() as i32); + let zb_keypair = rusqlite::blob::ZeroBlob(self.keypair.len() as i32); + let zb_cred = rusqlite::blob::ZeroBlob(self.credential_id.len() as i32); + + let transaction = conn.transaction()?; + let mut existing_rowid = transaction + .query_row( + "SELECT rowid FROM mls_signature_keypairs WHERE pk = ?", + [&self.pk], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + let row_id = if let Some(rowid) = existing_rowid.take() { + use rusqlite::ToSql as _; + transaction.execute( + "UPDATE mls_signature_keypairs SET pk = ?, keypair = ?, credential_id = ? WHERE rowid = ?", + [ + &zb_pk.to_sql()?, + &zb_keypair.to_sql()?, + &zb_cred.to_sql()?, + &rowid.to_sql()?, + ], + )?; + rowid + } else { + use rusqlite::ToSql as _; + let params: [rusqlite::types::ToSqlOutput; 4] = [ + self.signature_scheme.to_sql()?, + zb_pk.to_sql()?, + zb_keypair.to_sql()?, + zb_cred.to_sql()?, + ]; + + transaction.execute( + "INSERT INTO mls_signature_keypairs (signature_scheme, pk, keypair, credential_id) VALUES (?, ?, ?, ?)", + params, + )?; + transaction.last_insert_rowid() + }; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "pk", + row_id, + false, + )?; + + blob.write_all(&self.pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "keypair", + row_id, + false, + )?; + + blob.write_all(&self.keypair)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "credential_id", + row_id, + false, + )?; + + blob.write_all(&self.credential_id)?; + blob.close()?; + + transaction.commit()?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + let transaction = conn.transaction()?; + use rusqlite::OptionalExtension as _; + let maybe_rowid = transaction + .query_row( + "SELECT rowid, signature_scheme FROM mls_signature_keypairs WHERE pk = ?", + [id.as_slice()], + |r| Ok((r.get::<_, i64>(0)?, r.get(1)?)), + ) + .optional()?; + + if let Some((rowid, signature_scheme)) = maybe_rowid { + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "pk", + rowid, + true, + )?; + + let mut pk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "keypair", + rowid, + true, + )?; + + let mut keypair = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut keypair)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "credential_id", + rowid, + true, + )?; + + let mut credential_id = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut credential_id)?; + blob.close()?; + + Ok(Some(Self { + signature_scheme, + pk, + keypair, + credential_id, + })) + } else { + Ok(None) + } + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + Ok(conn.query_row("SELECT COUNT(*) FROM mls_signature_keypairs", [], |r| r.get(0))?) + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let transaction = conn.transaction()?; + let len = ids.len(); + let mut updated = 0; + for id in ids { + updated += transaction.execute("DELETE FROM mls_signature_keypairs WHERE pk = ?", [id.as_slice()])?; + } + + if updated == len { + transaction.commit()?; + Ok(()) + } else { + transaction.rollback()?; + Err(Self::to_missing_key_err_kind().into()) + } + } +} + +#[async_trait::async_trait(?Send)] +impl MlsSignatureKeyPairExt for MlsSignatureKeyPair { + async fn keypair_for_signature_scheme( + conn: &mut Self::ConnectionType, + credential_id: &[u8], + signature_scheme: SignatureScheme, + ) -> CryptoKeystoreResult> { + let transaction = conn.transaction()?; + + use rusqlite::{OptionalExtension as _, ToSql as _}; + + let signature_scheme = signature_scheme as u16; + + let maybe_rowid = transaction + .query_row( + "SELECT rowid FROM mls_signature_keypairs WHERE signature_scheme = ? AND credential_id = ?", + [signature_scheme.to_sql()?, credential_id.to_sql()?], + |r| r.get::<_, i64>(0), + ) + .optional()?; + + if let Some(rowid) = maybe_rowid { + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "pk", + rowid, + true, + )?; + + let mut pk = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut pk)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "keypair", + rowid, + true, + )?; + + let mut keypair = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut keypair)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_signature_keypairs", + "credential_id", + rowid, + true, + )?; + + let mut credential_id = Vec::with_capacity(blob.len()); + blob.read_to_end(&mut credential_id)?; + blob.close()?; + + Ok(Some(Self { + signature_scheme, + pk, + keypair, + credential_id, + })) + } else { + Ok(None) + } + } +} diff --git a/keystore/src/entities/platform/generic/proteus/prekey.rs b/keystore/src/entities/platform/generic/proteus/prekey.rs index 9f339f087f..15851e50fa 100644 --- a/keystore/src/entities/platform/generic/proteus/prekey.rs +++ b/keystore/src/entities/platform/generic/proteus/prekey.rs @@ -67,7 +67,7 @@ impl EntityBase for ProteusPrekey { conn: &mut Self::ConnectionType, id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { - let id = ProteusPrekey::id_from_slice(&id.into_bytes()); + let id = ProteusPrekey::id_from_slice(id.as_slice()); let transaction = conn.transaction()?; @@ -146,7 +146,7 @@ impl EntityBase for ProteusPrekey { let mut updated = 0; for id in ids { - let id = ProteusPrekey::id_from_slice(&id.into_bytes()); + let id = ProteusPrekey::id_from_slice(id.as_slice()); updated += transaction.execute("DELETE FROM proteus_prekeys WHERE id = ?", [id])?; } diff --git a/keystore/src/entities/platform/wasm/mls/identity.rs b/keystore/src/entities/platform/wasm/mls/credential.rs similarity index 58% rename from keystore/src/entities/platform/wasm/mls/identity.rs rename to keystore/src/entities/platform/wasm/mls/credential.rs index b4aced3e2a..f8fa2f1a22 100644 --- a/keystore/src/entities/platform/wasm/mls/identity.rs +++ b/keystore/src/entities/platform/wasm/mls/credential.rs @@ -16,26 +16,26 @@ use crate::{ connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::{Entity, EntityBase, EntityFindParams, MlsIdentity, MlsIdentityExt, StringEntityId}, + entities::{Entity, EntityBase, EntityFindParams, MlsCredential, StringEntityId}, CryptoKeystoreResult, MissingKeyErrorKind, }; #[async_trait::async_trait(?Send)] -impl EntityBase for MlsIdentity { +impl EntityBase for MlsCredential { type ConnectionType = KeystoreDatabaseConnection; fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::MlsIdentityBundle + MissingKeyErrorKind::MlsCredential } async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { let storage = conn.storage(); - storage.get_all("mls_identities", Some(params)).await + storage.get_all("mls_credentials", Some(params)).await } async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - storage.save("mls_identities", &mut [self.clone()]).await?; + storage.save("mls_credentials", &mut [self.clone()]).await?; Ok(()) } @@ -44,61 +44,35 @@ impl EntityBase for MlsIdentity { conn: &mut Self::ConnectionType, id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { - conn.storage().get("mls_identities", id.as_bytes()).await + conn.storage().get("mls_credentials", id.as_slice()).await } async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { - conn.storage().count("mls_identities").await + conn.storage().count("mls_credentials").await } async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); - storage.delete("mls_identities", &ids).await + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_credentials", &ids).await } } -impl Entity for MlsIdentity { +impl Entity for MlsCredential { fn id_raw(&self) -> &[u8] { - self.id.as_bytes() + self.id.as_slice() } fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.signature = Self::encrypt_data(cipher, self.signature.as_slice(), self.aad())?; self.credential = Self::encrypt_data(cipher, self.credential.as_slice(), self.aad())?; - Self::ConnectionType::check_buffer_size(self.signature.len())?; Self::ConnectionType::check_buffer_size(self.credential.len())?; Ok(()) } fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.signature = Self::decrypt_data(cipher, self.signature.as_slice(), self.aad())?; self.credential = Self::decrypt_data(cipher, self.credential.as_slice(), self.aad())?; Ok(()) } } - -#[async_trait::async_trait(?Send)] -impl MlsIdentityExt for MlsIdentity { - async fn find_by_signature( - conn: &mut Self::ConnectionType, - signature: &[u8], - ) -> CryptoKeystoreResult> { - conn.storage() - .get_indexed("mls_identities", "signature", signature) - .await - } - - async fn delete_by_signature(conn: &mut Self::ConnectionType, signature: &[u8]) -> CryptoKeystoreResult<()> { - if let Some(identity) = Self::find_by_signature(conn, signature).await? { - let _ = conn - .storage_mut() - .delete("mls_identities", &[identity.id.as_bytes()]) - .await?; - } - - Ok(()) - } -} diff --git a/keystore/src/entities/platform/wasm/mls/encryption_keypair.rs b/keystore/src/entities/platform/wasm/mls/encryption_keypair.rs new file mode 100644 index 0000000000..8866644d91 --- /dev/null +++ b/keystore/src/entities/platform/wasm/mls/encryption_keypair.rs @@ -0,0 +1,78 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsEncryptionKeyPair, StringEntityId}, + CryptoKeystoreResult, MissingKeyErrorKind, +}; + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsEncryptionKeyPair { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsEncryptionKeyPair + } + + async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { + let storage = conn.storage(); + storage.get_all("mls_encryption_keypairs", Some(params)).await + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + storage.save("mls_encryption_keypairs", &mut [self.clone()]).await?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + conn.storage().get("mls_encryption_keypairs", id.as_slice()).await + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count("mls_encryption_keypairs").await + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_encryption_keypairs", &ids).await + } +} + +impl Entity for MlsEncryptionKeyPair { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.sk = Self::encrypt_data(cipher, self.sk.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.sk.len())?; + + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.sk = Self::decrypt_data(cipher, self.sk.as_slice(), self.aad())?; + + Ok(()) + } +} diff --git a/keystore/src/entities/platform/wasm/mls/enrollment.rs b/keystore/src/entities/platform/wasm/mls/enrollment.rs index 727972ee12..c323628273 100644 --- a/keystore/src/entities/platform/wasm/mls/enrollment.rs +++ b/keystore/src/entities/platform/wasm/mls/enrollment.rs @@ -39,7 +39,7 @@ impl EntityBase for E2eiEnrollment { } async fn find_one(conn: &mut Self::ConnectionType, id: &StringEntityId) -> CryptoKeystoreResult> { - conn.storage().get("e2ei_enrollment", id.as_bytes()).await + conn.storage().get("e2ei_enrollment", id.as_slice()).await } async fn count(_conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { @@ -48,7 +48,7 @@ impl EntityBase for E2eiEnrollment { async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + let ids = ids.iter().map(StringEntityId::as_slice).collect::>(); storage.delete("e2ei_enrollment", &ids).await } } diff --git a/keystore/src/entities/platform/wasm/mls/group.rs b/keystore/src/entities/platform/wasm/mls/group.rs index b7b0bf3b11..55e93ca9c1 100644 --- a/keystore/src/entities/platform/wasm/mls/group.rs +++ b/keystore/src/entities/platform/wasm/mls/group.rs @@ -46,7 +46,7 @@ impl EntityBase for PersistedMlsGroup { id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { let storage = conn.storage(); - storage.get("mls_groups", id.as_bytes()).await + storage.get("mls_groups", id.as_slice()).await } async fn find_many( @@ -65,7 +65,7 @@ impl EntityBase for PersistedMlsGroup { async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); let _ = storage.delete("mls_groups", &ids).await?; Ok(()) } @@ -109,7 +109,7 @@ impl EntityBase for PersistedMlsPendingGroup { type ConnectionType = KeystoreDatabaseConnection; fn to_missing_key_err_kind() -> MissingKeyErrorKind { - MissingKeyErrorKind::MlsGroup + MissingKeyErrorKind::MlsPendingGroup } async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { @@ -129,7 +129,7 @@ impl EntityBase for PersistedMlsPendingGroup { conn: &mut Self::ConnectionType, id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { - conn.storage().get("mls_pending_groups", id.as_bytes()).await + conn.storage().get("mls_pending_groups", id.as_slice()).await } async fn find_many( @@ -144,8 +144,8 @@ impl EntityBase for PersistedMlsPendingGroup { conn.storage().count("mls_pending_groups").await } - async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> CryptoKeystoreResult<()> { - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); let _ = conn.storage_mut().delete("mls_pending_groups", &ids).await?; Ok(()) } diff --git a/keystore/src/entities/platform/wasm/mls/hpke_private_key.rs b/keystore/src/entities/platform/wasm/mls/hpke_private_key.rs new file mode 100644 index 0000000000..746aa00779 --- /dev/null +++ b/keystore/src/entities/platform/wasm/mls/hpke_private_key.rs @@ -0,0 +1,78 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsHpkePrivateKey, StringEntityId}, + CryptoKeystoreResult, MissingKeyErrorKind, +}; + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsHpkePrivateKey { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsHpkePrivateKey + } + + async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { + let storage = conn.storage(); + storage.get_all("mls_hpke_private_keys", Some(params)).await + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + storage.save("mls_hpke_private_keys", &mut [self.clone()]).await?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + conn.storage().get("mls_hpke_private_keys", id.as_slice()).await + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count("mls_hpke_private_keys").await + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_hpke_private_keys", &ids).await + } +} + +impl Entity for MlsHpkePrivateKey { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.sk = Self::encrypt_data(cipher, self.sk.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.sk.len())?; + + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.sk = Self::decrypt_data(cipher, self.sk.as_slice(), self.aad())?; + + Ok(()) + } +} diff --git a/keystore/src/entities/platform/wasm/mls/keypackage.rs b/keystore/src/entities/platform/wasm/mls/keypackage.rs index b98bcbf777..2e590e8a58 100644 --- a/keystore/src/entities/platform/wasm/mls/keypackage.rs +++ b/keystore/src/entities/platform/wasm/mls/keypackage.rs @@ -16,12 +16,12 @@ use crate::{ connection::{DatabaseConnection, KeystoreDatabaseConnection}, - entities::{Entity, EntityBase, EntityFindParams, MlsKeypackage, StringEntityId}, + entities::{Entity, EntityBase, EntityFindParams, MlsKeyPackage, StringEntityId}, CryptoKeystoreResult, MissingKeyErrorKind, }; #[async_trait::async_trait(?Send)] -impl EntityBase for MlsKeypackage { +impl EntityBase for MlsKeyPackage { type ConnectionType = KeystoreDatabaseConnection; fn to_missing_key_err_kind() -> MissingKeyErrorKind { @@ -30,45 +30,48 @@ impl EntityBase for MlsKeypackage { async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { let storage = conn.storage(); - storage.get_all("mls_keys", Some(params)).await + storage.get_all("mls_keypackages", Some(params)).await } async fn save(&self, conn: &mut Self::ConnectionType) -> CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - storage.save("mls_keys", &mut [self.clone()]).await?; + storage.save("mls_keypackages", &mut [self.clone()]).await?; Ok(()) } - async fn find_one(conn: &mut Self::ConnectionType, id: &StringEntityId) -> CryptoKeystoreResult> { - conn.storage().get("mls_keys", id.as_bytes()).await + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + conn.storage().get("mls_keypackages", id.as_slice()).await } - async fn count(conn: &mut Self::ConnectionType) -> CryptoKeystoreResult { - conn.storage().count("mls_keys").await + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count("mls_keypackages").await } async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); - storage.delete("mls_keys", &ids).await + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_keypackages", &ids).await } } -impl Entity for MlsKeypackage { +impl Entity for MlsKeyPackage { fn id_raw(&self) -> &[u8] { - self.id.as_bytes() + self.keypackage_ref.as_slice() } fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.key = Self::encrypt_data(cipher, self.key.as_slice(), self.aad())?; - Self::ConnectionType::check_buffer_size(self.key.len())?; + self.keypackage = Self::encrypt_data(cipher, self.keypackage.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.keypackage.len())?; Ok(()) } fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { - self.key = Self::decrypt_data(cipher, self.key.as_slice(), self.aad())?; + self.keypackage = Self::decrypt_data(cipher, self.keypackage.as_slice(), self.aad())?; Ok(()) } diff --git a/keystore/src/entities/platform/wasm/mls/mod.rs b/keystore/src/entities/platform/wasm/mls/mod.rs index 298ecb9882..03c502d799 100644 --- a/keystore/src/entities/platform/wasm/mls/mod.rs +++ b/keystore/src/entities/platform/wasm/mls/mod.rs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. +pub mod credential; +pub mod encryption_keypair; pub mod enrollment; pub mod group; -pub mod identity; +pub mod hpke_private_key; pub mod keypackage; +pub mod psk_bundle; +pub mod signature_keypair; diff --git a/keystore/src/entities/platform/wasm/mls/psk_bundle.rs b/keystore/src/entities/platform/wasm/mls/psk_bundle.rs new file mode 100644 index 0000000000..93d7436273 --- /dev/null +++ b/keystore/src/entities/platform/wasm/mls/psk_bundle.rs @@ -0,0 +1,78 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsPskBundle, StringEntityId}, + CryptoKeystoreResult, MissingKeyErrorKind, +}; + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsPskBundle { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsPskBundle + } + + async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { + let storage = conn.storage(); + storage.get_all("mls_psk_bundles", Some(params)).await + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + storage.save("mls_psk_bundles", &mut [self.clone()]).await?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + conn.storage().get("mls_psk_bundles", id.as_slice()).await + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count("mls_psk_bundles").await + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_psk_bundles", &ids).await + } +} + +impl Entity for MlsPskBundle { + fn id_raw(&self) -> &[u8] { + self.psk_id.as_slice() + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.psk = Self::encrypt_data(cipher, self.psk.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.psk.len())?; + + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.psk = Self::decrypt_data(cipher, self.psk.as_slice(), self.aad())?; + + Ok(()) + } +} diff --git a/keystore/src/entities/platform/wasm/mls/signature_keypair.rs b/keystore/src/entities/platform/wasm/mls/signature_keypair.rs new file mode 100644 index 0000000000..3314d1981e --- /dev/null +++ b/keystore/src/entities/platform/wasm/mls/signature_keypair.rs @@ -0,0 +1,103 @@ +// Wire +// Copyright (C) 2022 Wire Swiss GmbH + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +use crate::{ + connection::{DatabaseConnection, KeystoreDatabaseConnection}, + entities::{Entity, EntityBase, EntityFindParams, MlsSignatureKeyPair, MlsSignatureKeyPairExt, StringEntityId}, + CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, +}; + +use openmls_traits::types::SignatureScheme; + +#[async_trait::async_trait(?Send)] +impl EntityBase for MlsSignatureKeyPair { + type ConnectionType = KeystoreDatabaseConnection; + + fn to_missing_key_err_kind() -> MissingKeyErrorKind { + MissingKeyErrorKind::MlsSignatureKeyPair + } + + async fn find_all(conn: &mut Self::ConnectionType, params: EntityFindParams) -> CryptoKeystoreResult> { + let storage = conn.storage(); + storage.get_all("mls_signature_keypairs", Some(params)).await + } + + async fn save(&self, conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + storage.save("mls_signature_keypairs", &mut [self.clone()]).await?; + + Ok(()) + } + + async fn find_one( + conn: &mut Self::ConnectionType, + id: &StringEntityId, + ) -> crate::CryptoKeystoreResult> { + conn.storage().get("mls_signature_keypairs", id.as_slice()).await + } + + async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { + conn.storage().count("mls_signature_keypairs").await + } + + async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { + let storage = conn.storage_mut(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); + storage.delete("mls_signature_keypairs", &ids).await + } +} + +impl Entity for MlsSignatureKeyPair { + fn id_raw(&self) -> &[u8] { + self.pk.as_slice() + } + + fn encrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.keypair = Self::encrypt_data(cipher, self.keypair.as_slice(), self.aad())?; + Self::ConnectionType::check_buffer_size(self.keypair.len())?; + + Ok(()) + } + + fn decrypt(&mut self, cipher: &aes_gcm::Aes256Gcm) -> CryptoKeystoreResult<()> { + self.keypair = Self::decrypt_data(cipher, self.keypair.as_slice(), self.aad())?; + + Ok(()) + } +} + +#[async_trait::async_trait(?Send)] +impl MlsSignatureKeyPairExt for MlsSignatureKeyPair { + async fn keypair_for_signature_scheme( + conn: &mut Self::ConnectionType, + credential_id: &[u8], + signature_scheme: SignatureScheme, + ) -> CryptoKeystoreResult> { + let storage = conn.storage(); + let sc = wasm_bindgen::JsValue::from_f64(signature_scheme as u16 as _); + let Some(keypair) = storage + .get_indexed::("mls_signature_keypairs", "signature_scheme", &sc) + .await? else { + return Err(CryptoKeystoreError::MissingKeyInStore(MissingKeyErrorKind::MlsSignatureKeyPair)); + }; + + if !keypair.credential_id.is_empty() && keypair.credential_id != credential_id { + return Err(CryptoKeystoreError::SignatureKeyPairDoesNotBelongToCredential); + } + + Ok(Some(keypair)) + } +} diff --git a/keystore/src/entities/platform/wasm/proteus/identity.rs b/keystore/src/entities/platform/wasm/proteus/identity.rs index 44de5d735f..34f722fea9 100644 --- a/keystore/src/entities/platform/wasm/proteus/identity.rs +++ b/keystore/src/entities/platform/wasm/proteus/identity.rs @@ -57,7 +57,7 @@ impl EntityBase for ProteusIdentity { async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); storage.delete("proteus_identities", &ids).await } } diff --git a/keystore/src/entities/platform/wasm/proteus/prekey.rs b/keystore/src/entities/platform/wasm/proteus/prekey.rs index 1c771cf0de..1e66e7f5a4 100644 --- a/keystore/src/entities/platform/wasm/proteus/prekey.rs +++ b/keystore/src/entities/platform/wasm/proteus/prekey.rs @@ -43,7 +43,7 @@ impl EntityBase for ProteusPrekey { id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { let storage = conn.storage(); - storage.get("proteus_prekeys", id.as_bytes()).await + storage.get("proteus_prekeys", id.as_slice()).await } async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { @@ -53,7 +53,7 @@ impl EntityBase for ProteusPrekey { async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); storage.delete("proteus_prekeys", &ids).await } } diff --git a/keystore/src/entities/platform/wasm/proteus/session.rs b/keystore/src/entities/platform/wasm/proteus/session.rs index 1e9dd64b3a..d1826cc670 100644 --- a/keystore/src/entities/platform/wasm/proteus/session.rs +++ b/keystore/src/entities/platform/wasm/proteus/session.rs @@ -43,7 +43,7 @@ impl EntityBase for ProteusSession { id: &StringEntityId, ) -> crate::CryptoKeystoreResult> { let storage = conn.storage(); - storage.get("proteus_sessions", id.as_bytes()).await + storage.get("proteus_sessions", id.as_slice()).await } async fn count(conn: &mut Self::ConnectionType) -> crate::CryptoKeystoreResult { @@ -53,7 +53,7 @@ impl EntityBase for ProteusSession { async fn delete(conn: &mut Self::ConnectionType, ids: &[StringEntityId]) -> crate::CryptoKeystoreResult<()> { let storage = conn.storage_mut(); - let ids = ids.iter().map(StringEntityId::as_bytes).collect::>(); + let ids: Vec> = ids.iter().map(StringEntityId::to_bytes).collect(); storage.delete("proteus_sessions", &ids).await } } diff --git a/keystore/src/entities/proteus.rs b/keystore/src/entities/proteus.rs index dd6ec66e54..112e1a1929 100644 --- a/keystore/src/entities/proteus.rs +++ b/keystore/src/entities/proteus.rs @@ -105,21 +105,3 @@ pub struct ProteusSession { pub id: String, pub session: Vec, } - -// TODO: Implement this in CoreCrypto -// impl TryFrom for ProteusPrekey { -// type Error = crate::CryptoKeystoreError; -// fn try_from(prekey: proteus::keys::PreKey) -> crate::CryptoKeystoreResult { -// let id = prekey.key_id.value(); -// let prekey = prekey.serialise()?; -// Ok(Self { id, prekey }) -// } -// } - -// impl TryInto for ProteusPrekey { -// type Error = crate::CryptoKeystoreError; -// fn try_into(self) -> crate::CryptoKeystoreResult { -// let prekey = proteus::keys::PreKey::deserialise(&self.prekey)?; -// Ok(prekey) -// } -// } diff --git a/keystore/src/error.rs b/keystore/src/error.rs index 5895abf994..f081126e87 100644 --- a/keystore/src/error.rs +++ b/keystore/src/error.rs @@ -19,8 +19,16 @@ pub enum MissingKeyErrorKind { #[error("MLS KeyPackageBundle")] MlsKeyPackageBundle, + #[error("MLS SignatureKeyPair")] + MlsSignatureKeyPair, + #[error("MLS HpkePrivateKey")] + MlsHpkePrivateKey, + #[error("MLS EncryptionKeyPair")] + MlsEncryptionKeyPair, + #[error("MLS PreSharedKeyBundle")] + MlsPskBundle, #[error("MLS CredentialBundle")] - MlsIdentityBundle, + MlsCredential, #[error("MLS Persisted Group")] MlsGroup, #[error("MLS Persisted Pending Group")] @@ -53,13 +61,17 @@ pub enum CryptoKeystoreError { ImplementationError, #[error("The keystore has run out of keypackage bundles!")] OutOfKeyPackageBundles, + #[error("Incorrect API usage: {0}")] + IncorrectApiUsage(&'static str), + #[error("The credential tied to this signature keypair is different from the provided one")] + SignatureKeyPairDoesNotBelongToCredential, #[error("A uniqueness constraint has been violated")] AlreadyExists, #[error("The provided buffer is too big to be persisted in the store")] BlobTooBig, #[cfg(feature = "mls-keystore")] #[error(transparent)] - KeyStoreValueTransformError(Box), + KeyStoreValueTransformError(#[from] postcard::Error), #[error(transparent)] IoError(#[from] std::io::Error), #[cfg(target_family = "wasm")] diff --git a/keystore/src/lib.rs b/keystore/src/lib.rs index 08bd9d533e..1e48c28e30 100644 --- a/keystore/src/lib.rs +++ b/keystore/src/lib.rs @@ -27,6 +27,7 @@ cfg_if::cfg_if! { if #[cfg(feature = "mls-keystore")] { mod mls; pub use self::mls::CryptoKeystoreMls; + pub use self::mls::{ser, deser}; } } diff --git a/keystore/src/mls.rs b/keystore/src/mls.rs index 0209933671..90b8a9de45 100644 --- a/keystore/src/mls.rs +++ b/keystore/src/mls.rs @@ -14,13 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -use openmls_traits::key_store::{FromKeyStoreValue, ToKeyStoreValue}; +use openmls_basic_credential::SignatureKeyPair; +use openmls_traits::{ + key_store::{MlsEntity, MlsEntityId}, + types::SignatureScheme, +}; use crate::{ - connection::Connection, entities::{ - E2eiEnrollment, EntityFindParams, MlsIdentity, MlsIdentityExt, MlsKeypackage, PersistedMlsGroup, - PersistedMlsPendingGroup, StringEntityId, + E2eiEnrollment, EntityFindParams, MlsCredential, MlsEncryptionKeyPair, MlsHpkePrivateKey, MlsKeyPackage, + MlsPskBundle, MlsSignatureKeyPair, MlsSignatureKeyPairExt, PersistedMlsGroup, PersistedMlsPendingGroup, }, CryptoKeystoreError, CryptoKeystoreResult, MissingKeyErrorKind, }; @@ -28,37 +31,30 @@ use crate::{ /// An interface for the specialized queries in the KeyStore #[async_trait::async_trait(?Send)] pub trait CryptoKeystoreMls: Sized { - /// Retrieves an identity signature from a client id + /// Binds a signature keypair to the given credential within the keystore /// /// # Arguments - /// * `id` - client id - /// - /// # Errors - /// Any common error that can happen during a database connection. IoError being a common error - /// for example. - async fn mls_load_identity_signature(&self, id: &str) -> CryptoKeystoreResult>>; - /// Saves the signature and credentials for a client id - /// - /// # Arguments - /// * `id` - client id - /// * `signature` - the signature to be stored - /// * `credential` - the credential to be stored - /// - /// # Errors - /// Any common error that can happen during a database connection. IoError being a common error - /// for example. - async fn mls_save_identity_signature( + /// * `public_key` - The Signature key public key + /// * `credential_id` - The id of the credential we want to associate it with + async fn mls_bind_signature_keypair_to_credential( &self, - id: &str, - signature: &[u8], - credential: &[u8], + public_key: &[u8], + credential_id: &[u8], ) -> CryptoKeystoreResult<()>; + + async fn mls_keypair_for_signature_scheme( + &self, + credential_id: &[u8], + signature_scheme: SignatureScheme, + ) -> CryptoKeystoreResult>; + /// Counts how many KeyPackages are stored /// /// # Errors /// Any common error that can happen during a database connection. IoError being a common error /// for example. async fn mls_keypackagebundle_count(&self) -> CryptoKeystoreResult; + /// Fetches Keypackages /// /// # Arguments @@ -67,19 +63,22 @@ pub trait CryptoKeystoreMls: Sized { /// # Errors /// Any common error that can happen during a database connection. IoError being a common error /// for example. - async fn mls_fetch_keypackage_bundles(&self, count: u32) -> CryptoKeystoreResult>; + async fn mls_fetch_keypackages(&self, count: u32) -> CryptoKeystoreResult>; + /// Fetches a singles keypackage /// /// # Errors /// Any common error that can happen during a database connection. IoError being a common error /// for example. - async fn mls_get_keypackage(&self) -> CryptoKeystoreResult; + async fn mls_get_keypackage(&self) -> CryptoKeystoreResult; + /// Checks if the given MLS group id exists in the keystore /// Note: in case of any error, this will return false /// /// # Arguments /// * `group_id` - group/conversation id async fn mls_group_exists(&self, group_id: &[u8]) -> bool; + /// Persists a `MlsGroup` /// /// # Arguments @@ -95,6 +94,7 @@ pub trait CryptoKeystoreMls: Sized { state: &[u8], parent_group_id: Option<&[u8]>, ) -> CryptoKeystoreResult<()>; + /// Loads `MlsGroups` from the database. It will be returned as a `HashMap` where the key is /// the group/conversation id and the value the group state /// @@ -104,11 +104,13 @@ pub trait CryptoKeystoreMls: Sized { async fn mls_groups_restore( &self, ) -> CryptoKeystoreResult, (Option>, Vec)>>; + /// Deletes `MlsGroups` from the database. /// # Errors /// Any common error that can happen during a database connection. IoError being a common error /// for example. async fn mls_group_delete(&self, group_id: &[u8]) -> CryptoKeystoreResult<()>; + /// Saves a `MlsGroup` in a temporary table (typically used in scenarios where the group cannot /// be committed until the backend acknowledges it, like external commits) /// @@ -127,6 +129,7 @@ pub trait CryptoKeystoreMls: Sized { custom_configuration: &[u8], parent_group_id: Option<&[u8]>, ) -> CryptoKeystoreResult<()>; + /// Loads a temporary `MlsGroup` and its configuration from the database /// /// # Arguments @@ -136,6 +139,7 @@ pub trait CryptoKeystoreMls: Sized { /// Any common error that can happen during a database connection. IoError being a common error /// for example. async fn mls_pending_groups_load(&self, group_id: &[u8]) -> CryptoKeystoreResult<(Vec, Vec)>; + /// Deletes a temporary `MlsGroup` from the database /// /// # Arguments @@ -160,78 +164,52 @@ pub trait CryptoKeystoreMls: Sized { async fn pop_e2ei_enrollment(&self, id: &[u8]) -> CryptoKeystoreResult>; } -#[inline(always)] -fn bytes_to_string_id(raw: &[u8]) -> String { - StringEntityId::new(raw).as_hex_string() -} - -impl Connection { - #[cfg(feature = "memory-cache")] - #[inline(always)] - fn mls_cache_key(k: &[u8]) -> Vec { - let mut ret = vec![0; 4 + k.len()]; - ret[..4].copy_from_slice(b"mls:"); - ret[4..].copy_from_slice(k); - ret - } - - #[cfg(test)] - pub async fn mls_store_keypackage_bundle( +#[async_trait::async_trait(?Send)] +impl CryptoKeystoreMls for crate::connection::Connection { + async fn mls_bind_signature_keypair_to_credential( &self, - key: openmls::prelude::KeyPackageBundle, + public_key: &[u8], + credential_id: &[u8], ) -> CryptoKeystoreResult<()> { - let id = key.key_package().external_key_id()?; - use openmls_traits::key_store::OpenMlsKeyStore as _; - self.store(id, &key).await?; + let Some(mut keypair) = self.find::(public_key).await? else { + return Err(CryptoKeystoreError::MissingKeyInStore(MissingKeyErrorKind::MlsSignatureKeyPair)); + }; - Ok(()) - } -} + let Some(credential) = self.find::(credential_id).await? else { + return Err(CryptoKeystoreError::MissingKeyInStore(MissingKeyErrorKind::MlsCredential)); + }; -#[async_trait::async_trait(?Send)] -impl CryptoKeystoreMls for crate::connection::Connection { - // TODO: Review zero on drop behavior here - async fn mls_load_identity_signature(&self, id: &str) -> CryptoKeystoreResult>> { - Ok(self - .find(id.as_bytes()) - .await? - .map(|id: MlsIdentity| id.signature.clone())) + keypair.credential_id = credential.id.clone(); + self.save(keypair).await?; + + Ok(()) } - async fn mls_save_identity_signature( + async fn mls_keypair_for_signature_scheme( &self, - id: &str, - signature: &[u8], - credential: &[u8], - ) -> CryptoKeystoreResult<()> { - let identity = MlsIdentity { - id: id.into(), - // TODO: ciphersuite in MlsIdentity - ciphersuite: 0, - credential_type: 1, - signature: signature.into(), - credential: credential.into(), - }; + credential_id: &[u8], + signature_scheme: SignatureScheme, + ) -> CryptoKeystoreResult> { + let mut db = self.conn.lock().await; - self.save(identity).await?; - Ok(()) + Ok(MlsSignatureKeyPair::keypair_for_signature_scheme(&mut db, credential_id, signature_scheme).await?) } async fn mls_keypackagebundle_count(&self) -> CryptoKeystoreResult { - self.count::().await + self.count::().await } #[cfg(target_family = "wasm")] - async fn mls_fetch_keypackage_bundles(&self, count: u32) -> CryptoKeystoreResult> { + async fn mls_fetch_keypackages(&self, count: u32) -> CryptoKeystoreResult> { use crate::{connection::storage::WasmStorageWrapper, entities::Entity}; let conn = self.conn.lock_arc().await; let cipher = &conn.storage().cipher; let storage = &conn.storage().storage; - let raw_kps: Vec = match storage { + let raw_kps: Vec = match storage { WasmStorageWrapper::Persistent(rexie) => { - let transaction = rexie.transaction(&["mls_keys"], rexie::TransactionMode::ReadOnly)?; - let store = transaction.store("mls_keys")?; + let transaction = rexie.transaction(&["mls_keypackages"], rexie::TransactionMode::ReadOnly)?; + let store = transaction.store("mls_keypackages")?; let items_fut = store.get_all(None, Some(count), None, Some(rexie::Direction::Next)); let items = items_fut.await?; @@ -243,25 +221,25 @@ impl CryptoKeystoreMls for crate::connection::Connection { let kps = items .into_iter() .map(|(_k, v)| { - let mut kp: MlsKeypackage = serde_wasm_bindgen::from_value(v)?; + let mut kp: MlsKeyPackage = serde_wasm_bindgen::from_value(v)?; kp.decrypt(cipher)?; Ok(kp) }) - .collect::>>()?; + .collect::>>()?; CryptoKeystoreResult::Ok(kps) } WasmStorageWrapper::InMemory(map) => { - if let Some(collection) = map.get("mls_keys") { + if let Some(collection) = map.get("mls_keypackages") { let kps = collection .iter() .take(count as usize) .map(|(_k, v)| { - let mut entity: MlsKeypackage = serde_wasm_bindgen::from_value(v.clone())?; + let mut entity: MlsKeyPackage = serde_wasm_bindgen::from_value(v.clone())?; entity.decrypt(cipher)?; Ok(entity) }) - .collect::>>()?; + .collect::>>()?; Ok(kps) } else { @@ -272,21 +250,21 @@ impl CryptoKeystoreMls for crate::connection::Connection { Ok(raw_kps .into_iter() - .filter_map(|kpb| V::from_key_store_value(&kpb.key).ok()) + .filter_map(|kpb| deser(&kpb.keypackage).ok()) .collect()) } #[cfg(target_family = "wasm")] - async fn mls_get_keypackage(&self) -> CryptoKeystoreResult { + async fn mls_get_keypackage(&self) -> CryptoKeystoreResult { use crate::{connection::storage::WasmStorageWrapper, entities::Entity}; let conn = self.conn.lock_arc().await; let cipher = conn.storage().cipher.clone(); let storage = &conn.storage().storage; - let raw_kp: MlsKeypackage = match storage { + let raw_kp: MlsKeyPackage = match storage { WasmStorageWrapper::Persistent(rexie) => { - let transaction = rexie.transaction(&["mls_keys"], rexie::TransactionMode::ReadOnly)?; - let store = transaction.store("mls_keys")?; + let transaction = rexie.transaction(&["mls_keypackages"], rexie::TransactionMode::ReadOnly)?; + let store = transaction.store("mls_keypackages")?; let items_fut = store.get_all(None, Some(1), None, Some(rexie::Direction::Next)); @@ -297,7 +275,7 @@ impl CryptoKeystoreMls for crate::connection::Connection { } let (_, js_kp) = items[0].clone(); - let mut kp: MlsKeypackage = serde_wasm_bindgen::from_value(js_kp)?; + let mut kp: MlsKeyPackage = serde_wasm_bindgen::from_value(js_kp)?; kp.decrypt(&cipher)?; drop(items); @@ -307,9 +285,9 @@ impl CryptoKeystoreMls for crate::connection::Connection { Ok(kp) } WasmStorageWrapper::InMemory(map) => { - if let Some(collection) = map.get("mls_keys") { + if let Some(collection) = map.get("mls_keypackages") { if let Some((_, js_kp)) = collection.iter().next() { - let mut entity: MlsKeypackage = serde_wasm_bindgen::from_value(js_kp.clone())?; + let mut entity: MlsKeyPackage = serde_wasm_bindgen::from_value(js_kp.clone())?; entity.decrypt(&cipher)?; Ok(entity) } else { @@ -321,50 +299,81 @@ impl CryptoKeystoreMls for crate::connection::Connection { } }?; - Ok(V::from_key_store_value(&raw_kp.key) - .map_err(|e| CryptoKeystoreError::KeyStoreValueTransformError(Box::new(e)))?) + Ok(deser(&raw_kp.keypackage)?) } #[cfg(not(target_family = "wasm"))] - async fn mls_fetch_keypackage_bundles(&self, count: u32) -> CryptoKeystoreResult> { - let db = self.conn.lock().await; - - let mut stmt = db.prepare_cached("SELECT id FROM mls_keys ORDER BY rowid DESC LIMIT ?")?; - - let kpb_ids: Vec = stmt - .query_map([count], |r| r.get(0))? - .map(|r| r.map_err(CryptoKeystoreError::from)) - .collect::>>()?; - - drop(stmt); - drop(db); - - let keypackages: Vec = self.find_many(&kpb_ids).await?; + async fn mls_fetch_keypackages(&self, count: u32) -> CryptoKeystoreResult> { + let mut db = self.conn.lock().await; + + let transaction = db.transaction()?; + + let mut stmt = transaction.prepare_cached("SELECT rowid FROM mls_keypackages ORDER BY rowid DESC LIMIT ?")?; + + let mut keypackages: Vec = vec![]; + for kpb_rowid in stmt.query_map([count], |r| r.get(0))? { + use std::io::Read as _; + let rowid = kpb_rowid?; + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage_ref", + rowid, + true, + )?; + let mut keypackage_ref = vec![]; + blob.read_to_end(&mut keypackage_ref)?; + blob.close()?; + + let mut blob = transaction.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage", + rowid, + true, + )?; + let mut keypackage = vec![]; + blob.read_to_end(&mut keypackage)?; + blob.close()?; + + keypackages.push(MlsKeyPackage { + keypackage_ref, + keypackage, + }); + } Ok(keypackages .into_iter() - .filter_map(|kpb| V::from_key_store_value(&kpb.key).ok()) + .filter_map(|kpb| postcard::from_bytes(&kpb.keypackage).ok()) .collect()) } #[cfg(not(target_family = "wasm"))] - async fn mls_get_keypackage(&self) -> CryptoKeystoreResult { + async fn mls_get_keypackage(&self) -> CryptoKeystoreResult { if self.mls_keypackagebundle_count().await? == 0 { return Err(CryptoKeystoreError::OutOfKeyPackageBundles); } let db = self.conn.lock().await; - let rowid: i64 = db.query_row("SELECT rowid FROM mls_keys ORDER BY rowid ASC LIMIT 1", [], |r| { - r.get(0) - })?; - - let mut blob = db.blob_open(rusqlite::DatabaseName::Main, "mls_keys", "key", rowid, true)?; + let rowid: i64 = db.query_row( + "SELECT rowid FROM mls_keypackages ORDER BY rowid ASC LIMIT 1", + [], + |r| r.get(0), + )?; + + let mut blob = db.blob_open( + rusqlite::DatabaseName::Main, + "mls_keypackages", + "keypackage", + rowid, + true, + )?; use std::io::Read as _; let mut buf = Vec::with_capacity(blob.len()); blob.read_to_end(&mut buf)?; blob.close()?; - V::from_key_store_value(&buf).map_err(|e| CryptoKeystoreError::KeyStoreValueTransformError(Box::new(e))) + Ok(postcard::from_bytes(&buf)?) } async fn mls_group_persist( @@ -393,7 +402,6 @@ impl CryptoKeystoreMls for crate::connection::Connection { Ok(()) } - // TODO: Review zero on drop behavior async fn mls_groups_restore( &self, ) -> CryptoKeystoreResult, (Option>, Vec)>> { @@ -456,11 +464,21 @@ impl CryptoKeystoreMls for crate::connection::Connection { } } +#[inline(always)] +pub fn deser(bytes: &[u8]) -> Result { + Ok(postcard::from_bytes(bytes)?) +} + +#[inline(always)] +pub fn ser(value: &T) -> Result, CryptoKeystoreError> { + Ok(postcard::to_stdvec(value)?) +} + #[async_trait::async_trait(?Send)] impl openmls_traits::key_store::OpenMlsKeyStore for crate::connection::Connection { type Error = CryptoKeystoreError; - async fn store(&self, k: &[u8], v: &V) -> Result<(), Self::Error> + async fn store(&self, k: &[u8], v: &V) -> Result<(), Self::Error> where Self: Sized, { @@ -470,25 +488,55 @@ impl openmls_traits::key_store::OpenMlsKeyStore for crate::connection::Connectio )); } - let data = v - .to_key_store_value() - .map_err(|e| CryptoKeystoreError::KeyStoreValueTransformError(Box::new(e)))?; - let type_name = std::any::type_name::(); - - let id = bytes_to_string_id(k); + let data = ser(v)?; - match type_name { - "openmls::key_packages::KeyPackageBundle" => { - let kp = MlsKeypackage { id, key: data }; + match V::ID { + MlsEntityId::GroupState => { + return Err(CryptoKeystoreError::IncorrectApiUsage( + "Groups must not be saved using OpenMLS's APIs. You should use the keystore's provided methods", + )); + } + MlsEntityId::SignatureKeyPair => { + let concrete_signature_keypair: &SignatureKeyPair = v + .downcast() + .expect("There's an implementation issue in OpenMLS. This shouln't be happening."); + + let kp = MlsSignatureKeyPair { + pk: k.into(), + keypair: data, + credential_id: vec![], // FIXME: find a way to set the credential id + signature_scheme: concrete_signature_keypair.signature_scheme() as u16, + }; + self.save(kp).await?; + } + MlsEntityId::KeyPackage => { + let kp = MlsKeyPackage { + keypackage_ref: k.into(), + keypackage: data, + }; + self.save(kp).await?; + } + MlsEntityId::HpkePrivateKey => { + let kp = MlsHpkePrivateKey { pk: k.into(), sk: data }; + self.save(kp).await?; + } + MlsEntityId::PskBundle => { + let kp = MlsPskBundle { + psk_id: k.into(), + psk: data, + }; + self.save(kp).await?; + } + MlsEntityId::EncryptionKeyPair => { + let kp = MlsEncryptionKeyPair { pk: k.into(), sk: data }; self.save(kp).await?; } - _ => unreachable!("OpenMlsKeyStore::store: Unsupported ToKeyStoreValue type"), } Ok(()) } - async fn read(&self, k: &[u8]) -> Option + async fn read(&self, k: &[u8]) -> Option where Self: Sized, { @@ -496,70 +544,46 @@ impl openmls_traits::key_store::OpenMlsKeyStore for crate::connection::Connectio return None; } - let type_name = std::any::type_name::(); - - let hydrated_ksv = match type_name { - "openmls::key_packages::KeyPackageBundle" => { - let keypackage_id = bytes_to_string_id(k); - - #[cfg(feature = "memory-cache")] - if self.is_cache_enabled() { - if let Some(mut cache) = self.memory_cache.try_lock() { - if let Some(value) = cache - .get(&Self::mls_cache_key(k)) - .and_then(|buf| V::from_key_store_value(buf).ok()) - { - return Some(value); - } - } - } - - let kp: MlsKeypackage = self.find(keypackage_id).await.ok().flatten()?; - - #[cfg(feature = "memory-cache")] - if self.is_cache_enabled() { - let mut cache = self.memory_cache.lock().await; - cache.put(Self::mls_cache_key(k), kp.key.clone()); - } - - V::from_key_store_value(&kp.key).ok()? + match V::ID { + MlsEntityId::GroupState => { + let group: PersistedMlsGroup = self.find(k).await.ok().flatten()?; + deser(&group.state).ok() } - "openmls::credentials::CredentialBundle" => { - use crate::entities::MlsIdentityExt as _; - let mut conn = self.borrow_conn().await.ok()?; - - let identity = MlsIdentity::find_by_signature(&mut conn, k).await.ok().flatten()?; - - V::from_key_store_value(&identity.credential).ok()? + MlsEntityId::SignatureKeyPair => { + let sig: MlsSignatureKeyPair = self.find(k).await.ok().flatten()?; + deser(&sig.keypair).ok() } - _ => unreachable!("OpenMlsKeyStore::read: Unsupported FromKeyStoreValue type"), - }; - - Some(hydrated_ksv) + MlsEntityId::KeyPackage => { + let kp: MlsKeyPackage = self.find(k).await.ok().flatten()?; + deser(&kp.keypackage).ok() + } + MlsEntityId::HpkePrivateKey => { + let hpke_pk: MlsHpkePrivateKey = self.find(k).await.ok().flatten()?; + deser(&hpke_pk.sk).ok() + } + MlsEntityId::PskBundle => { + let psk_bundle: MlsPskBundle = self.find(k).await.ok().flatten()?; + deser(&psk_bundle.psk).ok() + } + MlsEntityId::EncryptionKeyPair => { + let kp: MlsEncryptionKeyPair = self.find(k).await.ok().flatten()?; + deser(&kp.sk).ok() + } + } } - async fn delete(&self, k: &[u8]) -> Result<(), Self::Error> { + async fn delete(&self, k: &[u8]) -> Result<(), Self::Error> { if k.is_empty() { return Ok(()); } - let id = bytes_to_string_id(k); - #[cfg(feature = "memory-cache")] - if self.is_cache_enabled() { - let _ = self.memory_cache.lock().await.pop(&Self::mls_cache_key(&k)); - } - - let type_name = std::any::type_name::(); - - match type_name { - "openmls::key_packages::KeyPackageBundle" => { - self.remove::(id.clone()).await?; - } - "openmls::credentials::CredentialBundle" => { - let mut conn = self.borrow_conn().await?; - MlsIdentity::delete_by_signature(&mut conn, k).await?; - } - _ => unreachable!("OpenMlsKeyStore::delete: Unsupported FromKeyStoreValue type"), + match V::ID { + MlsEntityId::GroupState => self.remove::(k).await?, + MlsEntityId::SignatureKeyPair => self.remove::(k).await?, + MlsEntityId::HpkePrivateKey => self.remove::(k).await?, + MlsEntityId::KeyPackage => self.remove::(k).await?, + MlsEntityId::PskBundle => self.remove::(k).await?, + MlsEntityId::EncryptionKeyPair => self.remove::(k).await?, } Ok(()) diff --git a/keystore/src/proteus.rs b/keystore/src/proteus.rs index 90039e640c..6272f30cce 100644 --- a/keystore/src/proteus.rs +++ b/keystore/src/proteus.rs @@ -30,14 +30,6 @@ impl CryptoKeystoreProteus for Connection { } } -impl Connection { - #[cfg(feature = "memory-cache")] - #[inline(always)] - fn proteus_memory_key(k: S) -> Vec { - format!("proteus:{}", k).into_bytes() - } -} - #[async_trait::async_trait(?Send)] impl proteus_traits::PreKeyStore for Connection { type Error = CryptoKeystoreError; @@ -46,15 +38,6 @@ impl proteus_traits::PreKeyStore for Connection { &mut self, id: proteus_traits::RawPreKeyId, ) -> Result, Self::Error> { - #[cfg(feature = "memory-cache")] - if self.is_cache_enabled() { - let mut cache = self.memory_cache.lock().await; - - if let Some(buf) = cache.get(&Self::proteus_memory_key(id)) { - return Ok(Some(buf.clone())); - } - } - Ok(self .find::(id.to_le_bytes()) .await? @@ -62,11 +45,6 @@ impl proteus_traits::PreKeyStore for Connection { } async fn remove(&mut self, id: proteus_traits::RawPreKeyId) -> Result<(), Self::Error> { - #[cfg(feature = "memory-cache")] - if self.is_cache_enabled() { - let _ = self.memory_cache.lock().await.pop(format!("proteus:{}", id).as_bytes()); - } - Connection::remove::(self, id.to_le_bytes()).await?; Ok(()) diff --git a/keystore/tests/general.rs b/keystore/tests/general.rs index e059490331..ff76706858 100644 --- a/keystore/tests/general.rs +++ b/keystore/tests/general.rs @@ -73,8 +73,12 @@ pub mod tests { assert!(store_names.contains(&"regression_check".into())); - assert!(store_names.contains(&"mls_keys".into())); - assert!(store_names.contains(&"mls_identities".into())); + assert!(store_names.contains(&"mls_psk_bundles".into())); + assert!(store_names.contains(&"mls_signature_keypairs".into())); + assert!(store_names.contains(&"mls_hpke_private_keys".into())); + assert!(store_names.contains(&"mls_encryption_keypairs".into())); + assert!(store_names.contains(&"mls_keypackages".into())); + assert!(store_names.contains(&"mls_credentials".into())); assert!(store_names.contains(&"mls_groups".into())); assert!(store_names.contains(&"mls_pending_groups".into())); assert!(store_names.contains(&"proteus_prekeys".into())); diff --git a/keystore/tests/mls.rs b/keystore/tests/mls.rs index 45fdccb926..bb5ad40364 100644 --- a/keystore/tests/mls.rs +++ b/keystore/tests/mls.rs @@ -21,7 +21,9 @@ mod common; pub mod tests { use crate::common::*; - use openmls::prelude::Ciphersuite; + use openmls::prelude::TlsDeserializeTrait; + use openmls::{credentials::Credential, prelude::Ciphersuite}; + use openmls_traits::random::OpenMlsRand; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -29,25 +31,23 @@ pub mod tests { use mls_crypto_provider::MlsCryptoProvider; use core_crypto_keystore::entities::{ - EntityBase, MlsIdentity, MlsIdentityExt as _, MlsKeypackage, PersistedMlsGroup, + EntityBase, MlsCredential, MlsHpkePrivateKey, MlsKeyPackage, MlsPskBundle, MlsSignatureKeyPair, + PersistedMlsGroup, PersistedMlsPendingGroup, }; use core_crypto_keystore::{Connection, MissingKeyErrorKind}; use openmls::prelude::TlsSerializeTrait as _; - use openmls_traits::{ - key_store::{FromKeyStoreValue as _, ToKeyStoreValue as _}, - OpenMlsCryptoProvider as _, - }; + use openmls_traits::OpenMlsCryptoProvider as _; #[test] #[wasm_bindgen_test] fn mls_entities_have_correct_error_kinds() { assert_eq!( - MlsIdentity::to_missing_key_err_kind(), - MissingKeyErrorKind::MlsIdentityBundle + MlsCredential::to_missing_key_err_kind(), + MissingKeyErrorKind::MlsCredential ); assert_eq!( - MlsKeypackage::to_missing_key_err_kind(), + MlsKeyPackage::to_missing_key_err_kind(), MissingKeyErrorKind::MlsKeyPackageBundle ); @@ -55,69 +55,32 @@ pub mod tests { PersistedMlsGroup::to_missing_key_err_kind(), MissingKeyErrorKind::MlsGroup ); - } - - #[apply(all_storage_types)] - #[wasm_bindgen_test] - pub async fn can_add_read_delete_credential_bundle_keystore(store: Connection) { - use openmls::credentials::CredentialBundle; - - let store = store.await; - let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; - - let backend = MlsCryptoProvider::new_with_store(store, None); - let identity_id: [u8; 16] = rand::random(); - let identity_id = uuid::Uuid::from_bytes(identity_id); - - let credentials = CredentialBundle::new_basic( - identity_id.as_bytes().as_slice().into(), - ciphersuite.signature_algorithm(), - &backend, - ) - .unwrap(); - - let signature = credentials - .credential() - .signature_key() - .tls_serialize_detached() - .unwrap(); - - let identity = MlsIdentity { - id: identity_id.hyphenated().to_string(), - ciphersuite: (&ciphersuite).into(), - credential_type: 1, - signature: signature.clone(), - credential: credentials.to_key_store_value().unwrap(), - }; - - backend.key_store().save(identity).await.unwrap(); - - let mut conn = backend.key_store().borrow_conn().await.unwrap(); - let identity2 = MlsIdentity::find_by_signature(&mut conn, &signature) - .await - .unwrap() - .unwrap(); - - drop(conn); - let credentials2 = CredentialBundle::from_key_store_value(&identity2.credential).unwrap(); - let (b1_kp, b1_sk) = credentials.into_parts(); - let (b2_kp, b2_sk) = credentials2.into_parts(); - assert_eq!(b1_kp, b2_kp); - assert_eq!(b1_sk.as_slice(), b2_sk.as_slice()); + assert_eq!( + PersistedMlsPendingGroup::to_missing_key_err_kind(), + MissingKeyErrorKind::MlsPendingGroup + ); - let mut conn = backend.key_store().borrow_conn().await.unwrap(); - MlsIdentity::delete_by_signature(&mut conn, &signature).await.unwrap(); + assert_eq!( + MlsHpkePrivateKey::to_missing_key_err_kind(), + MissingKeyErrorKind::MlsHpkePrivateKey + ); - drop(conn); + assert_eq!( + MlsSignatureKeyPair::to_missing_key_err_kind(), + MissingKeyErrorKind::MlsSignatureKeyPair + ); - teardown(backend.unwrap_keystore()).await; + assert_eq!( + MlsPskBundle::to_missing_key_err_kind(), + MissingKeyErrorKind::MlsPskBundle + ); } #[apply(all_storage_types)] #[wasm_bindgen_test] pub async fn can_add_read_delete_credential_bundle_openmls_traits(store: Connection) { - use openmls::credentials::CredentialBundle; + use openmls_basic_credential::SignatureKeyPair; let store = store.await; let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; @@ -126,148 +89,165 @@ pub mod tests { let identity_id: [u8; 16] = rand::random(); let identity_id = uuid::Uuid::from_bytes(identity_id); - let credentials = CredentialBundle::new_basic( - identity_id.as_bytes().as_slice().into(), - ciphersuite.signature_algorithm(), - &backend, - ) - .unwrap(); + let credential = Credential::new_basic(identity_id.as_bytes().as_slice().into()); - use openmls_traits::key_store::OpenMlsKeyStore as _; - let signature = credentials - .credential() - .signature_key() - .tls_serialize_detached() - .unwrap(); - let identity = MlsIdentity { - id: identity_id.hyphenated().to_string(), - ciphersuite: (&ciphersuite).into(), - credential_type: 1, - signature: signature.clone(), - credential: credentials.to_key_store_value().unwrap(), + let credential_id: Vec = credential.identity().into(); + + let store_credential = MlsCredential { + id: credential_id.clone(), + credential: credential.tls_serialize_detached().unwrap(), }; - backend.key_store().save(identity).await.unwrap(); - let credentials2: CredentialBundle = backend.key_store().read(&signature).await.unwrap(); - let (b1_kp, b1_sk) = credentials.into_parts(); - let (b2_kp, b2_sk) = credentials2.into_parts(); - assert_eq!(b1_kp, b2_kp); - assert_eq!(b1_sk.as_slice(), b2_sk.as_slice()); - backend - .key_store() - .delete::(&signature) - .await - .unwrap(); + backend.key_store().save(store_credential).await.unwrap(); - teardown(backend.unwrap_keystore()).await; - } + let keypair = SignatureKeyPair::new( + ciphersuite.signature_algorithm(), + &mut *backend.rand().borrow_rand().unwrap(), + ) + .unwrap(); - #[apply(all_storage_types)] - #[wasm_bindgen_test] - pub async fn can_add_read_delete_keypackage_bundle_openmls_traits(store: Connection) { - use openmls::{ - credentials::CredentialBundle, - extensions::{Extension, ExternalKeyIdExtension}, - key_packages::KeyPackageBundle, + let store_keypair = MlsSignatureKeyPair { + credential_id: credential_id.clone(), + pk: keypair.to_public_vec(), + signature_scheme: keypair.signature_scheme() as u16, + keypair: keypair.tls_serialize_detached().unwrap(), }; - let store = store.await; - let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; + backend.key_store().save(store_keypair).await.unwrap(); - let backend = MlsCryptoProvider::new_with_store(store, None); - let key_id: [u8; 16] = rand::random(); - let key_id = uuid::Uuid::from_bytes(key_id); + let credential2: MlsCredential = backend.key_store().find(&credential_id).await.unwrap().unwrap(); + let keypair2: MlsSignatureKeyPair = backend.key_store().find(keypair.public()).await.unwrap().unwrap(); - let credentials = - CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); + assert_eq!(keypair2.credential_id, credential2.id); - let keypackage_bundle = KeyPackageBundle::new( - &[ciphersuite], - &credentials, - &backend, - vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], - ) - .unwrap(); - let key_string = key_id.as_hyphenated().to_string(); - - keypackage_bundle.key_package().verify(&backend).unwrap(); + let keypair2 = SignatureKeyPair::tls_deserialize_bytes(&keypair2.keypair).unwrap(); - use openmls_traits::key_store::OpenMlsKeyStore as _; - backend - .key_store() - .store(key_string.as_bytes(), &keypackage_bundle) - .await - .unwrap(); - let bundle2: KeyPackageBundle = backend.key_store().read(key_string.as_bytes()).await.unwrap(); - let (b1_kp, (b1_sk, b1_ls)) = keypackage_bundle.into_parts(); - let (b2_kp, (b2_sk, b2_ls)) = bundle2.into_parts(); + let (b1_kp, b1_sk) = (keypair.to_public_vec(), keypair.private().to_vec()); + let (b2_kp, b2_sk) = (keypair2.to_public_vec(), keypair.private().to_vec()); assert_eq!(b1_kp, b2_kp); - assert_eq!(b1_sk, b2_sk); - assert_eq!(b1_ls, b2_ls); + assert_eq!(b1_sk.as_slice(), b2_sk.as_slice()); backend .key_store() - .delete::(key_string.as_bytes()) + .remove::(&credential_id) .await .unwrap(); - - teardown(backend.unwrap_keystore()).await; - } - - #[apply(all_storage_types)] - #[wasm_bindgen_test] - pub async fn can_add_read_delete_keypackage_bundle_keystore(store: Connection) { - use openmls::{ - credentials::CredentialBundle, - extensions::{Extension, ExternalKeyIdExtension}, - key_packages::KeyPackageBundle, - }; - - let store = store.await; - - let backend = MlsCryptoProvider::new_with_store(store, None); - let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; - - let key_id: [u8; 16] = rand::random(); - let key_id = uuid::Uuid::from_bytes(key_id); - - let credentials = - CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); - - let keypackage_bundle = KeyPackageBundle::new( - &[ciphersuite], - &credentials, - &backend, - vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], - ) - .unwrap(); - - keypackage_bundle.key_package().verify(&backend).unwrap(); - - let key_string = key_id.as_hyphenated().to_string(); - - let entity = MlsKeypackage { - id: key_string.clone(), - key: keypackage_bundle.to_key_store_value().unwrap(), - }; - - backend.key_store().save(entity).await.unwrap(); - - let entity2: MlsKeypackage = backend.key_store().find(key_string.as_bytes()).await.unwrap().unwrap(); - let bundle2 = KeyPackageBundle::from_key_store_value(&entity2.key).unwrap(); - - let (b1_kp, (b1_sk, b1_ls)) = keypackage_bundle.into_parts(); - let (b2_kp, (b2_sk, b2_ls)) = bundle2.into_parts(); - assert_eq!(b1_kp, b2_kp); - assert_eq!(b1_sk, b2_sk); - assert_eq!(b1_ls, b2_ls); - backend .key_store() - .remove::(key_string.as_bytes()) + .remove::(keypair.public()) .await .unwrap(); teardown(backend.unwrap_keystore()).await; } + + // FIXME: rewrite the tests using the new OpenMLS apis + // #[apply(all_storage_types)] + // #[wasm_bindgen_test] + // pub async fn can_add_read_delete_keypackage_bundle_openmls_traits(store: Connection) { + // use openmls::{ + // credentials::CredentialBundle, + // extensions::{Extension, ExternalKeyIdExtension}, + // key_packages::KeyPackageBundle, + // }; + + // let store = store.await; + // let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; + + // let backend = MlsCryptoProvider::new_with_store(store, None); + // let key_id: [u8; 16] = rand::random(); + // let key_id = uuid::Uuid::from_bytes(key_id); + + // let credentials = + // CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); + + // let keypackage_bundle = KeyPackageBundle::new( + // &[ciphersuite], + // &credentials, + // &backend, + // vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], + // ) + // .unwrap(); + // let key_string = key_id.as_hyphenated().to_string(); + + // keypackage_bundle.key_package().verify(&backend).unwrap(); + + // use openmls_traits::key_store::OpenMlsKeyStore as _; + // backend + // .key_store() + // .store(key_string.as_bytes(), &keypackage_bundle) + // .await + // .unwrap(); + // let bundle2: KeyPackageBundle = backend.key_store().read(key_string.as_bytes()).await.unwrap(); + // let (b1_kp, (b1_sk, b1_ls)) = keypackage_bundle.into_parts(); + // let (b2_kp, (b2_sk, b2_ls)) = bundle2.into_parts(); + // assert_eq!(b1_kp, b2_kp); + // assert_eq!(b1_sk, b2_sk); + // assert_eq!(b1_ls, b2_ls); + + // backend + // .key_store() + // .delete::(key_string.as_bytes()) + // .await + // .unwrap(); + + // teardown(backend.unwrap_keystore()).await; + // } + + // #[apply(all_storage_types)] + // #[wasm_bindgen_test] + // pub async fn can_add_read_delete_keypackage_bundle_keystore(store: Connection) { + // use openmls::{ + // credentials::CredentialBundle, + // extensions::{Extension, ExternalKeyIdExtension}, + // key_packages::KeyPackageBundle, + // }; + + // let store = store.await; + + // let backend = MlsCryptoProvider::new_with_store(store, None); + // let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; + + // let key_id: [u8; 16] = rand::random(); + // let key_id = uuid::Uuid::from_bytes(key_id); + + // let credentials = + // CredentialBundle::new_basic(vec![1, 2, 3], ciphersuite.signature_algorithm(), &backend).unwrap(); + + // let keypackage_bundle = KeyPackageBundle::new( + // &[ciphersuite], + // &credentials, + // &backend, + // vec![Extension::ExternalKeyId(ExternalKeyIdExtension::new(key_id.as_bytes()))], + // ) + // .unwrap(); + + // keypackage_bundle.key_package().verify(&backend).unwrap(); + + // let key_string = key_id.as_hyphenated().to_string(); + + // let entity = MlsKeyPackage { + // id: key_string.clone(), + // key: keypackage_bundle.to_key_store_value().unwrap(), + // }; + + // backend.key_store().save(entity).await.unwrap(); + + // let entity2: MlsKeyPackage = backend.key_store().find(key_string.as_bytes()).await.unwrap().unwrap(); + // let bundle2 = KeyPackageBundle::from_key_store_value(&entity2.key).unwrap(); + + // let (b1_kp, (b1_sk, b1_ls)) = keypackage_bundle.into_parts(); + // let (b2_kp, (b2_sk, b2_ls)) = bundle2.into_parts(); + // assert_eq!(b1_kp, b2_kp); + // assert_eq!(b1_sk, b2_sk); + // assert_eq!(b1_ls, b2_ls); + + // backend + // .key_store() + // .remove::(key_string.as_bytes()) + // .await + // .unwrap(); + + // teardown(backend.unwrap_keystore()).await; + // } } diff --git a/keystore/tests/z_entities.rs b/keystore/tests/z_entities.rs index 8b7aea248b..823dc93892 100644 --- a/keystore/tests/z_entities.rs +++ b/keystore/tests/z_entities.rs @@ -36,6 +36,7 @@ macro_rules! test_for_entity { #[wasm_bindgen_test] pub async fn $test_name(store: core_crypto_keystore::Connection) { let store = store.await; + let _ = pretty_env_logger::try_init(); let mut entity = crate::tests_impl::can_save_entity::<$entity>(&store).await; crate::tests_impl::can_find_entity::<$entity>(&store, &entity).await; @@ -144,8 +145,12 @@ pub mod tests { if #[cfg(feature = "mls-keystore")] { test_for_entity!(test_persisted_mls_group, PersistedMlsGroup); test_for_entity!(test_persisted_mls_pending_group, PersistedMlsPendingGroup); - test_for_entity!(test_mls_identity, MlsIdentity); - test_for_entity!(test_mls_keypackage, MlsKeypackage); + test_for_entity!(test_mls_credential, MlsCredential); + test_for_entity!(test_mls_keypackage, MlsKeyPackage); + test_for_entity!(test_mls_signature_keypair, MlsSignatureKeyPair); + test_for_entity!(test_mls_psk_bundle, MlsPskBundle); + test_for_entity!(test_mls_encryption_keypair, MlsEncryptionKeyPair); + test_for_entity!(test_mls_hpke_private_key, MlsHpkePrivateKey); } } cfg_if::cfg_if! { @@ -159,7 +164,6 @@ pub mod tests { #[cfg(test)] pub mod utils { - use openmls_traits::types::Ciphersuite; use rand::Rng as _; const MAX_BLOB_SIZE: std::ops::Range = 1024..8192; @@ -170,56 +174,147 @@ pub mod utils { cfg_if::cfg_if! { if #[cfg(feature = "mls-keystore")] { - impl EntityTestExt for core_crypto_keystore::entities::MlsKeypackage { + impl EntityTestExt for core_crypto_keystore::entities::MlsKeyPackage { fn random() -> Self { let mut rng = rand::thread_rng(); - let id: String = uuid::Uuid::new_v4().hyphenated().to_string(); - let mut key = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); - rng.fill(&mut key[..]); + let keypackage_ref = uuid::Uuid::new_v4().hyphenated().to_string().into_bytes(); + let mut keypackage = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut keypackage[..]); Self { - id, - key + keypackage_ref, + keypackage, } } fn random_update(&mut self) { let mut rng = rand::thread_rng(); - self.key = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); - rng.fill(&mut self.key[..]); + self.keypackage = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.keypackage[..]); } } - impl EntityTestExt for core_crypto_keystore::entities::MlsIdentity { + impl EntityTestExt for core_crypto_keystore::entities::MlsCredential { fn random() -> Self { let mut rng = rand::thread_rng(); let id: String = uuid::Uuid::new_v4().hyphenated().to_string(); - let mut signature = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); - rng.fill(&mut signature[..]); - let mut credential = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + let mut credential = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut credential[..]); Self { - id, - ciphersuite: (&Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519).into(), - credential_type: 1, - signature, + id: id.into(), credential, } } fn random_update(&mut self) { let mut rng = rand::thread_rng(); - self.signature = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); - self.credential = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); - rng.fill(&mut self.signature[..]); + self.credential = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut self.credential[..]); } } + impl EntityTestExt for core_crypto_keystore::entities::MlsSignatureKeyPair { + fn random() -> Self { + let mut rng = rand::thread_rng(); + + let mut pk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut pk[..]); + + let mut keypair = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut keypair[..]); + + let mut credential_id = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut credential_id[..]); + + Self { + signature_scheme: rand::random(), + keypair, pk, credential_id + } + } + + fn random_update(&mut self) { + let mut rng = rand::thread_rng(); + + self.keypair = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.keypair[..]); + + self.credential_id = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.credential_id[..]); + } + } + + impl EntityTestExt for core_crypto_keystore::entities::MlsHpkePrivateKey { + fn random() -> Self { + let mut rng = rand::thread_rng(); + + let mut pk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut pk[..]); + + let mut sk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut sk[..]); + + Self { + pk, sk + } + } + + fn random_update(&mut self) { + let mut rng = rand::thread_rng(); + + self.sk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.sk[..]); + } + } + + impl EntityTestExt for core_crypto_keystore::entities::MlsEncryptionKeyPair { + fn random() -> Self { + let mut rng = rand::thread_rng(); + + let mut pk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut pk[..]); + + let mut sk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut sk[..]); + + Self { + pk, sk + } + } + + fn random_update(&mut self) { + let mut rng = rand::thread_rng(); + + self.sk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.sk[..]); + } + } + + impl EntityTestExt for core_crypto_keystore::entities::MlsPskBundle { + fn random() -> Self { + let mut rng = rand::thread_rng(); + + let mut psk_id = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut psk_id[..]); + + let mut psk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut psk[..]); + + Self { + psk, psk_id + } + } + + fn random_update(&mut self) { + let mut rng = rand::thread_rng(); + self.psk = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; + rng.fill(&mut self.psk[..]); + } + } + impl EntityTestExt for core_crypto_keystore::entities::PersistedMlsGroup { fn random() -> Self { use rand::Rng as _; @@ -228,7 +323,7 @@ pub mod utils { let uuid = uuid::Uuid::new_v4(); let id: [u8; 16] = uuid.into_bytes(); - let mut state = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + let mut state = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut state[..]); Self { @@ -240,7 +335,7 @@ pub mod utils { fn random_update(&mut self) { let mut rng = rand::thread_rng(); - self.state = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + self.state = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut self.state[..]); } } @@ -253,10 +348,10 @@ pub mod utils { let uuid = uuid::Uuid::new_v4(); let id: [u8; 16] = uuid.into_bytes(); - let mut state = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + let mut state = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut state[..]); - let mut custom_configuration = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + let mut custom_configuration = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut custom_configuration[..]); Self { @@ -269,7 +364,7 @@ pub mod utils { fn random_update(&mut self) { let mut rng = rand::thread_rng(); - self.state = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + self.state = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut self.state[..]); } } @@ -331,7 +426,7 @@ pub mod utils { let uuid = uuid::Uuid::new_v4(); - let mut session = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + let mut session = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut session[..]); Self { @@ -343,7 +438,7 @@ pub mod utils { fn random_update(&mut self) { let mut rng = rand::thread_rng(); - self.session = Vec::with_capacity(rng.gen_range(MAX_BLOB_SIZE)); + self.session = vec![0; rng.gen_range(MAX_BLOB_SIZE)]; rng.fill(&mut self.session[..]); } } diff --git a/mls-provider/Cargo.toml b/mls-provider/Cargo.toml index c68a5b1fdd..717b25d637 100644 --- a/mls-provider/Cargo.toml +++ b/mls-provider/Cargo.toml @@ -3,7 +3,7 @@ name = "mls-crypto-provider" description = "MLS Crypto Provider wrapping core-crypto-keystore" repository = "https://github.com/wireapp/core-crypto" version = "0.11.0" -edition = "2018" +edition = "2021" license = "GPL-3.0-only" publish = false @@ -16,12 +16,14 @@ default = [] raw-rand-access = [] # TESTING ONLY [dependencies] -openmls_traits = { version = "0.1", features = ["single-threaded"] } +openmls_traits = "0.1" +tls_codec = { version = "0.3.0-pre.3", features = ["derive", "serde", "mls"] } aes-gcm = "0.10" sha2 = "0.10" chacha20poly1305 = "0.10" hmac = "0.12" -ed25519-dalek = "1.0" +ed25519-dalek = "2.0.0-rc.2" +signature = "2.1" ecdsa = { version = "0.16", features = ["der"] } p256 = "0.13" p384 = "0.13" @@ -30,13 +32,15 @@ p384 = "0.13" hkdf = "0.12" rand = { version = "0.8", features = ["getrandom"] } getrandom = { version = "0.2", features = ["js"] } +rand_core = "0.6" rand_chacha = "0.3" -hpke = { version = "0.2", package = "hpke-rs", default-features = false, features = ["hazmat", "serialization"] } -hpke-rs-crypto = "0.2" -hpke-rs-rust-crypto = "0.2" zeroize = "1.5" thiserror = "1.0" +[dependencies.hpke] +version = "0.10" +features = ["x25519", "p256", "p384", "serde_impls"] + [target.'cfg(not(target_os = "ios"))'.dependencies] core-crypto-keystore = { version = "^0.11.0", path = "../keystore" } @@ -46,7 +50,7 @@ core-crypto-keystore = { version = "^0.11.0", path = "../keystore", features = [ [dev-dependencies] wasm-bindgen-test = "0.3" uuid = { version = "1.0", features = ["v4", "js"] } -openmls = { version = "0.4", default-features = false } +openmls = { version = "0.20", default-features = false } rstest = "0.17" rstest_reuse = "0.5" async-std = { version = "1.12", features = ["attributes"] } diff --git a/mls-provider/src/crypto_provider.rs b/mls-provider/src/crypto_provider.rs index 5d89122a9f..2ceb27c366 100644 --- a/mls-provider/src/crypto_provider.rs +++ b/mls-provider/src/crypto_provider.rs @@ -1,3 +1,6 @@ +use crate::EntropySeed; +use crate::MlsProviderError; +use rand_core::{RngCore, SeedableRng}; use std::sync::RwLock; use aes_gcm::{ @@ -5,24 +8,17 @@ use aes_gcm::{ Aes128Gcm, Aes256Gcm, KeyInit, }; use chacha20poly1305::ChaCha20Poly1305; -use ecdsa::{signature::Verifier, Signature, SigningKey, VerifyingKey}; -use ed25519_dalek::Signer as DalekSigner; use hkdf::Hkdf; -use hpke::Hpke; -use hpke_rs_crypto::types as hpke_types; -use hpke_rs_rust_crypto::HpkeRustCrypto; use openmls_traits::{ crypto::OpenMlsCrypto, random::OpenMlsRand, types::{ - self, AeadType, Ciphersuite, CryptoError, HashType, HpkeAeadType, HpkeCiphertext, HpkeConfig, HpkeKdfType, - HpkeKemType, HpkeKeyPair, SignatureScheme, + self, AeadType, Ciphersuite, CryptoError, ExporterSecret, HashType, HpkeAeadType, HpkeConfig, HpkeKdfType, + HpkeKemType, SignatureScheme, }, }; -use rand::{RngCore, SeedableRng}; use sha2::{Digest, Sha256, Sha384, Sha512}; - -use crate::{EntropySeed, MlsProviderError}; +use tls_codec::SecretVLBytes; #[derive(Debug)] pub struct RustCrypto { @@ -32,7 +28,7 @@ pub struct RustCrypto { impl Default for RustCrypto { fn default() -> Self { Self { - rng: rand_chacha::ChaCha20Rng::from_entropy().into(), + rng: RwLock::new(rand_chacha::ChaCha20Rng::from_entropy()), } } } @@ -45,63 +41,6 @@ impl RustCrypto { } } -#[cfg(feature = "raw-rand-access")] -impl RustCrypto { - pub fn borrow_rand(&self) -> std::sync::RwLockWriteGuard { - self.rng.write().unwrap() - } -} - -impl OpenMlsRand for RustCrypto { - type Error = MlsProviderError; - - fn random_array(&self) -> Result<[u8; N], Self::Error> { - let mut rng = self.rng.write().map_err(|_| MlsProviderError::RngLockPoison)?; - let mut out = [0u8; N]; - rng.try_fill_bytes(&mut out) - .map_err(|_| MlsProviderError::UnsufficientEntropy)?; - Ok(out) - } - - fn random_vec(&self, len: usize) -> Result, Self::Error> { - let mut rng = self.rng.write().map_err(|_| MlsProviderError::RngLockPoison)?; - let mut out = vec![0u8; len]; - rng.try_fill_bytes(&mut out) - .map_err(|_| MlsProviderError::UnsufficientEntropy)?; - Ok(out) - } -} - -#[inline] -fn kem_mode(kem: HpkeKemType) -> hpke_types::KemAlgorithm { - match kem { - HpkeKemType::DhKemP256 => hpke_types::KemAlgorithm::DhKemP256, - HpkeKemType::DhKemP384 => hpke_types::KemAlgorithm::DhKemP384, - HpkeKemType::DhKemP521 => hpke_types::KemAlgorithm::DhKemP521, - HpkeKemType::DhKem25519 => hpke_types::KemAlgorithm::DhKem25519, - HpkeKemType::DhKem448 => hpke_types::KemAlgorithm::DhKem448, - } -} - -#[inline] -fn kdf_mode(kdf: HpkeKdfType) -> hpke_types::KdfAlgorithm { - match kdf { - HpkeKdfType::HkdfSha256 => hpke_types::KdfAlgorithm::HkdfSha256, - HpkeKdfType::HkdfSha384 => hpke_types::KdfAlgorithm::HkdfSha384, - HpkeKdfType::HkdfSha512 => hpke_types::KdfAlgorithm::HkdfSha512, - } -} - -#[inline] -fn aead_mode(aead: HpkeAeadType) -> hpke_types::AeadAlgorithm { - match aead { - HpkeAeadType::AesGcm128 => hpke_types::AeadAlgorithm::Aes128Gcm, - HpkeAeadType::AesGcm256 => hpke_types::AeadAlgorithm::Aes256Gcm, - HpkeAeadType::ChaCha20Poly1305 => hpke_types::AeadAlgorithm::ChaCha20Poly1305, - HpkeAeadType::Export => hpke_types::AeadAlgorithm::HpkeExport, - } -} - impl OpenMlsCrypto for RustCrypto { fn supports(&self, ciphersuite: Ciphersuite) -> Result<(), CryptoError> { match ciphersuite { @@ -127,7 +66,7 @@ impl OpenMlsCrypto for RustCrypto { hash_type: openmls_traits::types::HashType, salt: &[u8], ikm: &[u8], - ) -> Result, openmls_traits::types::CryptoError> { + ) -> Result { match hash_type { HashType::Sha2_256 => Ok(Hkdf::::extract(Some(salt), ikm).0.as_slice().into()), HashType::Sha2_384 => Ok(Hkdf::::extract(Some(salt), ikm).0.as_slice().into()), @@ -141,27 +80,36 @@ impl OpenMlsCrypto for RustCrypto { prk: &[u8], info: &[u8], okm_len: usize, - ) -> Result, openmls_traits::types::CryptoError> { - let mut okm = vec![0u8; okm_len]; + ) -> Result { match hash_type { HashType::Sha2_256 => { let hkdf = Hkdf::::from_prk(prk).map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + let mut okm = vec![0u8; okm_len]; hkdf.expand(info, &mut okm) .map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + Ok(okm.into()) } HashType::Sha2_512 => { let hkdf = Hkdf::::from_prk(prk).map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + let mut okm = vec![0u8; okm_len]; hkdf.expand(info, &mut okm) .map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + Ok(okm.into()) } HashType::Sha2_384 => { let hkdf = Hkdf::::from_prk(prk).map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + let mut okm = vec![0u8; okm_len]; hkdf.expand(info, &mut okm) .map_err(|_| CryptoError::HkdfOutputLengthInvalid)?; + + Ok(okm.into()) } } - - Ok(okm) } fn hash( @@ -186,23 +134,26 @@ impl OpenMlsCrypto for RustCrypto { ) -> Result, openmls_traits::types::CryptoError> { match alg { AeadType::Aes128Gcm => { - let aes = Aes128Gcm::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let aes = Aes128Gcm::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; + aes.encrypt(nonce.into(), Payload { msg: data, aad }) .map(|r| r.as_slice().into()) - .map_err(|_| CryptoError::AeadEncryptionError) + .map_err(|_| CryptoError::CryptoLibraryError) } AeadType::Aes256Gcm => { - let aes = Aes256Gcm::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let aes = Aes256Gcm::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; + aes.encrypt(nonce.into(), Payload { msg: data, aad }) .map(|r| r.as_slice().into()) - .map_err(|_| CryptoError::AeadEncryptionError) + .map_err(|_| CryptoError::CryptoLibraryError) } AeadType::ChaCha20Poly1305 => { - let chacha_poly = ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let chacha_poly = ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; + chacha_poly .encrypt(nonce.into(), Payload { msg: data, aad }) .map(|r| r.as_slice().into()) - .map_err(|_| CryptoError::AeadEncryptionError) + .map_err(|_| CryptoError::CryptoLibraryError) } } } @@ -217,19 +168,19 @@ impl OpenMlsCrypto for RustCrypto { ) -> Result, openmls_traits::types::CryptoError> { match alg { AeadType::Aes128Gcm => { - let aes = Aes128Gcm::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let aes = Aes128Gcm::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; aes.decrypt(nonce.into(), Payload { msg: ct_tag, aad }) .map(|r| r.as_slice().into()) .map_err(|_| CryptoError::AeadDecryptionError) } AeadType::Aes256Gcm => { - let aes = Aes256Gcm::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let aes = Aes256Gcm::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; aes.decrypt(nonce.into(), Payload { msg: ct_tag, aad }) .map(|r| r.as_slice().into()) .map_err(|_| CryptoError::AeadDecryptionError) } AeadType::ChaCha20Poly1305 => { - let chacha_poly = ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidLength)?; + let chacha_poly = ChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::CryptoLibraryError)?; chacha_poly .decrypt(nonce.into(), Payload { msg: ct_tag, aad }) .map(|r| r.as_slice().into()) @@ -242,59 +193,25 @@ impl OpenMlsCrypto for RustCrypto { &self, alg: openmls_traits::types::SignatureScheme, ) -> Result<(Vec, Vec), openmls_traits::types::CryptoError> { + let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; + match alg { SignatureScheme::ECDSA_SECP256R1_SHA256 => { - let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; - let k = p256::ecdsa::SigningKey::random(&mut *rng); - let pk = k.verifying_key().to_encoded_point(false).as_bytes().into(); - Ok((k.to_bytes().as_slice().into(), pk)) + let sk = p256::ecdsa::SigningKey::random(&mut *rng); + let pk = sk.verifying_key().to_encoded_point(false).to_bytes().into(); + Ok((sk.to_bytes().to_vec(), pk)) } SignatureScheme::ECDSA_SECP384R1_SHA384 => { - let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; - let k = p384::ecdsa::SigningKey::random(&mut *rng); - let pk = k.verifying_key().to_encoded_point(false).as_bytes().into(); - Ok((k.to_bytes().as_slice().into(), pk)) - } - SignatureScheme::ECDSA_SECP521R1_SHA512 => { - // TODO: Uncomment this once p521 crate is ready - // let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; - // let k = p521::ecdsa::SigningKey::random(&mut *rng); - // let pk = k.verifying_key().to_encoded_point(false).as_bytes().into(); - // Ok((k.to_bytes().as_slice().into(), pk)) - Err(CryptoError::UnsupportedSignatureScheme) + let sk = p384::ecdsa::SigningKey::random(&mut *rng); + let pk = sk.verifying_key().to_encoded_point(false).to_bytes().into(); + Ok((sk.to_bytes().to_vec(), pk)) } SignatureScheme::ED25519 => { - let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; - // TODO: Once ed25519_dalek updates its `rand` dependency to rand_core 0.6, uncomment the following line and get rid of this mess - // let kp = ed25519_dalek::Keypair::generate(&mut rng); - let mut sk_bytes = [0u8; ed25519_dalek::SECRET_KEY_LENGTH]; - rng.fill_bytes(&mut sk_bytes); - let sk = ed25519_dalek::SecretKey::from_bytes(sk_bytes.as_slice()) - .map_err(|_| CryptoError::InvalidLength)?; - let pk: ed25519_dalek::PublicKey = (&sk).into(); - let mut kp_bytes = [0u8; ed25519_dalek::KEYPAIR_LENGTH]; - - let sk_bytes = sk.to_bytes(); - if sk_bytes.len() != ed25519_dalek::SECRET_KEY_LENGTH { - return Err(CryptoError::InvalidLength); - } - kp_bytes[..ed25519_dalek::SECRET_KEY_LENGTH].copy_from_slice(&sk_bytes); - - let pk_bytes = pk.to_bytes(); - if pk_bytes.len() != ed25519_dalek::KEYPAIR_LENGTH - ed25519_dalek::SECRET_KEY_LENGTH { - return Err(CryptoError::InvalidLength); - } - kp_bytes[ed25519_dalek::SECRET_KEY_LENGTH..ed25519_dalek::KEYPAIR_LENGTH].copy_from_slice(&pk_bytes); - - let k = ed25519_dalek::Keypair::from_bytes(kp_bytes.as_slice()) - .map_err(|_| CryptoError::InvalidLength)? - .to_bytes(); - let pk = k[ed25519_dalek::SECRET_KEY_LENGTH..].to_vec(); - // full key here because we need it to sign... - let sk_pk = k.into(); - Ok((sk_pk, pk)) + let k = ed25519_dalek::SigningKey::generate(&mut *rng); + let pk = k.verifying_key(); + Ok((k.to_bytes().into(), pk.to_bytes().into())) } - SignatureScheme::ED448 => Err(CryptoError::UnsupportedSignatureScheme), + _ => Err(CryptoError::UnsupportedSignatureScheme), } } @@ -305,59 +222,32 @@ impl OpenMlsCrypto for RustCrypto { pk: &[u8], signature: &[u8], ) -> Result<(), openmls_traits::types::CryptoError> { + use signature::Verifier as _; match alg { SignatureScheme::ECDSA_SECP256R1_SHA256 => { - let k: p256::ecdsa::VerifyingKey = VerifyingKey::from_encoded_point( - &p256::EncodedPoint::from_bytes(pk).map_err(|_| CryptoError::InvalidLength)?, - ) - .map_err(|_| CryptoError::InvalidLength)?; + let k = p256::ecdsa::VerifyingKey::from_sec1_bytes(pk).map_err(|_| CryptoError::CryptoLibraryError)?; - k.verify( - data, - &Signature::from_der(signature).map_err(|_| CryptoError::InvalidSignature)?, - ) - .map_err(|_| CryptoError::InvalidSignature) + let signature = + p256::ecdsa::DerSignature::from_bytes(signature).map_err(|_| CryptoError::InvalidSignature)?; + + k.verify(data, &signature).map_err(|_| CryptoError::InvalidSignature) } SignatureScheme::ECDSA_SECP384R1_SHA384 => { - let k: p384::ecdsa::VerifyingKey = VerifyingKey::from_encoded_point( - &p384::EncodedPoint::from_bytes(pk).map_err(|_| CryptoError::InvalidLength)?, - ) - .map_err(|_| CryptoError::InvalidLength)?; + let k = p384::ecdsa::VerifyingKey::from_sec1_bytes(pk).map_err(|_| CryptoError::CryptoLibraryError)?; - k.verify( - data, - &Signature::from_der(signature).map_err(|_| CryptoError::InvalidSignature)?, - ) - .map_err(|_| CryptoError::InvalidSignature) - } - SignatureScheme::ECDSA_SECP521R1_SHA512 => { - // TODO: Uncomment this once p521 crate is ready - // let k: p521::ecdsa::VerifyingKey = VerifyingKey::from_encoded_point( - // &p521::EncodedPoint::from_bytes(pk).map_err(|_| CryptoError::InvalidLength)?, - // ) - // .map_err(|_| CryptoError::InvalidLength)?; - - // k.verify( - // data, - // &Signature::from_der(signature).map_err(|_| CryptoError::InvalidSignature)?, - // ) - // .map_err(|_| CryptoError::InvalidSignature) - Err(CryptoError::UnsupportedSignatureScheme) + let signature = + p384::ecdsa::DerSignature::from_bytes(signature).map_err(|_| CryptoError::InvalidSignature)?; + + k.verify(data, &signature).map_err(|_| CryptoError::InvalidSignature) } SignatureScheme::ED25519 => { - let k = ed25519_dalek::PublicKey::from_bytes(pk).map_err(|_| CryptoError::InvalidLength)?; - if signature.len() != ed25519_dalek::SIGNATURE_LENGTH { - return Err(CryptoError::InvalidLength); - } - let mut sig = [0u8; ed25519_dalek::SIGNATURE_LENGTH]; - sig.clone_from_slice(signature); - k.verify_strict( - data, - &ed25519_dalek::Signature::from_bytes(&sig).map_err(|_| CryptoError::InvalidLength)?, - ) - .map_err(|_| CryptoError::InvalidSignature) + let k = ed25519_dalek::VerifyingKey::try_from(pk).map_err(|_| CryptoError::CryptoLibraryError)?; + + let sig = ed25519_dalek::Signature::from_slice(signature).map_err(|_| CryptoError::InvalidSignature)?; + + k.verify_strict(data, &sig).map_err(|_| CryptoError::InvalidSignature) } - SignatureScheme::ED448 => Err(CryptoError::UnsupportedSignatureScheme), + _ => Err(CryptoError::UnsupportedSignatureScheme), } } @@ -367,27 +257,41 @@ impl OpenMlsCrypto for RustCrypto { data: &[u8], key: &[u8], ) -> Result, openmls_traits::types::CryptoError> { - use ecdsa::signature::Signer as _; + use signature::Signer as _; + match alg { SignatureScheme::ECDSA_SECP256R1_SHA256 => { - let k: p256::ecdsa::SigningKey = - SigningKey::from_bytes(key.into()).map_err(|_| CryptoError::InvalidLength)?; - let signature: p256::ecdsa::Signature = k.sign(data); - Ok(signature.to_der().to_bytes().into()) + let k = p256::ecdsa::SigningKey::from_bytes(key.into()).map_err(|_| CryptoError::CryptoLibraryError)?; + let signature: p256::ecdsa::DerSignature = + k.try_sign(data).map_err(|_| CryptoError::CryptoLibraryError)?; + Ok(signature.to_bytes().into()) } SignatureScheme::ECDSA_SECP384R1_SHA384 => { - let k: p384::ecdsa::SigningKey = - SigningKey::from_bytes(key.into()).map_err(|_| CryptoError::InvalidLength)?; - let signature: p384::ecdsa::Signature = k.sign(data); - Ok(signature.to_der().to_bytes().into()) + let k = p384::ecdsa::SigningKey::from_bytes(key.into()).map_err(|_| CryptoError::CryptoLibraryError)?; + let signature: p384::ecdsa::DerSignature = + k.try_sign(data).map_err(|_| CryptoError::CryptoLibraryError)?; + Ok(signature.to_bytes().into()) } - SignatureScheme::ECDSA_SECP521R1_SHA512 => Err(CryptoError::UnsupportedSignatureScheme), SignatureScheme::ED25519 => { - let k = ed25519_dalek::Keypair::from_bytes(key).map_err(|_| CryptoError::InvalidLength)?; - let signature = k.sign(data); + let k = match key.len() { + // Compat layer for legacy keypairs [seed, pk] + ed25519_dalek::KEYPAIR_LENGTH => { + let mut sk = zeroize::Zeroizing::new([0u8; ed25519_dalek::KEYPAIR_LENGTH]); + sk.copy_from_slice(key); + ed25519_dalek::SigningKey::from_keypair_bytes(&sk) + .map_err(|_| CryptoError::CryptoLibraryError)? + } + ed25519_dalek::SECRET_KEY_LENGTH => { + let mut sk = zeroize::Zeroizing::new([0u8; ed25519_dalek::SECRET_KEY_LENGTH]); + sk.copy_from_slice(key); + ed25519_dalek::SigningKey::from_bytes(&sk) + } + _ => return Err(CryptoError::CryptoLibraryError), + }; + let signature = k.try_sign(data).map_err(|_| CryptoError::CryptoLibraryError)?; Ok(signature.to_bytes().into()) } - SignatureScheme::ED448 => Err(CryptoError::UnsupportedSignatureScheme), + _ => Err(CryptoError::UnsupportedSignatureScheme), } } @@ -398,13 +302,31 @@ impl OpenMlsCrypto for RustCrypto { info: &[u8], aad: &[u8], ptxt: &[u8], - ) -> types::HpkeCiphertext { - let (kem_output, ciphertext) = hpke_from_config(config) - .seal(&pk_r.into(), info, aad, ptxt, None, None, None) - .unwrap(); - HpkeCiphertext { - kem_output: kem_output.into(), - ciphertext: ciphertext.into(), + ) -> Result { + let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; + + match config { + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_seal::( + pk_r, info, aad, ptxt, &mut *rng, + ) + } + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::ChaCha20Poly1305) => { + hpke_core::hpke_seal::( + pk_r, info, aad, ptxt, &mut *rng, + ) + } + HpkeConfig(HpkeKemType::DhKemP256, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_seal::( + pk_r, info, aad, ptxt, &mut *rng, + ) + } + HpkeConfig(HpkeKemType::DhKemP384, HpkeKdfType::HkdfSha384, HpkeAeadType::AesGcm256) => { + hpke_core::hpke_seal::( + pk_r, info, aad, ptxt, &mut *rng, + ) + } + _ => Err(CryptoError::UnsupportedKem), } } @@ -416,18 +338,47 @@ impl OpenMlsCrypto for RustCrypto { info: &[u8], aad: &[u8], ) -> Result, CryptoError> { - hpke_from_config(config) - .open( - input.kem_output.as_slice(), - &sk_r.into(), - info, - aad, - input.ciphertext.as_slice(), - None, - None, - None, - ) - .map_err(|_| CryptoError::HpkeDecryptionError) + let plaintext = match config { + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_open::( + sk_r, + input.kem_output.as_slice(), + info, + aad, + input.ciphertext.as_slice(), + )? + } + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::ChaCha20Poly1305) => { + hpke_core::hpke_open::( + sk_r, + input.kem_output.as_slice(), + info, + aad, + input.ciphertext.as_slice(), + )? + } + HpkeConfig(HpkeKemType::DhKemP256, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_open::( + sk_r, + input.kem_output.as_slice(), + info, + aad, + input.ciphertext.as_slice(), + )? + } + HpkeConfig(HpkeKemType::DhKemP384, HpkeKdfType::HkdfSha384, HpkeAeadType::AesGcm256) => { + hpke_core::hpke_open::( + sk_r, + input.kem_output.as_slice(), + info, + aad, + input.ciphertext.as_slice(), + )? + } + _ => return Err(CryptoError::UnsupportedKem), + }; + + Ok(plaintext) } fn hpke_setup_sender_and_export( @@ -437,14 +388,45 @@ impl OpenMlsCrypto for RustCrypto { info: &[u8], exporter_context: &[u8], exporter_length: usize, - ) -> Result<(Vec, Vec), CryptoError> { - let (kem_output, context) = hpke_from_config(config) - .setup_sender(&pk_r.into(), info, None, None, None) - .map_err(|_| CryptoError::SenderSetupError)?; - let exported_secret = context - .export(exporter_context, exporter_length) - .map_err(|_| CryptoError::ExporterError)?; - Ok((kem_output, exported_secret)) + ) -> Result<(Vec, ExporterSecret), openmls_traits::types::CryptoError> { + let mut rng = self.rng.write().map_err(|_| CryptoError::InsufficientRandomness)?; + + let (kem_output, export) = + match config { + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_export_tx::< + hpke::aead::AesGcm128, + hpke::kdf::HkdfSha256, + hpke::kem::X25519HkdfSha256, + >(pk_r, info, exporter_context, exporter_length, &mut *rng)? + } + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::ChaCha20Poly1305) => { + hpke_core::hpke_export_tx::< + hpke::aead::ChaCha20Poly1305, + hpke::kdf::HkdfSha256, + hpke::kem::X25519HkdfSha256, + >(pk_r, info, exporter_context, exporter_length, &mut *rng)? + } + HpkeConfig(HpkeKemType::DhKemP256, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_export_tx::< + hpke::aead::AesGcm128, + hpke::kdf::HkdfSha256, + hpke::kem::DhP256HkdfSha256, + >(pk_r, info, exporter_context, exporter_length, &mut *rng)? + } + HpkeConfig(HpkeKemType::DhKemP384, HpkeKdfType::HkdfSha384, HpkeAeadType::AesGcm256) => { + hpke_core::hpke_export_tx::< + hpke::aead::AesGcm256, + hpke::kdf::HkdfSha384, + hpke::kem::DhP384HkdfSha384, + >(pk_r, info, exporter_context, exporter_length, &mut *rng)? + } + _ => return Err(CryptoError::UnsupportedKem), + }; + + debug_assert_eq!(export.len(), exporter_length); + + Ok((kem_output, export.into())) } fn hpke_setup_receiver_and_export( @@ -455,30 +437,178 @@ impl OpenMlsCrypto for RustCrypto { info: &[u8], exporter_context: &[u8], exporter_length: usize, + ) -> Result { + let export = + match config { + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_export_rx::< + hpke::aead::AesGcm128, + hpke::kdf::HkdfSha256, + hpke::kem::X25519HkdfSha256, + >(enc, sk_r, info, exporter_context, exporter_length)? + } + HpkeConfig(HpkeKemType::DhKem25519, HpkeKdfType::HkdfSha256, HpkeAeadType::ChaCha20Poly1305) => { + hpke_core::hpke_export_rx::< + hpke::aead::ChaCha20Poly1305, + hpke::kdf::HkdfSha256, + hpke::kem::X25519HkdfSha256, + >(enc, sk_r, info, exporter_context, exporter_length)? + } + HpkeConfig(HpkeKemType::DhKemP256, HpkeKdfType::HkdfSha256, HpkeAeadType::AesGcm128) => { + hpke_core::hpke_export_rx::< + hpke::aead::AesGcm128, + hpke::kdf::HkdfSha256, + hpke::kem::DhP256HkdfSha256, + >(enc, sk_r, info, exporter_context, exporter_length)? + } + HpkeConfig(HpkeKemType::DhKemP384, HpkeKdfType::HkdfSha384, HpkeAeadType::AesGcm256) => { + hpke_core::hpke_export_rx::< + hpke::aead::AesGcm256, + hpke::kdf::HkdfSha384, + hpke::kem::DhP384HkdfSha384, + >(enc, sk_r, info, exporter_context, exporter_length)? + } + _ => return Err(CryptoError::UnsupportedKem), + }; + + debug_assert_eq!(export.len(), exporter_length); + + Ok(export.into()) + } + + fn derive_hpke_keypair(&self, config: HpkeConfig, ikm: &[u8]) -> Result { + match config.0 { + HpkeKemType::DhKemP256 => hpke_core::hpke_derive_keypair::(ikm), + HpkeKemType::DhKemP384 => hpke_core::hpke_derive_keypair::(ikm), + HpkeKemType::DhKem25519 => hpke_core::hpke_derive_keypair::(ikm), + _ => Err(CryptoError::UnsupportedKem), + } + } +} + +mod hpke_core { + use openmls_traits::types::{CryptoError, HpkeCiphertext, HpkeKeyPair}; + + pub(crate) fn hpke_open( + private_key: &[u8], + kem_output: &[u8], + info: &[u8], + aad: &[u8], + ciphertext: &[u8], ) -> Result, CryptoError> { - let context = hpke_from_config(config) - .setup_receiver(enc, &sk_r.into(), info, None, None, None) + use hpke::Deserializable as _; + let encapped_key = Kem::EncappedKey::from_bytes(kem_output).map_err(|_| CryptoError::HpkeDecryptionError)?; + let key = Kem::PrivateKey::from_bytes(private_key).map_err(|_| CryptoError::HpkeDecryptionError)?; + let plaintext = + hpke::single_shot_open::(&hpke::OpModeR::Base, &key, &encapped_key, info, ciphertext, aad) + .map_err(|_| CryptoError::HpkeDecryptionError)?; + + Ok(plaintext) + } + + pub(crate) fn hpke_seal( + public_key: &[u8], + info: &[u8], + aad: &[u8], + plaintext: &[u8], + csprng: &mut impl rand_core::CryptoRngCore, + ) -> Result { + use hpke::{Deserializable as _, Serializable as _}; + let key = Kem::PublicKey::from_bytes(public_key).map_err(|_| CryptoError::HpkeEncryptionError)?; + let (encapped, ciphertext) = + hpke::single_shot_seal::(&hpke::OpModeS::Base, &key, info, plaintext, aad, csprng) + .map_err(|_| CryptoError::HpkeEncryptionError)?; + + Ok(HpkeCiphertext { + kem_output: encapped.to_bytes().to_vec().into(), + ciphertext: ciphertext.into(), + }) + } + + #[allow(dead_code)] + pub(crate) fn hpke_gen_keypair( + csprng: &mut impl rand_core::CryptoRngCore, + ) -> Result { + use hpke::Serializable as _; + let (sk, pk) = Kem::gen_keypair(csprng); + let (private, public) = (sk.to_bytes().to_vec().into(), pk.to_bytes().to_vec()); + + Ok(HpkeKeyPair { private, public }) + } + + pub(crate) fn hpke_derive_keypair(ikm: &[u8]) -> Result { + use hpke::Serializable as _; + let (sk, pk) = Kem::derive_keypair(ikm); + let (private, public) = (sk.to_bytes().to_vec().into(), pk.to_bytes().to_vec()); + + Ok(HpkeKeyPair { private, public }) + } + + pub(crate) fn hpke_export_rx( + encapped_key: &[u8], + rx_private_key: &[u8], + info: &[u8], + export_info: &[u8], + export_len: usize, + ) -> Result, CryptoError> { + use hpke::Deserializable as _; + let key = Kem::PrivateKey::from_bytes(rx_private_key).map_err(|_| CryptoError::ReceiverSetupError)?; + let encapped_key = Kem::EncappedKey::from_bytes(encapped_key).map_err(|_| CryptoError::ReceiverSetupError)?; + let ctx = hpke::setup_receiver::(&hpke::OpModeR::Base, &key, &encapped_key, info) .map_err(|_| CryptoError::ReceiverSetupError)?; - let exported_secret = context - .export(exporter_context, exporter_length) + + let mut export = vec![0u8; export_len]; + + ctx.export(export_info, &mut export) .map_err(|_| CryptoError::ExporterError)?; - Ok(exported_secret) + + Ok(export) } - fn derive_hpke_keypair(&self, config: HpkeConfig, ikm: &[u8]) -> types::HpkeKeyPair { - let kp = hpke_from_config(config).derive_key_pair(ikm).unwrap().into_keys(); - HpkeKeyPair { - private: kp.0.as_slice().into(), - public: kp.1.as_slice().into(), - } + pub(crate) fn hpke_export_tx( + tx_public_key: &[u8], + info: &[u8], + export_info: &[u8], + export_len: usize, + csprng: &mut impl rand_core::CryptoRngCore, + ) -> Result<(Vec, Vec), CryptoError> { + use hpke::{Deserializable as _, Serializable as _}; + let key = Kem::PublicKey::from_bytes(tx_public_key).map_err(|_| CryptoError::SenderSetupError)?; + let (kem_output, ctx) = hpke::setup_sender::(&hpke::OpModeS::Base, &key, info, csprng) + .map_err(|_| CryptoError::SenderSetupError)?; + + let mut export = vec![0u8; export_len]; + + ctx.export(export_info, &mut export) + .map_err(|_| CryptoError::ExporterError)?; + + Ok((kem_output.to_bytes().to_vec(), export)) } } -fn hpke_from_config(config: HpkeConfig) -> Hpke { - Hpke::::new( - hpke::Mode::Base, - kem_mode(config.0), - kdf_mode(config.1), - aead_mode(config.2), - ) +impl OpenMlsRand for RustCrypto { + type Error = MlsProviderError; + + type RandImpl = rand_chacha::ChaCha20Rng; + type BorrowTarget<'a> = std::sync::RwLockWriteGuard<'a, Self::RandImpl>; + + fn borrow_rand(&self) -> Result, Self::Error> { + self.rng.write().map_err(|_| MlsProviderError::RngLockPoison) + } + + fn random_array(&self) -> Result<[u8; N], Self::Error> { + let mut rng = self.borrow_rand()?; + let mut out = [0u8; N]; + rng.try_fill_bytes(&mut out) + .map_err(|_| MlsProviderError::UnsufficientEntropy)?; + Ok(out) + } + + fn random_vec(&self, len: usize) -> Result, Self::Error> { + let mut rng = self.borrow_rand()?; + let mut out = vec![0u8; len]; + rng.try_fill_bytes(&mut out) + .map_err(|_| MlsProviderError::UnsufficientEntropy)?; + Ok(out) + } } diff --git a/mls-provider/tests/crypto.rs b/mls-provider/tests/crypto.rs index 0f104b2708..991de4ae4d 100644 --- a/mls-provider/tests/crypto.rs +++ b/mls-provider/tests/crypto.rs @@ -63,13 +63,19 @@ pub mod tests { let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); let salt = hex!("000102030405060708090a0b0c"); let info = hex!("f0f1f2f3f4f5f6f7f8f9"); - let prk = crypto.hkdf_extract(ciphersuite.hash_algorithm(), &salt, &ikm).unwrap(); + let prk = crypto + .hkdf_extract(ciphersuite.hash_algorithm(), &salt, &ikm) + .unwrap() + .as_slice() + .to_vec(); let supposed_prk_len = ciphersuite.hash_length(); assert_eq!(prk.len(), supposed_prk_len); let okm = crypto .hkdf_expand(ciphersuite.hash_algorithm(), &prk, &info, len) - .unwrap(); + .unwrap() + .as_slice() + .to_vec(); assert_eq!(okm.len(), len); @@ -138,11 +144,13 @@ pub mod tests { ) { let mut backend = backend.await; backend.reseed(entropy_seed); - let crypto = backend.crypto(); - let (sk, pk) = crypto.signature_key_gen(ciphersuite.signature_algorithm()).unwrap(); let len = rand::thread_rng().gen_range(LEN_RANGE); let data = backend.rand().random_vec(len).unwrap(); + + let crypto = backend.crypto(); + let (sk, pk) = crypto.signature_key_gen(ciphersuite.signature_algorithm()).unwrap(); + let signature = crypto.sign(ciphersuite.signature_algorithm(), &data, &sk).unwrap(); crypto .verify_signature(ciphersuite.signature_algorithm(), &data, &pk, &signature) @@ -178,15 +186,19 @@ pub mod tests { .random_vec(rand::thread_rng().gen_range(LEN_RANGE)) .unwrap(); - let alice = crypto.derive_hpke_keypair( - ciphersuite.hpke_config(), - &backend - .rand() - .random_vec(rand::thread_rng().gen_range(LEN_RANGE)) - .unwrap(), - ); + let alice = crypto + .derive_hpke_keypair( + ciphersuite.hpke_config(), + &backend + .rand() + .random_vec(rand::thread_rng().gen_range(LEN_RANGE)) + .unwrap(), + ) + .unwrap(); - let secret_message = crypto.hpke_seal(ciphersuite.hpke_config(), &alice.public, &info, &aad, &message); + let secret_message = crypto + .hpke_seal(ciphersuite.hpke_config(), &alice.public, &info, &aad, &message) + .unwrap(); let unsealed_secret_message = crypto .hpke_open(ciphersuite.hpke_config(), &secret_message, &alice.private, &info, &aad) .unwrap(); @@ -216,7 +228,7 @@ pub mod tests { ) .unwrap(); - assert_eq!(secret_tx, secret_rx); + assert_eq!(*secret_tx, *secret_rx); teardown(backend).await; } diff --git a/mls-provider/tests/randomness.rs b/mls-provider/tests/randomness.rs index 6fad9cc38f..142916d59c 100644 --- a/mls-provider/tests/randomness.rs +++ b/mls-provider/tests/randomness.rs @@ -91,7 +91,7 @@ pub mod tests { // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 let seed = [0u8; 32]; backend.reseed(Some(EntropySeed::from_raw(seed))); - let mut rng = backend.rand().borrow_rand(); + let mut rng = backend.rand().borrow_rand().unwrap(); let mut results = [0u32; 16]; for i in results.iter_mut() { @@ -127,7 +127,7 @@ pub mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ]; backend.reseed(Some(EntropySeed::from_raw(seed))); - let mut rng = backend.rand().borrow_rand(); + let mut rng = backend.rand().borrow_rand().unwrap(); // Skip block 0 for _ in 0..16 { @@ -167,7 +167,7 @@ pub mod tests { // Test block 2 by skipping block 0 and 1 backend.reseed(Some(EntropySeed::from_raw(seed))); - let mut rng1 = backend.rand().borrow_rand(); + let mut rng1 = backend.rand().borrow_rand().unwrap(); for _ in 0..32 { rng1.next_u32(); } @@ -181,7 +181,7 @@ pub mod tests { // Test block 2 by using `set_word_pos` backend.reseed(Some(EntropySeed::from_raw(seed))); - let mut rng2 = backend.rand().borrow_rand(); + let mut rng2 = backend.rand().borrow_rand().unwrap(); rng2.set_word_pos(2 * 16); for i in results.iter_mut() { *i = rng2.next_u32(); @@ -214,7 +214,7 @@ pub mod tests { // https://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04 let seed = [0u8; 32]; backend.reseed(Some(EntropySeed::from_raw(seed))); - let mut rng = backend.rand().borrow_rand(); + let mut rng = backend.rand().borrow_rand().unwrap(); // 96-bit nonce in LE order is: 0,0,0,0, 0,0,0,0, 0,0,0,2 rng.set_stream(2u64 << (24 + 32));