refactor: application dependency management (#2363)
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
AsymmetricSignatureVerificationDetachedResult,
|
||||
SNRootKeyParams,
|
||||
KeyedDecryptionSplit,
|
||||
KeyedEncryptionSplit,
|
||||
ItemAuthenticatedData,
|
||||
AsymmetricallyEncryptedString,
|
||||
} from '@standardnotes/encryption'
|
||||
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
|
||||
import {
|
||||
BackupFile,
|
||||
DecryptedPayloadInterface,
|
||||
EncryptedPayloadInterface,
|
||||
ItemContent,
|
||||
ItemsKeyInterface,
|
||||
RootKeyInterface,
|
||||
KeySystemIdentifier,
|
||||
KeySystemItemsKeyInterface,
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
PortablePublicKeySet,
|
||||
} from '@standardnotes/models'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export interface EncryptionProviderInterface {
|
||||
initialize(): Promise<void>
|
||||
|
||||
encryptSplitSingle(split: KeyedEncryptionSplit): Promise<EncryptedPayloadInterface>
|
||||
encryptSplit(split: KeyedEncryptionSplit): Promise<EncryptedPayloadInterface[]>
|
||||
decryptSplitSingle<
|
||||
C extends ItemContent = ItemContent,
|
||||
P extends DecryptedPayloadInterface<C> = DecryptedPayloadInterface<C>,
|
||||
>(
|
||||
split: KeyedDecryptionSplit,
|
||||
): Promise<P | EncryptedPayloadInterface>
|
||||
decryptSplit<
|
||||
C extends ItemContent = ItemContent,
|
||||
P extends DecryptedPayloadInterface<C> = DecryptedPayloadInterface<C>,
|
||||
>(
|
||||
split: KeyedDecryptionSplit,
|
||||
): Promise<(P | EncryptedPayloadInterface)[]>
|
||||
|
||||
getEmbeddedPayloadAuthenticatedData<D extends ItemAuthenticatedData>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): D | undefined
|
||||
getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): SNRootKeyParams | undefined
|
||||
|
||||
supportedVersions(): ProtocolVersion[]
|
||||
isVersionNewerThanLibraryVersion(version: ProtocolVersion): boolean
|
||||
platformSupportsKeyDerivation(keyParams: SNRootKeyParams): boolean
|
||||
|
||||
getPasswordCreatedDate(): Date | undefined
|
||||
getEncryptionDisplayName(): Promise<string>
|
||||
upgradeAvailable(): Promise<boolean>
|
||||
|
||||
createEncryptedBackupFile(): Promise<BackupFile>
|
||||
createDecryptedBackupFile(): BackupFile
|
||||
|
||||
getUserVersion(): ProtocolVersion | undefined
|
||||
hasAccount(): boolean
|
||||
hasPasscode(): boolean
|
||||
removePasscode(): Promise<void>
|
||||
validateAccountPassword(password: string): Promise<
|
||||
| {
|
||||
valid: true
|
||||
artifacts: {
|
||||
rootKey: RootKeyInterface
|
||||
}
|
||||
}
|
||||
| {
|
||||
valid: boolean
|
||||
}
|
||||
>
|
||||
|
||||
decryptErroredPayloads(): Promise<void>
|
||||
deleteWorkspaceSpecificKeyStateFromDevice(): Promise<void>
|
||||
|
||||
unwrapRootKey(wrappingKey: RootKeyInterface): Promise<void>
|
||||
computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface>
|
||||
computeWrappingKey(passcode: string): Promise<RootKeyInterface>
|
||||
hasRootKeyEncryptionSource(): boolean
|
||||
createRootKey<K extends RootKeyInterface>(
|
||||
identifier: string,
|
||||
password: string,
|
||||
origination: KeyParamsOrigination,
|
||||
version?: ProtocolVersion,
|
||||
): Promise<K>
|
||||
getRootKeyParams(): SNRootKeyParams | undefined
|
||||
setNewRootKeyWrapper(wrappingKey: RootKeyInterface): Promise<void>
|
||||
|
||||
createNewItemsKeyWithRollback(): Promise<() => Promise<void>>
|
||||
reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void>
|
||||
getSureDefaultItemsKey(): ItemsKeyInterface
|
||||
|
||||
createRandomizedKeySystemRootKey(dto: { systemIdentifier: KeySystemIdentifier }): KeySystemRootKeyInterface
|
||||
|
||||
createUserInputtedKeySystemRootKey(dto: {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface
|
||||
|
||||
deriveUserInputtedKeySystemRootKey(dto: {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface
|
||||
|
||||
createKeySystemItemsKey(
|
||||
uuid: string,
|
||||
keySystemIdentifier: KeySystemIdentifier,
|
||||
sharedVaultUuid: string | undefined,
|
||||
rootKeyToken: string,
|
||||
): KeySystemItemsKeyInterface
|
||||
|
||||
getKeyPair(): PkcKeyPair
|
||||
getSigningKeyPair(): PkcKeyPair
|
||||
|
||||
asymmetricSignatureVerifyDetached(
|
||||
encryptedString: AsymmetricallyEncryptedString,
|
||||
): AsymmetricSignatureVerificationDetachedResult
|
||||
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: string): PortablePublicKeySet
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FindDefaultItemsKey } from './UseCase/ItemsKey/FindDefaultItemsKey'
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
@@ -5,27 +6,22 @@ import {
|
||||
CreateAnyKeyParams,
|
||||
CreateEncryptionSplitWithKeyLookup,
|
||||
encryptedInputParametersFromPayload,
|
||||
EncryptionProviderInterface,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
FindPayloadInDecryptionSplit,
|
||||
FindPayloadInEncryptionSplit,
|
||||
isErrorDecryptingParameters,
|
||||
ItemAuthenticatedData,
|
||||
KeyedDecryptionSplit,
|
||||
KeyedEncryptionSplit,
|
||||
KeyMode,
|
||||
LegacyAttachedData,
|
||||
OperatorManager,
|
||||
RootKeyEncryptedAuthenticatedData,
|
||||
SplitPayloadsByEncryptionType,
|
||||
V001Algorithm,
|
||||
V002Algorithm,
|
||||
PublicKeySet,
|
||||
EncryptedOutputParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
AsymmetricSignatureVerificationDetachedResult,
|
||||
AsymmetricallyEncryptedString,
|
||||
EncryptionOperatorsInterface,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
BackupFile,
|
||||
@@ -42,13 +38,11 @@ import {
|
||||
RootKeyInterface,
|
||||
KeySystemItemsKeyInterface,
|
||||
KeySystemIdentifier,
|
||||
AsymmetricMessagePayload,
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
TrustedContactInterface,
|
||||
PortablePublicKeySet,
|
||||
RootKeyParamsInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import {
|
||||
extendArray,
|
||||
@@ -60,7 +54,6 @@ import {
|
||||
} from '@standardnotes/utils'
|
||||
import {
|
||||
AnyKeyParamsContent,
|
||||
ApplicationIdentifier,
|
||||
compareVersions,
|
||||
isVersionLessThanOrEqualTo,
|
||||
KeyParamsOrigination,
|
||||
@@ -70,28 +63,27 @@ import {
|
||||
} from '@standardnotes/common'
|
||||
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { ItemsEncryptionService } from './ItemsEncryption'
|
||||
import { ItemsEncryptionService } from '../ItemsEncryption/ItemsEncryption'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
|
||||
import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { DecryptBackupFileUseCase } from './DecryptBackupFileUseCase'
|
||||
import { EncryptionServiceEvent } from './EncryptionServiceEvent'
|
||||
import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/DecryptedParameters'
|
||||
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 { RootKeyManager } from '../RootKeyManager/RootKeyManager'
|
||||
import { RootKeyManagerEvent } from '../RootKeyManager/RootKeyManagerEvent'
|
||||
import { CreateNewItemsKeyWithRollback } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback'
|
||||
import { DecryptErroredTypeAPayloads } from './UseCase/TypeA/DecryptErroredPayloads'
|
||||
import { CreateNewDefaultItemsKey } from './UseCase/ItemsKey/CreateNewDefaultItemsKey'
|
||||
import { DecryptTypeAPayload } from './UseCase/TypeA/DecryptPayload'
|
||||
import { DecryptTypeAPayloadWithKeyLookup } from './UseCase/TypeA/DecryptPayloadWithKeyLookup'
|
||||
import { EncryptTypeAPayloadWithKeyLookup } from './UseCase/TypeA/EncryptPayloadWithKeyLookup'
|
||||
import { EncryptTypeAPayload } from './UseCase/TypeA/EncryptPayload'
|
||||
import { ValidateAccountPasswordResult } from '../RootKeyManager/ValidateAccountPasswordResult'
|
||||
import { ValidatePasscodeResult } from '../RootKeyManager/ValidatePasscodeResult'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { EncryptionProviderInterface } from './EncryptionProviderInterface'
|
||||
import { KeyMode } from '../RootKeyManager/KeyMode'
|
||||
|
||||
/**
|
||||
* The encryption service is responsible for the encryption and decryption of payloads, and
|
||||
@@ -124,40 +116,28 @@ export class EncryptionService
|
||||
extends AbstractService<EncryptionServiceEvent>
|
||||
implements EncryptionProviderInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private operators: OperatorManager
|
||||
private readonly itemsEncryption: ItemsEncryptionService
|
||||
private readonly rootKeyManager: RootKeyManager
|
||||
|
||||
constructor(
|
||||
private items: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private payloads: PayloadManagerInterface,
|
||||
public device: DeviceInterface,
|
||||
private storage: StorageServiceInterface,
|
||||
public readonly keys: KeySystemKeyManagerInterface,
|
||||
identifier: ApplicationIdentifier,
|
||||
public crypto: PureCryptoInterface,
|
||||
private operators: EncryptionOperatorsInterface,
|
||||
private itemsEncryption: ItemsEncryptionService,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
private crypto: PureCryptoInterface,
|
||||
private _createNewItemsKeyWithRollback: CreateNewItemsKeyWithRollback,
|
||||
private _findDefaultItemsKey: FindDefaultItemsKey,
|
||||
private _decryptErroredRootPayloads: DecryptErroredTypeAPayloads,
|
||||
private _rootKeyEncryptPayloadWithKeyLookup: EncryptTypeAPayloadWithKeyLookup,
|
||||
private _rootKeyEncryptPayload: EncryptTypeAPayload,
|
||||
private _rootKeyDecryptPayload: DecryptTypeAPayload,
|
||||
private _rootKeyDecryptPayloadWithKeyLookup: DecryptTypeAPayloadWithKeyLookup,
|
||||
private _createDefaultItemsKey: CreateNewDefaultItemsKey,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -168,25 +148,21 @@ export class EncryptionService
|
||||
}
|
||||
}
|
||||
|
||||
public override async blockDeinit(): Promise<void> {
|
||||
await Promise.all([this.rootKeyManager.blockDeinit(), this.itemsEncryption.blockDeinit()])
|
||||
|
||||
return super.blockDeinit()
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.items as unknown) = undefined
|
||||
;(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()
|
||||
;(this.itemsEncryption as unknown) = undefined
|
||||
|
||||
this.rootKeyManager.deinit()
|
||||
;(this.rootKeyManager as unknown) = undefined
|
||||
;(this.crypto as unknown) = undefined
|
||||
;(this._createNewItemsKeyWithRollback as unknown) = undefined
|
||||
;(this._findDefaultItemsKey as unknown) = undefined
|
||||
;(this._decryptErroredRootPayloads as unknown) = undefined
|
||||
;(this._rootKeyEncryptPayloadWithKeyLookup as unknown) = undefined
|
||||
;(this._rootKeyEncryptPayload as unknown) = undefined
|
||||
;(this._rootKeyDecryptPayload as unknown) = undefined
|
||||
;(this._rootKeyDecryptPayloadWithKeyLookup as unknown) = undefined
|
||||
;(this._createDefaultItemsKey as unknown) = undefined
|
||||
|
||||
super.deinit()
|
||||
}
|
||||
@@ -217,7 +193,7 @@ export class EncryptionService
|
||||
return !!this.getRootKey()?.signingKeyPair
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
public async initialize(): Promise<void> {
|
||||
await this.rootKeyManager.initialize()
|
||||
}
|
||||
|
||||
@@ -250,7 +226,7 @@ export class EncryptionService
|
||||
return this.rootKeyManager.getUserVersion()
|
||||
}
|
||||
|
||||
public async upgradeAvailable() {
|
||||
public async upgradeAvailable(): Promise<boolean> {
|
||||
const accountUpgradeAvailable = this.accountUpgradeAvailable()
|
||||
const passcodeUpgradeAvailable = await this.passcodeUpgradeAvailable()
|
||||
return accountUpgradeAvailable || passcodeUpgradeAvailable
|
||||
@@ -268,31 +244,12 @@ export class EncryptionService
|
||||
await this.rootKeyManager.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* When the key system root key changes, we must re-encrypt all vault items keys
|
||||
* with this new key system root key (by simply re-syncing).
|
||||
*/
|
||||
public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
|
||||
const keySystemItemsKeys = this.keys.getKeySystemItemsKeys(keySystemIdentifier)
|
||||
if (keySystemItemsKeys.length > 0) {
|
||||
await this.mutator.setItemsDirty(keySystemItemsKeys)
|
||||
}
|
||||
}
|
||||
|
||||
public async createNewItemsKeyWithRollback(): Promise<() => Promise<void>> {
|
||||
const usecase = new CreateNewItemsKeyWithRollbackUseCase(
|
||||
this.mutator,
|
||||
this.items,
|
||||
this.storage,
|
||||
this.operators,
|
||||
this.rootKeyManager,
|
||||
)
|
||||
return usecase.execute()
|
||||
return this._createNewItemsKeyWithRollback.execute()
|
||||
}
|
||||
|
||||
public async decryptErroredPayloads(): Promise<void> {
|
||||
const usecase = new DecryptErroredRootPayloadsUseCase(this.payloads, this.operators, this.keys, this.rootKeyManager)
|
||||
await usecase.execute()
|
||||
await this._decryptErroredRootPayloads.execute()
|
||||
|
||||
await this.itemsEncryption.decryptErroredItemPayloads()
|
||||
}
|
||||
@@ -326,18 +283,10 @@ 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(
|
||||
const rootKeyEncrypted = await this._rootKeyEncryptPayload.executeMany(
|
||||
usesRootKey.items,
|
||||
usesRootKey.key,
|
||||
signingKeyPair,
|
||||
@@ -346,7 +295,7 @@ export class EncryptionService
|
||||
}
|
||||
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
|
||||
const rootKeyEncrypted = await this._rootKeyEncryptPayloadWithKeyLookup.executeMany(
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
@@ -354,7 +303,7 @@ export class EncryptionService
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
|
||||
const keySystemRootKeyEncrypted = await this._rootKeyEncryptPayload.executeMany(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
signingKeyPair,
|
||||
@@ -363,7 +312,7 @@ export class EncryptionService
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
|
||||
const keySystemRootKeyEncrypted = await this._rootKeyEncryptPayloadWithKeyLookup.executeMany(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
@@ -423,32 +372,26 @@ 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._rootKeyDecryptPayload.executeMany<C>(usesRootKey.items, usesRootKey.key)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(usesRootKeyWithKeyLookup.items)
|
||||
const rootKeyDecrypted = await this._rootKeyDecryptPayloadWithKeyLookup.executeMany<C>(
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(
|
||||
const keySystemRootKeyDecrypted = await this._rootKeyDecryptPayload.executeMany<C>(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
}
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(
|
||||
const keySystemRootKeyDecrypted = await this._rootKeyDecryptPayloadWithKeyLookup.executeMany<C>(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
@@ -640,56 +583,6 @@ export class EncryptionService
|
||||
.createKeySystemItemsKey(uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken)
|
||||
}
|
||||
|
||||
asymmetricallyEncryptMessage(dto: {
|
||||
message: AsymmetricMessagePayload
|
||||
senderKeyPair: PkcKeyPair
|
||||
senderSigningKeyPair: PkcKeyPair
|
||||
recipientPublicKey: string
|
||||
}): AsymmetricallyEncryptedString {
|
||||
const operator = this.operators.defaultOperator()
|
||||
const encrypted = operator.asymmetricEncrypt({
|
||||
stringToEncrypt: JSON.stringify(dto.message),
|
||||
senderKeyPair: dto.senderKeyPair,
|
||||
senderSigningKeyPair: dto.senderSigningKeyPair,
|
||||
recipientPublicKey: dto.recipientPublicKey,
|
||||
})
|
||||
return encrypted
|
||||
}
|
||||
|
||||
asymmetricallyDecryptMessage<M extends AsymmetricMessagePayload>(dto: {
|
||||
encryptedString: AsymmetricallyEncryptedString
|
||||
trustedSender: TrustedContactInterface | undefined
|
||||
privateKey: string
|
||||
}): M | undefined {
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.encryptedString)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
const decryptedResult = keyOperator.asymmetricDecrypt({
|
||||
stringToDecrypt: dto.encryptedString,
|
||||
recipientSecretKey: dto.privateKey,
|
||||
})
|
||||
|
||||
if (!decryptedResult) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!decryptedResult.signatureVerified) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (dto.trustedSender) {
|
||||
if (!dto.trustedSender.isPublicKeyTrusted(decryptedResult.senderPublicKey)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!dto.trustedSender.isSigningKeyTrusted(decryptedResult.signaturePublicKey)) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.parse(decryptedResult.plaintext)
|
||||
}
|
||||
|
||||
asymmetricSignatureVerifyDetached(
|
||||
encryptedString: AsymmetricallyEncryptedString,
|
||||
): AsymmetricSignatureVerificationDetachedResult {
|
||||
@@ -699,7 +592,7 @@ export class EncryptionService
|
||||
return keyOperator.asymmetricSignatureVerifyDetached(encryptedString)
|
||||
}
|
||||
|
||||
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
|
||||
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet {
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(string)
|
||||
|
||||
@@ -707,15 +600,6 @@ export class EncryptionService
|
||||
return keyOperator.getSenderPublicKeySetFromAsymmetricallyEncryptedString(string)
|
||||
}
|
||||
|
||||
public async decryptBackupFile(
|
||||
file: BackupFile,
|
||||
password?: string,
|
||||
): Promise<ClientDisplayableError | (EncryptedPayloadInterface | DecryptedPayloadInterface<ItemContent>)[]> {
|
||||
const usecase = new DecryptBackupFileUseCase(this)
|
||||
const result = await usecase.execute(file, password)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key params object from a raw object
|
||||
* @param keyParams - The raw key params object to create a KeyParams object from
|
||||
@@ -800,7 +684,7 @@ export class EncryptionService
|
||||
* If so, they must generate the unwrapping key by getting our saved wrapping key keyParams.
|
||||
* After unwrapping, the root key is automatically loaded.
|
||||
*/
|
||||
public async unwrapRootKey(wrappingKey: RootKeyInterface) {
|
||||
public async unwrapRootKey(wrappingKey: RootKeyInterface): Promise<void> {
|
||||
return this.rootKeyManager.unwrapRootKey(wrappingKey)
|
||||
}
|
||||
/**
|
||||
@@ -902,7 +786,7 @@ export class EncryptionService
|
||||
* A new root key based items key is needed if our default items key content
|
||||
* isnt equal to our current root key
|
||||
*/
|
||||
const defaultItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys())
|
||||
const defaultItemsKey = this._findDefaultItemsKey.execute(this.itemsEncryption.getItemsKeys()).getValue()
|
||||
|
||||
/** Shouldn't be undefined, but if it is, we'll take the corrective action */
|
||||
if (!defaultItemsKey) {
|
||||
@@ -913,8 +797,7 @@ export class EncryptionService
|
||||
}
|
||||
|
||||
public async createNewDefaultItemsKey(): Promise<ItemsKeyInterface> {
|
||||
const usecase = new CreateNewDefaultItemsKeyUseCase(this.mutator, this.items, this.operators, this.rootKeyManager)
|
||||
return usecase.execute()
|
||||
return this._createDefaultItemsKey.execute()
|
||||
}
|
||||
|
||||
public getPasswordCreatedDate(): Date | undefined {
|
||||
@@ -1001,7 +884,7 @@ export class EncryptionService
|
||||
|
||||
private async handleFullSyncCompletion() {
|
||||
/** Always create a new items key after full sync, if no items key is found */
|
||||
const currentItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys())
|
||||
const currentItemsKey = this._findDefaultItemsKey.execute(this.itemsEncryption.getItemsKeys()).getValue()
|
||||
if (!currentItemsKey) {
|
||||
await this.createNewDefaultItemsKey()
|
||||
if (this.rootKeyManager.getKeyMode() === KeyMode.WrapperOnly) {
|
||||
|
||||
@@ -5,11 +5,12 @@ import {
|
||||
ItemsKeyContent,
|
||||
RootKeyInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { EncryptionProviderInterface, KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption'
|
||||
import { ChallengeServiceInterface } from '../Challenge/ChallengeServiceInterface'
|
||||
import { ChallengePrompt } from '../Challenge/Prompt/ChallengePrompt'
|
||||
import { ChallengeReason } from '../Challenge/Types/ChallengeReason'
|
||||
import { ChallengeValidation } from '../Challenge/Types/ChallengeValidation'
|
||||
import { EncryptionProviderInterface } from './EncryptionProviderInterface'
|
||||
|
||||
export async function DecryptItemsKeyWithUserFallback(
|
||||
itemsKey: EncryptedPayloadInterface,
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import {
|
||||
DecryptedParameters,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
isErrorDecryptingParameters,
|
||||
OperatorManager,
|
||||
StandardException,
|
||||
encryptPayload,
|
||||
decryptPayload,
|
||||
EncryptedOutputParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
DecryptedPayload,
|
||||
DecryptedPayloadInterface,
|
||||
EncryptedPayload,
|
||||
EncryptedPayloadInterface,
|
||||
KeySystemRootKeyInterface,
|
||||
isEncryptedPayload,
|
||||
ItemContent,
|
||||
ItemsKeyInterface,
|
||||
PayloadEmitSource,
|
||||
KeySystemItemsKeyInterface,
|
||||
SureFindPayload,
|
||||
ContentTypeUsesRootKeyEncryption,
|
||||
} from '@standardnotes/models'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export class ItemsEncryptionService extends AbstractService {
|
||||
private removeItemsObserver!: () => void
|
||||
public userVersion?: ProtocolVersion
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManagerInterface,
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
private storageService: StorageServiceInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private keys: KeySystemKeyManagerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
|
||||
this.removeItemsObserver = this.itemManager.addObserver([ContentType.TYPES.ItemsKey], ({ changed, inserted }) => {
|
||||
if (changed.concat(inserted).length > 0) {
|
||||
void this.decryptErroredItemPayloads()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.itemManager as unknown) = undefined
|
||||
;(this.payloadManager as unknown) = undefined
|
||||
;(this.storageService as unknown) = undefined
|
||||
;(this.operatorManager as unknown) = undefined
|
||||
;(this.keys as unknown) = undefined
|
||||
this.removeItemsObserver()
|
||||
;(this.removeItemsObserver as unknown) = undefined
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
/**
|
||||
* If encryption status changes (esp. on mobile, where local storage encryption
|
||||
* can be disabled), consumers may call this function to repersist all items to
|
||||
* disk using latest encryption status.
|
||||
*/
|
||||
async repersistAllItems(): Promise<void> {
|
||||
const items = this.itemManager.items
|
||||
const payloads = items.map((item) => item.payload)
|
||||
return this.storageService.savePayloads(payloads)
|
||||
}
|
||||
|
||||
public getItemsKeys(): ItemsKeyInterface[] {
|
||||
return this.itemManager.getDisplayableItemsKeys()
|
||||
}
|
||||
|
||||
public itemsKeyForEncryptedPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
const itemsKeys = this.getItemsKeys()
|
||||
const keySystemItemsKeys = this.itemManager.getItems<KeySystemItemsKeyInterface>(
|
||||
ContentType.TYPES.KeySystemItemsKey,
|
||||
)
|
||||
|
||||
return [...itemsKeys, ...keySystemItemsKeys].find(
|
||||
(key) => key.uuid === payload.items_key_id || key.duplicateOf === payload.items_key_id,
|
||||
)
|
||||
}
|
||||
|
||||
public getDefaultItemsKey(): ItemsKeyInterface | undefined {
|
||||
return findDefaultItemsKey(this.getItemsKeys())
|
||||
}
|
||||
|
||||
private keyToUseForItemEncryption(
|
||||
payload: DecryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | StandardException {
|
||||
if (payload.key_system_identifier) {
|
||||
const keySystemItemsKey = this.keys.getPrimaryKeySystemItemsKey(payload.key_system_identifier)
|
||||
if (!keySystemItemsKey) {
|
||||
return new StandardException('Cannot find key system items key to use for encryption')
|
||||
}
|
||||
|
||||
return keySystemItemsKey
|
||||
}
|
||||
|
||||
const defaultKey = this.getDefaultItemsKey()
|
||||
|
||||
let result: ItemsKeyInterface | undefined = undefined
|
||||
|
||||
if (this.userVersion && this.userVersion !== defaultKey?.keyVersion) {
|
||||
/**
|
||||
* The default key appears to be either newer or older than the user's account version
|
||||
* We could throw an exception here, but will instead fall back to a corrective action:
|
||||
* return any items key that corresponds to the user's version
|
||||
*/
|
||||
const itemsKeys = this.getItemsKeys()
|
||||
result = itemsKeys.find((key) => key.keyVersion === this.userVersion)
|
||||
} else {
|
||||
result = defaultKey
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return new StandardException('Cannot find items key to use for encryption')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private keyToUseForDecryptionOfPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
if (payload.items_key_id) {
|
||||
const itemsKey = this.itemsKeyForEncryptedPayload(payload)
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
const defaultKey = this.defaultItemsKeyForItemVersion(payload.version)
|
||||
return defaultKey
|
||||
}
|
||||
|
||||
public async encryptPayloadWithKeyLookup(
|
||||
payload: DecryptedPayloadInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters> {
|
||||
const key = this.keyToUseForItemEncryption(payload)
|
||||
|
||||
if (key instanceof StandardException) {
|
||||
throw Error(key.message)
|
||||
}
|
||||
|
||||
return this.encryptPayload(payload, key, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayload(
|
||||
payload: DecryptedPayloadInterface,
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters> {
|
||||
if (isEncryptedPayload(payload)) {
|
||||
throw Error('Attempting to encrypt already encrypted payload.')
|
||||
}
|
||||
if (!payload.content) {
|
||||
throw Error('Attempting to encrypt payload with no content.')
|
||||
}
|
||||
if (!payload.uuid) {
|
||||
throw Error('Attempting to encrypt payload with no UuidGenerator.')
|
||||
}
|
||||
|
||||
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayloads(
|
||||
payloads: DecryptedPayloadInterface[],
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key, signingKeyPair)))
|
||||
}
|
||||
|
||||
public async encryptPayloadsWithKeyLookup(
|
||||
payloads: DecryptedPayloadInterface[],
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayloadWithKeyLookup(payload, signingKeyPair)))
|
||||
}
|
||||
|
||||
public async decryptPayloadWithKeyLookup<C extends ItemContent = ItemContent>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
|
||||
const key = this.keyToUseForDecryptionOfPayload(payload)
|
||||
|
||||
if (key == undefined) {
|
||||
return {
|
||||
uuid: payload.uuid,
|
||||
errorDecrypting: true,
|
||||
waitingForKey: true,
|
||||
}
|
||||
}
|
||||
|
||||
return this.decryptPayload(payload, key)
|
||||
}
|
||||
|
||||
public async decryptPayload<C extends ItemContent = ItemContent>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
|
||||
if (!payload.content) {
|
||||
return {
|
||||
uuid: payload.uuid,
|
||||
errorDecrypting: true,
|
||||
}
|
||||
}
|
||||
|
||||
return decryptPayload(payload, key, this.operatorManager)
|
||||
}
|
||||
|
||||
public async decryptPayloadsWithKeyLookup<C extends ItemContent = ItemContent>(
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
|
||||
return Promise.all(payloads.map((payload) => this.decryptPayloadWithKeyLookup<C>(payload)))
|
||||
}
|
||||
|
||||
public async decryptPayloads<C extends ItemContent = ItemContent>(
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
|
||||
return Promise.all(payloads.map((payload) => this.decryptPayload<C>(payload, key)))
|
||||
}
|
||||
|
||||
public async decryptErroredItemPayloads(): Promise<void> {
|
||||
const erroredItemPayloads = this.payloadManager.invalidPayloads.filter(
|
||||
(i) =>
|
||||
!ContentTypeUsesRootKeyEncryption(i.content_type) && !ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
|
||||
)
|
||||
if (erroredItemPayloads.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const resultParams = await this.decryptPayloadsWithKeyLookup(erroredItemPayloads)
|
||||
|
||||
const decryptedPayloads = resultParams.map((params) => {
|
||||
const original = SureFindPayload(erroredItemPayloads, params.uuid)
|
||||
if (isErrorDecryptingParameters(params)) {
|
||||
return new EncryptedPayload({
|
||||
...original.ejected(),
|
||||
...params,
|
||||
})
|
||||
} else {
|
||||
return new DecryptedPayload({
|
||||
...original.ejected(),
|
||||
...params,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
await this.payloadManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
|
||||
}
|
||||
|
||||
/**
|
||||
* When migrating from non-items key architecture, many items will not have a
|
||||
* relationship with any key object. For those items, we can be sure that only 1 key
|
||||
* object will correspond to that protocol version.
|
||||
* @returns The items key object to decrypt items encrypted
|
||||
* with previous protocol version.
|
||||
*/
|
||||
public defaultItemsKeyForItemVersion(
|
||||
version: ProtocolVersion,
|
||||
fromKeys?: ItemsKeyInterface[],
|
||||
): ItemsKeyInterface | undefined {
|
||||
/** Try to find one marked default first */
|
||||
const searchKeys = fromKeys || this.getItemsKeys()
|
||||
const priorityKey = searchKeys.find((key) => {
|
||||
return key.isDefault && key.keyVersion === version
|
||||
})
|
||||
if (priorityKey) {
|
||||
return priorityKey
|
||||
}
|
||||
return searchKeys.find((key) => {
|
||||
return key.keyVersion === version
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
import {
|
||||
AnyKeyParamsContent,
|
||||
ApplicationIdentifier,
|
||||
KeyParamsOrigination,
|
||||
ProtocolVersion,
|
||||
ProtocolVersionLatest,
|
||||
} from '@standardnotes/common'
|
||||
import {
|
||||
KeyMode,
|
||||
CreateNewRootKey,
|
||||
CreateAnyKeyParams,
|
||||
SNRootKey,
|
||||
isErrorDecryptingParameters,
|
||||
OperatorManager,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypesUsingRootKeyEncryption,
|
||||
DecryptedPayload,
|
||||
DecryptedTransferPayload,
|
||||
EncryptedPayload,
|
||||
EncryptedTransferPayload,
|
||||
FillItemContentSpecialized,
|
||||
NamespacedRootKeyInKeychain,
|
||||
RootKeyContent,
|
||||
RootKeyInterface,
|
||||
RootKeyParamsInterface,
|
||||
} 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'
|
||||
|
||||
export class RootKeyManager extends AbstractService<RootKeyManagerEvent> {
|
||||
private rootKey?: RootKeyInterface
|
||||
private keyMode = KeyMode.RootKeyNone
|
||||
private memoizedRootKeyParams?: RootKeyParamsInterface
|
||||
|
||||
constructor(
|
||||
private device: DeviceInterface,
|
||||
private storage: StorageServiceInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private operators: OperatorManager,
|
||||
private identifier: ApplicationIdentifier,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
super.deinit()
|
||||
this.rootKey = undefined
|
||||
this.memoizedRootKeyParams = undefined
|
||||
}
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
const wrappedRootKey = this.getWrappedRootKey()
|
||||
const accountKeyParams = this.recomputeAccountKeyParams()
|
||||
const hasWrapper = await this.hasRootKeyWrapper()
|
||||
const hasRootKey = wrappedRootKey != undefined || accountKeyParams != undefined
|
||||
|
||||
if (hasWrapper && hasRootKey) {
|
||||
this.keyMode = KeyMode.RootKeyPlusWrapper
|
||||
} else if (hasWrapper && !hasRootKey) {
|
||||
this.keyMode = KeyMode.WrapperOnly
|
||||
} else if (!hasWrapper && hasRootKey) {
|
||||
this.keyMode = KeyMode.RootKeyOnly
|
||||
} else if (!hasWrapper && !hasRootKey) {
|
||||
this.keyMode = KeyMode.RootKeyNone
|
||||
} else {
|
||||
throw 'Invalid key mode condition'
|
||||
}
|
||||
|
||||
if (this.keyMode === KeyMode.RootKeyOnly) {
|
||||
this.setRootKeyInstance(await this.getRootKeyFromKeychain())
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
}
|
||||
|
||||
public getMemoizedRootKeyParams(): RootKeyParamsInterface | undefined {
|
||||
return this.memoizedRootKeyParams
|
||||
}
|
||||
|
||||
public getKeyMode(): KeyMode {
|
||||
return this.keyMode
|
||||
}
|
||||
|
||||
public async hasRootKeyWrapper(): Promise<boolean> {
|
||||
const wrapper = this.getRootKeyWrapperKeyParams()
|
||||
return wrapper != undefined
|
||||
}
|
||||
|
||||
public getRootKeyWrapperKeyParams(): RootKeyParamsInterface | undefined {
|
||||
const rawKeyParams = this.storage.getValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
|
||||
}
|
||||
|
||||
public async passcodeUpgradeAvailable(): Promise<boolean> {
|
||||
const passcodeParams = this.getRootKeyWrapperKeyParams()
|
||||
if (!passcodeParams) {
|
||||
return false
|
||||
}
|
||||
return passcodeParams.version !== ProtocolVersionLatest
|
||||
}
|
||||
|
||||
public hasAccount(): boolean {
|
||||
switch (this.keyMode) {
|
||||
case KeyMode.RootKeyNone:
|
||||
case KeyMode.WrapperOnly:
|
||||
return false
|
||||
case KeyMode.RootKeyOnly:
|
||||
case KeyMode.RootKeyPlusWrapper:
|
||||
return true
|
||||
default:
|
||||
throw Error(`Unhandled keyMode value '${this.keyMode}'.`)
|
||||
}
|
||||
}
|
||||
|
||||
public getUserVersion(): ProtocolVersion | undefined {
|
||||
const keyParams = this.memoizedRootKeyParams
|
||||
return keyParams?.version
|
||||
}
|
||||
|
||||
public hasRootKeyEncryptionSource(): boolean {
|
||||
return this.hasAccount() || this.hasPasscode()
|
||||
}
|
||||
|
||||
public async computeRootKey<K extends RootKeyInterface>(
|
||||
password: string,
|
||||
keyParams: RootKeyParamsInterface,
|
||||
): Promise<K> {
|
||||
const version = keyParams.version
|
||||
const operator = this.operators.operatorForVersion(version)
|
||||
return operator.computeRootKey(password, keyParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes root key and wrapper from keychain. Used when signing out of application.
|
||||
*/
|
||||
public async deleteWorkspaceSpecificKeyStateFromDevice(): Promise<void> {
|
||||
await this.device.clearNamespacedKeychainValue(this.identifier)
|
||||
|
||||
await this.storage.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
|
||||
await this.storage.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
await this.storage.removeValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
this.keyMode = KeyMode.RootKeyNone
|
||||
this.setRootKeyInstance(undefined)
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
|
||||
public async createRootKey<K extends RootKeyInterface>(
|
||||
identifier: string,
|
||||
password: string,
|
||||
origination: KeyParamsOrigination,
|
||||
version?: ProtocolVersion,
|
||||
): Promise<K> {
|
||||
const operator = version ? this.operators.operatorForVersion(version) : this.operators.defaultOperator()
|
||||
return operator.createRootKey(identifier, password, origination)
|
||||
}
|
||||
|
||||
public async validateAccountPassword(password: string): Promise<ValidateAccountPasswordResult> {
|
||||
const key = await this.computeRootKey(password, this.memoizedRootKeyParams as RootKeyParamsInterface)
|
||||
const valid = this.getSureRootKey().compare(key)
|
||||
if (valid) {
|
||||
return { valid, artifacts: { rootKey: key } }
|
||||
} else {
|
||||
return { valid: false }
|
||||
}
|
||||
}
|
||||
|
||||
public async validatePasscode(passcode: string): Promise<ValidatePasscodeResult> {
|
||||
const keyParams = this.getSureRootKeyWrapperKeyParams()
|
||||
const key = await this.computeRootKey(passcode, keyParams)
|
||||
const valid = await this.validateWrappingKey(key)
|
||||
if (valid) {
|
||||
return { valid, artifacts: { wrappingKey: key } }
|
||||
} else {
|
||||
return { valid: false }
|
||||
}
|
||||
}
|
||||
|
||||
public async getEncryptionSourceVersion(): Promise<ProtocolVersion> {
|
||||
if (this.hasAccount()) {
|
||||
return this.getSureUserVersion()
|
||||
} else if (this.hasPasscode()) {
|
||||
const passcodeParams = this.getSureRootKeyWrapperKeyParams()
|
||||
return passcodeParams.version
|
||||
}
|
||||
|
||||
throw Error('Attempting to access encryption source version without source')
|
||||
}
|
||||
|
||||
public getSureUserVersion(): ProtocolVersion {
|
||||
const keyParams = this.memoizedRootKeyParams as RootKeyParamsInterface
|
||||
return keyParams.version
|
||||
}
|
||||
|
||||
private async handleKeyStatusChange(): Promise<void> {
|
||||
this.recomputeAccountKeyParams()
|
||||
void this.notifyEvent(RootKeyManagerEvent.RootKeyManagerKeyStatusChanged)
|
||||
}
|
||||
|
||||
public hasPasscode(): boolean {
|
||||
return this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper
|
||||
}
|
||||
|
||||
public recomputeAccountKeyParams(): RootKeyParamsInterface | undefined {
|
||||
const rawKeyParams = this.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return
|
||||
}
|
||||
|
||||
this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
|
||||
return this.memoizedRootKeyParams
|
||||
}
|
||||
|
||||
public getSureRootKeyWrapperKeyParams() {
|
||||
return this.getRootKeyWrapperKeyParams() as RootKeyParamsInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the current in-memory root key value using the wrappingKey,
|
||||
* then persists the wrapped value to disk.
|
||||
*/
|
||||
public async wrapAndPersistRootKey(wrappingKey: RootKeyInterface): Promise<void> {
|
||||
const rootKey = this.getSureRootKey()
|
||||
|
||||
const value: DecryptedTransferPayload = {
|
||||
...rootKey.payload.ejected(),
|
||||
content: FillItemContentSpecialized(rootKey.persistableValueWhenWrapping()),
|
||||
}
|
||||
|
||||
const payload = new DecryptedPayload(value)
|
||||
|
||||
const usecase = new RootKeyEncryptPayloadUseCase(this.operators)
|
||||
const wrappedKey = await usecase.executeOne(payload, wrappingKey)
|
||||
const wrappedKeyPayload = new EncryptedPayload({
|
||||
...payload.ejected(),
|
||||
...wrappedKey,
|
||||
errorDecrypting: false,
|
||||
waitingForKey: false,
|
||||
})
|
||||
|
||||
this.storage.setValue(StorageKey.WrappedRootKey, wrappedKeyPayload.ejected(), StorageValueModes.Nonwrapped)
|
||||
}
|
||||
|
||||
public async unwrapRootKey(wrappingKey: RootKeyInterface): Promise<void> {
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
this.setRootKeyInstance(wrappingKey)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.keyMode !== KeyMode.RootKeyPlusWrapper) {
|
||||
throw 'Invalid key mode condition for unwrapping.'
|
||||
}
|
||||
|
||||
const wrappedKey = this.getWrappedRootKey()
|
||||
const payload = new EncryptedPayload(wrappedKey)
|
||||
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
|
||||
const decrypted = await usecase.executeOne<RootKeyContent>(payload, wrappingKey)
|
||||
|
||||
if (isErrorDecryptingParameters(decrypted)) {
|
||||
throw Error('Unable to decrypt root key with provided wrapping key.')
|
||||
} else {
|
||||
const decryptedPayload = new DecryptedPayload<RootKeyContent>({
|
||||
...payload.ejected(),
|
||||
...decrypted,
|
||||
})
|
||||
this.setRootKeyInstance(new SNRootKey(decryptedPayload))
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encrypts rootKey and saves it in storage instead of keychain, and then
|
||||
* clears keychain. This is because we don't want to store large encrypted
|
||||
* payloads in the keychain. If the root key is not wrapped, it is stored
|
||||
* in plain form in the user's secure keychain.
|
||||
*/
|
||||
public async setNewRootKeyWrapper(wrappingKey: RootKeyInterface) {
|
||||
if (this.keyMode === KeyMode.RootKeyNone) {
|
||||
this.keyMode = KeyMode.WrapperOnly
|
||||
} else if (this.keyMode === KeyMode.RootKeyOnly) {
|
||||
this.keyMode = KeyMode.RootKeyPlusWrapper
|
||||
} else {
|
||||
throw Error('Attempting to set wrapper on already wrapped key.')
|
||||
}
|
||||
|
||||
await this.device.clearNamespacedKeychainValue(this.identifier)
|
||||
|
||||
if (this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
this.setRootKeyInstance(wrappingKey)
|
||||
await this.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
} else {
|
||||
await this.wrapAndPersistRootKey(wrappingKey)
|
||||
}
|
||||
|
||||
this.storage.setValue(
|
||||
StorageKey.RootKeyWrapperKeyParams,
|
||||
wrappingKey.keyParams.getPortableValue(),
|
||||
StorageValueModes.Nonwrapped,
|
||||
)
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
} else {
|
||||
throw Error('Invalid keyMode on setNewRootKeyWrapper')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes root key wrapper from local storage and stores root key bare in secure keychain.
|
||||
*/
|
||||
public async removeRootKeyWrapper(): Promise<void> {
|
||||
if (this.keyMode !== KeyMode.WrapperOnly && this.keyMode !== KeyMode.RootKeyPlusWrapper) {
|
||||
throw Error('Attempting to remove root key wrapper on unwrapped key.')
|
||||
}
|
||||
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
this.keyMode = KeyMode.RootKeyNone
|
||||
this.setRootKeyInstance(undefined)
|
||||
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
this.keyMode = KeyMode.RootKeyOnly
|
||||
}
|
||||
|
||||
await this.storage.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
|
||||
await this.storage.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (this.keyMode === KeyMode.RootKeyOnly) {
|
||||
await this.saveRootKeyToKeychain()
|
||||
}
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
|
||||
public async setRootKey(key: RootKeyInterface, wrappingKey?: RootKeyInterface) {
|
||||
if (!key.keyParams) {
|
||||
throw Error('keyParams must be supplied if setting root key.')
|
||||
}
|
||||
|
||||
if (this.getRootKey() === key) {
|
||||
throw Error('Attempting to set root key as same current value.')
|
||||
}
|
||||
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
this.keyMode = KeyMode.RootKeyPlusWrapper
|
||||
} else if (this.keyMode === KeyMode.RootKeyNone) {
|
||||
this.keyMode = KeyMode.RootKeyOnly
|
||||
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
/** Root key is simply changing, mode stays the same */
|
||||
/** this.keyMode = this.keyMode; */
|
||||
} else {
|
||||
throw Error(`Unhandled key mode for setNewRootKey ${this.keyMode}`)
|
||||
}
|
||||
|
||||
this.setRootKeyInstance(key)
|
||||
|
||||
this.storage.setValue(StorageKey.RootKeyParams, key.keyParams.getPortableValue(), StorageValueModes.Nonwrapped)
|
||||
|
||||
if (this.keyMode === KeyMode.RootKeyOnly) {
|
||||
await this.saveRootKeyToKeychain()
|
||||
} else if (this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
if (!wrappingKey) {
|
||||
throw Error('wrappingKey must be supplied')
|
||||
}
|
||||
await this.wrapAndPersistRootKey(wrappingKey)
|
||||
}
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
|
||||
public getRootKeyParams(): RootKeyParamsInterface | undefined {
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
return this.getRootKeyWrapperKeyParams()
|
||||
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
return this.recomputeAccountKeyParams()
|
||||
} else if (this.keyMode === KeyMode.RootKeyNone) {
|
||||
return undefined
|
||||
} else {
|
||||
throw `Unhandled key mode for getRootKeyParams ${this.keyMode}`
|
||||
}
|
||||
}
|
||||
|
||||
public getSureRootKeyParams(): RootKeyParamsInterface {
|
||||
return this.getRootKeyParams() as RootKeyParamsInterface
|
||||
}
|
||||
|
||||
public async saveRootKeyToKeychain() {
|
||||
if (this.getRootKey() == undefined) {
|
||||
throw 'Attempting to non-existent root key to the keychain.'
|
||||
}
|
||||
if (this.keyMode !== KeyMode.RootKeyOnly) {
|
||||
throw 'Should not be persisting wrapped key to keychain.'
|
||||
}
|
||||
|
||||
const rawKey = this.getSureRootKey().getKeychainValue()
|
||||
|
||||
return this.executeCriticalFunction(() => {
|
||||
return this.device.setNamespacedKeychainValue(rawKey, this.identifier)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* We know a wrappingKey is correct if it correctly decrypts
|
||||
* wrapped root key.
|
||||
*/
|
||||
public async validateWrappingKey(wrappingKey: RootKeyInterface): Promise<boolean> {
|
||||
const wrappedRootKey = this.getWrappedRootKey()
|
||||
|
||||
/** If wrapper only, storage is encrypted directly with wrappingKey */
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
return this.storage.canDecryptWithKey(wrappingKey)
|
||||
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
/**
|
||||
* In these modes, storage is encrypted with account keys, and
|
||||
* account keys are encrypted with wrappingKey. Here we validate
|
||||
* by attempting to decrypt account keys.
|
||||
*/
|
||||
const wrappedKeyPayload = new EncryptedPayload(wrappedRootKey)
|
||||
const usecase = new RootKeyDecryptPayloadUseCase(this.operators)
|
||||
const decrypted = await usecase.executeOne(wrappedKeyPayload, wrappingKey)
|
||||
return !isErrorDecryptingParameters(decrypted)
|
||||
} else {
|
||||
throw 'Unhandled case in validateWrappingKey'
|
||||
}
|
||||
}
|
||||
|
||||
private getWrappedRootKey(): EncryptedTransferPayload {
|
||||
return this.storage.getValue<EncryptedTransferPayload>(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
|
||||
}
|
||||
|
||||
public setRootKeyInstance(rootKey: RootKeyInterface | undefined): void {
|
||||
this.rootKey = rootKey
|
||||
}
|
||||
|
||||
public getRootKey(): RootKeyInterface | undefined {
|
||||
return this.rootKey
|
||||
}
|
||||
|
||||
public getSureRootKey(): RootKeyInterface {
|
||||
return this.rootKey as RootKeyInterface
|
||||
}
|
||||
|
||||
public async getRootKeyFromKeychain(): Promise<RootKeyInterface | undefined> {
|
||||
const rawKey = (await this.device.getNamespacedKeychainValue(this.identifier)) as
|
||||
| NamespacedRootKeyInKeychain
|
||||
| undefined
|
||||
|
||||
if (rawKey == undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const keyParams = this.getSureRootKeyParams()
|
||||
|
||||
return CreateNewRootKey({
|
||||
...rawKey,
|
||||
keyParams: keyParams.getPortableValue(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When the root key changes, we must re-encrypt all relevant items with this new root key (by simply re-syncing).
|
||||
*/
|
||||
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
|
||||
const items = this.items.getItems(ContentTypesUsingRootKeyEncryption())
|
||||
if (items.length > 0) {
|
||||
/**
|
||||
* Do not call sync after marking dirty.
|
||||
* Re-encrypting items keys is called by consumers who have specific flows who
|
||||
* will sync on their own timing
|
||||
*/
|
||||
await this.mutator.setItemsDirty(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export enum RootKeyManagerEvent {
|
||||
RootKeyManagerKeyStatusChanged = 'RootKeyManagerKeyStatusChanged',
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { RootKeyInterface } from '@standardnotes/models'
|
||||
|
||||
export type ValidateAccountPasswordResult =
|
||||
| {
|
||||
valid: true
|
||||
artifacts: {
|
||||
rootKey: RootKeyInterface
|
||||
}
|
||||
}
|
||||
| {
|
||||
valid: boolean
|
||||
artifacts?: undefined
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { RootKeyInterface } from '@standardnotes/models'
|
||||
|
||||
export type ValidatePasscodeResult =
|
||||
| {
|
||||
valid: true
|
||||
artifacts: {
|
||||
wrappingKey: RootKeyInterface
|
||||
}
|
||||
}
|
||||
| {
|
||||
valid: boolean
|
||||
artifacts?: undefined
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import {
|
||||
ContactPublicKeySet,
|
||||
ContactPublicKeySetInterface,
|
||||
PublicKeyTrustStatus,
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { DecryptMessage } from './DecryptMessage'
|
||||
import { OperatorInterface, EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
|
||||
function createMockPublicKeySetChain(): ContactPublicKeySetInterface {
|
||||
const nMinusOne = new ContactPublicKeySet({
|
||||
encryption: 'encryption-public-key-n-1',
|
||||
signing: 'signing-public-key-n-1',
|
||||
timestamp: new Date(-1),
|
||||
previousKeySet: undefined,
|
||||
})
|
||||
|
||||
const root = new ContactPublicKeySet({
|
||||
encryption: 'encryption-public-key',
|
||||
signing: 'signing-public-key',
|
||||
timestamp: new Date(),
|
||||
previousKeySet: nMinusOne,
|
||||
})
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
describe('DecryptMessage', () => {
|
||||
let usecase: DecryptMessage
|
||||
let operator: jest.Mocked<OperatorInterface>
|
||||
|
||||
beforeEach(() => {
|
||||
operator = {} as jest.Mocked<OperatorInterface>
|
||||
operator.versionForAsymmetricallyEncryptedString = jest.fn().mockReturnValue(ProtocolVersion.V004)
|
||||
|
||||
const operators = {} as jest.Mocked<EncryptionOperatorsInterface>
|
||||
operators.defaultOperator = jest.fn().mockReturnValue(operator)
|
||||
operators.operatorForVersion = jest.fn().mockReturnValue(operator)
|
||||
|
||||
usecase = new DecryptMessage(operators)
|
||||
})
|
||||
|
||||
it('should fail if fails to decrypt', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue(null)
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: undefined,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(true)
|
||||
expect(result.getError()).toEqual('Failed to decrypt message')
|
||||
})
|
||||
|
||||
it('should fail if signature is invalid', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue({
|
||||
plaintext: 'decrypted',
|
||||
signatureVerified: false,
|
||||
signaturePublicKey: 'signing-public-key',
|
||||
senderPublicKey: 'encryption-public-key',
|
||||
})
|
||||
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: undefined,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(true)
|
||||
expect(result.getError()).toEqual('Failed to verify signature')
|
||||
})
|
||||
|
||||
describe('with trusted sender', () => {
|
||||
it('should fail if encryption public key is not trusted', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue({
|
||||
plaintext: 'decrypted',
|
||||
signatureVerified: true,
|
||||
signaturePublicKey: 'signing-public-key',
|
||||
senderPublicKey: 'encryption-public-key',
|
||||
})
|
||||
|
||||
const senderContact = {
|
||||
name: 'Other',
|
||||
contactUuid: '456',
|
||||
publicKeySet: createMockPublicKeySetChain(),
|
||||
isMe: false,
|
||||
} as jest.Mocked<TrustedContactInterface>
|
||||
|
||||
senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.NotTrusted)
|
||||
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: senderContact,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(true)
|
||||
expect(result.getError()).toEqual('Sender public key is not trusted')
|
||||
})
|
||||
|
||||
it('should fail if signing public key is not trusted', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue({
|
||||
plaintext: 'decrypted',
|
||||
signatureVerified: true,
|
||||
signaturePublicKey: 'signing-public-key',
|
||||
senderPublicKey: 'encryption-public-key',
|
||||
})
|
||||
|
||||
const senderContact = {
|
||||
name: 'Other',
|
||||
contactUuid: '456',
|
||||
publicKeySet: createMockPublicKeySetChain(),
|
||||
isMe: false,
|
||||
} as jest.Mocked<TrustedContactInterface>
|
||||
|
||||
senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted)
|
||||
senderContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.NotTrusted)
|
||||
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: senderContact,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(true)
|
||||
expect(result.getError()).toEqual('Signature public key is not trusted')
|
||||
})
|
||||
|
||||
it('should succeed with valid signature and encryption key', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue({
|
||||
plaintext: '{"foo": "bar"}',
|
||||
signatureVerified: true,
|
||||
signaturePublicKey: 'signing-public-key',
|
||||
senderPublicKey: 'encryption-public-key',
|
||||
})
|
||||
|
||||
const senderContact = {
|
||||
name: 'Other',
|
||||
contactUuid: '456',
|
||||
publicKeySet: createMockPublicKeySetChain(),
|
||||
isMe: false,
|
||||
} as jest.Mocked<TrustedContactInterface>
|
||||
|
||||
senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted)
|
||||
senderContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted)
|
||||
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: senderContact,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(false)
|
||||
expect(result.getValue()).toEqual({ foo: 'bar' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('without trusted sender', () => {
|
||||
it('should succeed with valid signature and encryption key', () => {
|
||||
operator.asymmetricDecrypt = jest.fn().mockReturnValue({
|
||||
plaintext: '{"foo": "bar"}',
|
||||
signatureVerified: true,
|
||||
signaturePublicKey: 'signing-public-key',
|
||||
senderPublicKey: 'encryption-public-key',
|
||||
})
|
||||
|
||||
const result = usecase.execute({
|
||||
message: 'encrypted',
|
||||
sender: undefined,
|
||||
privateKey: 'private-key',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toEqual(false)
|
||||
expect(result.getValue()).toEqual({ foo: 'bar' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core'
|
||||
import { EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessagePayload, PublicKeyTrustStatus, TrustedContactInterface } from '@standardnotes/models'
|
||||
|
||||
export class DecryptMessage implements SyncUseCaseInterface<AsymmetricMessagePayload> {
|
||||
constructor(private operators: EncryptionOperatorsInterface) {}
|
||||
|
||||
execute<M extends AsymmetricMessagePayload>(dto: {
|
||||
message: string
|
||||
sender: TrustedContactInterface | undefined
|
||||
privateKey: string
|
||||
}): Result<M> {
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.message)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
|
||||
const decryptedResult = keyOperator.asymmetricDecrypt({
|
||||
stringToDecrypt: dto.message,
|
||||
recipientSecretKey: dto.privateKey,
|
||||
})
|
||||
|
||||
if (!decryptedResult) {
|
||||
return Result.fail('Failed to decrypt message')
|
||||
}
|
||||
|
||||
if (!decryptedResult.signatureVerified) {
|
||||
return Result.fail('Failed to verify signature')
|
||||
}
|
||||
|
||||
if (dto.sender) {
|
||||
const publicKeyTrustStatus = dto.sender.getTrustStatusForPublicKey(decryptedResult.senderPublicKey)
|
||||
if (publicKeyTrustStatus !== PublicKeyTrustStatus.Trusted) {
|
||||
return Result.fail('Sender public key is not trusted')
|
||||
}
|
||||
|
||||
const signingKeyTrustStatus = dto.sender.getTrustStatusForSigningPublicKey(decryptedResult.signaturePublicKey)
|
||||
if (signingKeyTrustStatus !== PublicKeyTrustStatus.Trusted) {
|
||||
return Result.fail('Signature public key is not trusted')
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(JSON.parse(decryptedResult.plaintext))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core'
|
||||
import { EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessagePayload } from '@standardnotes/models'
|
||||
|
||||
export class DecryptOwnMessage<M extends AsymmetricMessagePayload> implements SyncUseCaseInterface<M> {
|
||||
constructor(private operators: EncryptionOperatorsInterface) {}
|
||||
|
||||
execute(dto: { message: string; privateKey: string; recipientPublicKey: string }): Result<M> {
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.message)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
|
||||
const result = keyOperator.asymmetricDecryptOwnMessage({
|
||||
message: dto.message,
|
||||
ownPrivateKey: dto.privateKey,
|
||||
recipientPublicKey: dto.recipientPublicKey,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
const decryptedObject = result.getValue()
|
||||
|
||||
if (!decryptedObject.signatureVerified) {
|
||||
return Result.fail('Failed to verify signature')
|
||||
}
|
||||
|
||||
return Result.ok(JSON.parse(decryptedObject.plaintext))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core'
|
||||
import { EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricMessagePayload } from '@standardnotes/models'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export class EncryptMessage implements SyncUseCaseInterface<string> {
|
||||
constructor(private operators: EncryptionOperatorsInterface) {}
|
||||
|
||||
execute(dto: {
|
||||
message: AsymmetricMessagePayload
|
||||
keys: {
|
||||
encryption: PkcKeyPair
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
recipientPublicKey: string
|
||||
}): Result<string> {
|
||||
const operator = this.operators.defaultOperator()
|
||||
|
||||
const encrypted = operator.asymmetricEncrypt({
|
||||
stringToEncrypt: JSON.stringify(dto.message),
|
||||
senderKeyPair: dto.keys.encryption,
|
||||
senderSigningKeyPair: dto.keys.signing,
|
||||
recipientPublicKey: dto.recipientPublicKey,
|
||||
})
|
||||
|
||||
return Result.ok(encrypted)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core'
|
||||
import { AsymmetricallyEncryptedString, EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { AsymmetricItemAdditionalData } from '@standardnotes/encryption/src/Domain/Types/EncryptionAdditionalData'
|
||||
|
||||
export class GetMessageAdditionalData implements SyncUseCaseInterface<AsymmetricItemAdditionalData> {
|
||||
constructor(private operators: EncryptionOperatorsInterface) {}
|
||||
|
||||
execute(dto: { message: AsymmetricallyEncryptedString }): Result<AsymmetricItemAdditionalData> {
|
||||
const operator = this.operators.defaultOperator()
|
||||
|
||||
return operator.asymmetricStringGetAdditionalData({ encryptedString: dto.message })
|
||||
}
|
||||
}
|
||||
@@ -37,10 +37,10 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { extendArray } from '@standardnotes/utils'
|
||||
import { EncryptionService } from './EncryptionService'
|
||||
import { EncryptionService } from '../EncryptionService'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
export class DecryptBackupFileUseCase {
|
||||
export class DecryptBackupFile {
|
||||
constructor(private encryption: EncryptionService) {}
|
||||
|
||||
async execute(
|
||||
@@ -53,7 +53,7 @@ export class DecryptBackupFileUseCase {
|
||||
} else if (isDecryptedTransferPayload(item)) {
|
||||
return new DecryptedPayload(item)
|
||||
} else {
|
||||
throw Error('Unhandled case in decryptBackupFile')
|
||||
throw Error('Unhandled case in DecryptBackupFile')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OperatorManager } from '@standardnotes/encryption'
|
||||
import { EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { ProtocolVersionLastNonrootItemsKey, ProtocolVersionLatest, compareVersions } from '@standardnotes/common'
|
||||
import {
|
||||
CreateDecryptedItemFromPayload,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
|
||||
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager'
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
|
||||
/**
|
||||
@@ -20,11 +20,11 @@ import { ContentType } from '@standardnotes/domain-core'
|
||||
* Consumer must call sync. If the protocol version <= 003, only one items key should be created,
|
||||
* and its .itemsKey value should be equal to the root key masterKey value.
|
||||
*/
|
||||
export class CreateNewDefaultItemsKeyUseCase {
|
||||
export class CreateNewDefaultItemsKey {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private operators: EncryptionOperatorsInterface,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
|
||||
@@ -48,7 +48,7 @@ export class CreateNewDefaultItemsKeyUseCase {
|
||||
itemTemplate = CreateDecryptedItemFromPayload(payload)
|
||||
} else {
|
||||
/** Create independent items key */
|
||||
itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey()
|
||||
itemTemplate = this.operators.operatorForVersion(operatorVersion).createItemsKey()
|
||||
}
|
||||
|
||||
const itemsKeys = this.items.getDisplayableItemsKeys()
|
||||
|
||||
@@ -1,35 +1,25 @@
|
||||
import { StorageServiceInterface } from './../../../Storage/StorageServiceInterface'
|
||||
import { ItemsKeyMutator, OperatorManager, findDefaultItemsKey } from '@standardnotes/encryption'
|
||||
import { ItemsKeyMutator } from '@standardnotes/encryption'
|
||||
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
|
||||
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
import { CreateNewDefaultItemsKeyUseCase } from './CreateNewDefaultItemsKey'
|
||||
import { RemoveItemsLocallyUseCase } from '../../../UseCase/RemoveItemsLocally'
|
||||
|
||||
export class CreateNewItemsKeyWithRollbackUseCase {
|
||||
private createDefaultItemsKeyUseCase = new CreateNewDefaultItemsKeyUseCase(
|
||||
this.mutator,
|
||||
this.items,
|
||||
this.operatorManager,
|
||||
this.rootKeyManager,
|
||||
)
|
||||
|
||||
private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)
|
||||
import { CreateNewDefaultItemsKey } from './CreateNewDefaultItemsKey'
|
||||
import { RemoveItemsLocally } from '../../../UseCase/RemoveItemsLocally'
|
||||
import { FindDefaultItemsKey } from './FindDefaultItemsKey'
|
||||
|
||||
export class CreateNewItemsKeyWithRollback {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private storage: StorageServiceInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
private createDefaultItemsKey: CreateNewDefaultItemsKey,
|
||||
private removeItemsLocally: RemoveItemsLocally,
|
||||
private findDefaultItemsKey: FindDefaultItemsKey,
|
||||
) {}
|
||||
|
||||
async execute(): Promise<() => Promise<void>> {
|
||||
const currentDefaultItemsKey = findDefaultItemsKey(this.items.getDisplayableItemsKeys())
|
||||
const newDefaultItemsKey = await this.createDefaultItemsKeyUseCase.execute()
|
||||
const currentDefaultItemsKey = this.findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue()
|
||||
const newDefaultItemsKey = await this.createDefaultItemsKey.execute()
|
||||
|
||||
const rollback = async () => {
|
||||
await this.removeItemsLocallyUsecase.execute([newDefaultItemsKey])
|
||||
await this.removeItemsLocally.execute([newDefaultItemsKey])
|
||||
|
||||
if (currentDefaultItemsKey) {
|
||||
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { ItemsKeyInterface } from '@standardnotes/models'
|
||||
|
||||
export class FindDefaultItemsKey implements SyncUseCaseInterface<ItemsKeyInterface | undefined> {
|
||||
execute(itemsKeys: ItemsKeyInterface[]): Result<ItemsKeyInterface | undefined> {
|
||||
if (itemsKeys.length === 1) {
|
||||
return Result.ok(itemsKeys[0])
|
||||
}
|
||||
|
||||
const defaultKeys = itemsKeys.filter((key) => {
|
||||
return key.isDefault
|
||||
})
|
||||
|
||||
if (defaultKeys.length === 0) {
|
||||
return Result.ok(undefined)
|
||||
}
|
||||
|
||||
if (defaultKeys.length === 1) {
|
||||
return Result.ok(defaultKeys[0])
|
||||
}
|
||||
|
||||
/**
|
||||
* Prioritize one that is synced, as neverSynced keys will likely be deleted after
|
||||
* DownloadFirst sync.
|
||||
*/
|
||||
const syncedKeys = defaultKeys.filter((key) => !key.neverSynced)
|
||||
if (syncedKeys.length > 0) {
|
||||
return Result.ok(syncedKeys[0])
|
||||
}
|
||||
|
||||
return Result.ok(undefined)
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,16 @@ import {
|
||||
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'
|
||||
import { PayloadManagerInterface } from '../../../Payloads/PayloadManagerInterface'
|
||||
import { isErrorDecryptingParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import { DecryptTypeAPayloadWithKeyLookup } from './DecryptPayloadWithKeyLookup'
|
||||
import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager'
|
||||
import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface'
|
||||
|
||||
export class DecryptErroredRootPayloadsUseCase {
|
||||
export class DecryptErroredTypeAPayloads {
|
||||
constructor(
|
||||
private payloads: PayloadManagerInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private operatorManager: EncryptionOperatorsInterface,
|
||||
private keySystemKeyManager: KeySystemKeyManagerInterface,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
@@ -28,7 +29,7 @@ export class DecryptErroredRootPayloadsUseCase {
|
||||
return
|
||||
}
|
||||
|
||||
const usecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
|
||||
const usecase = new DecryptTypeAPayloadWithKeyLookup(
|
||||
this.operatorManager,
|
||||
this.keySystemKeyManager,
|
||||
this.rootKeyManager,
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
DecryptedParameters,
|
||||
ErrorDecryptingParameters,
|
||||
OperatorManager,
|
||||
decryptPayload,
|
||||
EncryptionOperatorsInterface,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
EncryptedPayloadInterface,
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
RootKeyInterface,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
export class RootKeyDecryptPayloadUseCase {
|
||||
constructor(private operatorManager: OperatorManager) {}
|
||||
export class DecryptTypeAPayload {
|
||||
constructor(private operatorManager: EncryptionOperatorsInterface) {}
|
||||
|
||||
async executeOne<C extends ItemContent = ItemContent>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
DecryptedParameters,
|
||||
ErrorDecryptingParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
OperatorManager,
|
||||
} from '@standardnotes/encryption'
|
||||
import { DecryptedParameters, ErrorDecryptingParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
EncryptedPayloadInterface,
|
||||
@@ -12,12 +7,13 @@ import {
|
||||
RootKeyInterface,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
import { RootKeyDecryptPayloadUseCase } from './DecryptPayload'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
import { DecryptTypeAPayload } from './DecryptPayload'
|
||||
import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager'
|
||||
import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface'
|
||||
|
||||
export class RootKeyDecryptPayloadWithKeyLookupUseCase {
|
||||
export class DecryptTypeAPayloadWithKeyLookup {
|
||||
constructor(
|
||||
private operatorManager: OperatorManager,
|
||||
private operators: EncryptionOperatorsInterface,
|
||||
private keySystemKeyManager: KeySystemKeyManagerInterface,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
@@ -43,7 +39,7 @@ export class RootKeyDecryptPayloadWithKeyLookupUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
const usecase = new RootKeyDecryptPayloadUseCase(this.operatorManager)
|
||||
const usecase = new DecryptTypeAPayload(this.operators)
|
||||
|
||||
return usecase.executeOne(payload, key)
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { EncryptedOutputParameters, OperatorManager, encryptPayload } from '@standardnotes/encryption'
|
||||
import { EncryptedOutputParameters, EncryptionOperatorsInterface, encryptPayload } from '@standardnotes/encryption'
|
||||
import { DecryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export class RootKeyEncryptPayloadUseCase {
|
||||
constructor(private operatorManager: OperatorManager) {}
|
||||
export class EncryptTypeAPayload {
|
||||
constructor(private operators: EncryptionOperatorsInterface) {}
|
||||
|
||||
async executeOne(
|
||||
payload: DecryptedPayloadInterface,
|
||||
key: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters> {
|
||||
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
|
||||
return encryptPayload(payload, key, this.operators, signingKeyPair)
|
||||
}
|
||||
|
||||
async executeMany(
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EncryptedOutputParameters, KeySystemKeyManagerInterface, OperatorManager } from '@standardnotes/encryption'
|
||||
import { EncryptedOutputParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
DecryptedPayloadInterface,
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
import { RootKeyEncryptPayloadUseCase } from './EncryptPayload'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
import { EncryptTypeAPayload } from './EncryptPayload'
|
||||
import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager'
|
||||
import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface'
|
||||
|
||||
export class RootKeyEncryptPayloadWithKeyLookupUseCase {
|
||||
export class EncryptTypeAPayloadWithKeyLookup {
|
||||
constructor(
|
||||
private operatorManager: OperatorManager,
|
||||
private operators: EncryptionOperatorsInterface,
|
||||
private keySystemKeyManager: KeySystemKeyManagerInterface,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
@@ -35,7 +36,7 @@ export class RootKeyEncryptPayloadWithKeyLookupUseCase {
|
||||
throw Error('Attempting root key encryption with no root key')
|
||||
}
|
||||
|
||||
const usecase = new RootKeyEncryptPayloadUseCase(this.operatorManager)
|
||||
const usecase = new EncryptTypeAPayload(this.operators)
|
||||
return usecase.executeOne(payload, key, signingKeyPair)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user