Skip to content

Commit

Permalink
Fix encryption errors for MailDetails tutadb#1633
Browse files Browse the repository at this point in the history
We never set ownerEncSessionKey on a cached instance so we would be
missing an ownerEncSessionKey on Mail instance (either temporarily
until the update or persistently if the cache is out of sync). Because
of that we could not decrypt MailDetailsBlob in some cases.
  • Loading branch information
mpfau authored and charlag committed Oct 5, 2023
1 parent 6af9e8d commit 67c7ef5
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 17 deletions.
10 changes: 9 additions & 1 deletion src/api/worker/crypto/CryptoFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ import { assertWorkerOrNode } from "../../common/Env"
import type { EntityClient } from "../../common/EntityClient"
import { RestClient } from "../rest/RestClient"
import {
aesEncrypt,
Aes128Key,
aes128RandomKey,
aesEncrypt,
bitArrayToUint8Array,
decryptKey,
decryptRsaKey,
Expand Down Expand Up @@ -174,6 +174,11 @@ export class CryptoFacade {
return decryptKey(ownerKey, key)
}

decryptSessionKey(instance: Record<string, any>, ownerEncSessionKey: Uint8Array): Aes128Key {
const gk = this.userFacade.getGroupKey(instance._ownerGroup)
return decryptKey(gk, ownerEncSessionKey)
}

/**
* Returns the session key for the provided type/instance:
* * null, if the instance is unencrypted
Expand Down Expand Up @@ -282,6 +287,9 @@ export class CryptoFacade {
this.ownerEncSessionKeysUpdateQueue.updateInstanceSessionKeys(instanceSessionKeys)

if (resolvedSessionKeyForInstance) {
// for symmetrically encrypted instances _ownerEncSessionKey is sent from the server.
// in this case it is not yet and we need to set it because the rest of the app expects it.
instance._ownerEncSessionKey = uint8ArrayToBase64(encryptKey(this.userFacade.getGroupKey(instance._ownerGroup), resolvedSessionKeyForInstance))
return resolvedSessionKeyForInstance
} else {
throw new SessionKeyNotFoundError("no session key for instance " + instance._id)
Expand Down
17 changes: 12 additions & 5 deletions src/api/worker/crypto/InstanceMapper.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { resolveTypeReference } from "../../common/EntityFunctions"
import { ProgrammingError } from "../../common/error/ProgrammingError"
import { base64ToBase64Url, base64ToUint8Array, downcast, stringToUtf8Uint8Array, uint8ArrayToBase64, utf8Uint8ArrayToString } from "@tutao/tutanota-utils"
import type { Base64 } from "@tutao/tutanota-utils"
import {
assertNotNull,
base64ToBase64Url,
base64ToUint8Array,
downcast,
promiseMap,
stringToUtf8Uint8Array,
TypeRef,
uint8ArrayToBase64,
utf8Uint8ArrayToString,
} from "@tutao/tutanota-utils"
import { AssociationType, Cardinality, Type, ValueType } from "../../common/EntityConstants"
import { compress, uncompress } from "../Compression"
import { TypeRef } from "@tutao/tutanota-utils"
import { promiseMap } from "@tutao/tutanota-utils"
import type { ModelValue, TypeModel } from "../../common/EntityTypes"
import { assertNotNull } from "@tutao/tutanota-utils"
import { assertWorkerOrNode } from "../../common/Env"
import type { Base64 } from "@tutao/tutanota-utils"
import { aesDecrypt, aesEncrypt, ENABLE_MAC, IV_BYTE_LENGTH, random } from "@tutao/tutanota-crypto"

assertWorkerOrNode()
Expand Down
26 changes: 15 additions & 11 deletions src/api/worker/rest/EntityRestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PayloadTooLargeError,
} from "../../common/error/RestError"
import type { lazy } from "@tutao/tutanota-utils"
import { isSameTypeRef, Mapper, ofClass, promiseMap, splitInChunks, TypeRef } from "@tutao/tutanota-utils"
import { assertNotNull, isSameTypeRef, Mapper, ofClass, promiseMap, splitInChunks, TypeRef } from "@tutao/tutanota-utils"
import { assertWorkerOrNode } from "../../common/Env"
import type { ListElementEntity, SomeEntity, TypeModel } from "../../common/EntityTypes"
import { getElementId, LOAD_MULTIPLE_LIMIT, POST_MULTIPLE_LIMIT } from "../../common/utils/EntityUtils"
Expand Down Expand Up @@ -273,24 +273,28 @@ export class EntityRestClient implements EntityRestInterface {
loadedEntities,
(instance) => {
if (providedOwnerEncSessionKeys) {
instance._ownerEncSessionKey = providedOwnerEncSessionKeys.get(getElementId(instance))
return this._decryptMapAndMigrate(instance, model, assertNotNull(providedOwnerEncSessionKeys.get(getElementId(instance))))
}
return this._decryptMapAndMigrate(instance, model)
},
{ concurrency: 5 },
)
}

async _decryptMapAndMigrate<T>(instance: any, model: TypeModel): Promise<T> {
async _decryptMapAndMigrate<T>(instance: any, model: TypeModel, providedOwnerEncSessionKey?: Uint8Array): Promise<T> {
let sessionKey
try {
sessionKey = await this._crypto.resolveSessionKey(model, instance)
} catch (e) {
if (e instanceof SessionKeyNotFoundError) {
console.log("could not resolve session key", e)
sessionKey = null // will result in _errors being set on the instance
} else {
throw e
if (providedOwnerEncSessionKey) {
sessionKey = this._crypto.decryptSessionKey(instance, providedOwnerEncSessionKey)
} else {
try {
sessionKey = await this._crypto.resolveSessionKey(model, instance)
} catch (e) {
if (e instanceof SessionKeyNotFoundError) {
console.log("could not resolve session key", e)
sessionKey = null // will result in _errors being set on the instance
} else {
throw e
}
}
}
const decryptedInstance = await this.instanceMapper.decryptAndMapToInstance<T>(model, instance, sessionKey)
Expand Down

0 comments on commit 67c7ef5

Please sign in to comment.