refactor: root key manager (#2344)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import {
|
||||
CreateAnyKeyParams,
|
||||
@@ -16,9 +18,6 @@ import {
|
||||
LegacyAttachedData,
|
||||
OperatorManager,
|
||||
RootKeyEncryptedAuthenticatedData,
|
||||
RootKeyServiceEvent,
|
||||
SNRootKey,
|
||||
SNRootKeyParams,
|
||||
SplitPayloadsByEncryptionType,
|
||||
V001Algorithm,
|
||||
V002Algorithm,
|
||||
@@ -47,6 +46,7 @@ import {
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
TrustedContactInterface,
|
||||
RootKeyParamsInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
@@ -78,10 +78,20 @@ import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { RootKeyEncryptionService } from './RootKeyEncryption'
|
||||
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'
|
||||
|
||||
/**
|
||||
* The encryption service is responsible for the encryption and decryption of payloads, and
|
||||
@@ -110,75 +120,73 @@ import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/
|
||||
* It also exposes public methods that allows consumers to retrieve an items key
|
||||
* for a particular payload, and also retrieve all available items keys.
|
||||
*/
|
||||
export class EncryptionService extends AbstractService<EncryptionServiceEvent> implements EncryptionProviderInterface {
|
||||
private operatorManager: OperatorManager
|
||||
export class EncryptionService
|
||||
extends AbstractService<EncryptionServiceEvent>
|
||||
implements EncryptionProviderInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private operators: OperatorManager
|
||||
private readonly itemsEncryption: ItemsEncryptionService
|
||||
private readonly rootKeyEncryption: RootKeyEncryptionService
|
||||
private rootKeyObserverDisposer: () => void
|
||||
private readonly rootKeyManager: RootKeyManager
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManagerInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
public deviceInterface: DeviceInterface,
|
||||
private storageService: StorageServiceInterface,
|
||||
private payloads: PayloadManagerInterface,
|
||||
public device: DeviceInterface,
|
||||
private storage: StorageServiceInterface,
|
||||
public readonly keys: KeySystemKeyManagerInterface,
|
||||
private identifier: ApplicationIdentifier,
|
||||
identifier: ApplicationIdentifier,
|
||||
public crypto: PureCryptoInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
this.crypto = crypto
|
||||
|
||||
this.operatorManager = new OperatorManager(crypto)
|
||||
this.operators = new OperatorManager(crypto)
|
||||
|
||||
this.itemsEncryption = new ItemsEncryptionService(
|
||||
itemManager,
|
||||
payloadManager,
|
||||
storageService,
|
||||
this.operatorManager,
|
||||
keys,
|
||||
this.rootKeyManager = new RootKeyManager(
|
||||
device,
|
||||
storage,
|
||||
items,
|
||||
mutator,
|
||||
this.operators,
|
||||
identifier,
|
||||
internalEventBus,
|
||||
)
|
||||
|
||||
this.rootKeyEncryption = new RootKeyEncryptionService(
|
||||
this.itemManager,
|
||||
this.mutator,
|
||||
this.operatorManager,
|
||||
this.deviceInterface,
|
||||
this.storageService,
|
||||
this.payloadManager,
|
||||
keys,
|
||||
this.identifier,
|
||||
this.internalEventBus,
|
||||
)
|
||||
internalEventBus.addEventHandler(this, RootKeyManagerEvent.RootKeyManagerKeyStatusChanged)
|
||||
|
||||
this.rootKeyObserverDisposer = this.rootKeyEncryption.addEventObserver((event) => {
|
||||
this.itemsEncryption.userVersion = this.getUserVersion()
|
||||
if (event === RootKeyServiceEvent.RootKeyStatusChanged) {
|
||||
void this.notifyEvent(EncryptionServiceEvent.RootKeyStatusChanged)
|
||||
}
|
||||
})
|
||||
this.itemsEncryption = new ItemsEncryptionService(items, payloads, storage, this.operators, keys, internalEventBus)
|
||||
|
||||
UuidGenerator.SetGenerator(this.crypto.generateUUID)
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.itemManager as unknown) = undefined
|
||||
;(this.payloadManager as unknown) = undefined
|
||||
;(this.deviceInterface as unknown) = undefined
|
||||
;(this.storageService as unknown) = undefined
|
||||
;(this.crypto as unknown) = undefined
|
||||
;(this.operatorManager as unknown) = undefined
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === RootKeyManagerEvent.RootKeyManagerKeyStatusChanged) {
|
||||
this.itemsEncryption.userVersion = this.getUserVersion()
|
||||
void this.notifyEvent(EncryptionServiceEvent.RootKeyStatusChanged)
|
||||
}
|
||||
}
|
||||
|
||||
this.rootKeyObserverDisposer()
|
||||
;(this.rootKeyObserverDisposer as unknown) = undefined
|
||||
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.rootKeyEncryption.deinit()
|
||||
;(this.rootKeyEncryption as unknown) = undefined
|
||||
this.rootKeyManager.deinit()
|
||||
;(this.rootKeyManager as unknown) = undefined
|
||||
|
||||
super.deinit()
|
||||
}
|
||||
@@ -210,17 +218,17 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
await this.rootKeyEncryption.initialize()
|
||||
await this.rootKeyManager.initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encryption protocol display name for active account/wrapper
|
||||
*/
|
||||
public async getEncryptionDisplayName(): Promise<string> {
|
||||
const version = await this.rootKeyEncryption.getEncryptionSourceVersion()
|
||||
const version = await this.rootKeyManager.getEncryptionSourceVersion()
|
||||
|
||||
if (version) {
|
||||
return this.operatorManager.operatorForVersion(version).getEncryptionDisplayName()
|
||||
return this.operators.operatorForVersion(version).getEncryptionDisplayName()
|
||||
}
|
||||
|
||||
throw Error('Attempting to access encryption display name wtihout source')
|
||||
@@ -231,15 +239,15 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public hasAccount() {
|
||||
return this.rootKeyEncryption.hasAccount()
|
||||
return this.rootKeyManager.hasAccount()
|
||||
}
|
||||
|
||||
public hasRootKeyEncryptionSource(): boolean {
|
||||
return this.rootKeyEncryption.hasRootKeyEncryptionSource()
|
||||
return this.rootKeyManager.hasRootKeyEncryptionSource()
|
||||
}
|
||||
|
||||
public getUserVersion(): ProtocolVersion | undefined {
|
||||
return this.rootKeyEncryption.getUserVersion()
|
||||
return this.rootKeyManager.getUserVersion()
|
||||
}
|
||||
|
||||
public async upgradeAvailable() {
|
||||
@@ -257,19 +265,34 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
|
||||
await this.rootKeyEncryption.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
await this.rootKeyManager.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
}
|
||||
|
||||
public reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
|
||||
return this.rootKeyEncryption.reencryptKeySystemItemsKeysForVault(keySystemIdentifier)
|
||||
/**
|
||||
* 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>> {
|
||||
return this.rootKeyEncryption.createNewItemsKeyWithRollback()
|
||||
const usecase = new CreateNewItemsKeyWithRollbackUseCase(
|
||||
this.mutator,
|
||||
this.items,
|
||||
this.operators,
|
||||
this.rootKeyManager,
|
||||
)
|
||||
return usecase.execute()
|
||||
}
|
||||
|
||||
public async decryptErroredPayloads(): Promise<void> {
|
||||
await this.rootKeyEncryption.decryptErroredRootPayloads()
|
||||
const usecase = new DecryptErroredRootPayloadsUseCase(this.payloads, this.operators, this.keys, this.rootKeyManager)
|
||||
await usecase.execute()
|
||||
|
||||
await this.itemsEncryption.decryptErroredItemPayloads()
|
||||
}
|
||||
|
||||
@@ -302,10 +325,18 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
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 this.rootKeyEncryption.encryptPayloads(
|
||||
const rootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
|
||||
usesRootKey.items,
|
||||
usesRootKey.key,
|
||||
signingKeyPair,
|
||||
@@ -314,7 +345,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
|
||||
const rootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
@@ -322,7 +353,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloads(
|
||||
const keySystemRootKeyEncrypted = await rootKeyEncryptUsecase.executeMany(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
signingKeyPair,
|
||||
@@ -331,7 +362,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
|
||||
const keySystemRootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
@@ -391,26 +422,32 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
usesKeySystemRootKeyWithKeyLookup,
|
||||
} = split
|
||||
|
||||
const rootKeyDecryptUseCase = new RootKeyDecryptPayloadUseCase(this.operators)
|
||||
|
||||
const rootKeyDecryptWithKeyLookupUsecase = new RootKeyDecryptPayloadWithKeyLookupUseCase(
|
||||
this.operators,
|
||||
this.keys,
|
||||
this.rootKeyManager,
|
||||
)
|
||||
|
||||
if (usesRootKey) {
|
||||
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(usesRootKey.items, usesRootKey.key)
|
||||
const rootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(usesRootKey.items, usesRootKey.key)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
)
|
||||
const rootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(usesRootKeyWithKeyLookup.items)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(
|
||||
const keySystemRootKeyDecrypted = await rootKeyDecryptUseCase.executeMany<C>(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
}
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
|
||||
const keySystemRootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany<C>(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
@@ -492,14 +529,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* Returns true if the user's account protocol version is not equal to the latest version.
|
||||
*/
|
||||
public async passcodeUpgradeAvailable(): Promise<boolean> {
|
||||
return this.rootKeyEncryption.passcodeUpgradeAvailable()
|
||||
return this.rootKeyManager.passcodeUpgradeAvailable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current environment is capable of supporting
|
||||
* key derivation.
|
||||
*/
|
||||
public platformSupportsKeyDerivation(keyParams: SNRootKeyParams) {
|
||||
public platformSupportsKeyDerivation(keyParams: RootKeyParamsInterface) {
|
||||
/**
|
||||
* If the version is 003 or lower, key derivation is supported unless the browser is
|
||||
* IE or Edge (or generally, where WebCrypto is not available) or React Native environment is detected.
|
||||
@@ -548,8 +585,11 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* Computes a root key given a password and key params.
|
||||
* Delegates computation to respective protocol operator.
|
||||
*/
|
||||
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
|
||||
return this.rootKeyEncryption.computeRootKey(password, keyParams)
|
||||
public async computeRootKey<K extends RootKeyInterface>(
|
||||
password: string,
|
||||
keyParams: RootKeyParamsInterface,
|
||||
): Promise<K> {
|
||||
return this.rootKeyManager.computeRootKey(password, keyParams)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,7 +601,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
origination: KeyParamsOrigination,
|
||||
version?: ProtocolVersion,
|
||||
): Promise<K> {
|
||||
return this.rootKeyEncryption.createRootKey(identifier, password, origination, version)
|
||||
return this.rootKeyManager.createRootKey(identifier, password, origination, version)
|
||||
}
|
||||
|
||||
createRandomizedKeySystemRootKey(dto: {
|
||||
@@ -569,7 +609,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
systemName: string
|
||||
systemDescription?: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().createRandomizedKeySystemRootKey(dto)
|
||||
return this.operators.defaultOperator().createRandomizedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
createUserInputtedKeySystemRootKey(dto: {
|
||||
@@ -578,14 +618,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
systemDescription?: string
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().createUserInputtedKeySystemRootKey(dto)
|
||||
return this.operators.defaultOperator().createUserInputtedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
deriveUserInputtedKeySystemRootKey(dto: {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().deriveUserInputtedKeySystemRootKey(dto)
|
||||
return this.operators.defaultOperator().deriveUserInputtedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
createKeySystemItemsKey(
|
||||
@@ -594,7 +634,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
sharedVaultUuid: string | undefined,
|
||||
rootKeyToken: string,
|
||||
): KeySystemItemsKeyInterface {
|
||||
return this.operatorManager
|
||||
return this.operators
|
||||
.defaultOperator()
|
||||
.createKeySystemItemsKey(uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken)
|
||||
}
|
||||
@@ -605,7 +645,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
senderSigningKeyPair: PkcKeyPair
|
||||
recipientPublicKey: string
|
||||
}): AsymmetricallyEncryptedString {
|
||||
const operator = this.operatorManager.defaultOperator()
|
||||
const operator = this.operators.defaultOperator()
|
||||
const encrypted = operator.asymmetricEncrypt({
|
||||
stringToEncrypt: JSON.stringify(dto.message),
|
||||
senderKeyPair: dto.senderKeyPair,
|
||||
@@ -620,9 +660,9 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
trustedSender: TrustedContactInterface | undefined
|
||||
privateKey: string
|
||||
}): M | undefined {
|
||||
const defaultOperator = this.operatorManager.defaultOperator()
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.encryptedString)
|
||||
const keyOperator = this.operatorManager.operatorForVersion(version)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
const decryptedResult = keyOperator.asymmetricDecrypt({
|
||||
stringToDecrypt: dto.encryptedString,
|
||||
recipientSecretKey: dto.privateKey,
|
||||
@@ -652,17 +692,17 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
asymmetricSignatureVerifyDetached(
|
||||
encryptedString: AsymmetricallyEncryptedString,
|
||||
): AsymmetricSignatureVerificationDetachedResult {
|
||||
const defaultOperator = this.operatorManager.defaultOperator()
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(encryptedString)
|
||||
const keyOperator = this.operatorManager.operatorForVersion(version)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
return keyOperator.asymmetricSignatureVerifyDetached(encryptedString)
|
||||
}
|
||||
|
||||
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
|
||||
const defaultOperator = this.operatorManager.defaultOperator()
|
||||
const defaultOperator = this.operators.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(string)
|
||||
|
||||
const keyOperator = this.operatorManager.operatorForVersion(version)
|
||||
const keyOperator = this.operators.operatorForVersion(version)
|
||||
return keyOperator.getSenderPublicKeySetFromAsymmetricallyEncryptedString(string)
|
||||
}
|
||||
|
||||
@@ -684,7 +724,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async createEncryptedBackupFile(): Promise<BackupFile> {
|
||||
const payloads = this.itemManager.items.map((item) => item.payload)
|
||||
const payloads = this.items.items.map((item) => item.payload)
|
||||
|
||||
const split = SplitPayloadsByEncryptionType(payloads)
|
||||
|
||||
@@ -705,7 +745,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public createDecryptedBackupFile(): BackupFile {
|
||||
const payloads = this.payloadManager.nonDeletedItems.filter((item) => item.content_type !== ContentType.ItemsKey)
|
||||
const payloads = this.payloads.nonDeletedItems.filter((item) => item.content_type !== ContentType.ItemsKey)
|
||||
|
||||
const data: BackupFile = {
|
||||
version: ProtocolVersionLatest,
|
||||
@@ -725,22 +765,22 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public hasPasscode(): boolean {
|
||||
return this.rootKeyEncryption.hasPasscode()
|
||||
return this.rootKeyManager.hasPasscode()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns True if the root key has not yet been unwrapped (passcode locked).
|
||||
*/
|
||||
public async isPasscodeLocked() {
|
||||
return (await this.rootKeyEncryption.hasRootKeyWrapper()) && this.rootKeyEncryption.getRootKey() == undefined
|
||||
return (await this.rootKeyManager.hasRootKeyWrapper()) && this.rootKeyManager.getRootKey() == undefined
|
||||
}
|
||||
|
||||
public getRootKeyParams() {
|
||||
return this.rootKeyEncryption.getRootKeyParams()
|
||||
return this.rootKeyManager.getRootKeyParams()
|
||||
}
|
||||
|
||||
public getAccountKeyParams() {
|
||||
return this.rootKeyEncryption.memoizedRootKeyParams
|
||||
return this.rootKeyManager.getMemoizedRootKeyParams()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,7 +788,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* Wrapping key params are read from disk.
|
||||
*/
|
||||
public async computeWrappingKey(passcode: string) {
|
||||
const keyParams = this.rootKeyEncryption.getSureRootKeyWrapperKeyParams()
|
||||
const keyParams = this.rootKeyManager.getSureRootKeyWrapperKeyParams()
|
||||
const key = await this.computeRootKey(passcode, keyParams)
|
||||
return key
|
||||
}
|
||||
@@ -760,7 +800,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* After unwrapping, the root key is automatically loaded.
|
||||
*/
|
||||
public async unwrapRootKey(wrappingKey: RootKeyInterface) {
|
||||
return this.rootKeyEncryption.unwrapRootKey(wrappingKey)
|
||||
return this.rootKeyManager.unwrapRootKey(wrappingKey)
|
||||
}
|
||||
/**
|
||||
* Encrypts rootKey and saves it in storage instead of keychain, and then
|
||||
@@ -768,42 +808,42 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* 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: SNRootKey) {
|
||||
return this.rootKeyEncryption.setNewRootKeyWrapper(wrappingKey)
|
||||
public async setNewRootKeyWrapper(wrappingKey: RootKeyInterface) {
|
||||
return this.rootKeyManager.setNewRootKeyWrapper(wrappingKey)
|
||||
}
|
||||
|
||||
public async removePasscode(): Promise<void> {
|
||||
await this.rootKeyEncryption.removeRootKeyWrapper()
|
||||
await this.rootKeyManager.removeRootKeyWrapper()
|
||||
}
|
||||
|
||||
public async setRootKey(key: RootKeyInterface, wrappingKey?: SNRootKey) {
|
||||
await this.rootKeyEncryption.setRootKey(key, wrappingKey)
|
||||
public async setRootKey(key: RootKeyInterface, wrappingKey?: RootKeyInterface) {
|
||||
await this.rootKeyManager.setRootKey(key, wrappingKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the in-memory root key value.
|
||||
*/
|
||||
public getRootKey(): RootKeyInterface | undefined {
|
||||
return this.rootKeyEncryption.getRootKey()
|
||||
return this.rootKeyManager.getRootKey()
|
||||
}
|
||||
|
||||
public getSureRootKey(): RootKeyInterface {
|
||||
return this.rootKeyEncryption.getRootKey() as RootKeyInterface
|
||||
return this.rootKeyManager.getRootKey() as RootKeyInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes root key and wrapper from keychain. Used when signing out of application.
|
||||
*/
|
||||
public async deleteWorkspaceSpecificKeyStateFromDevice() {
|
||||
await this.rootKeyEncryption.deleteWorkspaceSpecificKeyStateFromDevice()
|
||||
await this.rootKeyManager.deleteWorkspaceSpecificKeyStateFromDevice()
|
||||
}
|
||||
|
||||
public async validateAccountPassword(password: string) {
|
||||
return this.rootKeyEncryption.validateAccountPassword(password)
|
||||
public async validateAccountPassword(password: string): Promise<ValidateAccountPasswordResult> {
|
||||
return this.rootKeyManager.validateAccountPassword(password)
|
||||
}
|
||||
|
||||
public async validatePasscode(passcode: string) {
|
||||
return this.rootKeyEncryption.validatePasscode(passcode)
|
||||
public async validatePasscode(passcode: string): Promise<ValidatePasscodeResult> {
|
||||
return this.rootKeyManager.validatePasscode(passcode)
|
||||
}
|
||||
|
||||
public getEmbeddedPayloadAuthenticatedData<D extends ItemAuthenticatedData>(
|
||||
@@ -814,7 +854,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return undefined
|
||||
}
|
||||
|
||||
const operator = this.operatorManager.operatorForVersion(version)
|
||||
const operator = this.operators.operatorForVersion(version)
|
||||
|
||||
const authenticatedData = operator.getPayloadAuthenticatedDataForExternalUse(
|
||||
encryptedInputParametersFromPayload(payload),
|
||||
@@ -824,7 +864,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
/** Returns the key params attached to this key's encrypted payload */
|
||||
public getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): SNRootKeyParams | undefined {
|
||||
public getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): RootKeyParamsInterface | undefined {
|
||||
const authenticatedData = this.getEmbeddedPayloadAuthenticatedData(key)
|
||||
if (!authenticatedData) {
|
||||
return undefined
|
||||
@@ -847,7 +887,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return false
|
||||
}
|
||||
|
||||
const rootKey = this.rootKeyEncryption.getRootKey()
|
||||
const rootKey = this.rootKeyManager.getRootKey()
|
||||
if (!rootKey) {
|
||||
return false
|
||||
}
|
||||
@@ -872,7 +912,8 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async createNewDefaultItemsKey(): Promise<ItemsKeyInterface> {
|
||||
return this.rootKeyEncryption.createNewDefaultItemsKey()
|
||||
const usecase = new CreateNewDefaultItemsKeyUseCase(this.mutator, this.items, this.operators, this.rootKeyManager)
|
||||
return usecase.execute()
|
||||
}
|
||||
|
||||
public getPasswordCreatedDate(): Date | undefined {
|
||||
@@ -943,7 +984,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
if (this.itemsEncryption.getItemsKeys().length === 0) {
|
||||
await this.rootKeyEncryption.createNewDefaultItemsKey()
|
||||
await this.createNewDefaultItemsKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -951,7 +992,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
const userVersion = this.getUserVersion()
|
||||
const accountVersionedKey = this.itemsEncryption.getItemsKeys().find((key) => key.keyVersion === userVersion)
|
||||
if (isNullOrUndefined(accountVersionedKey)) {
|
||||
await this.rootKeyEncryption.createNewDefaultItemsKey()
|
||||
await this.createNewDefaultItemsKey()
|
||||
}
|
||||
|
||||
this.syncUnsyncedItemsKeys()
|
||||
@@ -961,8 +1002,8 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
/** Always create a new items key after full sync, if no items key is found */
|
||||
const currentItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys())
|
||||
if (!currentItemsKey) {
|
||||
await this.rootKeyEncryption.createNewDefaultItemsKey()
|
||||
if (this.rootKeyEncryption.keyMode === KeyMode.WrapperOnly) {
|
||||
await this.createNewDefaultItemsKey()
|
||||
if (this.rootKeyManager.getKeyMode() === KeyMode.WrapperOnly) {
|
||||
return this.itemsEncryption.repersistAllItems()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum RootKeyManagerEvent {
|
||||
RootKeyManagerKeyStatusChanged = 'RootKeyManagerKeyStatusChanged',
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { RootKeyInterface } from '@standardnotes/models'
|
||||
|
||||
export type ValidateAccountPasswordResult =
|
||||
| {
|
||||
valid: true
|
||||
artifacts: {
|
||||
rootKey: RootKeyInterface
|
||||
}
|
||||
}
|
||||
| {
|
||||
valid: boolean
|
||||
artifacts?: undefined
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { RootKeyInterface } from '@standardnotes/models'
|
||||
|
||||
export type ValidatePasscodeResult =
|
||||
| {
|
||||
valid: true
|
||||
artifacts: {
|
||||
wrappingKey: RootKeyInterface
|
||||
}
|
||||
}
|
||||
| {
|
||||
valid: boolean
|
||||
artifacts?: undefined
|
||||
}
|
||||
@@ -1,716 +0,0 @@
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import {
|
||||
ApplicationIdentifier,
|
||||
ProtocolVersionLatest,
|
||||
ProtocolVersion,
|
||||
AnyKeyParamsContent,
|
||||
KeyParamsOrigination,
|
||||
compareVersions,
|
||||
ProtocolVersionLastNonrootItemsKey,
|
||||
ContentType,
|
||||
} from '@standardnotes/common'
|
||||
import {
|
||||
RootKeyServiceEvent,
|
||||
KeyMode,
|
||||
SNRootKeyParams,
|
||||
OperatorManager,
|
||||
CreateNewRootKey,
|
||||
CreateAnyKeyParams,
|
||||
SNRootKey,
|
||||
isErrorDecryptingParameters,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
ItemsKeyMutator,
|
||||
encryptPayload,
|
||||
decryptPayload,
|
||||
EncryptedOutputParameters,
|
||||
DecryptedParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
ContentTypesUsingRootKeyEncryption,
|
||||
ContentTypeUsesRootKeyEncryption,
|
||||
CreateDecryptedItemFromPayload,
|
||||
DecryptedPayload,
|
||||
DecryptedPayloadInterface,
|
||||
DecryptedTransferPayload,
|
||||
EncryptedPayload,
|
||||
EncryptedPayloadInterface,
|
||||
EncryptedTransferPayload,
|
||||
FillItemContentSpecialized,
|
||||
KeySystemRootKeyInterface,
|
||||
ItemContent,
|
||||
ItemsKeyContent,
|
||||
ItemsKeyContentSpecialized,
|
||||
ItemsKeyInterface,
|
||||
NamespacedRootKeyInKeychain,
|
||||
PayloadEmitSource,
|
||||
PayloadTimestampDefaults,
|
||||
RootKeyContent,
|
||||
RootKeyInterface,
|
||||
SureFindPayload,
|
||||
KeySystemIdentifier,
|
||||
} from '@standardnotes/models'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { StorageKey } from '../Storage/StorageKeys'
|
||||
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
|
||||
import { StorageValueModes } from '../Storage/StorageTypes'
|
||||
import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
|
||||
export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEvent> {
|
||||
private rootKey?: RootKeyInterface
|
||||
public keyMode = KeyMode.RootKeyNone
|
||||
public memoizedRootKeyParams?: SNRootKeyParams
|
||||
|
||||
constructor(
|
||||
private items: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
public deviceInterface: DeviceInterface,
|
||||
private storageService: StorageServiceInterface,
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
private keys: KeySystemKeyManagerInterface,
|
||||
private identifier: ApplicationIdentifier,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.operatorManager as unknown) = undefined
|
||||
;(this.deviceInterface as unknown) = undefined
|
||||
;(this.storageService as unknown) = undefined
|
||||
;(this.payloadManager as unknown) = undefined
|
||||
;(this.keys as unknown) = undefined
|
||||
|
||||
this.rootKey = undefined
|
||||
this.memoizedRootKeyParams = undefined
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
const wrappedRootKey = this.getWrappedRootKey()
|
||||
const accountKeyParams = await 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()
|
||||
}
|
||||
}
|
||||
|
||||
private async handleKeyStatusChange() {
|
||||
await this.recomputeAccountKeyParams()
|
||||
void this.notifyEvent(RootKeyServiceEvent.RootKeyStatusChanged)
|
||||
}
|
||||
|
||||
public async passcodeUpgradeAvailable() {
|
||||
const passcodeParams = await this.getRootKeyWrapperKeyParams()
|
||||
if (!passcodeParams) {
|
||||
return false
|
||||
}
|
||||
return passcodeParams.version !== ProtocolVersionLatest
|
||||
}
|
||||
|
||||
public async hasRootKeyWrapper() {
|
||||
const wrapper = await this.getRootKeyWrapperKeyParams()
|
||||
return wrapper != undefined
|
||||
}
|
||||
|
||||
public hasAccount() {
|
||||
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 hasRootKeyEncryptionSource(): boolean {
|
||||
return this.hasAccount() || this.hasPasscode()
|
||||
}
|
||||
|
||||
public hasPasscode() {
|
||||
return this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper
|
||||
}
|
||||
|
||||
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 getUserVersion(): ProtocolVersion | undefined {
|
||||
const keyParams = this.memoizedRootKeyParams
|
||||
return keyParams?.version
|
||||
}
|
||||
|
||||
private getSureUserVersion(): ProtocolVersion {
|
||||
const keyParams = this.memoizedRootKeyParams as SNRootKeyParams
|
||||
return keyParams.version
|
||||
}
|
||||
|
||||
private async getRootKeyFromKeychain() {
|
||||
const rawKey = (await this.deviceInterface.getNamespacedKeychainValue(this.identifier)) as
|
||||
| NamespacedRootKeyInKeychain
|
||||
| undefined
|
||||
|
||||
if (rawKey == undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const keyParams = this.getSureRootKeyParams()
|
||||
|
||||
return CreateNewRootKey({
|
||||
...rawKey,
|
||||
keyParams: keyParams.getPortableValue(),
|
||||
})
|
||||
}
|
||||
|
||||
private 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.deviceInterface.setNamespacedKeychainValue(rawKey, this.identifier)
|
||||
})
|
||||
}
|
||||
|
||||
public getRootKeyWrapperKeyParams(): SNRootKeyParams | undefined {
|
||||
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
|
||||
}
|
||||
|
||||
public getSureRootKeyWrapperKeyParams() {
|
||||
return this.getRootKeyWrapperKeyParams() as SNRootKeyParams
|
||||
}
|
||||
|
||||
public getRootKeyParams(): SNRootKeyParams | 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(): SNRootKeyParams {
|
||||
return this.getRootKeyParams() as SNRootKeyParams
|
||||
}
|
||||
|
||||
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
|
||||
const version = keyParams.version
|
||||
const operator = this.operatorManager.operatorForVersion(version)
|
||||
return operator.computeRootKey(password, keyParams)
|
||||
}
|
||||
|
||||
public async createRootKey<K extends RootKeyInterface>(
|
||||
identifier: string,
|
||||
password: string,
|
||||
origination: KeyParamsOrigination,
|
||||
version?: ProtocolVersion,
|
||||
): Promise<K> {
|
||||
const operator = version ? this.operatorManager.operatorForVersion(version) : this.operatorManager.defaultOperator()
|
||||
return operator.createRootKey(identifier, password, origination)
|
||||
}
|
||||
|
||||
private getSureMemoizedRootKeyParams(): SNRootKeyParams {
|
||||
return this.memoizedRootKeyParams as SNRootKeyParams
|
||||
}
|
||||
|
||||
public async validateAccountPassword(password: string) {
|
||||
const key = await this.computeRootKey(password, this.getSureMemoizedRootKeyParams())
|
||||
const valid = this.getSureRootKey().compare(key)
|
||||
if (valid) {
|
||||
return { valid, artifacts: { rootKey: key } }
|
||||
} else {
|
||||
return { valid: false }
|
||||
}
|
||||
}
|
||||
|
||||
public async validatePasscode(passcode: string) {
|
||||
const keyParams = await 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 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We know a wrappingKey is correct if it correctly decrypts
|
||||
* wrapped root key.
|
||||
*/
|
||||
public async validateWrappingKey(wrappingKey: SNRootKey) {
|
||||
const wrappedRootKey = this.getWrappedRootKey()
|
||||
|
||||
/** If wrapper only, storage is encrypted directly with wrappingKey */
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
return this.storageService.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 decrypted = await this.decryptPayload(wrappedKeyPayload, wrappingKey)
|
||||
return !isErrorDecryptingParameters(decrypted)
|
||||
} else {
|
||||
throw 'Unhandled case in validateWrappingKey'
|
||||
}
|
||||
}
|
||||
|
||||
private recomputeAccountKeyParams(): SNRootKeyParams | undefined {
|
||||
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return
|
||||
}
|
||||
|
||||
this.memoizedRootKeyParams = CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
|
||||
return this.memoizedRootKeyParams
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the current in-memory root key value using the wrappingKey,
|
||||
* then persists the wrapped value to disk.
|
||||
*/
|
||||
private async wrapAndPersistRootKey(wrappingKey: SNRootKey) {
|
||||
const rootKey = this.getSureRootKey()
|
||||
|
||||
const value: DecryptedTransferPayload = {
|
||||
...rootKey.payload.ejected(),
|
||||
content: FillItemContentSpecialized(rootKey.persistableValueWhenWrapping()),
|
||||
}
|
||||
|
||||
const payload = new DecryptedPayload(value)
|
||||
|
||||
const wrappedKey = await this.encryptPayload(payload, wrappingKey)
|
||||
const wrappedKeyPayload = new EncryptedPayload({
|
||||
...payload.ejected(),
|
||||
...wrappedKey,
|
||||
errorDecrypting: false,
|
||||
waitingForKey: false,
|
||||
})
|
||||
|
||||
this.storageService.setValue(StorageKey.WrappedRootKey, wrappedKeyPayload.ejected(), StorageValueModes.Nonwrapped)
|
||||
}
|
||||
|
||||
public async unwrapRootKey(wrappingKey: RootKeyInterface) {
|
||||
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 decrypted = await this.decryptPayload<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: SNRootKey) {
|
||||
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.deviceInterface.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.storageService.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.storageService.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
|
||||
await this.storageService.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (this.keyMode === KeyMode.RootKeyOnly) {
|
||||
await this.saveRootKeyToKeychain()
|
||||
}
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
|
||||
public async setRootKey(key: SNRootKey, wrappingKey?: SNRootKey) {
|
||||
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.storageService.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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes root key and wrapper from keychain. Used when signing out of application.
|
||||
*/
|
||||
public async deleteWorkspaceSpecificKeyStateFromDevice() {
|
||||
await this.deviceInterface.clearNamespacedKeychainValue(this.identifier)
|
||||
await this.storageService.removeValue(StorageKey.WrappedRootKey, StorageValueModes.Nonwrapped)
|
||||
await this.storageService.removeValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
await this.storageService.removeValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
this.keyMode = KeyMode.RootKeyNone
|
||||
this.setRootKeyInstance(undefined)
|
||||
|
||||
await this.handleKeyStatusChange()
|
||||
}
|
||||
|
||||
private getWrappedRootKey() {
|
||||
return this.storageService.getValue<EncryptedTransferPayload>(
|
||||
StorageKey.WrappedRootKey,
|
||||
StorageValueModes.Nonwrapped,
|
||||
)
|
||||
}
|
||||
|
||||
public setRootKeyInstance(rootKey: RootKeyInterface | undefined): void {
|
||||
this.rootKey = rootKey
|
||||
}
|
||||
|
||||
public getRootKey(): RootKeyInterface | undefined {
|
||||
return this.rootKey
|
||||
}
|
||||
|
||||
private getSureRootKey(): RootKeyInterface {
|
||||
return this.rootKey as RootKeyInterface
|
||||
}
|
||||
|
||||
private getItemsKeys() {
|
||||
return this.items.getDisplayableItemsKeys()
|
||||
}
|
||||
|
||||
private async encryptPayloadWithKeyLookup(
|
||||
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.keys.getPrimaryKeySystemRootKey(payload.key_system_identifier)
|
||||
} else {
|
||||
key = this.getRootKey()
|
||||
}
|
||||
|
||||
if (key == undefined) {
|
||||
throw Error('Attempting root key encryption with no root key')
|
||||
}
|
||||
|
||||
return 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 encryptPayload(
|
||||
payload: DecryptedPayloadInterface,
|
||||
key: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters> {
|
||||
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayloads(
|
||||
payloads: DecryptedPayloadInterface[],
|
||||
key: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
) {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key, signingKeyPair)))
|
||||
}
|
||||
|
||||
public async decryptPayloadWithKeyLookup<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.keys.getPrimaryKeySystemRootKey(payload.key_system_identifier)
|
||||
} else {
|
||||
key = this.getRootKey()
|
||||
}
|
||||
|
||||
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: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
|
||||
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: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
|
||||
return Promise.all(payloads.map((payload) => this.decryptPayload<C>(payload, key)))
|
||||
}
|
||||
|
||||
public async decryptErroredRootPayloads(): Promise<void> {
|
||||
const erroredRootPayloads = this.payloadManager.invalidPayloads.filter(
|
||||
(i) =>
|
||||
ContentTypeUsesRootKeyEncryption(i.content_type) || ContentTypeUsesKeySystemRootKeyEncryption(i.content_type),
|
||||
)
|
||||
if (erroredRootPayloads.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const resultParams = await this.decryptPayloadsWithKeyLookup(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.payloadManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new random items key to use for item encryption, and adds it to model management.
|
||||
* 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.
|
||||
*/
|
||||
public async createNewDefaultItemsKey(): Promise<ItemsKeyInterface> {
|
||||
const rootKey = this.getSureRootKey()
|
||||
const operatorVersion = rootKey ? rootKey.keyVersion : ProtocolVersionLatest
|
||||
let itemTemplate: ItemsKeyInterface
|
||||
|
||||
if (compareVersions(operatorVersion, ProtocolVersionLastNonrootItemsKey) <= 0) {
|
||||
/** Create root key based items key */
|
||||
const payload = new DecryptedPayload<ItemsKeyContent>({
|
||||
uuid: UuidGenerator.GenerateUuid(),
|
||||
content_type: ContentType.ItemsKey,
|
||||
content: FillItemContentSpecialized<ItemsKeyContentSpecialized, ItemsKeyContent>({
|
||||
itemsKey: rootKey.masterKey,
|
||||
dataAuthenticationKey: rootKey.dataAuthenticationKey,
|
||||
version: operatorVersion,
|
||||
}),
|
||||
...PayloadTimestampDefaults(),
|
||||
})
|
||||
itemTemplate = CreateDecryptedItemFromPayload(payload)
|
||||
} else {
|
||||
/** Create independent items key */
|
||||
itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey()
|
||||
}
|
||||
|
||||
const itemsKeys = this.getItemsKeys()
|
||||
const defaultKeys = itemsKeys.filter((key) => {
|
||||
return key.isDefault
|
||||
})
|
||||
|
||||
for (const key of defaultKeys) {
|
||||
await this.mutator.changeItemsKey(key, (mutator) => {
|
||||
mutator.isDefault = false
|
||||
})
|
||||
}
|
||||
|
||||
const itemsKey = await this.mutator.insertItem<ItemsKeyInterface>(itemTemplate)
|
||||
await this.mutator.changeItemsKey(itemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
public async createNewItemsKeyWithRollback(): Promise<() => Promise<void>> {
|
||||
const currentDefaultItemsKey = findDefaultItemsKey(this.getItemsKeys())
|
||||
const newDefaultItemsKey = await this.createNewDefaultItemsKey()
|
||||
|
||||
const rollback = async () => {
|
||||
await this.mutator.setItemToBeDeleted(newDefaultItemsKey)
|
||||
|
||||
if (currentDefaultItemsKey) {
|
||||
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return rollback
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { OperatorManager } from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentType,
|
||||
ProtocolVersionLastNonrootItemsKey,
|
||||
ProtocolVersionLatest,
|
||||
compareVersions,
|
||||
} from '@standardnotes/common'
|
||||
import {
|
||||
CreateDecryptedItemFromPayload,
|
||||
DecryptedPayload,
|
||||
FillItemContentSpecialized,
|
||||
ItemsKeyContent,
|
||||
ItemsKeyContentSpecialized,
|
||||
ItemsKeyInterface,
|
||||
PayloadTimestampDefaults,
|
||||
} from '@standardnotes/models'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
|
||||
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
|
||||
/**
|
||||
* Creates a new random items key to use for item encryption, and adds it to model management.
|
||||
* 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 {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
|
||||
async execute(): Promise<ItemsKeyInterface> {
|
||||
const rootKey = this.rootKeyManager.getSureRootKey()
|
||||
const operatorVersion = rootKey ? rootKey.keyVersion : ProtocolVersionLatest
|
||||
let itemTemplate: ItemsKeyInterface
|
||||
|
||||
if (compareVersions(operatorVersion, ProtocolVersionLastNonrootItemsKey) <= 0) {
|
||||
/** Create root key based items key */
|
||||
const payload = new DecryptedPayload<ItemsKeyContent>({
|
||||
uuid: UuidGenerator.GenerateUuid(),
|
||||
content_type: ContentType.ItemsKey,
|
||||
content: FillItemContentSpecialized<ItemsKeyContentSpecialized, ItemsKeyContent>({
|
||||
itemsKey: rootKey.masterKey,
|
||||
dataAuthenticationKey: rootKey.dataAuthenticationKey,
|
||||
version: operatorVersion,
|
||||
}),
|
||||
...PayloadTimestampDefaults(),
|
||||
})
|
||||
itemTemplate = CreateDecryptedItemFromPayload(payload)
|
||||
} else {
|
||||
/** Create independent items key */
|
||||
itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey()
|
||||
}
|
||||
|
||||
const itemsKeys = this.items.getDisplayableItemsKeys()
|
||||
const defaultKeys = itemsKeys.filter((key) => {
|
||||
return key.isDefault
|
||||
})
|
||||
|
||||
for (const key of defaultKeys) {
|
||||
await this.mutator.changeItemsKey(key, (mutator) => {
|
||||
mutator.isDefault = false
|
||||
})
|
||||
}
|
||||
|
||||
const itemsKey = await this.mutator.insertItem<ItemsKeyInterface>(itemTemplate)
|
||||
await this.mutator.changeItemsKey(itemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
|
||||
return itemsKey
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ItemsKeyMutator, OperatorManager, findDefaultItemsKey } from '@standardnotes/encryption'
|
||||
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
|
||||
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
|
||||
import { RootKeyManager } from '../../RootKey/RootKeyManager'
|
||||
import { CreateNewDefaultItemsKeyUseCase } from './CreateNewDefaultItemsKey'
|
||||
|
||||
export class CreateNewItemsKeyWithRollbackUseCase {
|
||||
private createDefaultItemsKeyUseCase = new CreateNewDefaultItemsKeyUseCase(
|
||||
this.mutator,
|
||||
this.items,
|
||||
this.operatorManager,
|
||||
this.rootKeyManager,
|
||||
)
|
||||
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private rootKeyManager: RootKeyManager,
|
||||
) {}
|
||||
|
||||
async execute(): Promise<() => Promise<void>> {
|
||||
const currentDefaultItemsKey = findDefaultItemsKey(this.items.getDisplayableItemsKeys())
|
||||
const newDefaultItemsKey = await this.createDefaultItemsKeyUseCase.execute()
|
||||
|
||||
const rollback = async () => {
|
||||
await this.mutator.setItemToBeDeleted(newDefaultItemsKey)
|
||||
|
||||
if (currentDefaultItemsKey) {
|
||||
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return rollback
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user