chore: loosen coupling with encryption services

This commit is contained in:
Karol Sójko
2023-07-14 13:34:17 +02:00
parent 1c8d2f4fb9
commit 2bd34b8d70
21 changed files with 411 additions and 326 deletions

View File

@@ -72,7 +72,6 @@ export interface EncryptionProviderInterface {
}
>
decryptErroredPayloads(): Promise<void>
deleteWorkspaceSpecificKeyStateFromDevice(): Promise<void>
computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface>

View File

@@ -19,7 +19,6 @@ import {
} from '@standardnotes/models'
import { HandleTrustedSharedVaultRootKeyChangedMessage } from './UseCase/HandleTrustedSharedVaultRootKeyChangedMessage'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
import { SessionEvent } from '../Session/SessionEvent'
import { AsymmetricMessageServer, HttpServiceInterface } from '@standardnotes/api'
import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventData'
@@ -41,7 +40,7 @@ export class AsymmetricMessageService
private contacts: ContactServiceInterface,
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
private sync: SyncServiceInterface,
private handleTrustedSharedVaultRootKeyChangedMessageUseCase: HandleTrustedSharedVaultRootKeyChangedMessage,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -189,12 +188,9 @@ export class AsymmetricMessageService
_message: AsymmetricMessageServerHash,
trustedPayload: AsymmetricMessageSharedVaultRootKeyChanged,
): Promise<void> {
const useCase = new HandleTrustedSharedVaultRootKeyChangedMessage(
this.mutator,
this.items,
this.sync,
this.encryption,
)
await useCase.execute(trustedPayload)
const resultOrError = await this.handleTrustedSharedVaultRootKeyChangedMessageUseCase.execute(trustedPayload)
if (resultOrError.isFailed()) {
throw new Error(resultOrError.getError())
}
}
}

View File

