diff --git a/packages/tutanota-crypto/lib/encryption/KeyEncryption.ts b/packages/tutanota-crypto/lib/encryption/KeyEncryption.ts index 9a2f2e6f5b37..7f65519395c2 100644 --- a/packages/tutanota-crypto/lib/encryption/KeyEncryption.ts +++ b/packages/tutanota-crypto/lib/encryption/KeyEncryption.ts @@ -11,6 +11,15 @@ import type { PQKeyPairs } from "./PQKeyPairs.js" export type EncryptedKeyPairs = EncryptedPqKeyPairs | EncryptedRsaKeyPairs | EncryptedRsaEccKeyPairs +export type AbstractEncryptedKeyPair = { + pubEccKey: null | Uint8Array + pubKyberKey: null | Uint8Array + pubRsaKey: null | Uint8Array + symEncPrivEccKey: null | Uint8Array + symEncPrivKyberKey: null | Uint8Array + symEncPrivRsaKey: null | Uint8Array +} + export type EncryptedPqKeyPairs = { pubEccKey: Uint8Array pubKyberKey: Uint8Array @@ -38,6 +47,17 @@ export type EncryptedRsaEccKeyPairs = { symEncPrivRsaKey: Uint8Array } +export function isEncryptedPqKeyPairs(keyPair: AbstractEncryptedKeyPair): keyPair is EncryptedPqKeyPairs { + return ( + keyPair.pubEccKey != null && + keyPair.pubKyberKey != null && + keyPair.symEncPrivEccKey != null && + keyPair.symEncPrivKyberKey != null && + keyPair.pubRsaKey == null && + keyPair.symEncPrivRsaKey == null + ) +} + export function encryptKey(encryptionKey: AesKey, keyToBeEncrypted: AesKey): Uint8Array { const keyLength = getKeyLengthBytes(encryptionKey) if (keyLength === KEY_LENGTH_BYTES_AES_128) { diff --git a/packages/tutanota-crypto/lib/index.ts b/packages/tutanota-crypto/lib/index.ts index cf2867ef8ff8..2452de07640c 100644 --- a/packages/tutanota-crypto/lib/index.ts +++ b/packages/tutanota-crypto/lib/index.ts @@ -45,10 +45,12 @@ export { } from "./hashes/Argon2id/Argon2id.js" export { KeyLength, EntropySource, HkdfKeyDerivationDomains } from "./misc/Constants.js" export { + AbstractEncryptedKeyPair, EncryptedKeyPairs, EncryptedPqKeyPairs, EncryptedRsaKeyPairs, EncryptedRsaEccKeyPairs, + isEncryptedPqKeyPairs, encryptKey, decryptKey, encryptRsaKey, diff --git a/schemas/sys.json b/schemas/sys.json index 1c9890242525..0d532d78512e 100644 --- a/schemas/sys.json +++ b/schemas/sys.json @@ -495,9 +495,9 @@ "info": "RenameAttribute KeyRotation: adminGroupKeyAuthenticationData -> userEncAdminPubKeyHash." }, { - "name": "AddValue", + "name": "AddAssociation", "sourceType": "KeyRotation", - "info": "AddValue KeyRotation/distEncAdminGroupSymKey/2505." + "info": "AddAssociation KeyRotation/distEncAdminGroupSymKey/AGGREGATION/2505." }, { "name": "AddAssociation", @@ -517,7 +517,17 @@ { "name": "AddValue", "sourceType": "UserGroupKeyRotationData", - "info": "AddValue UserGroupKeyRotationData/userGroupEncAdminGroupKey/2537." + "info": "AddValue UserGroupKeyRotationData/userGroupEncAdminGroupKey/2529." + }, + { + "name": "AddValue", + "sourceType": "PubEncKeyData", + "info": "AddValue PubEncKeyData/senderIdentifier/2530." + }, + { + "name": "AddValue", + "sourceType": "PubEncKeyData", + "info": "AddValue PubEncKeyData/senderIdentifierType/2531." } ] } diff --git a/src/common/api/common/TutanotaConstants.ts b/src/common/api/common/TutanotaConstants.ts index beb59f31bd57..be5c9fccd6a8 100644 --- a/src/common/api/common/TutanotaConstants.ts +++ b/src/common/api/common/TutanotaConstants.ts @@ -1230,6 +1230,7 @@ export const DEFAULT_ERROR = "defaultError" export enum PublicKeyIdentifierType { MAIL_ADDRESS = "0", GROUP_ID = "1", + KEY_ROTATION_ID = "2", } export function asPublicKeyIdentifier(maybe: NumberString): PublicKeyIdentifierType { diff --git a/src/common/api/entities/sys/TypeModels.js b/src/common/api/entities/sys/TypeModels.js index 951b8f4022f4..05ddb2426db5 100644 --- a/src/common/api/entities/sys/TypeModels.js +++ b/src/common/api/entities/sys/TypeModels.js @@ -238,18 +238,19 @@ export const typeModels = { "type": "CustomId", "cardinality": "One", "encrypted": false - }, + } + }, + "associations": { "distEncAdminGroupKey": { "final": false, "name": "distEncAdminGroupKey", "id": 2512, "since": 116, - "type": "Bytes", + "type": "AGGREGATION", "cardinality": "One", - "encrypted": false - } - }, - "associations": { + "refType": "PubEncKeyData", + "dependency": null + }, "userEncAdminSymKeyHash": { "final": false, "name": "userEncAdminSymKeyHash", @@ -426,77 +427,6 @@ export const typeModels = { "app": "sys", "version": "116" }, - "AdminMembershipUpdateData": { - "name": "AdminMembershipUpdateData", - "since": 116, - "type": "AGGREGATED_TYPE", - "id": 2529, - "rootId": "A3N5cwAJ4Q", - "versioned": false, - "encrypted": false, - "values": { - "_id": { - "final": true, - "name": "_id", - "id": 2530, - "since": 116, - "type": "CustomId", - "cardinality": "One", - "encrypted": false - }, - "adminGroupKeyVersion": { - "final": false, - "name": "adminGroupKeyVersion", - "id": 2533, - "since": 116, - "type": "Number", - "cardinality": "One", - "encrypted": false - }, - "userEncAdminGroupKey": { - "final": false, - "name": "userEncAdminGroupKey", - "id": 2531, - "since": 116, - "type": "Bytes", - "cardinality": "One", - "encrypted": false - }, - "userGroupKeyVersion": { - "final": false, - "name": "userGroupKeyVersion", - "id": 2532, - "since": 116, - "type": "Number", - "cardinality": "One", - "encrypted": false - } - }, - "associations": { - "adminGroup": { - "final": false, - "name": "adminGroup", - "id": 2535, - "since": 116, - "type": "ELEMENT_ASSOCIATION", - "cardinality": "One", - "refType": "Group", - "dependency": null - }, - "userGroup": { - "final": false, - "name": "userGroup", - "id": 2534, - "since": 116, - "type": "ELEMENT_ASSOCIATION", - "cardinality": "One", - "refType": "Group", - "dependency": null - } - }, - "app": "sys", - "version": "116" - }, "AdministratedGroup": { "name": "AdministratedGroup", "since": 27, @@ -7790,15 +7720,6 @@ export const typeModels = { "cardinality": "One", "encrypted": false }, - "distEncAdminGroupSymKey": { - "final": false, - "name": "distEncAdminGroupSymKey", - "id": 2505, - "since": 116, - "type": "Bytes", - "cardinality": "ZeroOrOne", - "encrypted": false - }, "groupKeyRotationType": { "final": true, "name": "groupKeyRotationType", @@ -7839,6 +7760,16 @@ export const typeModels = { "refType": "EncryptedKeyHash", "dependency": null }, + "distEncAdminGroupSymKey": { + "final": false, + "name": "distEncAdminGroupSymKey", + "id": 2505, + "since": 116, + "type": "AGGREGATION", + "cardinality": "ZeroOrOne", + "refType": "PubEncKeyData", + "dependency": null + }, "userEncAdminPubKeyHash": { "final": false, "name": "userEncAdminPubKeyHash", @@ -10234,6 +10165,24 @@ export const typeModels = { "cardinality": "One", "encrypted": false }, + "senderIdentifier": { + "final": true, + "name": "senderIdentifier", + "id": 2530, + "since": 116, + "type": "String", + "cardinality": "ZeroOrOne", + "encrypted": false + }, + "senderIdentifierType": { + "final": true, + "name": "senderIdentifierType", + "id": 2531, + "since": 116, + "type": "Number", + "cardinality": "ZeroOrOne", + "encrypted": false + }, "senderKeyVersion": { "final": true, "name": "senderKeyVersion", @@ -14132,7 +14081,7 @@ export const typeModels = { "userGroupEncAdminGroupKey": { "final": false, "name": "userGroupEncAdminGroupKey", - "id": 2537, + "id": 2529, "since": 116, "type": "Bytes", "cardinality": "ZeroOrOne", @@ -14222,16 +14171,6 @@ export const typeModels = { } }, "associations": { - "adminMembershipUpdateData": { - "final": false, - "name": "adminMembershipUpdateData", - "id": 2536, - "since": 116, - "type": "AGGREGATION", - "cardinality": "ZeroOrOne", - "refType": "AdminMembershipUpdateData", - "dependency": null - }, "userGroupKeyData": { "final": false, "name": "userGroupKeyData", diff --git a/src/common/api/entities/sys/TypeRefs.ts b/src/common/api/entities/sys/TypeRefs.ts index 0f707b227538..898dcc7d2a51 100644 --- a/src/common/api/entities/sys/TypeRefs.ts +++ b/src/common/api/entities/sys/TypeRefs.ts @@ -1,4 +1,4 @@ -import { create, StrippedEntity } from "../../common/utils/EntityUtils.js" +import { create, Stripped, StrippedEntity } from "../../common/utils/EntityUtils.js" import { TypeRef } from "@tutao/tutanota-utils" import { typeModels } from "./TypeModels.js" @@ -47,8 +47,8 @@ export type AdminGroupKeyDistributionElement = { _type: TypeRef; _id: Id; - distEncAdminGroupKey: Uint8Array; + distEncAdminGroupKey: PubEncKeyData; userEncAdminSymKeyHash: EncryptedKeyHash; userGroupId: Id; } @@ -96,23 +96,6 @@ export type AdminGroupKeyRotationPutIn = { adminDistKeyPair: KeyPair; adminEncDistKeyHash: EncryptedKeyHash; } -export const AdminMembershipUpdateDataTypeRef: TypeRef = new TypeRef("sys", "AdminMembershipUpdateData") - -export function createAdminMembershipUpdateData(values: StrippedEntity): AdminMembershipUpdateData { - return Object.assign(create(typeModels.AdminMembershipUpdateData, AdminMembershipUpdateDataTypeRef), values) -} - -export type AdminMembershipUpdateData = { - _type: TypeRef; - - _id: Id; - adminGroupKeyVersion: NumberString; - userEncAdminGroupKey: Uint8Array; - userGroupKeyVersion: NumberString; - - adminGroup: Id; - userGroup: Id; -} export const AdministratedGroupTypeRef: TypeRef = new TypeRef("sys", "AdministratedGroup") export function createAdministratedGroup(values: StrippedEntity): AdministratedGroup { @@ -1879,12 +1862,12 @@ export type KeyRotation = { _id: IdTuple; _ownerGroup: null | Id; _permissions: Id; - distEncAdminGroupSymKey: null | Uint8Array; groupKeyRotationType: NumberString; targetKeyVersion: NumberString; adminDistKeyPair: null | KeyPair; adminEncDistKeyHash: null | EncryptedKeyHash; + distEncAdminGroupSymKey: null | PubEncKeyData; userEncAdminPubKeyHash: null | EncryptedKeyHash; userEncAdminSymKeyHash: null | EncryptedKeyHash; } @@ -2512,6 +2495,8 @@ export type PubEncKeyData = { recipientIdentifier: string; recipientIdentifierType: NumberString; recipientKeyVersion: NumberString; + senderIdentifier: null | string; + senderIdentifierType: null | NumberString; senderKeyVersion: null | NumberString; } export const PublicKeyGetInTypeRef: TypeRef = new TypeRef("sys", "PublicKeyGetIn") @@ -3503,7 +3488,6 @@ export type UserGroupKeyRotationPostIn = { _format: NumberString; - adminMembershipUpdateData: null | AdminMembershipUpdateData; userGroupKeyData: UserGroupKeyRotationData; } export const UserGroupRootTypeRef: TypeRef = new TypeRef("sys", "UserGroupRoot") diff --git a/src/common/api/worker/facades/KeyRotationFacade.ts b/src/common/api/worker/facades/KeyRotationFacade.ts index 4124b531ea40..353e53b2108f 100644 --- a/src/common/api/worker/facades/KeyRotationFacade.ts +++ b/src/common/api/worker/facades/KeyRotationFacade.ts @@ -42,7 +42,14 @@ import { UserGroupRootTypeRef, UserTypeRef, } from "../../entities/sys/TypeRefs.js" -import { assertEnumValue, GroupKeyRotationType, GroupType, PublicKeyIdentifierType } from "../../common/TutanotaConstants.js" +import { + asPublicKeyIdentifier, + assertEnumValue, + CryptoProtocolVersion, + GroupKeyRotationType, + GroupType, + PublicKeyIdentifierType, +} from "../../common/TutanotaConstants.js" import { arrayEquals, assertNotNull, @@ -63,13 +70,13 @@ import { customIdToUint8array, elementIdPart, getElementId, isSameId, listIdPart import { KeyLoaderFacade } from "./KeyLoaderFacade.js" import { Aes256Key, - aes256RandomKey, AesKey, bitArrayToUint8Array, createAuthVerifier, EccKeyPair, EncryptedPqKeyPairs, getKeyLengthBytes, + isEncryptedPqKeyPairs, KEY_LENGTH_BYTES_AES_256, PQKeyPairs, uint8ArrayToKey, @@ -393,7 +400,7 @@ export class KeyRotationFacade { const adminKeyPair = assertNotNull(newAdminGroupKeys.encryptedKeyPair) const pubEccKey = assertNotNull(adminKeyPair.pubEccKey) const pubKyberKey = assertNotNull(adminKeyPair.pubKyberKey) - const userEncAdminPubKeyHashList = await this.generateEncryptedKeyHashes( + const userEncAdminPubKeyHashList = await this.generateEncryptedPubKeyHashForNonAdminUsers( pubEccKey, pubKyberKey, newAdminGroupKeys.symGroupKey.version, @@ -451,7 +458,7 @@ export class KeyRotationFacade { } } - private async generateEncryptedKeyHashes( + private async generateEncryptedPubKeyHashForNonAdminUsers( pubEccKey: Uint8Array, pubKyberKey: Uint8Array, adminGroupKeyVersion: number, @@ -483,10 +490,10 @@ export class KeyRotationFacade { return keyHashes } - private deriveAdminGroupDistributionKeyPairKey(adminGroupId: string, currentAdminGroupKey: VersionedKey, pwKey: Aes256Key) { + private deriveAdminGroupDistributionKeyPairKey(adminGroupId: string, currentAdminGroupKeyVersion: number, pwKey: Aes256Key) { // when creating new distribution keys we encrypt the private keys with this derivation from the password key return this.cryptoWrapper.deriveKeyWithHkdf({ - salt: `adminGroupId: ${adminGroupId}, adminKeyVersion: ${currentAdminGroupKey.version}`, + salt: `adminGroupId: ${adminGroupId}, adminKeyVersion: ${currentAdminGroupKeyVersion}`, key: pwKey, context: "adminGroupDistributionKeyPairKey", }) @@ -763,8 +770,9 @@ export class KeyRotationFacade { // always pass an empty list because we don't want the encryption to be skipped in case other recipients weren't found // recipients that are not found will be null anyway, and added to membersToRemove const notFoundRecipients: Array = [] + const senderGroupId = this.userFacade.getUserGroupId() const recipientKeyData = await this.cryptoFacade.encryptBucketKeyForInternalRecipient( - this.userFacade.getUserGroupId(), + senderGroupId, bucketKey, memberMailAddress, notFoundRecipients, @@ -778,6 +786,8 @@ export class KeyRotationFacade { recipientKeyVersion: keyData.recipientKeyVersion, senderKeyVersion: keyData.senderKeyVersion, protocolVersion: keyData.protocolVersion, + senderIdentifier: senderGroupId, + senderIdentifierType: PublicKeyIdentifierType.GROUP_ID, }) const groupKeyUpdateData = createGroupKeyUpdateData({ sessionKeyEncGroupKey: this.cryptoWrapper.encryptBytes(sessionKey, bitArrayToUint8Array(newGroupKey.object)), @@ -942,11 +952,85 @@ export class KeyRotationFacade { const userGroupId = userGroupMembership.group const currentUserGroupKey = this.keyLoaderFacade.getCurrentSymUserGroupKey() console.log(`KeyRotationFacade: rotate key for group: ${userGroupId}, groupKeyRotationType: ${userGroupKeyRotation.groupKeyRotationType}`) + + const userGroup: Group = await this.entityClient.load(GroupTypeRef, userGroupId) + + const adminGroupId = assertNotNull(userGroup.admin) + + const newUserGroupKeys = await this.generateGroupKeys(userGroup) + + const { membershipSymEncNewGroupKey, distributionKeyEncNewUserGroupKey, authVerifier, newGroupKeyEncCurrentGroupKey } = this.encryptUserGroupKeyForUser( + pwKey, + newUserGroupKeys, + userGroup, + currentUserGroupKey, + ) + const recoverCodeData = await this.reencryptRecoverCodeIfExists(user, pwKey, newUserGroupKeys) + + let pubAdminGroupEncUserGroupKey: null | PubEncKeyData = null + let adminGroupEncUserGroupKey: null | Uint8Array = null + let userGroupEncAdminGroupKey: null | Uint8Array = null + let adminGroupKeyVersion: NumberString + //optionally decrypt new admin group key + if (userGroupKeyRotation.distEncAdminGroupSymKey != null && userGroupKeyRotation.userEncAdminSymKeyHash != null) { + const encryptedKeysForAdmin = await this.handleUserGroupKeyRotationAsAdmin( + userGroupKeyRotation, + adminGroupId, + pwKey, + userGroupId, + currentUserGroupKey, + newUserGroupKeys, + ) + adminGroupEncUserGroupKey = encryptedKeysForAdmin.adminGroupEncUserGroupKey + adminGroupKeyVersion = encryptedKeysForAdmin.adminGroupKeyVersion + userGroupEncAdminGroupKey = encryptedKeysForAdmin.userGroupEncAdminGroupKey + } else { + const encryptedKeysForUser = await this.handleUserGroupKeyRotationAsUser( + userGroupKeyRotation, + currentUserGroupKey, + userGroupId, + adminGroupId, + newUserGroupKeys, + ) + pubAdminGroupEncUserGroupKey = encryptedKeysForUser.pubAdminGroupEncUserGroupKey + adminGroupKeyVersion = encryptedKeysForUser.adminGroupKeyVersion + } + + const userGroupKeyData = createUserGroupKeyRotationData({ + userGroupKeyVersion: String(newUserGroupKeys.symGroupKey.version), + userGroupEncPreviousGroupKey: newGroupKeyEncCurrentGroupKey.key, + passphraseEncUserGroupKey: membershipSymEncNewGroupKey.key, + group: userGroupId, + distributionKeyEncUserGroupKey: distributionKeyEncNewUserGroupKey, + keyPair: assertNotNull(makeKeyPair(newUserGroupKeys.encryptedKeyPair)), + authVerifier, + adminGroupKeyVersion, + pubAdminGroupEncUserGroupKey, + adminGroupEncUserGroupKey, + recoverCodeData, + userGroupEncAdminGroupKey, + }) + + await this.serviceExecutor.post( + UserGroupKeyRotationService, + createUserGroupKeyRotationPostIn({ + userGroupKeyData, + }), + ) + } + + private async handleUserGroupKeyRotationAsUser( + userGroupKeyRotation: KeyRotation, + currentUserGroupKey: VersionedKey, + userGroupId: Id, + adminGroupId: Id, + newUserGroupKeys: GeneratedGroupKeys, + ) { // check hashes if (userGroupKeyRotation.userEncAdminPubKeyHash == null) { throw new Error("The hash encrypted by admin is not present in the user group key rotation !") } - const { hashedKeyVersion: adminGroupKeyVersion, encryptingKeyEncKeyHash, encryptingKeyVersion } = userGroupKeyRotation.userEncAdminPubKeyHash + const { hashedKeyVersion: adminGroupKeyVersionFromHash, encryptingKeyEncKeyHash, encryptingKeyVersion } = userGroupKeyRotation.userEncAdminPubKeyHash if (Number(encryptingKeyVersion) !== currentUserGroupKey.version) { throw new Error( `the encrypting key version in the userEncAdminPubKeyHash does not match hash: ${encryptingKeyVersion} current user group key:${currentUserGroupKey.version}`, @@ -955,11 +1039,7 @@ export class KeyRotationFacade { const authKey = this.deriveTargetUserGroupKeyAuthKeyForNewAdminPubKeyHash(userGroupId, currentUserGroupKey) const decryptedAdminHash = this.cryptoWrapper.aesDecrypt(authKey, encryptingKeyEncKeyHash, true) - - const userGroup: Group = await this.entityClient.load(GroupTypeRef, userGroupId) - // get admin group public keys - const adminGroupId = assertNotNull(userGroup.admin) const adminPublicKeyGetIn = createPublicKeyGetIn({ identifier: adminGroupId, identifierType: PublicKeyIdentifierType.GROUP_ID, @@ -973,48 +1053,86 @@ export class KeyRotationFacade { if (pubKyberKey == null) { throw new Error("tried to generate a keyhash when rotating but received an empty public kyber key!") } - const clientGeneratedKeyHash = this.generateAdminPubKeyHash(Number(adminGroupKeyVersion), adminGroupId, pubEccKey, pubKyberKey) + const clientGeneratedKeyHash = this.generateAdminPubKeyHash(Number(adminGroupKeyVersionFromHash), adminGroupId, pubEccKey, pubKyberKey) // at this point the decrypted admin key hash MUST equal the one that we generated for this key rotation if (!arrayEquals(decryptedAdminHash, clientGeneratedKeyHash)) { throw new Error("mismatch between client generated hash and encrypted admin hash, aborting rotation") } - const newUserGroupKeys = await this.generateGroupKeys(userGroup) - const { membershipSymEncNewGroupKey, distributionKeyEncNewUserGroupKey, authVerifier, newGroupKeyEncCurrentGroupKey } = this.encryptUserGroupKeyForUser( - pwKey, + const pubAdminGroupEncUserGroupKey = await this.encryptUserGroupKeyForAdminAsymetrically( + userGroupId, newUserGroupKeys, - userGroup, - currentUserGroupKey, + adminPublicKeyGetOut, + adminGroupId, ) - const recoverCodeData = await this.reencryptRecoverCodeIfExists(user, pwKey, newUserGroupKeys) - - const pubAdminGroupEncUserGroupKey = await this.encryptUserGroupKeyForAdmin(newUserGroupKeys, adminPublicKeyGetOut, adminGroupId) + const adminGroupKeyVersion = pubAdminGroupEncUserGroupKey.recipientKeyVersion + return { pubAdminGroupEncUserGroupKey, adminGroupKeyVersion } + } - const userGroupKeyData = createUserGroupKeyRotationData({ - userGroupKeyVersion: String(newUserGroupKeys.symGroupKey.version), - userGroupEncPreviousGroupKey: newGroupKeyEncCurrentGroupKey.key, - passphraseEncUserGroupKey: membershipSymEncNewGroupKey.key, - group: userGroupId, - distributionKeyEncUserGroupKey: distributionKeyEncNewUserGroupKey, - keyPair: assertNotNull(makeKeyPair(newUserGroupKeys.encryptedKeyPair)), - authVerifier, - adminGroupKeyVersion: pubAdminGroupEncUserGroupKey.recipientKeyVersion, - pubAdminGroupEncUserGroupKey, - adminGroupEncUserGroupKey: null, - recoverCodeData: recoverCodeData, - userGroupEncAdminGroupKey: null, - }) + private async handleUserGroupKeyRotationAsAdmin( + userGroupKeyRotation: KeyRotation, + adminGroupId: Id, + pwKey: Aes256Key, + userGroupId: Id, + currentUserGroupKey: VersionedKey, + newUserGroupKeys: GeneratedGroupKeys, + ) { + const distEncAdminGroupSymKey = userGroupKeyRotation.distEncAdminGroupSymKey + if ( + distEncAdminGroupSymKey == null || + userGroupKeyRotation.userEncAdminSymKeyHash == null || + userGroupKeyRotation.adminDistKeyPair == null || + !isEncryptedPqKeyPairs(userGroupKeyRotation.adminDistKeyPair) + ) { + throw new Error("missing some required parameters for a user group key rotation as admin") + } + //derive adminDistKeyPairDistributionKey + const currentAdminGroupKeyFromMembership = await this.keyLoaderFacade.getCurrentSymGroupKey(adminGroupId) // get admin group key from the membership (not yet rotated) + const adminGroupKeyDistributionKeyPairKey = this.deriveAdminGroupDistributionKeyPairKey(adminGroupId, currentAdminGroupKeyFromMembership.version, pwKey) + + // decrypt his private distribution key + const adminGroupDistKeyPair = this.cryptoWrapper.decryptKeyPair(adminGroupKeyDistributionKeyPairKey, userGroupKeyRotation.adminDistKeyPair) + //decrypt new symmetric admin group key + const senderIdentifier = { + identifier: assertNotNull(distEncAdminGroupSymKey.senderIdentifier), + identifierType: asPublicKeyIdentifier(assertNotNull(distEncAdminGroupSymKey.senderIdentifierType)), + } + const decapsulatedNewAdminGroupKey = await this.asymmetricCryptoFacade.decryptSymKeyWithKeyPairAndAuthenticate( + adminGroupDistKeyPair, + distEncAdminGroupSymKey, + senderIdentifier, + ) + const versionedNewAdminGroupKey = { + object: decapsulatedNewAdminGroupKey.decryptedAesKey, + version: Number(userGroupKeyRotation.userEncAdminSymKeyHash.hashedKeyVersion), + } - await this.serviceExecutor.post( - UserGroupKeyRotationService, - createUserGroupKeyRotationPostIn({ - userGroupKeyData, - adminMembershipUpdateData: null, - }), + //Verify hash (encrypted with user group key) + const computedNewAdminSymKeyHash = this.generateAdminSymKeyHash(versionedNewAdminGroupKey) + const targetUserGroupKeyAuthKey = this.deriveTargetUserGroupKeyAuthKeyForNewAdminSymKeyHash( + adminGroupId, + userGroupId, + currentUserGroupKey, + versionedNewAdminGroupKey.version, + ) + const givenAdminSymKeyHash = this.cryptoWrapper.aesDecrypt( + targetUserGroupKeyAuthKey, + userGroupKeyRotation.userEncAdminSymKeyHash.encryptingKeyEncKeyHash, + true, ) + const verified = arrayEquals(computedNewAdminSymKeyHash, givenAdminSymKeyHash) + if (!verified) { + throw new Error("mismatch between client generated hash and encrypted admin hash, aborting rotation") + } + + const adminGroupEncUserGroupKey = this.cryptoWrapper.encryptKeyWithVersionedKey(versionedNewAdminGroupKey, newUserGroupKeys.symGroupKey.object).key + const userGroupEncAdminGroupKey = this.cryptoWrapper.encryptKeyWithVersionedKey(newUserGroupKeys.symGroupKey, versionedNewAdminGroupKey.object).key + const adminGroupKeyVersion = String(versionedNewAdminGroupKey.version) + return { adminGroupEncUserGroupKey, userGroupEncAdminGroupKey, adminGroupKeyVersion } } - private async encryptUserGroupKeyForAdmin( + private async encryptUserGroupKeyForAdminAsymetrically( + userGroupId: Id, newUserGroupKeys: GeneratedGroupKeys, publicKeyGetOut: PublicKeyGetOut, adminGroupId: Id, @@ -1043,13 +1161,15 @@ export class KeyRotationFacade { protocolVersion: pubEncSymKey.cryptoProtocolVersion, senderKeyVersion: pubEncSymKey.senderKeyVersion != null ? pubEncSymKey.senderKeyVersion.toString() : null, recipientKeyVersion: pubEncSymKey.recipientKeyVersion.toString(), + senderIdentifier: userGroupId, + senderIdentifierType: PublicKeyIdentifierType.GROUP_ID, }) } private async createDistributionKeyPair(pwKey: Aes256Key, multiAdminKeyRotation: KeyRotation) { let adminGroupId = getElementId(multiAdminKeyRotation) const currentAdminGroupKey = await this.keyLoaderFacade.getCurrentSymGroupKey(adminGroupId) - const adminDistKeyPairDistributionKey = this.deriveAdminGroupDistributionKeyPairKey(adminGroupId, currentAdminGroupKey, pwKey) + const adminDistKeyPairDistributionKey = this.deriveAdminGroupDistributionKeyPairKey(adminGroupId, currentAdminGroupKey.version, pwKey) const adminDistributionKeyPair = await this.generateAndEncryptPqKeyPairs(adminDistKeyPairDistributionKey) const pubDistKeyHash = this.generatePubDistKeyHash(adminDistributionKeyPair.pubEccKey, adminDistributionKeyPair.pubKyberKey) @@ -1140,12 +1260,24 @@ export class KeyRotationFacade { pubKyberKey: distributionKey.pubKyberKey, }, } + const encryptedAdminGroupKeyForThisAdmin = await this.asymmetricCryptoFacade.tutaCryptEncryptSymKey( newSymAdminGroupKey.object, recipientPublicDistKeys, generatedEccKeyPair, ) + const pubEncKeyData = createPubEncKeyData({ + recipientIdentifierType: PublicKeyIdentifierType.GROUP_ID, + recipientIdentifier: "dummy", + recipientKeyVersion: "0", + pubEncSymKey: encryptedAdminGroupKeyForThisAdmin.pubEncSymKeyBytes, + senderIdentifierType: PublicKeyIdentifierType.GROUP_ID, + senderIdentifier: user.userGroup.group, + senderKeyVersion: String(generatedEccKeyPair.version), + protocolVersion: CryptoProtocolVersion.TUTA_CRYPT, + }) + const computedNewAdminSymKeyHash = this.generateAdminSymKeyHash(newSymAdminGroupKey) const groupManagementFacade = await this.groupManagementFacade() @@ -1154,13 +1286,13 @@ export class KeyRotationFacade { adminGroupId, distributionKey.userGroupId, targetUserGroupKey, - currentAdminGroupKey.version, + newAdminGroupKeys.symGroupKey.version, ) const targetUserGroupKeyAuthEncAdminSymKeyHash = this.cryptoWrapper.aesEncrypt(targetUserGroupKeyAuthKey, computedNewAdminSymKeyHash) const thisAdminDistributionElement: AdminGroupKeyDistributionElement = createAdminGroupKeyDistributionElement({ userGroupId: distributionKey.userGroupId, - distEncAdminGroupKey: encryptedAdminGroupKeyForThisAdmin.pubEncSymKeyBytes, + distEncAdminGroupKey: pubEncKeyData, userEncAdminSymKeyHash: createEncryptedKeyHash({ encryptingGroup: adminGroupId, hashedKeyVersion: String(newSymAdminGroupKey.version), diff --git a/test/tests/api/worker/facades/KeyRotationFacadeTest.ts b/test/tests/api/worker/facades/KeyRotationFacadeTest.ts index fa239ff6dd87..d3ca602f8384 100644 --- a/test/tests/api/worker/facades/KeyRotationFacadeTest.ts +++ b/test/tests/api/worker/facades/KeyRotationFacadeTest.ts @@ -7,6 +7,8 @@ import { AdminGroupKeyRotationGetOutTypeRef, AdminGroupKeyRotationPostIn, AdminGroupKeyRotationPutIn, + createKeyPair, + createPubEncKeyData, createPublicKeyGetOut, Customer, CustomerTypeRef, @@ -256,6 +258,7 @@ function prepareUserKeyRotation( _id: [keyRotationsListId, userGroupId], targetKeyVersion: String(Number(userGroup.groupKeyVersion) + 1), groupKeyRotationType: GroupKeyRotationType.User, + distEncAdminGroupSymKey: null, userEncAdminPubKeyHash: createTestEntity(EncryptedKeyHashTypeRef, { encryptingKeyEncKeyHash: adminGeneratedHash, hashedKeyVersion: "1", @@ -286,6 +289,86 @@ function prepareUserKeyRotation( }) } +function prepareMultiAdminUserKeyRotation( + mocks: { + serviceExecutor: IServiceExecutor + cryptoWrapper: CryptoWrapper + entityClient: EntityClient + asymmetricCryptoFacade: AsymmetricCryptoFacade + keyLoaderFacade: KeyLoaderFacade + }, + keyRotationFacade: KeyRotationFacade, + userGroup: Group, +) { + const pubEncNewAdminGroupKey = new Uint8Array([9, 9, 9, 9]) + + const distEncAdminGroupSymKey = createPubEncKeyData({ + recipientIdentifierType: PublicKeyIdentifierType.KEY_ROTATION_ID, + recipientIdentifier: userGroupId, + recipientKeyVersion: "0", + protocolVersion: CryptoProtocolVersion.TUTA_CRYPT, + pubEncSymKey: pubEncNewAdminGroupKey, + senderIdentifier: userGroup._id, + senderIdentifierType: PublicKeyIdentifierType.GROUP_ID, + senderKeyVersion: "1", + }) + + const userEncNewAdminGroupKeyHash = object() + const encryptedAdminDistKeyPair = createKeyPair({ + pubEccKey: object(), + symEncPrivEccKey: object(), + pubKyberKey: object(), + symEncPrivKyberKey: object(), + pubRsaKey: null, + symEncPrivRsaKey: null, + }) + const adminDistPqKeyPair = object() + const adminGroupDistributionKeyPairKey = object() + + const userGroupKeyRotation = createTestEntity(KeyRotationTypeRef, { + _id: [keyRotationsListId, userGroupId], + targetKeyVersion: String(Number(userGroup.groupKeyVersion) + 1), + groupKeyRotationType: GroupKeyRotationType.User, + distEncAdminGroupSymKey, + userEncAdminSymKeyHash: createTestEntity(EncryptedKeyHashTypeRef, { + encryptingKeyEncKeyHash: userEncNewAdminGroupKeyHash, + hashedKeyVersion: String(NEW_ADMIN_GROUP_KEY.version), + encryptingGroup: userGroupId, + encryptingKeyVersion: String(CURRENT_USER_GROUP_KEY.version), + }), + adminDistKeyPair: encryptedAdminDistKeyPair, + }) + keyRotationFacade.setPendingKeyRotations({ + pwKey: PW_KEY, + adminOrUserGroupKeyRotation: userGroupKeyRotation, + teamOrCustomerGroupKeyRotations: [], + userAreaGroupsKeyRotations: [], + }) + + when(mocks.keyLoaderFacade.getCurrentSymGroupKey(adminGroupId)).thenResolve(CURRENT_ADMIN_GROUP_KEY) + + when(mocks.cryptoWrapper.decryptKeyPair(adminGroupDistributionKeyPairKey, encryptedAdminDistKeyPair as EncryptedPqKeyPairs)).thenReturn(adminDistPqKeyPair) + when(mocks.asymmetricCryptoFacade.decryptSymKeyWithKeyPairAndAuthenticate(adminDistPqKeyPair, distEncAdminGroupSymKey, anything())).thenResolve({ + decryptedAesKey: NEW_ADMIN_GROUP_KEY.object, + }) + + const newAdminGroupHashData = concat(Uint8Array.from([0, NEW_ADMIN_GROUP_KEY.version]), Uint8Array.from(NEW_ADMIN_GROUP_KEY.object)) + const newAdminGroupSymKeyHash = object() + when(mocks.cryptoWrapper.sha256Hash(newAdminGroupHashData)).thenReturn(newAdminGroupSymKeyHash) + // public key service request to get the admin keys + + const targetUserGroupKeyAuthKey = object() + + when(mocks.cryptoWrapper.deriveKeyWithHkdf(matchers.anything())).thenReturn(adminGroupDistributionKeyPairKey, targetUserGroupKeyAuthKey) + + when(mocks.cryptoWrapper.aesDecrypt(targetUserGroupKeyAuthKey, userGroupKeyRotation.userEncAdminSymKeyHash!.encryptingKeyEncKeyHash, true)).thenReturn( + newAdminGroupSymKeyHash, + ) + + when(mocks.cryptoWrapper.encryptKeyWithVersionedKey(NEW_ADMIN_GROUP_KEY, NEW_USER_GROUP_KEY.object)).thenReturn(NEW_ADMIN_GROUP_ENC_NEW_USER_GROUP_KEY) + when(mocks.cryptoWrapper.encryptKeyWithVersionedKey(NEW_USER_GROUP_KEY, NEW_ADMIN_GROUP_KEY.object)).thenReturn(NEW_USER_GROUP_ENC_NEW_ADMIN_GROUP_KEY) +} + o.spec("KeyRotationFacadeTest", function () { let entityClientMock: EntityClient let keyRotationFacade: KeyRotationFacade @@ -1136,11 +1219,12 @@ o.spec("KeyRotationFacadeTest", function () { o(arg.distribution.length).equals(1) // this checks that we don't distribute to ourselves const distributionElement = arg.distribution[0] o(distributionElement.userGroupId).equals(otherAdmin) // this checks that we don't distribute to ourselves - o(distributionElement.distEncAdminGroupKey).equals(encryptedAdminGroupKeyForThisAdmin.pubEncSymKeyBytes) + o(distributionElement.distEncAdminGroupKey.pubEncSymKey).equals(encryptedAdminGroupKeyForThisAdmin.pubEncSymKeyBytes) o(distributionElement.userEncAdminSymKeyHash.encryptingGroup).equals(adminGroupId) o(distributionElement.userEncAdminSymKeyHash.hashedKeyVersion).equals(targetAdminKeyVersion) o(distributionElement.userEncAdminSymKeyHash.encryptingKeyVersion).equals(String(currentAdminGroupKey.version)) o(distributionElement.userEncAdminSymKeyHash.encryptingKeyEncKeyHash).equals(encryptedHash) + return true }), ), @@ -1449,6 +1533,119 @@ o.spec("KeyRotationFacadeTest", function () { }) }) + o.spec("User group key rotation - multiple admin", function () { + let userGroup: Group + let generatedKeyPairs: Map + let recoverData: RecoverData + + let adminGroup: Group + + o.beforeEach(function () { + userGroup = makeGroupWithMembership(userGroupId, user).group + userGroup.adminGroupEncGKey = CURRENT_ADMIN_GROUP_ENC_CURRENT_USER_GROUP_KEY.key + userGroup.adminGroupKeyVersion = String(CURRENT_ADMIN_GROUP_ENC_CURRENT_USER_GROUP_KEY.encryptingKeyVersion) + userGroup.type = GroupType.User + userGroup.currentKeys = object() + adminGroup = makeGroupWithMembership(adminGroupId, user).group + adminGroup.adminGroupEncGKey = CURRENT_ADMIN_GROUP_ENC_CURRENT_ADMIN_GROUP_KEY.key + adminGroup.adminGroupKeyVersion = String(CURRENT_ADMIN_GROUP_ENC_CURRENT_ADMIN_GROUP_KEY.encryptingKeyVersion) + adminGroup.type = GroupType.Admin + adminGroup.currentKeys = object() + + prepareRecoverData(recoverData, recoverCodeFacade) + + when(groupManagementFacade.hasAdminEncGKey(userGroup)).thenReturn(true) + when(userFacade.deriveUserGroupKeyDistributionKey(userGroupId, PW_KEY)).thenReturn(DISTRIBUTION_KEY) + + const encryptingKeyCaptor = matchers.captor() + const keyCaptor = matchers.captor() + when(cryptoWrapperMock.aes256RandomKey()).thenReturn(NEW_USER_GROUP_KEY.object) + when(cryptoWrapperMock.encryptKeyWithVersionedKey(encryptingKeyCaptor.capture(), keyCaptor.capture())).thenDo((arg) => ({ + encryptingKeyVersion: encryptingKeyCaptor.value.version, + key: new Uint8Array(encryptingKeyCaptor.value.object.concat(keyCaptor.value)), + })) + + when(cryptoWrapperMock.encryptKey(DISTRIBUTION_KEY, NEW_USER_GROUP_KEY.object)).thenReturn(DISTRIBUTION_KEY_ENC_NEW_USER_GROUP_KEY) + + generatedKeyPairs = mockGenerateKeyPairs(pqFacadeMock, cryptoWrapperMock, NEW_USER_GROUP_KEY.object) + const newUserPqKeyPair = generatedKeyPairs.get(NEW_USER_GROUP_KEY.object)!.newKeyPairs + const latestAdminKeyVersion = 1 + const publicKeyGetOut = createPublicKeyGetOut({ + pubKeyVersion: latestAdminKeyVersion.toString(), + pubRsaKey: null, + pubEccKey: object(), + pubKyberKey: object(), + }) + }) + + o("Successful rotation - user group key rotation as admin", async function () { + prepareMultiAdminUserKeyRotation( + { + serviceExecutor: serviceExecutorMock, + cryptoWrapper: cryptoWrapperMock, + entityClient: entityClientMock, + asymmetricCryptoFacade: asymmetricCryptoFacade, + keyLoaderFacade: keyLoaderFacadeMock, + }, + keyRotationFacade, + userGroup, + ) + + await keyRotationFacade.processPendingKeyRotation(user) + + verify( + serviceExecutorMock.post( + UserGroupKeyRotationService, + matchers.argThat((arg) => { + const userGroupKeyData: UserGroupKeyRotationData = arg.userGroupKeyData + verifyRecoverCodeData(userGroupKeyData) + o(userGroupKeyData.adminGroupEncUserGroupKey).deepEquals(NEW_ADMIN_GROUP_ENC_NEW_USER_GROUP_KEY.key) + o(userGroupKeyData.adminGroupKeyVersion).deepEquals(String(NEW_ADMIN_GROUP_KEY.version)) + o(userGroupKeyData.pubAdminGroupEncUserGroupKey).equals(null) + o(userGroupKeyData.userGroupEncAdminGroupKey).deepEquals(NEW_USER_GROUP_ENC_NEW_ADMIN_GROUP_KEY.key) + o(userGroupKeyData.userGroupKeyVersion).deepEquals(String(NEW_USER_GROUP_KEY.version)) + verifyUserGroupKeyDataExceptAdminKey(userGroupKeyData, generatedKeyPairs) + return true + }), + ), + ) + + const kdfCaptor = matchers.captor() + verify(cryptoWrapperMock.deriveKeyWithHkdf(kdfCaptor.capture())) + const values = kdfCaptor.values! + o(values.length).equals(2) + o(values[0]).deepEquals({ + salt: `adminGroupId: ${adminGroupId}, adminKeyVersion: 0`, + key: PW_KEY, + context: "adminGroupDistributionKeyPairKey", + }) + o(values[1]).deepEquals({ + salt: `adminGroup: ${adminGroupId}, userGroup: ${userGroupId}, userGroupKeyVersion: ${CURRENT_USER_GROUP_KEY.version}, adminGroupKeyVersion: ${NEW_ADMIN_GROUP_KEY.version}`, + key: CURRENT_USER_GROUP_KEY.object, + context: "multiAdminKeyRotationNewAdminSymKeyHash", + }) + + o(keyRotationFacade.pendingKeyRotations.adminOrUserGroupKeyRotation).equals(null) + o(keyRotationFacade.pendingKeyRotations.pwKey).equals(null) + }) + + o("fails if admin sym key hash does not match", async function () { + prepareMultiAdminUserKeyRotation( + { + serviceExecutor: serviceExecutorMock, + cryptoWrapper: cryptoWrapperMock, + entityClient: entityClientMock, + asymmetricCryptoFacade: asymmetricCryptoFacade, + keyLoaderFacade: keyLoaderFacadeMock, + }, + keyRotationFacade, + userGroup, + ) + when(cryptoWrapperMock.sha256Hash(matchers.anything())).thenReturn(new Uint8Array([9, 8, 7, 1])) + await assertThrows(Error, async () => keyRotationFacade.processPendingKeyRotation(user)) + }) + }) + o.spec("Ignore currently unsupported cases", function () { o("If the user group key is not quantum-safe yet, the user area group key rotations are ignored", async function () { keyRotationFacade.setPendingKeyRotations({ diff --git a/tuta-sdk/rust/sdk/src/crypto/asymmetric_crypto_facade.rs b/tuta-sdk/rust/sdk/src/crypto/asymmetric_crypto_facade.rs index 6c82f064055e..af993af3ceab 100644 --- a/tuta-sdk/rust/sdk/src/crypto/asymmetric_crypto_facade.rs +++ b/tuta-sdk/rust/sdk/src/crypto/asymmetric_crypto_facade.rs @@ -655,6 +655,8 @@ mod tests { recipientIdentifierType: recipient_identifier_type as i64, recipientKeyVersion: 0, senderKeyVersion: Some(sender_key_version), + senderIdentifier: Some(sender_identifier.clone()), + senderIdentifierType: Some(sender_identifier_type.clone() as i64), }; let result = asymmetric_crypto_facade @@ -704,6 +706,8 @@ mod tests { recipientKeyVersion: 0, protocolVersion: CryptoProtocolVersion::Rsa as i64, senderKeyVersion: None, + senderIdentifier: Some(sender_identifier.clone()), + senderIdentifierType: Some(sender_identifier_type.clone() as i64), }; let result = asymmetric_crypto_facade diff --git a/tuta-sdk/rust/sdk/src/entities/generated/sys.rs b/tuta-sdk/rust/sdk/src/entities/generated/sys.rs index 738d8e8b76c5..e3d356b22a24 100644 --- a/tuta-sdk/rust/sdk/src/entities/generated/sys.rs +++ b/tuta-sdk/rust/sdk/src/entities/generated/sys.rs @@ -47,8 +47,7 @@ impl Entity for AccountingInfo { #[cfg_attr(test, derive(PartialEq, Debug))] pub struct AdminGroupKeyDistributionElement { pub _id: Option, - #[serde(with = "serde_bytes")] - pub distEncAdminGroupKey: Vec, + pub distEncAdminGroupKey: PubEncKeyData, pub userEncAdminSymKeyHash: EncryptedKeyHash, pub userGroupId: GeneratedId, } @@ -115,27 +114,6 @@ impl Entity for AdminGroupKeyRotationPutIn { } } -#[derive(uniffi::Record, Clone, Serialize, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Debug))] -pub struct AdminMembershipUpdateData { - pub _id: Option, - pub adminGroupKeyVersion: i64, - #[serde(with = "serde_bytes")] - pub userEncAdminGroupKey: Vec, - pub userGroupKeyVersion: i64, - pub adminGroup: GeneratedId, - pub userGroup: GeneratedId, -} - -impl Entity for AdminMembershipUpdateData { - fn type_ref() -> TypeRef { - TypeRef { - app: "sys", - type_: "AdminMembershipUpdateData", - } - } -} - #[derive(uniffi::Record, Clone, Serialize, Deserialize)] #[cfg_attr(test, derive(PartialEq, Debug))] pub struct AdministratedGroup { @@ -2334,12 +2312,11 @@ pub struct KeyRotation { pub _id: Option, pub _ownerGroup: Option, pub _permissions: GeneratedId, - #[serde(with = "serde_bytes")] - pub distEncAdminGroupSymKey: Option>, pub groupKeyRotationType: i64, pub targetKeyVersion: i64, pub adminDistKeyPair: Option, pub adminEncDistKeyHash: Option, + pub distEncAdminGroupSymKey: Option, pub userEncAdminPubKeyHash: Option, pub userEncAdminSymKeyHash: Option, } @@ -3116,6 +3093,8 @@ pub struct PubEncKeyData { pub recipientIdentifier: String, pub recipientIdentifierType: i64, pub recipientKeyVersion: i64, + pub senderIdentifier: Option, + pub senderIdentifierType: Option, pub senderKeyVersion: Option, } @@ -4374,7 +4353,6 @@ impl Entity for UserGroupKeyRotationData { #[cfg_attr(test, derive(PartialEq, Debug))] pub struct UserGroupKeyRotationPostIn { pub _format: i64, - pub adminMembershipUpdateData: Option, pub userGroupKeyData: UserGroupKeyRotationData, } diff --git a/tuta-sdk/rust/sdk/src/tutanota_constants.rs b/tuta-sdk/rust/sdk/src/tutanota_constants.rs index 7418cbbdcbeb..10541418bf2b 100644 --- a/tuta-sdk/rust/sdk/src/tutanota_constants.rs +++ b/tuta-sdk/rust/sdk/src/tutanota_constants.rs @@ -9,6 +9,7 @@ use num_enum::TryFromPrimitive; pub enum PublicKeyIdentifierType { MailAddress = 0, // the default to retrieve public keys. identify the group by mail address. GroupId = 1, // e.g. needed if a user's needs the admin groups public key. identify by groupId. + KeyRotationId = 2, // used for distribution key pairs during multi admin key rotation. } #[allow(dead_code)] diff --git a/tuta-sdk/rust/sdk/src/type_models/sys.json b/tuta-sdk/rust/sdk/src/type_models/sys.json index ee28dfd6544a..0d063dd6d7e4 100644 --- a/tuta-sdk/rust/sdk/src/type_models/sys.json +++ b/tuta-sdk/rust/sdk/src/type_models/sys.json @@ -231,18 +231,19 @@ "type": "CustomId", "cardinality": "One", "encrypted": false - }, + } + }, + "associations": { "distEncAdminGroupKey": { "final": false, "name": "distEncAdminGroupKey", "id": 2512, "since": 116, - "type": "Bytes", + "type": "AGGREGATION", "cardinality": "One", - "encrypted": false - } - }, - "associations": { + "refType": "PubEncKeyData", + "dependency": null + }, "userEncAdminSymKeyHash": { "final": false, "name": "userEncAdminSymKeyHash", @@ -419,77 +420,6 @@ "app": "sys", "version": "116" }, - "AdminMembershipUpdateData": { - "name": "AdminMembershipUpdateData", - "since": 116, - "type": "AGGREGATED_TYPE", - "id": 2529, - "rootId": "A3N5cwAJ4Q", - "versioned": false, - "encrypted": false, - "values": { - "_id": { - "final": true, - "name": "_id", - "id": 2530, - "since": 116, - "type": "CustomId", - "cardinality": "One", - "encrypted": false - }, - "adminGroupKeyVersion": { - "final": false, - "name": "adminGroupKeyVersion", - "id": 2533, - "since": 116, - "type": "Number", - "cardinality": "One", - "encrypted": false - }, - "userEncAdminGroupKey": { - "final": false, - "name": "userEncAdminGroupKey", - "id": 2531, - "since": 116, - "type": "Bytes", - "cardinality": "One", - "encrypted": false - }, - "userGroupKeyVersion": { - "final": false, - "name": "userGroupKeyVersion", - "id": 2532, - "since": 116, - "type": "Number", - "cardinality": "One", - "encrypted": false - } - }, - "associations": { - "adminGroup": { - "final": false, - "name": "adminGroup", - "id": 2535, - "since": 116, - "type": "ELEMENT_ASSOCIATION", - "cardinality": "One", - "refType": "Group", - "dependency": null - }, - "userGroup": { - "final": false, - "name": "userGroup", - "id": 2534, - "since": 116, - "type": "ELEMENT_ASSOCIATION", - "cardinality": "One", - "refType": "Group", - "dependency": null - } - }, - "app": "sys", - "version": "116" - }, "AdministratedGroup": { "name": "AdministratedGroup", "since": 27, @@ -7783,15 +7713,6 @@ "cardinality": "One", "encrypted": false }, - "distEncAdminGroupSymKey": { - "final": false, - "name": "distEncAdminGroupSymKey", - "id": 2505, - "since": 116, - "type": "Bytes", - "cardinality": "ZeroOrOne", - "encrypted": false - }, "groupKeyRotationType": { "final": true, "name": "groupKeyRotationType", @@ -7832,6 +7753,16 @@ "refType": "EncryptedKeyHash", "dependency": null }, + "distEncAdminGroupSymKey": { + "final": false, + "name": "distEncAdminGroupSymKey", + "id": 2505, + "since": 116, + "type": "AGGREGATION", + "cardinality": "ZeroOrOne", + "refType": "PubEncKeyData", + "dependency": null + }, "userEncAdminPubKeyHash": { "final": false, "name": "userEncAdminPubKeyHash", @@ -10227,6 +10158,24 @@ "cardinality": "One", "encrypted": false }, + "senderIdentifier": { + "final": true, + "name": "senderIdentifier", + "id": 2530, + "since": 116, + "type": "String", + "cardinality": "ZeroOrOne", + "encrypted": false + }, + "senderIdentifierType": { + "final": true, + "name": "senderIdentifierType", + "id": 2531, + "since": 116, + "type": "Number", + "cardinality": "ZeroOrOne", + "encrypted": false + }, "senderKeyVersion": { "final": true, "name": "senderKeyVersion", @@ -14125,7 +14074,7 @@ "userGroupEncAdminGroupKey": { "final": false, "name": "userGroupEncAdminGroupKey", - "id": 2537, + "id": 2529, "since": 116, "type": "Bytes", "cardinality": "ZeroOrOne", @@ -14215,16 +14164,6 @@ } }, "associations": { - "adminMembershipUpdateData": { - "final": false, - "name": "adminMembershipUpdateData", - "id": 2536, - "since": 116, - "type": "AGGREGATION", - "cardinality": "ZeroOrOne", - "refType": "AdminMembershipUpdateData", - "dependency": null - }, "userGroupKeyData": { "final": false, "name": "userGroupKeyData", diff --git a/tuta-sdk/rust/sdk/src/util/test_utils.rs b/tuta-sdk/rust/sdk/src/util/test_utils.rs index 4628f31e12b3..63d0a29156e1 100644 --- a/tuta-sdk/rust/sdk/src/util/test_utils.rs +++ b/tuta-sdk/rust/sdk/src/util/test_utils.rs @@ -32,9 +32,10 @@ pub fn generate_random_group( current_keys: Option, former_keys: Option, ) -> Group { + let group_id = GeneratedId::test_random(); Group { _format: 0, - _id: Some(GeneratedId::test_random()), + _id: Some(group_id.clone()), _ownerGroup: None, _permissions: GeneratedId::test_random(), groupInfo: IdTupleGenerated::new(GeneratedId::test_random(), GeneratedId::test_random()), @@ -72,6 +73,8 @@ pub fn generate_random_group( pubEncSymKey: vec![1, 2, 3], recipientKeyVersion: 0, senderKeyVersion: Some(0), + senderIdentifier: Some(group_id.clone().to_string()), + senderIdentifierType: Some(PublicKeyIdentifierType::GroupId as i64), }), storageCounter: None, user: None,