@@ -1,6 +1,3 @@
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import {
KeySystemRootKeyInterface,
AsymmetricMessageSharedVaultRootKeyChanged,
@@ -8,20 +5,23 @@ import {
KeySystemRootKeyContent,
VaultListingMutator,
} from '@standardnotes/models'
import { ContentType, UseCaseInterface, Result } from '@standardnotes/domain-core'
import { ContentType } from '@standardnotes/domain-core'
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
import { GetVaultUseCase } from '../../Vaults/UseCase/GetVault'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { EmitDecryptedErroredPayloads } from '../../Encryption/UseCase/EmitDecryptedErroredPayloads/EmitDecryptedErroredPayloads'
export class HandleTrustedSharedVaultRootKeyChangedMessage {
export class HandleTrustedSharedVaultRootKeyChangedMessage implements UseCaseInterface<void> {
constructor(
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private sync: SyncServiceInterface,
private encryption: EncryptionProviderInterface,
private emitDecryptedErroredPayloadsUseCase: EmitDecryptedErroredPayloads,
) {}
async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise<void> {
async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise<Result<void>> {
const rootKeyContent = message.data.rootKey
await this.mutator.createItem<KeySystemRootKeyInterface>(
@@ -37,8 +37,13 @@ export class HandleTrustedSharedVaultRootKeyChangedMessage {
})
}
await this.encryption.decryptErroredPayloads()
const emitedOrFailed = await this.emitDecryptedErroredPayloadsUseCase.execute()
if (emitedOrFailed.isFailed()) {
return Result.fail(emitedOrFailed.getError())
}
void this.sync.sync({ sourceDescription: 'Not awaiting due to this event handler running from sync response' })
return Result.ok()
}
}

View File

@@ -49,14 +49,13 @@ import {
RootKeyParamsInterface,
} from '@standardnotes/models'
import { ClientDisplayableError } from '@standardnotes/responses'
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import {
extendArray,
isNotUndefined,
isNullOrUndefined,
isReactNativeEnvironment,
isWebCryptoAvailable,
UuidGenerator,
} from '@standardnotes/utils'
import {
AnyKeyParamsContent,
@@ -83,15 +82,12 @@ import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/
import { RootKeyManager } from './RootKey/RootKeyManager'
import { RootKeyManagerEvent } from './RootKey/RootKeyManagerEvent'
import { CreateNewItemsKeyWithRollbackUseCase } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
import { DecryptErroredRootPayloadsUseCase } from './UseCase/RootEncryption/DecryptErroredPayloads'
import { CreateNewDefaultItemsKeyUseCase } from './UseCase/ItemsKey/CreateNewDefaultItemsKey'
import { RootKeyDecryptPayloadUseCase } from './UseCase/RootEncryption/DecryptPayload'
import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/DecryptPayloadWithKeyLookup'
import { RootKeyEncryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/EncryptPayloadWithKeyLookup'
import { RootKeyEncryptPayloadUseCase } from './UseCase/RootEncryption/EncryptPayload'
import { ValidateAccountPasswordResult } from './RootKey/ValidateAccountPasswordResult'
import { ValidatePasscodeResult } from './RootKey/ValidatePasscodeResult'
import { ContentType } from '@standardnotes/domain-core'
import { EncryptPayloads } from './UseCase/EncryptPayloads/EncryptPayloads'
import { DecryptPayloads } from './UseCase/DecryptPayloads/DecryptPayloads'
/**
* The encryption service is responsible for the encryption and decryption of payloads, and
@@ -124,9 +120,7 @@ export class EncryptionService
extends AbstractService<EncryptionServiceEvent>
implements EncryptionProviderInterface, InternalEventHandlerInterface
{
private operators: OperatorManager
private readonly itemsEncryption: ItemsEncryptionService
private readonly rootKeyManager: RootKeyManager
constructor(
private items: ItemManagerInterface,
@@ -135,30 +129,18 @@ export class EncryptionService
public device: DeviceInterface,
private storage: StorageServiceInterface,
public readonly keys: KeySystemKeyManagerInterface,
identifier: ApplicationIdentifier,
public crypto: PureCryptoInterface,
private rootKeyManager: RootKeyManager,
public identifier: ApplicationIdentifier,
private encryptPayloadsUseCase: EncryptPayloads,
private decryptPayloadsUseCase: DecryptPayloads,
private operators: OperatorManager,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
this.crypto = crypto
this.operators = new OperatorManager(crypto)
this.rootKeyManager = new RootKeyManager(
device,
storage,
items,
mutator,
this.operators,
identifier,
internalEventBus,
)
internalEventBus.addEventHandler(this, RootKeyManagerEvent.RootKeyManagerKeyStatusChanged)
this.itemsEncryption = new ItemsEncryptionService(items, payloads, storage, this.operators, keys, internalEventBus)
UuidGenerator.SetGenerator(this.crypto.generateUUID)
}
async handleEvent(event: InternalEventInterface): Promise<void> {
@@ -179,7 +161,6 @@ export class EncryptionService
;(this.payloads as unknown) = undefined
;(this.device as unknown) = undefined
;(this.storage as unknown) = undefined
;(this.crypto as unknown) = undefined
;(this.operators as unknown) = undefined
this.itemsEncryption.deinit()
@@ -290,13 +271,6 @@ export class EncryptionService
return usecase.execute()
}
public async decryptErroredPayloads(): Promise<void> {
const usecase = new DecryptErroredRootPayloadsUseCase(this.payloads, this.operators, this.keys, this.rootKeyManager)
await usecase.execute()
await this.itemsEncryption.decryptErroredItemPayloads()
}
public itemsKeyForEncryptedPayload(
payload: EncryptedPayloadInterface,
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
@@ -326,47 +300,41 @@ export class EncryptionService
usesKeySystemRootKeyWithKeyLookup,
} = split
const rootKeyEncryptWithKeyLookupUsecase = new RootKeyEncryptPayloadWithKeyLookupUseCase(
this.operators,
this.keys,
this.rootKeyManager,
)
const rootKeyEncryptUsecase = new RootKeyEncryptPayloadUseCase(this.operators)
const signingKeyPair = this.hasSigningKeyPair() ? this.getSigningKeyPair() : undefined
if (usesRootKey) {
const rootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
usesRootKey.items,
usesRootKey.key,
const rootKeyEncrypted = await this.encryptPayloads({
payloads: usesRootKey.items,
key: usesRootKey.key,
signingKeyPair,
)
})
extendArray(allEncryptedParams, rootKeyEncrypted)
}
if (usesRootKeyWithKeyLookup) {
const rootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
usesRootKeyWithKeyLookup.items,
const rootKeyEncrypted = await this.encryptPayloads({
payloads: usesRootKeyWithKeyLookup.items,
signingKeyPair,
)
})
extendArray(allEncryptedParams, rootKeyEncrypted)
}
if (usesKeySystemRootKey) {
const keySystemRootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
usesKeySystemRootKey.items,
usesKeySystemRootKey.key,
const keySystemRootKeyEncrypted = await this.encryptPayloads({
payloads: usesKeySystemRootKey.items,
key: usesKeySystemRootKey.key,
signingKeyPair,
)
})
extendArray(allEncryptedParams, keySystemRootKeyEncrypted)
}
if (usesKeySystemRootKeyWithKeyLookup) {
const keySystemRootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
usesKeySystemRootKeyWithKeyLookup.items,
const keySystemRootKeyEncrypted = await this.encryptPayloads({
payloads: usesKeySystemRootKeyWithKeyLookup.items,
signingKeyPair,
)
})
extendArray(allEncryptedParams, keySystemRootKeyEncrypted)
}
@@ -423,34 +391,29 @@ export class EncryptionService
usesKeySystemRootKeyWithKeyLookup,
} = split
const rootKeyDecryptUseCase = new RootKeyDecryptPayloadUseCase(this.operators)
const rootKeyDecryptWithKeyLookupUsecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
this.operators,
this.keys,
this.rootKeyManager,
)
if (usesRootKey) {
const rootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(usesRootKey.items, usesRootKey.key)
const rootKeyDecrypted = await this.decryptPayloads<C>({
payloads: usesRootKey.items,
key: usesRootKey.key,
})
extendArray(resultParams, rootKeyDecrypted)
}
if (usesRootKeyWithKeyLookup) {
const rootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(usesRootKeyWithKeyLookup.items)
const rootKeyDecrypted = await this.decryptPayloads<C>({ payloads: usesRootKeyWithKeyLookup.items })
extendArray(resultParams, rootKeyDecrypted)
}
if (usesKeySystemRootKey) {
const keySystemRootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(
usesKeySystemRootKey.items,
usesKeySystemRootKey.key,
)
const keySystemRootKeyDecrypted = await this.decryptPayloads<C>({
payloads: usesKeySystemRootKey.items,
key: usesKeySystemRootKey.key,
})
extendArray(resultParams, keySystemRootKeyDecrypted)
}
if (usesKeySystemRootKeyWithKeyLookup) {
const keySystemRootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(
usesKeySystemRootKeyWithKeyLookup.items,
)
const keySystemRootKeyDecrypted = await this.decryptPayloads<C>({
payloads: usesKeySystemRootKeyWithKeyLookup.items,
})
extendArray(resultParams, keySystemRootKeyDecrypted)
}
@@ -1026,4 +989,37 @@ export class EncryptionService
void this.mutator.setItemsDirty(unsyncedKeys)
}
}
private async encryptPayloads(dto: {
payloads: DecryptedPayloadInterface[]
key?: RootKeyInterface | KeySystemRootKeyInterface
signingKeyPair?: PkcKeyPair
}): Promise<EncryptedOutputParameters[]> {
const encryptedPayloadsOrError = await this.encryptPayloadsUseCase.execute({
...dto,
fallbackRootKey: this.rootKeyManager.getRootKey(),
})
if (encryptedPayloadsOrError.isFailed()) {
throw new Error(encryptedPayloadsOrError.getError())
}
const encryptedPayloads = encryptedPayloadsOrError.getValue()
return encryptedPayloads
}
private async decryptPayloads<C extends ItemContent = ItemContent>(dto: {
payloads: EncryptedPayloadInterface[]
key?: RootKeyInterface | KeySystemRootKeyInterface
}): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
const decryptedPayloadsOrError = await this.decryptPayloadsUseCase.execute<C>({
...dto,
fallbackRootKey: this.rootKeyManager.getRootKey(),
})
if (decryptedPayloadsOrError.isFailed()) {
throw new Error(decryptedPayloadsOrError.getError())
}
const decryptedPayloads = decryptedPayloadsOrError.getValue()
return decryptedPayloads
}
}

View File

@@ -16,28 +16,28 @@ import {
import {
ContentTypesUsingRootKeyEncryption,
DecryptedPayload,
DecryptedTransferPayload,
EncryptedPayload,
EncryptedTransferPayload,
FillItemContentSpecialized,
NamespacedRootKeyInKeychain,
RootKeyContent,
RootKeyInterface,
RootKeyParamsInterface,
DecryptedTransferPayload,
FillItemContentSpecialized,
} from '@standardnotes/models'
import { DeviceInterface } from '../../Device/DeviceInterface'
import { InternalEventBusInterface } from '../../Internal/InternalEventBusInterface'
import { StorageKey } from '../../Storage/StorageKeys'
import { StorageServiceInterface } from '../../Storage/StorageServiceInterface'
import { StorageValueModes } from '../../Storage/StorageTypes'
import { RootKeyEncryptPayloadUseCase } from '../UseCase/RootEncryption/EncryptPayload'
import { RootKeyDecryptPayloadUseCase } from '../UseCase/RootEncryption/DecryptPayload'
import { AbstractService } from '../../Service/AbstractService'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RootKeyManagerEvent } from './RootKeyManagerEvent'
import { ValidatePasscodeResult } from './ValidatePasscodeResult'
import { ValidateAccountPasswordResult } from './ValidateAccountPasswordResult'
import { DecryptPayloads } from '../UseCase/DecryptPayloads/DecryptPayloads'
import { EncryptPayloads } from '../UseCase/EncryptPayloads/EncryptPayloads'
export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
private rootKey?: RootKeyInterface
@@ -51,6 +51,8 @@ export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
private mutator: MutatorClientInterface,
private operators: OperatorManager,
private identifier: ApplicationIdentifier,
private encryptPayloadsUseCase: EncryptPayloads,
private decryptPayloadsUseCase: DecryptPayloads,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -249,8 +251,16 @@ export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
const payload = new DecryptedPayload(value)
const usecase = new RootKeyEncryptPayloadUseCase(this.operators)
const wrappedKey = await usecase.executeOne(payload, wrappingKey)
const wrappedKeysOrError = await this.encryptPayloadsUseCase.execute({
payloads: [payload],
key: wrappingKey,
fallbackRootKey: this.getRootKey(),
})
if (wrappedKeysOrError.isFailed()) {
throw Error(wrappedKeysOrError.getError())
}
const wrappedKey = wrappedKeysOrError.getValue()[0]
const wrappedKeyPayload = new EncryptedPayload({
...payload.ejected(),
...wrappedKey,
@@ -273,8 +283,15 @@ export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
const wrappedKey = this.getWrappedRootKey()
const payload = new EncryptedPayload(wrappedKey)
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
const decrypted = await usecase.executeOne<RootKeyContent>(payload, wrappingKey)
const decryptedPayloadsOrError = await this.decryptPayloadsUseCase.execute<RootKeyContent>({
payloads: [payload],
key: wrappingKey,
fallbackRootKey: this.getRootKey(),
})
if (decryptedPayloadsOrError.isFailed()) {
throw Error(decryptedPayloadsOrError.getError())
}
const decrypted = decryptedPayloadsOrError.getValue()[0]
if (isErrorDecryptingParameters(decrypted)) {
throw Error('Unable to decrypt root key with provided wrapping key.')
@@ -433,8 +450,16 @@ export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
* by attempting to decrypt account keys.
*/
const wrappedKeyPayload = new EncryptedPayload(wrappedRootKey)
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
const decrypted = await usecase.executeOne(wrappedKeyPayload, wrappingKey)
const decryptedPayloadsOrError = await this.decryptPayloadsUseCase.execute({
payloads: [wrappedKeyPayload],
key: wrappingKey,
fallbackRootKey: this.getRootKey(),
})
if (decryptedPayloadsOrError.isFailed()) {
throw Error(decryptedPayloadsOrError.getError())
}
const decrypted = decryptedPayloadsOrError.getValue()[0]
return !isErrorDecryptingParameters(decrypted)
} else {
throw 'Unhandled case in validateWrappingKey'

View File

@@ -0,0 +1,73 @@
import {
DecryptedParameters,
ErrorDecryptingParameters,
KeySystemKeyManagerInterface,
OperatorManager,
decryptPayload,
} from '@standardnotes/encryption'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
EncryptedPayloadInterface,
ItemContent,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { DecryptPayloadsDTO } from './DecryptPayloadsDTO'
export class DecryptPayloads implements UseCaseInterface<(DecryptedParameters | ErrorDecryptingParameters)[]> {
constructor(private operatorManager: OperatorManager, private keySystemKeyManager: KeySystemKeyManagerInterface) {}
async execute<C extends ItemContent = ItemContent>(
dto: DecryptPayloadsDTO,
): Promise<Result<(DecryptedParameters<C> | ErrorDecryptingParameters)[]>> {
const decryptedOrErroredParameters = []
for (const payload of dto.payloads) {
const keyForPayloadOrError = this.getKeyForPayload({
payload,
existingKey: dto.key,
fallbackRootKey: dto.fallbackRootKey,
})
if (keyForPayloadOrError.isFailed()) {
const errorDecryptingParamteres: ErrorDecryptingParameters = {
uuid: payload.uuid,
errorDecrypting: true,
waitingForKey: true,
}
decryptedOrErroredParameters.push(errorDecryptingParamteres)
continue
}
const key = keyForPayloadOrError.getValue()
decryptedOrErroredParameters.push(await decryptPayload<C>(payload, key, this.operatorManager))
}
return Result.ok(decryptedOrErroredParameters)
}
private getKeyForPayload(dto: {
payload: EncryptedPayloadInterface
existingKey?: RootKeyInterface | KeySystemRootKeyInterface
fallbackRootKey?: RootKeyInterface
}): Result<RootKeyInterface | KeySystemRootKeyInterface> {
let key = dto.existingKey
if (key === undefined) {
if (ContentTypeUsesKeySystemRootKeyEncryption(dto.payload.content_type)) {
if (!dto.payload.key_system_identifier) {
return Result.fail('Key system root key encrypted payload is missing key_system_identifier')
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(dto.payload.key_system_identifier)
} else {
key = dto.fallbackRootKey
}
}
if (key === undefined) {
return Result.fail('Attempting root key decryption with no root key')
}
return Result.ok(key)
}
}

View File

@@ -0,0 +1,7 @@
import { EncryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models'
export interface DecryptPayloadsDTO {
payloads: EncryptedPayloadInterface[]
key?: RootKeyInterface | KeySystemRootKeyInterface
fallbackRootKey?: RootKeyInterface
}

View File

@@ -0,0 +1,55 @@
import {
ContentTypeUsesKeySystemRootKeyEncryption,
ContentTypeUsesRootKeyEncryption,
DecryptedPayload,
EncryptedPayload,
PayloadEmitSource,
SureFindPayload,
} from '@standardnotes/models'
import { isErrorDecryptingParameters } from '@standardnotes/encryption'
import { UseCaseInterface, Result } from '@standardnotes/domain-core'
import { PayloadManagerInterface } from '../../../Payloads/PayloadManagerInterface'
import { DecryptPayloads } from '../DecryptPayloads/DecryptPayloads'
export class EmitDecryptedErroredPayloads implements UseCaseInterface<void> {
constructor(private payloadsManager: PayloadManagerInterface, private decryptPayloadsUseCase: DecryptPayloads) {}
async execute(): Promise<Result<void>> {
const erroredRootPayloads = this.payloadsManager.invalidPayloads.filter(
(i) =>
ContentTypeUsesRootKeyEncryption(i.content_type) || ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
)
if (erroredRootPayloads.length === 0) {
return Result.fail('No errored payloads found')
}
const decryptedPaylodsAndErrorsOrFail = await this.decryptPayloadsUseCase.execute({
payloads: erroredRootPayloads,
})
if (decryptedPaylodsAndErrorsOrFail.isFailed()) {
return Result.fail(decryptedPaylodsAndErrorsOrFail.getError())
}
const decryptedPaylodsAndErrors = decryptedPaylodsAndErrorsOrFail.getValue()
const decryptedPayloads = decryptedPaylodsAndErrors.map((params) => {
const original = SureFindPayload(erroredRootPayloads, params.uuid)
if (isErrorDecryptingParameters(params)) {
return new EncryptedPayload({
...original.ejected(),
...params,
})
} else {
return new DecryptedPayload({
...original.ejected(),
...params,
})
}
})
await this.payloadsManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
return Result.ok()
}
}

View File

@@ -0,0 +1,68 @@
import {
EncryptedOutputParameters,
KeySystemKeyManagerInterface,
OperatorManager,
encryptPayload,
} from '@standardnotes/encryption'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
DecryptedPayloadInterface,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { EncryptPayloadsDTO } from './EncryptPayloadsDTO'
export class EncryptPayloads implements UseCaseInterface<EncryptedOutputParameters[]> {
constructor(private operatorManager: OperatorManager, private keySystemKeyManager: KeySystemKeyManagerInterface) {}
async execute(dto: EncryptPayloadsDTO): Promise<Result<EncryptedOutputParameters[]>> {
try {
const encryptedPayloads = []
for (const payload of dto.payloads) {
const keyForPayloadOrError = this.getKeyForPayload({
payload,
existingKey: dto.key,
fallbackRootKey: dto.fallbackRootKey,
})
if (keyForPayloadOrError.isFailed()) {
return Result.fail(keyForPayloadOrError.getError())
}
const key = keyForPayloadOrError.getValue()
encryptedPayloads.push(await encryptPayload(payload, key, this.operatorManager, dto.signingKeyPair))
}
return Result.ok(encryptedPayloads)
} catch (error) {
return Result.fail((error as Error).message)
}
}
private getKeyForPayload(dto: {
payload: DecryptedPayloadInterface
existingKey?: RootKeyInterface | KeySystemRootKeyInterface
fallbackRootKey?: RootKeyInterface
}): Result<RootKeyInterface | KeySystemRootKeyInterface> {
let key = dto.existingKey
if (key === undefined) {
if (ContentTypeUsesKeySystemRootKeyEncryption(dto.payload.content_type)) {
if (!dto.payload.key_system_identifier) {
return Result.fail(
`Key system-encrypted payload ${dto.payload.content_type}is missing a key_system_identifier`,
)
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(dto.payload.key_system_identifier)
} else {
key = dto.fallbackRootKey
}
}
if (key === undefined) {
return Result.fail('Attempting root key encryption with no root key')
}
return Result.ok(key)
}
}

View File

@@ -0,0 +1,9 @@
import { DecryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export interface EncryptPayloadsDTO {
payloads: DecryptedPayloadInterface[]
key?: RootKeyInterface | KeySystemRootKeyInterface
fallbackRootKey?: RootKeyInterface
signingKeyPair?: PkcKeyPair
}

View File

@@ -1,55 +0,0 @@
import {
ContentTypeUsesKeySystemRootKeyEncryption,
ContentTypeUsesRootKeyEncryption,
DecryptedPayload,
EncryptedPayload,
PayloadEmitSource,
SureFindPayload,
} from '@standardnotes/models'
import { PayloadManagerInterface } from './../../../Payloads/PayloadManagerInterface'
import { KeySystemKeyManagerInterface, OperatorManager, isErrorDecryptingParameters } from '@standardnotes/encryption'
import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './DecryptPayloadWithKeyLookup'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class DecryptErroredRootPayloadsUseCase {
constructor(
private payloads: PayloadManagerInterface,
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async execute(): Promise<void> {
const erroredRootPayloads = this.payloads.invalidPayloads.filter(
(i) =>
ContentTypeUsesRootKeyEncryption(i.content_type) || ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
)
if (erroredRootPayloads.length === 0) {
return
}
const usecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
this.operatorManager,
this.keySystemKeyManager,
this.rootKeyManager,
)
const resultParams = await usecase.executeMany(erroredRootPayloads)
const decryptedPayloads = resultParams.map((params) => {
const original = SureFindPayload(erroredRootPayloads, params.uuid)
if (isErrorDecryptingParameters(params)) {
return new EncryptedPayload({
...original.ejected(),
...params,
})
} else {
return new DecryptedPayload({
...original.ejected(),
...params,
})
}
})
await this.payloads.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
}
}

View File

@@ -1,30 +0,0 @@
import {
DecryptedParameters,
ErrorDecryptingParameters,
OperatorManager,
decryptPayload,
} from '@standardnotes/encryption'
import {
EncryptedPayloadInterface,
ItemContent,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
export class RootKeyDecryptPayloadUseCase {
constructor(private operatorManager: OperatorManager) {}
async executeOne<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
return decryptPayload(payload, key, this.operatorManager)
}
async executeMany<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.executeOne<C>(payload, key)))
}
}

View File

@@ -1,56 +0,0 @@
import {
DecryptedParameters,
ErrorDecryptingParameters,
KeySystemKeyManagerInterface,
OperatorManager,
} from '@standardnotes/encryption'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
EncryptedPayloadInterface,
ItemContent,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { RootKeyDecryptPayloadUseCase } from './DecryptPayload'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class RootKeyDecryptPayloadWithKeyLookupUseCase {
constructor(
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async executeOne<C extends ItemContent = ItemContent>(
payload: EncryptedPayloadInterface,
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error('Key system root key encrypted payload is missing key_system_identifier')
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.rootKeyManager.getRootKey()
}
if (key == undefined) {
return {
uuid: payload.uuid,
errorDecrypting: true,
waitingForKey: true,
}
}
const usecase = new RootKeyDecryptPayloadUseCase(this.operatorManager)
return usecase.executeOne(payload, key)
}
async executeMany<C extends ItemContent = ItemContent>(
payloads: EncryptedPayloadInterface[],
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
return Promise.all(payloads.map((payload) => this.executeOne<C>(payload)))
}
}

View File

@@ -1,23 +0,0 @@
import { EncryptedOutputParameters, OperatorManager, encryptPayload } from '@standardnotes/encryption'
import { DecryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
export class RootKeyEncryptPayloadUseCase {
constructor(private operatorManager: OperatorManager) {}
async executeOne(
payload: DecryptedPayloadInterface,
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
}
async executeMany(
payloads: DecryptedPayloadInterface[],
key: RootKeyInterface | KeySystemRootKeyInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters[]> {
return Promise.all(payloads.map((payload) => this.executeOne(payload, key, signingKeyPair)))
}
}

View File

@@ -1,48 +0,0 @@
import { EncryptedOutputParameters, KeySystemKeyManagerInterface, OperatorManager } from '@standardnotes/encryption'
import {
ContentTypeUsesKeySystemRootKeyEncryption,
DecryptedPayloadInterface,
KeySystemRootKeyInterface,
RootKeyInterface,
} from '@standardnotes/models'
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
import { RootKeyEncryptPayloadUseCase } from './EncryptPayload'
import { RootKeyManager } from '../../RootKey/RootKeyManager'
export class RootKeyEncryptPayloadWithKeyLookupUseCase {
constructor(
private operatorManager: OperatorManager,
private keySystemKeyManager: KeySystemKeyManagerInterface,
private rootKeyManager: RootKeyManager,
) {}
async executeOne(
payload: DecryptedPayloadInterface,
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters> {
let key: RootKeyInterface | KeySystemRootKeyInterface | undefined
if (ContentTypeUsesKeySystemRootKeyEncryption(payload.content_type)) {
if (!payload.key_system_identifier) {
throw Error(`Key system-encrypted payload ${payload.content_type}is missing a key_system_identifier`)
}
key = this.keySystemKeyManager.getPrimaryKeySystemRootKey(payload.key_system_identifier)
} else {
key = this.rootKeyManager.getRootKey()
}
if (key == undefined) {
throw Error('Attempting root key encryption with no root key')
}
const usecase = new RootKeyEncryptPayloadUseCase(this.operatorManager)
return usecase.executeOne(payload, key, signingKeyPair)
}
async executeMany(
payloads: DecryptedPayloadInterface[],
signingKeyPair?: PkcKeyPair,
): Promise<EncryptedOutputParameters[]> {
return Promise.all(payloads.map((payload) => this.executeOne(payload, signingKeyPair)))
}
}

View File

@@ -64,6 +64,7 @@ import { SendSharedVaultMetadataChangedMessageToAll } from './UseCase/SendShared
import { ConvertToSharedVaultUseCase } from './UseCase/ConvertToSharedVault'
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
import { ContentType } from '@standardnotes/domain-core'
import { EmitDecryptedErroredPayloads } from '../Encryption/UseCase/EmitDecryptedErroredPayloads/EmitDecryptedErroredPayloads'
export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
@@ -87,6 +88,7 @@ export class SharedVaultService
private files: FilesClientInterface,
private vaults: VaultServiceInterface,
private storage: StorageServiceInterface,
private emitDecryptedErroredPayloadsUseCase: EmitDecryptedErroredPayloads,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -399,15 +401,14 @@ export class SharedVaultService
void this.sync.sync()
await this.decryptErroredItemsAfterInviteAccept()
const emitedOrFailed = await this.emitDecryptedErroredPayloadsUseCase.execute()
if (emitedOrFailed.isFailed()) {
throw new Error(emitedOrFailed.getError())
}
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
}
private async decryptErroredItemsAfterInviteAccept(): Promise<void> {
await this.encryption.decryptErroredPayloads()
}
public async getInvitableContactsForSharedVault(
sharedVault: SharedVaultListingInterface,
): Promise<TrustedContactInterface[]> {

View File

@@ -36,6 +36,7 @@ import { AccountEventData } from './AccountEventData'
import { AccountEvent } from './AccountEvent'
import { SignedInOrRegisteredEventPayload } from './SignedInOrRegisteredEventPayload'
import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse'
import { EmitDecryptedErroredPayloads } from '../Encryption/UseCase/EmitDecryptedErroredPayloads/EmitDecryptedErroredPayloads'
export class UserService
extends AbstractService<AccountEvent, AccountEventData>
@@ -57,6 +58,7 @@ export class UserService
private challengeService: ChallengeServiceInterface,
private protectionService: ProtectionsClientInterface,
private userApiService: UserApiServiceInterface,
private emitDecryptedErroredPayloadsUseCase: EmitDecryptedErroredPayloads,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -87,14 +89,14 @@ export class UserService
})
.then(() => {
if (!payload.awaitSync) {
void this.encryptionService.decryptErroredPayloads()
void this.emitDecryptedErroredPayloadsUseCase.execute()
}
})
if (payload.awaitSync) {
await syncPromise
await this.encryptionService.decryptErroredPayloads()
await this.emitDecryptedErroredPayloadsUseCase.execute()
}
}
}

View File

@@ -29,6 +29,7 @@ import { ChangeVaultKeyOptionsUseCase } from './UseCase/ChangeVaultKeyOptions'
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
import { AlertService } from '../Alert/AlertService'
import { ContentType } from '@standardnotes/domain-core'
import { EmitDecryptedErroredPayloads } from '../Encryption/UseCase/EmitDecryptedErroredPayloads/EmitDecryptedErroredPayloads'
export class VaultService
extends AbstractService<VaultServiceEvent, VaultServiceEventPayload[VaultServiceEvent]>
@@ -43,6 +44,7 @@ export class VaultService
private encryption: EncryptionProviderInterface,
private files: FilesClientInterface,
private alerts: AlertService,
private emitDecryptedErroredPayloadsUseCase: EmitDecryptedErroredPayloads,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
@@ -271,7 +273,10 @@ export class VaultService
this.encryption.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode)
await this.encryption.decryptErroredPayloads()
const emitedOrFailed = await this.emitDecryptedErroredPayloadsUseCase.execute()
if (emitedOrFailed.isFailed()) {
throw emitedOrFailed.getError()
}
if (this.computeVaultLockState(vault) === 'locked') {
this.encryption.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier)

View File

@@ -15,6 +15,7 @@ export * from './Application/DeinitSource'
export * from './AsymmetricMessage/AsymmetricMessageService'
export * from './AsymmetricMessage/AsymmetricMessageServiceInterface'
export * from './AsymmetricMessage/UseCase/HandleTrustedSharedVaultRootKeyChangedMessage'
export * from './Auth/AuthClientInterface'
export * from './Auth/AuthManager'
@@ -68,6 +69,12 @@ export * from './Encryption/EncryptionService'
export * from './Encryption/EncryptionServiceEvent'
export * from './Encryption/Functions'
export * from './Encryption/ItemsEncryption'
export * from './Encryption/RootKey/RootKeyManager'
export * from './Encryption/UseCase/DecryptPayloads/DecryptPayloads'
export * from './Encryption/UseCase/DecryptPayloads/DecryptPayloadsDTO'
export * from './Encryption/UseCase/EmitDecryptedErroredPayloads/EmitDecryptedErroredPayloads'
export * from './Encryption/UseCase/EncryptPayloads/EncryptPayloads'
export * from './Encryption/UseCase/EncryptPayloads/EncryptPayloadsDTO'
export * from './Event/ApplicationEvent'
export * from './Event/ApplicationEventCallback'

View File

@@ -68,6 +68,11 @@ import {
RevisionClientInterface,
RevisionManager,
ApiServiceEvent,
DecryptPayloads,
EncryptPayloads,
RootKeyManager,
HandleTrustedSharedVaultRootKeyChangedMessage,
EmitDecryptedErroredPayloads,
} from '@standardnotes/services'
import {
BackupServiceInterface,
@@ -75,7 +80,7 @@ import {
FileBackupsDevice,
FilesClientInterface,
} from '@standardnotes/files'
import { ComputePrivateUsername } from '@standardnotes/encryption'
import { ComputePrivateUsername, OperatorManager } from '@standardnotes/encryption'
import { useBoolean } from '@standardnotes/utils'
import {
BackupFile,
@@ -189,6 +194,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private declare authManager: AuthClientInterface
private declare revisionManager: RevisionClientInterface
private homeServerService?: ExternalServices.HomeServerService
private declare operatorManager: OperatorManager
private declare _signInWithRecoveryCodes: SignInWithRecoveryCodes
private declare _getRecoveryCodes: GetRecoveryCodes
@@ -200,6 +206,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
private declare _listRevisions: ListRevisions
private declare _getRevision: GetRevision
private declare _deleteRevision: DeleteRevision
private declare _encryptPayloads: EncryptPayloads
private declare _decryptPayloads: DecryptPayloads
private declare _emitDecryptedErroredPayloads: EmitDecryptedErroredPayloads
public internalEventBus!: ExternalServices.InternalEventBusInterface
@@ -1308,6 +1317,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
;(this.httpService as unknown) = undefined
;(this.payloadManager as unknown) = undefined
;(this.encryptionService as unknown) = undefined
;(this.operatorManager as unknown) = undefined
;(this.diskStorageService as unknown) = undefined
;(this.inMemoryStore as unknown) = undefined
;(this.apiService as unknown) = undefined
@@ -1356,6 +1366,9 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
;(this._listRevisions as unknown) = undefined
;(this._getRevision as unknown) = undefined
;(this._deleteRevision as unknown) = undefined
;(this._encryptPayloads as unknown) = undefined
;(this._decryptPayloads as unknown) = undefined
;(this._emitDecryptedErroredPayloads as unknown) = undefined
;(this.vaultService as unknown) = undefined
;(this.contactService as unknown) = undefined
;(this.sharedVaultService as unknown) = undefined
@@ -1389,13 +1402,20 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
private createAsymmetricMessageService() {
const handleTrustedSharedVaultRootKeyChangedMessageUseCase = new HandleTrustedSharedVaultRootKeyChangedMessage(
this.mutator,
this.itemManager,
this.syncService,
this._emitDecryptedErroredPayloads,
)
this.asymmetricMessageService = new ExternalServices.AsymmetricMessageService(
this.httpService,
this.encryptionService,
this.contacts,
this.itemManager,
this.mutator,
this.syncService,
handleTrustedSharedVaultRootKeyChangedMessageUseCase,
this.internalEventBus,
)
this.services.push(this.asymmetricMessageService)
@@ -1429,6 +1449,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.files,
this.vaults,
this.storage,
this._emitDecryptedErroredPayloads,
this.internalEventBus,
)
this.services.push(this.sharedVaultService)
@@ -1442,6 +1463,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.encryptionService,
this.files,
this.alertService,
this._emitDecryptedErroredPayloads,
this.internalEventBus,
)
@@ -1573,6 +1595,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.challengeService,
this.protectionService,
this.userApiService,
this._emitDecryptedErroredPayloads,
this.internalEventBus,
)
this.serviceObservers.push(
@@ -1727,6 +1750,26 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
}
private createProtocolService() {
this.operatorManager = new OperatorManager(this.options.crypto)
this._decryptPayloads = new DecryptPayloads(this.operatorManager, this.keySystemKeyManager)
this._encryptPayloads = new EncryptPayloads(this.operatorManager, this.keySystemKeyManager)
this._emitDecryptedErroredPayloads = new EmitDecryptedErroredPayloads(this.payloadManager, this._decryptPayloads)
const rootKeyManager = new RootKeyManager(
this.deviceInterface,
this.diskStorageService,
this.itemManager,
this.mutator,
this.operatorManager,
this.identifier,
this._encryptPayloads,
this._decryptPayloads,
this.internalEventBus,
)
this.encryptionService = new EncryptionService(
this.itemManager,
this.mutator,
@@ -1734,8 +1777,11 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.deviceInterface,
this.diskStorageService,
this.keySystemKeyManager,
rootKeyManager,
this.identifier,
this.options.crypto,
this._encryptPayloads,
this._decryptPayloads,
this.operatorManager,
this.internalEventBus,
)
this.serviceObservers.push(
@@ -1788,6 +1834,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
this.sessionStorageMapper,
this.legacySessionStorageMapper,
this.identifier,
this.options.crypto,
this.internalEventBus,
)
this.serviceObservers.push(

View File

@@ -65,6 +65,7 @@ import {
UserApiServiceInterface,
UserRegistrationResponseBody,
} from '@standardnotes/api'
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
export const MINIMUM_PASSWORD_LENGTH = 8
export const MissingAccountParams = 'missing-params'
@@ -98,6 +99,7 @@ export class SNSessionManager
private sessionStorageMapper: MapperInterface<Session, Record<string, unknown>>,
private legacySessionStorageMapper: MapperInterface<LegacySession, Record<string, unknown>>,
private workspaceIdentifier: string,
private crypto: PureCryptoInterface,
protected override internalEventBus: InternalEventBusInterface,
) {
super(internalEventBus)
@@ -694,7 +696,7 @@ export class SNSessionManager
}
private decodeDemoShareToken(token: Base64String): ShareToken {
const jsonString = this.encryptionService.crypto.base64Decode(token)
const jsonString = this.crypto.base64Decode(token)
return JSON.parse(jsonString)
}