internal: incomplete vault systems behind feature flag (#2340)
This commit is contained in:
@@ -1,251 +0,0 @@
|
||||
import {
|
||||
AnyKeyParamsContent,
|
||||
compareVersions,
|
||||
ContentType,
|
||||
leftVersionGreaterThanOrEqualToRight,
|
||||
ProtocolVersion,
|
||||
} from '@standardnotes/common'
|
||||
import {
|
||||
BackupFileType,
|
||||
ContentTypeUsesRootKeyEncryption,
|
||||
CreateAnyKeyParams,
|
||||
isItemsKey,
|
||||
SNItemsKey,
|
||||
SNRootKey,
|
||||
SNRootKeyParams,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
BackupFile,
|
||||
CreateDecryptedItemFromPayload,
|
||||
CreatePayloadSplit,
|
||||
DecryptedPayload,
|
||||
DecryptedPayloadInterface,
|
||||
EncryptedPayload,
|
||||
EncryptedPayloadInterface,
|
||||
isDecryptedPayload,
|
||||
isDecryptedTransferPayload,
|
||||
isEncryptedPayload,
|
||||
isEncryptedTransferPayload,
|
||||
ItemsKeyContent,
|
||||
ItemsKeyInterface,
|
||||
PayloadInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { extendArray } from '@standardnotes/utils'
|
||||
import { EncryptionService } from './EncryptionService'
|
||||
|
||||
export async function DecryptBackupFile(
|
||||
file: BackupFile,
|
||||
protocolService: EncryptionService,
|
||||
password?: string,
|
||||
): Promise<ClientDisplayableError | (EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const payloads: (EncryptedPayloadInterface | DecryptedPayloadInterface)[] = file.items.map((item) => {
|
||||
if (isEncryptedTransferPayload(item)) {
|
||||
return new EncryptedPayload(item)
|
||||
} else if (isDecryptedTransferPayload(item)) {
|
||||
return new DecryptedPayload(item)
|
||||
} else {
|
||||
throw Error('Unhandled case in decryptBackupFile')
|
||||
}
|
||||
})
|
||||
|
||||
const { encrypted, decrypted } = CreatePayloadSplit(payloads)
|
||||
|
||||
const type = getBackupFileType(file, payloads)
|
||||
|
||||
switch (type) {
|
||||
case BackupFileType.Corrupt:
|
||||
return new ClientDisplayableError('Invalid backup file.')
|
||||
case BackupFileType.Encrypted: {
|
||||
if (!password) {
|
||||
throw Error('Attempting to decrypt encrypted file with no password')
|
||||
}
|
||||
|
||||
const keyParamsData = (file.keyParams || file.auth_params) as AnyKeyParamsContent
|
||||
|
||||
return [
|
||||
...decrypted,
|
||||
...(await decryptEncrypted(password, CreateAnyKeyParams(keyParamsData), encrypted, protocolService)),
|
||||
]
|
||||
}
|
||||
case BackupFileType.EncryptedWithNonEncryptedItemsKey:
|
||||
return [...decrypted, ...(await decryptEncryptedWithNonEncryptedItemsKey(payloads, protocolService))]
|
||||
case BackupFileType.FullyDecrypted:
|
||||
return [...decrypted, ...encrypted]
|
||||
}
|
||||
}
|
||||
|
||||
function getBackupFileType(file: BackupFile, payloads: PayloadInterface[]): BackupFileType {
|
||||
if (file.keyParams || file.auth_params) {
|
||||
return BackupFileType.Encrypted
|
||||
} else {
|
||||
const hasEncryptedItem = payloads.find(isEncryptedPayload)
|
||||
const hasDecryptedItemsKey = payloads.find(
|
||||
(payload) => payload.content_type === ContentType.ItemsKey && isDecryptedPayload(payload),
|
||||
)
|
||||
|
||||
if (hasEncryptedItem && hasDecryptedItemsKey) {
|
||||
return BackupFileType.EncryptedWithNonEncryptedItemsKey
|
||||
} else if (!hasEncryptedItem) {
|
||||
return BackupFileType.FullyDecrypted
|
||||
} else {
|
||||
return BackupFileType.Corrupt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function decryptEncryptedWithNonEncryptedItemsKey(
|
||||
allPayloads: (EncryptedPayloadInterface | DecryptedPayloadInterface)[],
|
||||
protocolService: EncryptionService,
|
||||
): Promise<(EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const decryptedItemsKeys: DecryptedPayloadInterface<ItemsKeyContent>[] = []
|
||||
const encryptedPayloads: EncryptedPayloadInterface[] = []
|
||||
|
||||
allPayloads.forEach((payload) => {
|
||||
if (payload.content_type === ContentType.ItemsKey && isDecryptedPayload(payload)) {
|
||||
decryptedItemsKeys.push(payload as DecryptedPayloadInterface<ItemsKeyContent>)
|
||||
} else if (isEncryptedPayload(payload)) {
|
||||
encryptedPayloads.push(payload)
|
||||
}
|
||||
})
|
||||
|
||||
const itemsKeys = decryptedItemsKeys.map((p) => CreateDecryptedItemFromPayload<ItemsKeyContent, SNItemsKey>(p))
|
||||
|
||||
return decryptWithItemsKeys(encryptedPayloads, itemsKeys, protocolService)
|
||||
}
|
||||
|
||||
function findKeyToUseForPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
availableKeys: ItemsKeyInterface[],
|
||||
protocolService: EncryptionService,
|
||||
keyParams?: SNRootKeyParams,
|
||||
fallbackRootKey?: SNRootKey,
|
||||
): ItemsKeyInterface | SNRootKey | undefined {
|
||||
let itemsKey: ItemsKeyInterface | SNRootKey | undefined
|
||||
|
||||
if (payload.items_key_id) {
|
||||
itemsKey = protocolService.itemsKeyForPayload(payload)
|
||||
if (itemsKey) {
|
||||
return itemsKey
|
||||
}
|
||||
}
|
||||
|
||||
itemsKey = availableKeys.find((itemsKeyPayload) => {
|
||||
return payload.items_key_id === itemsKeyPayload.uuid
|
||||
})
|
||||
|
||||
if (itemsKey) {
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
if (!keyParams) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadVersion = payload.version as ProtocolVersion
|
||||
|
||||
/**
|
||||
* Payloads with versions <= 003 use root key directly for encryption.
|
||||
* However, if the incoming key params are >= 004, this means we should
|
||||
* have an items key based off the 003 root key. We can't use the 004
|
||||
* root key directly because it's missing dataAuthenticationKey.
|
||||
*/
|
||||
if (leftVersionGreaterThanOrEqualToRight(keyParams.version, ProtocolVersion.V004)) {
|
||||
itemsKey = protocolService.defaultItemsKeyForItemVersion(payloadVersion, availableKeys)
|
||||
} else if (compareVersions(payloadVersion, ProtocolVersion.V003) <= 0) {
|
||||
itemsKey = fallbackRootKey
|
||||
}
|
||||
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
async function decryptWithItemsKeys(
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
itemsKeys: ItemsKeyInterface[],
|
||||
protocolService: EncryptionService,
|
||||
keyParams?: SNRootKeyParams,
|
||||
fallbackRootKey?: SNRootKey,
|
||||
): Promise<(DecryptedPayloadInterface | EncryptedPayloadInterface)[]> {
|
||||
const results: (DecryptedPayloadInterface | EncryptedPayloadInterface)[] = []
|
||||
|
||||
for (const encryptedPayload of payloads) {
|
||||
if (ContentTypeUsesRootKeyEncryption(encryptedPayload.content_type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const key = findKeyToUseForPayload(encryptedPayload, itemsKeys, protocolService, keyParams, fallbackRootKey)
|
||||
|
||||
if (!key) {
|
||||
results.push(
|
||||
encryptedPayload.copy({
|
||||
errorDecrypting: true,
|
||||
}),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (isItemsKey(key)) {
|
||||
const decryptedPayload = await protocolService.decryptSplitSingle({
|
||||
usesItemsKey: {
|
||||
items: [encryptedPayload],
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
results.push(decryptedPayload)
|
||||
} else {
|
||||
const decryptedPayload = await protocolService.decryptSplitSingle({
|
||||
usesRootKey: {
|
||||
items: [encryptedPayload],
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
results.push(decryptedPayload)
|
||||
}
|
||||
} catch (e) {
|
||||
results.push(
|
||||
encryptedPayload.copy({
|
||||
errorDecrypting: true,
|
||||
}),
|
||||
)
|
||||
console.error('Error decrypting payload', encryptedPayload, e)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
async function decryptEncrypted(
|
||||
password: string,
|
||||
keyParams: SNRootKeyParams,
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
protocolService: EncryptionService,
|
||||
): Promise<(EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const results: (EncryptedPayloadInterface | DecryptedPayloadInterface)[] = []
|
||||
const rootKey = await protocolService.computeRootKey(password, keyParams)
|
||||
|
||||
const itemsKeysPayloads = payloads.filter((payload) => {
|
||||
return payload.content_type === ContentType.ItemsKey
|
||||
})
|
||||
|
||||
const itemsKeysDecryptionResults = await protocolService.decryptSplit({
|
||||
usesRootKey: {
|
||||
items: itemsKeysPayloads,
|
||||
key: rootKey,
|
||||
},
|
||||
})
|
||||
|
||||
extendArray(results, itemsKeysDecryptionResults)
|
||||
|
||||
const decryptedPayloads = await decryptWithItemsKeys(
|
||||
payloads,
|
||||
itemsKeysDecryptionResults.filter(isDecryptedPayload).map((p) => CreateDecryptedItemFromPayload(p)),
|
||||
protocolService,
|
||||
keyParams,
|
||||
rootKey,
|
||||
)
|
||||
|
||||
extendArray(results, decryptedPayloads)
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
import {
|
||||
AnyKeyParamsContent,
|
||||
compareVersions,
|
||||
ContentType,
|
||||
leftVersionGreaterThanOrEqualToRight,
|
||||
ProtocolVersion,
|
||||
} from '@standardnotes/common'
|
||||
import {
|
||||
BackupFileType,
|
||||
CreateAnyKeyParams,
|
||||
isItemsKey,
|
||||
isKeySystemItemsKey,
|
||||
SNItemsKey,
|
||||
SplitPayloadsByEncryptionType,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
ContentTypeUsesRootKeyEncryption,
|
||||
BackupFile,
|
||||
CreateDecryptedItemFromPayload,
|
||||
CreatePayloadSplit,
|
||||
DecryptedPayload,
|
||||
DecryptedPayloadInterface,
|
||||
EncryptedPayload,
|
||||
EncryptedPayloadInterface,
|
||||
isDecryptedPayload,
|
||||
isDecryptedTransferPayload,
|
||||
isEncryptedPayload,
|
||||
isEncryptedTransferPayload,
|
||||
ItemsKeyContent,
|
||||
ItemsKeyInterface,
|
||||
PayloadInterface,
|
||||
KeySystemItemsKeyInterface,
|
||||
RootKeyInterface,
|
||||
KeySystemRootKeyInterface,
|
||||
isKeySystemRootKey,
|
||||
RootKeyParamsInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { extendArray } from '@standardnotes/utils'
|
||||
import { EncryptionService } from './EncryptionService'
|
||||
|
||||
export class DecryptBackupFileUseCase {
|
||||
constructor(private encryption: EncryptionService) {}
|
||||
|
||||
async execute(
|
||||
file: BackupFile,
|
||||
password?: string,
|
||||
): Promise<ClientDisplayableError | (EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const payloads: (EncryptedPayloadInterface | DecryptedPayloadInterface)[] = file.items.map((item) => {
|
||||
if (isEncryptedTransferPayload(item)) {
|
||||
return new EncryptedPayload(item)
|
||||
} else if (isDecryptedTransferPayload(item)) {
|
||||
return new DecryptedPayload(item)
|
||||
} else {
|
||||
throw Error('Unhandled case in decryptBackupFile')
|
||||
}
|
||||
})
|
||||
|
||||
const { encrypted, decrypted } = CreatePayloadSplit(payloads)
|
||||
|
||||
const type = this.getBackupFileType(file, payloads)
|
||||
|
||||
switch (type) {
|
||||
case BackupFileType.Corrupt:
|
||||
return new ClientDisplayableError('Invalid backup file.')
|
||||
case BackupFileType.Encrypted: {
|
||||
if (!password) {
|
||||
throw Error('Attempting to decrypt encrypted file with no password')
|
||||
}
|
||||
|
||||
const keyParamsData = (file.keyParams || file.auth_params) as AnyKeyParamsContent
|
||||
|
||||
const rootKey = await this.encryption.computeRootKey(password, CreateAnyKeyParams(keyParamsData))
|
||||
|
||||
const results = await this.decryptEncrypted({
|
||||
password,
|
||||
payloads: encrypted,
|
||||
rootKey,
|
||||
keyParams: CreateAnyKeyParams(keyParamsData),
|
||||
})
|
||||
|
||||
return [...decrypted, ...results]
|
||||
}
|
||||
case BackupFileType.EncryptedWithNonEncryptedItemsKey:
|
||||
return [...decrypted, ...(await this.decryptEncryptedWithNonEncryptedItemsKey(payloads))]
|
||||
case BackupFileType.FullyDecrypted:
|
||||
return [...decrypted, ...encrypted]
|
||||
}
|
||||
}
|
||||
|
||||
private getBackupFileType(file: BackupFile, payloads: PayloadInterface[]): BackupFileType {
|
||||
if (file.keyParams || file.auth_params) {
|
||||
return BackupFileType.Encrypted
|
||||
} else {
|
||||
const hasEncryptedItem = payloads.find(isEncryptedPayload)
|
||||
const hasDecryptedItemsKey = payloads.find(
|
||||
(payload) => payload.content_type === ContentType.ItemsKey && isDecryptedPayload(payload),
|
||||
)
|
||||
|
||||
if (hasEncryptedItem && hasDecryptedItemsKey) {
|
||||
return BackupFileType.EncryptedWithNonEncryptedItemsKey
|
||||
} else if (!hasEncryptedItem) {
|
||||
return BackupFileType.FullyDecrypted
|
||||
} else {
|
||||
return BackupFileType.Corrupt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async decryptEncrypted(dto: {
|
||||
password: string
|
||||
keyParams: RootKeyParamsInterface
|
||||
payloads: EncryptedPayloadInterface[]
|
||||
rootKey: RootKeyInterface
|
||||
}): Promise<(EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const results: (EncryptedPayloadInterface | DecryptedPayloadInterface)[] = []
|
||||
|
||||
const { rootKeyEncryption, itemsKeyEncryption } = SplitPayloadsByEncryptionType(dto.payloads)
|
||||
|
||||
const rootKeyBasedDecryptionResults = await this.encryption.decryptSplit({
|
||||
usesRootKey: {
|
||||
items: rootKeyEncryption || [],
|
||||
key: dto.rootKey,
|
||||
},
|
||||
})
|
||||
|
||||
extendArray(results, rootKeyBasedDecryptionResults)
|
||||
|
||||
const decryptedPayloads = await this.decrypt({
|
||||
payloads: itemsKeyEncryption || [],
|
||||
availableItemsKeys: rootKeyBasedDecryptionResults
|
||||
.filter(isItemsKey)
|
||||
.filter(isDecryptedPayload)
|
||||
.map((p) => CreateDecryptedItemFromPayload(p)),
|
||||
keyParams: dto.keyParams,
|
||||
rootKey: dto.rootKey,
|
||||
})
|
||||
|
||||
extendArray(results, decryptedPayloads)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
private async decryptEncryptedWithNonEncryptedItemsKey(
|
||||
payloads: (EncryptedPayloadInterface | DecryptedPayloadInterface)[],
|
||||
): Promise<(EncryptedPayloadInterface | DecryptedPayloadInterface)[]> {
|
||||
const decryptedItemsKeys: DecryptedPayloadInterface<ItemsKeyContent>[] = []
|
||||
const encryptedPayloads: EncryptedPayloadInterface[] = []
|
||||
|
||||
payloads.forEach((payload) => {
|
||||
if (payload.content_type === ContentType.ItemsKey && isDecryptedPayload(payload)) {
|
||||
decryptedItemsKeys.push(payload as DecryptedPayloadInterface<ItemsKeyContent>)
|
||||
} else if (isEncryptedPayload(payload)) {
|
||||
encryptedPayloads.push(payload)
|
||||
}
|
||||
})
|
||||
|
||||
const itemsKeys = decryptedItemsKeys.map((p) => CreateDecryptedItemFromPayload<ItemsKeyContent, SNItemsKey>(p))
|
||||
|
||||
return this.decrypt({ payloads: encryptedPayloads, availableItemsKeys: itemsKeys, rootKey: undefined })
|
||||
}
|
||||
|
||||
private findKeyToUseForPayload(dto: {
|
||||
payload: EncryptedPayloadInterface
|
||||
availableKeys: ItemsKeyInterface[]
|
||||
keyParams?: RootKeyParamsInterface
|
||||
rootKey?: RootKeyInterface
|
||||
}): ItemsKeyInterface | RootKeyInterface | KeySystemRootKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
if (ContentTypeUsesRootKeyEncryption(dto.payload.content_type)) {
|
||||
if (!dto.rootKey) {
|
||||
throw new Error('Attempting to decrypt root key encrypted payload with no root key')
|
||||
}
|
||||
return dto.rootKey
|
||||
}
|
||||
|
||||
if (ContentTypeUsesKeySystemRootKeyEncryption(dto.payload.content_type)) {
|
||||
throw new Error('Backup file key system root key encryption is not supported')
|
||||
}
|
||||
|
||||
let itemsKey: ItemsKeyInterface | RootKeyInterface | KeySystemItemsKeyInterface | undefined
|
||||
|
||||
if (dto.payload.items_key_id) {
|
||||
itemsKey = this.encryption.itemsKeyForEncryptedPayload(dto.payload)
|
||||
if (itemsKey) {
|
||||
return itemsKey
|
||||
}
|
||||
}
|
||||
|
||||
itemsKey = dto.availableKeys.find((itemsKeyPayload) => {
|
||||
return dto.payload.items_key_id === itemsKeyPayload.uuid
|
||||
})
|
||||
|
||||
if (itemsKey) {
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
if (!dto.keyParams) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadVersion = dto.payload.version as ProtocolVersion
|
||||
|
||||
/**
|
||||
* Payloads with versions <= 003 use root key directly for encryption.
|
||||
* However, if the incoming key params are >= 004, this means we should
|
||||
* have an items key based off the 003 root key. We can't use the 004
|
||||
* root key directly because it's missing dataAuthenticationKey.
|
||||
*/
|
||||
if (leftVersionGreaterThanOrEqualToRight(dto.keyParams.version, ProtocolVersion.V004)) {
|
||||
itemsKey = this.encryption.defaultItemsKeyForItemVersion(payloadVersion, dto.availableKeys)
|
||||
} else if (compareVersions(payloadVersion, ProtocolVersion.V003) <= 0) {
|
||||
itemsKey = dto.rootKey
|
||||
}
|
||||
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
private async decrypt(dto: {
|
||||
payloads: EncryptedPayloadInterface[]
|
||||
availableItemsKeys: ItemsKeyInterface[]
|
||||
rootKey: RootKeyInterface | undefined
|
||||
keyParams?: RootKeyParamsInterface
|
||||
}): Promise<(DecryptedPayloadInterface | EncryptedPayloadInterface)[]> {
|
||||
const results: (DecryptedPayloadInterface | EncryptedPayloadInterface)[] = []
|
||||
|
||||
for (const encryptedPayload of dto.payloads) {
|
||||
try {
|
||||
const key = this.findKeyToUseForPayload({
|
||||
payload: encryptedPayload,
|
||||
availableKeys: dto.availableItemsKeys,
|
||||
keyParams: dto.keyParams,
|
||||
rootKey: dto.rootKey,
|
||||
})
|
||||
|
||||
if (!key) {
|
||||
results.push(
|
||||
encryptedPayload.copy({
|
||||
errorDecrypting: true,
|
||||
}),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (isItemsKey(key) || isKeySystemItemsKey(key)) {
|
||||
const decryptedPayload = await this.encryption.decryptSplitSingle({
|
||||
usesItemsKey: {
|
||||
items: [encryptedPayload],
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
results.push(decryptedPayload)
|
||||
} else if (isKeySystemRootKey(key)) {
|
||||
const decryptedPayload = await this.encryption.decryptSplitSingle({
|
||||
usesKeySystemRootKey: {
|
||||
items: [encryptedPayload],
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
results.push(decryptedPayload)
|
||||
} else {
|
||||
const decryptedPayload = await this.encryption.decryptSplitSingle({
|
||||
usesRootKey: {
|
||||
items: [encryptedPayload],
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
results.push(decryptedPayload)
|
||||
}
|
||||
} catch (e) {
|
||||
results.push(
|
||||
encryptedPayload.copy({
|
||||
errorDecrypting: true,
|
||||
}),
|
||||
)
|
||||
console.error('Error decrypting payload', encryptedPayload, e)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import {
|
||||
CreateAnyKeyParams,
|
||||
CreateEncryptionSplitWithKeyLookup,
|
||||
DecryptedParameters,
|
||||
EncryptedParameters,
|
||||
encryptedParametersFromPayload,
|
||||
encryptedInputParametersFromPayload,
|
||||
EncryptionProviderInterface,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
@@ -23,6 +22,11 @@ import {
|
||||
SplitPayloadsByEncryptionType,
|
||||
V001Algorithm,
|
||||
V002Algorithm,
|
||||
PublicKeySet,
|
||||
EncryptedOutputParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
AsymmetricSignatureVerificationDetachedResult,
|
||||
AsymmetricallyEncryptedString,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
BackupFile,
|
||||
@@ -37,9 +41,15 @@ import {
|
||||
ItemContent,
|
||||
ItemsKeyInterface,
|
||||
RootKeyInterface,
|
||||
KeySystemItemsKeyInterface,
|
||||
KeySystemIdentifier,
|
||||
AsymmetricMessagePayload,
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import {
|
||||
extendArray,
|
||||
isNotUndefined,
|
||||
@@ -68,10 +78,10 @@ import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics'
|
||||
import { RootKeyEncryptionService } from './RootKeyEncryption'
|
||||
import { DecryptBackupFile } from './BackupFileDecryptor'
|
||||
import { DecryptBackupFileUseCase } from './DecryptBackupFileUseCase'
|
||||
import { EncryptionServiceEvent } from './EncryptionServiceEvent'
|
||||
import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/DecryptedParameters'
|
||||
|
||||
/**
|
||||
* The encryption service is responsible for the encryption and decryption of payloads, and
|
||||
@@ -108,9 +118,11 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManagerInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
public deviceInterface: DeviceInterface,
|
||||
private storageService: StorageServiceInterface,
|
||||
public readonly keys: KeySystemKeyManagerInterface,
|
||||
private identifier: ApplicationIdentifier,
|
||||
public crypto: PureCryptoInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
@@ -125,17 +137,22 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
payloadManager,
|
||||
storageService,
|
||||
this.operatorManager,
|
||||
keys,
|
||||
internalEventBus,
|
||||
)
|
||||
|
||||
this.rootKeyEncryption = new RootKeyEncryptionService(
|
||||
this.itemManager,
|
||||
this.mutator,
|
||||
this.operatorManager,
|
||||
this.deviceInterface,
|
||||
this.storageService,
|
||||
this.payloadManager,
|
||||
keys,
|
||||
this.identifier,
|
||||
this.internalEventBus,
|
||||
)
|
||||
|
||||
this.rootKeyObserverDisposer = this.rootKeyEncryption.addEventObserver((event) => {
|
||||
this.itemsEncryption.userVersion = this.getUserVersion()
|
||||
if (event === RootKeyServiceEvent.RootKeyStatusChanged) {
|
||||
@@ -166,6 +183,32 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
/** @throws */
|
||||
getKeyPair(): PkcKeyPair {
|
||||
const rootKey = this.getRootKey()
|
||||
|
||||
if (!rootKey?.encryptionKeyPair) {
|
||||
throw new Error('Account keypair not found')
|
||||
}
|
||||
|
||||
return rootKey.encryptionKeyPair
|
||||
}
|
||||
|
||||
/** @throws */
|
||||
getSigningKeyPair(): PkcKeyPair {
|
||||
const rootKey = this.getRootKey()
|
||||
|
||||
if (!rootKey?.signingKeyPair) {
|
||||
throw new Error('Account keypair not found')
|
||||
}
|
||||
|
||||
return rootKey.signingKeyPair
|
||||
}
|
||||
|
||||
hasSigningKeyPair(): boolean {
|
||||
return !!this.getRootKey()?.signingKeyPair
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
await this.rootKeyEncryption.initialize()
|
||||
}
|
||||
@@ -213,8 +256,12 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return this.itemsEncryption.repersistAllItems()
|
||||
}
|
||||
|
||||
public async reencryptItemsKeys(): Promise<void> {
|
||||
await this.rootKeyEncryption.reencryptItemsKeys()
|
||||
public async reencryptApplicableItemsAfterUserRootKeyChange(): Promise<void> {
|
||||
await this.rootKeyEncryption.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
}
|
||||
|
||||
public reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
|
||||
return this.rootKeyEncryption.reencryptKeySystemItemsKeysForVault(keySystemIdentifier)
|
||||
}
|
||||
|
||||
public async createNewItemsKeyWithRollback(): Promise<() => Promise<void>> {
|
||||
@@ -222,11 +269,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async decryptErroredPayloads(): Promise<void> {
|
||||
await this.itemsEncryption.decryptErroredPayloads()
|
||||
await this.rootKeyEncryption.decryptErroredRootPayloads()
|
||||
await this.itemsEncryption.decryptErroredItemPayloads()
|
||||
}
|
||||
|
||||
public itemsKeyForPayload(payload: EncryptedPayloadInterface): ItemsKeyInterface | undefined {
|
||||
return this.itemsEncryption.itemsKeyForPayload(payload)
|
||||
public itemsKeyForEncryptedPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
return this.itemsEncryption.itemsKeyForEncryptedPayload(payload)
|
||||
}
|
||||
|
||||
public defaultItemsKeyForItemVersion(
|
||||
@@ -241,34 +291,66 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
}
|
||||
|
||||
public async encryptSplit(split: KeyedEncryptionSplit): Promise<EncryptedPayloadInterface[]> {
|
||||
const allEncryptedParams: EncryptedParameters[] = []
|
||||
const allEncryptedParams: EncryptedOutputParameters[] = []
|
||||
|
||||
if (split.usesRootKey) {
|
||||
const {
|
||||
usesRootKey,
|
||||
usesItemsKey,
|
||||
usesKeySystemRootKey,
|
||||
usesRootKeyWithKeyLookup,
|
||||
usesItemsKeyWithKeyLookup,
|
||||
usesKeySystemRootKeyWithKeyLookup,
|
||||
} = split
|
||||
|
||||
const signingKeyPair = this.hasSigningKeyPair() ? this.getSigningKeyPair() : undefined
|
||||
|
||||
if (usesRootKey) {
|
||||
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloads(
|
||||
split.usesRootKey.items,
|
||||
split.usesRootKey.key,
|
||||
usesRootKey.items,
|
||||
usesRootKey.key,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, rootKeyEncrypted)
|
||||
}
|
||||
|
||||
if (split.usesItemsKey) {
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, rootKeyEncrypted)
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloads(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, keySystemRootKeyEncrypted)
|
||||
}
|
||||
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, keySystemRootKeyEncrypted)
|
||||
}
|
||||
|
||||
if (usesItemsKey) {
|
||||
const itemsKeyEncrypted = await this.itemsEncryption.encryptPayloads(
|
||||
split.usesItemsKey.items,
|
||||
split.usesItemsKey.key,
|
||||
usesItemsKey.items,
|
||||
usesItemsKey.key,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, itemsKeyEncrypted)
|
||||
}
|
||||
|
||||
if (split.usesRootKeyWithKeyLookup) {
|
||||
const rootKeyEncrypted = await this.rootKeyEncryption.encryptPayloadsWithKeyLookup(
|
||||
split.usesRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(allEncryptedParams, rootKeyEncrypted)
|
||||
}
|
||||
|
||||
if (split.usesItemsKeyWithKeyLookup) {
|
||||
if (usesItemsKeyWithKeyLookup) {
|
||||
const itemsKeyEncrypted = await this.itemsEncryption.encryptPayloadsWithKeyLookup(
|
||||
split.usesItemsKeyWithKeyLookup.items,
|
||||
usesItemsKeyWithKeyLookup.items,
|
||||
signingKeyPair,
|
||||
)
|
||||
extendArray(allEncryptedParams, itemsKeyEncrypted)
|
||||
}
|
||||
@@ -300,32 +382,48 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
>(split: KeyedDecryptionSplit): Promise<(P | EncryptedPayloadInterface)[]> {
|
||||
const resultParams: (DecryptedParameters<C> | ErrorDecryptingParameters)[] = []
|
||||
|
||||
if (split.usesRootKey) {
|
||||
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(
|
||||
split.usesRootKey.items,
|
||||
split.usesRootKey.key,
|
||||
)
|
||||
const {
|
||||
usesRootKey,
|
||||
usesItemsKey,
|
||||
usesKeySystemRootKey,
|
||||
usesRootKeyWithKeyLookup,
|
||||
usesItemsKeyWithKeyLookup,
|
||||
usesKeySystemRootKeyWithKeyLookup,
|
||||
} = split
|
||||
|
||||
if (usesRootKey) {
|
||||
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(usesRootKey.items, usesRootKey.key)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
|
||||
if (split.usesRootKeyWithKeyLookup) {
|
||||
if (usesRootKeyWithKeyLookup) {
|
||||
const rootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
|
||||
split.usesRootKeyWithKeyLookup.items,
|
||||
usesRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, rootKeyDecrypted)
|
||||
}
|
||||
|
||||
if (split.usesItemsKey) {
|
||||
const itemsKeyDecrypted = await this.itemsEncryption.decryptPayloads<C>(
|
||||
split.usesItemsKey.items,
|
||||
split.usesItemsKey.key,
|
||||
if (usesKeySystemRootKey) {
|
||||
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloads<C>(
|
||||
usesKeySystemRootKey.items,
|
||||
usesKeySystemRootKey.key,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
}
|
||||
if (usesKeySystemRootKeyWithKeyLookup) {
|
||||
const keySystemRootKeyDecrypted = await this.rootKeyEncryption.decryptPayloadsWithKeyLookup<C>(
|
||||
usesKeySystemRootKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, keySystemRootKeyDecrypted)
|
||||
}
|
||||
|
||||
if (usesItemsKey) {
|
||||
const itemsKeyDecrypted = await this.itemsEncryption.decryptPayloads<C>(usesItemsKey.items, usesItemsKey.key)
|
||||
extendArray(resultParams, itemsKeyDecrypted)
|
||||
}
|
||||
|
||||
if (split.usesItemsKeyWithKeyLookup) {
|
||||
if (usesItemsKeyWithKeyLookup) {
|
||||
const itemsKeyDecrypted = await this.itemsEncryption.decryptPayloadsWithKeyLookup<C>(
|
||||
split.usesItemsKeyWithKeyLookup.items,
|
||||
usesItemsKeyWithKeyLookup.items,
|
||||
)
|
||||
extendArray(resultParams, itemsKeyDecrypted)
|
||||
}
|
||||
@@ -349,6 +447,36 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return packagedResults
|
||||
}
|
||||
|
||||
async decryptPayloadWithKeyLookup<
|
||||
C extends ItemContent = ItemContent,
|
||||
P extends DecryptedPayloadInterface<C> = DecryptedPayloadInterface<C>,
|
||||
>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): Promise<{
|
||||
parameters: DecryptedParameters<C> | ErrorDecryptingParameters
|
||||
payload: P | EncryptedPayloadInterface
|
||||
}> {
|
||||
const decryptedParameters = await this.itemsEncryption.decryptPayloadWithKeyLookup<C>(payload)
|
||||
|
||||
if (isErrorDecryptingParameters(decryptedParameters)) {
|
||||
return {
|
||||
parameters: decryptedParameters,
|
||||
payload: new EncryptedPayload({
|
||||
...payload.ejected(),
|
||||
...decryptedParameters,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
parameters: decryptedParameters,
|
||||
payload: new DecryptedPayload<C>({
|
||||
...payload.ejected(),
|
||||
...decryptedParameters,
|
||||
}) as P,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user's account protocol version is not equal to the latest version.
|
||||
*/
|
||||
@@ -420,27 +548,130 @@ 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(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface> {
|
||||
public async computeRootKey<K extends RootKeyInterface>(password: string, keyParams: SNRootKeyParams): Promise<K> {
|
||||
return this.rootKeyEncryption.computeRootKey(password, keyParams)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a root key using the latest protocol version
|
||||
*/
|
||||
public async createRootKey(
|
||||
public async createRootKey<K extends RootKeyInterface>(
|
||||
identifier: string,
|
||||
password: string,
|
||||
origination: KeyParamsOrigination,
|
||||
version?: ProtocolVersion,
|
||||
) {
|
||||
): Promise<K> {
|
||||
return this.rootKeyEncryption.createRootKey(identifier, password, origination, version)
|
||||
}
|
||||
|
||||
createRandomizedKeySystemRootKey(dto: {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
systemName: string
|
||||
systemDescription?: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().createRandomizedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
createUserInputtedKeySystemRootKey(dto: {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
systemName: string
|
||||
systemDescription?: string
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().createUserInputtedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
deriveUserInputtedKeySystemRootKey(dto: {
|
||||
keyParams: KeySystemRootKeyParamsInterface
|
||||
userInputtedPassword: string
|
||||
}): KeySystemRootKeyInterface {
|
||||
return this.operatorManager.defaultOperator().deriveUserInputtedKeySystemRootKey(dto)
|
||||
}
|
||||
|
||||
createKeySystemItemsKey(
|
||||
uuid: string,
|
||||
keySystemIdentifier: KeySystemIdentifier,
|
||||
sharedVaultUuid: string | undefined,
|
||||
rootKeyToken: string,
|
||||
): KeySystemItemsKeyInterface {
|
||||
return this.operatorManager
|
||||
.defaultOperator()
|
||||
.createKeySystemItemsKey(uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken)
|
||||
}
|
||||
|
||||
asymmetricallyEncryptMessage(dto: {
|
||||
message: AsymmetricMessagePayload
|
||||
senderKeyPair: PkcKeyPair
|
||||
senderSigningKeyPair: PkcKeyPair
|
||||
recipientPublicKey: string
|
||||
}): AsymmetricallyEncryptedString {
|
||||
const operator = this.operatorManager.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.operatorManager.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.encryptedString)
|
||||
const keyOperator = this.operatorManager.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 {
|
||||
const defaultOperator = this.operatorManager.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(encryptedString)
|
||||
const keyOperator = this.operatorManager.operatorForVersion(version)
|
||||
return keyOperator.asymmetricSignatureVerifyDetached(encryptedString)
|
||||
}
|
||||
|
||||
getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet {
|
||||
const defaultOperator = this.operatorManager.defaultOperator()
|
||||
const version = defaultOperator.versionForAsymmetricallyEncryptedString(string)
|
||||
|
||||
const keyOperator = this.operatorManager.operatorForVersion(version)
|
||||
return keyOperator.getSenderPublicKeySetFromAsymmetricallyEncryptedString(string)
|
||||
}
|
||||
|
||||
public async decryptBackupFile(
|
||||
file: BackupFile,
|
||||
password?: string,
|
||||
): Promise<ClientDisplayableError | (EncryptedPayloadInterface | DecryptedPayloadInterface<ItemContent>)[]> {
|
||||
const result = await DecryptBackupFile(file, this, password)
|
||||
const usecase = new DecryptBackupFileUseCase(this)
|
||||
const result = await usecase.execute(file, password)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -468,7 +699,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
items: ejected,
|
||||
}
|
||||
|
||||
const keyParams = await this.getRootKeyParams()
|
||||
const keyParams = this.getRootKeyParams()
|
||||
data.keyParams = keyParams?.getPortableValue()
|
||||
return data
|
||||
}
|
||||
@@ -504,7 +735,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return (await this.rootKeyEncryption.hasRootKeyWrapper()) && this.rootKeyEncryption.getRootKey() == undefined
|
||||
}
|
||||
|
||||
public async getRootKeyParams() {
|
||||
public getRootKeyParams() {
|
||||
return this.rootKeyEncryption.getRootKeyParams()
|
||||
}
|
||||
|
||||
@@ -517,7 +748,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* Wrapping key params are read from disk.
|
||||
*/
|
||||
public async computeWrappingKey(passcode: string) {
|
||||
const keyParams = await this.rootKeyEncryption.getSureRootKeyWrapperKeyParams()
|
||||
const keyParams = this.rootKeyEncryption.getSureRootKeyWrapperKeyParams()
|
||||
const key = await this.computeRootKey(passcode, keyParams)
|
||||
return key
|
||||
}
|
||||
@@ -545,17 +776,21 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
await this.rootKeyEncryption.removeRootKeyWrapper()
|
||||
}
|
||||
|
||||
public async setRootKey(key: SNRootKey, wrappingKey?: SNRootKey) {
|
||||
public async setRootKey(key: RootKeyInterface, wrappingKey?: SNRootKey) {
|
||||
await this.rootKeyEncryption.setRootKey(key, wrappingKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the in-memory root key value.
|
||||
*/
|
||||
public getRootKey() {
|
||||
public getRootKey(): RootKeyInterface | undefined {
|
||||
return this.rootKeyEncryption.getRootKey()
|
||||
}
|
||||
|
||||
public getSureRootKey(): RootKeyInterface {
|
||||
return this.rootKeyEncryption.getRootKey() as RootKeyInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes root key and wrapper from keychain. Used when signing out of application.
|
||||
*/
|
||||
@@ -571,26 +806,31 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
return this.rootKeyEncryption.validatePasscode(passcode)
|
||||
}
|
||||
|
||||
public getEmbeddedPayloadAuthenticatedData(
|
||||
public getEmbeddedPayloadAuthenticatedData<D extends ItemAuthenticatedData>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): RootKeyEncryptedAuthenticatedData | ItemAuthenticatedData | LegacyAttachedData | undefined {
|
||||
): D | undefined {
|
||||
const version = payload.version
|
||||
if (!version) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const operator = this.operatorManager.operatorForVersion(version)
|
||||
const authenticatedData = operator.getPayloadAuthenticatedData(encryptedParametersFromPayload(payload))
|
||||
return authenticatedData
|
||||
|
||||
const authenticatedData = operator.getPayloadAuthenticatedDataForExternalUse(
|
||||
encryptedInputParametersFromPayload(payload),
|
||||
)
|
||||
|
||||
return authenticatedData as D
|
||||
}
|
||||
|
||||
/** Returns the key params attached to this key's encrypted payload */
|
||||
public getKeyEmbeddedKeyParams(key: EncryptedPayloadInterface): SNRootKeyParams | undefined {
|
||||
public getKeyEmbeddedKeyParamsFromItemsKey(key: EncryptedPayloadInterface): SNRootKeyParams | undefined {
|
||||
const authenticatedData = this.getEmbeddedPayloadAuthenticatedData(key)
|
||||
if (!authenticatedData) {
|
||||
return undefined
|
||||
}
|
||||
if (isVersionLessThanOrEqualTo(key.version, ProtocolVersion.V003)) {
|
||||
const rawKeyParams = authenticatedData as LegacyAttachedData
|
||||
const rawKeyParams = authenticatedData as unknown as LegacyAttachedData
|
||||
return this.createKeyParams(rawKeyParams)
|
||||
} else {
|
||||
const rawKeyParams = (authenticatedData as RootKeyEncryptedAuthenticatedData).kp
|
||||
@@ -683,7 +923,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
const hasSyncedItemsKey = !isNullOrUndefined(defaultSyncedKey)
|
||||
if (hasSyncedItemsKey) {
|
||||
/** Delete all never synced keys */
|
||||
await this.itemManager.setItemsToBeDeleted(neverSyncedKeys)
|
||||
await this.mutator.setItemsToBeDeleted(neverSyncedKeys)
|
||||
} else {
|
||||
/**
|
||||
* No previous synced items key.
|
||||
@@ -692,14 +932,14 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
* we end up with 0 items keys, create a new one. This covers the case when you open
|
||||
* the app offline and it creates an 004 key, and then you sign into an 003 account.
|
||||
*/
|
||||
const rootKeyParams = await this.getRootKeyParams()
|
||||
const rootKeyParams = this.getRootKeyParams()
|
||||
if (rootKeyParams) {
|
||||
/** If neverSynced.version != rootKey.version, delete. */
|
||||
const toDelete = neverSyncedKeys.filter((itemsKey) => {
|
||||
return itemsKey.keyVersion !== rootKeyParams.version
|
||||
})
|
||||
if (toDelete.length > 0) {
|
||||
await this.itemManager.setItemsToBeDeleted(toDelete)
|
||||
await this.mutator.setItemsToBeDeleted(toDelete)
|
||||
}
|
||||
|
||||
if (this.itemsEncryption.getItemsKeys().length === 0) {
|
||||
@@ -741,26 +981,7 @@ export class EncryptionService extends AbstractService<EncryptionServiceEvent> i
|
||||
|
||||
const unsyncedKeys = this.itemsEncryption.getItemsKeys().filter((key) => key.neverSynced && !key.dirty)
|
||||
if (unsyncedKeys.length > 0) {
|
||||
void this.itemManager.setItemsDirty(unsyncedKeys)
|
||||
}
|
||||
}
|
||||
|
||||
override async getDiagnostics(): Promise<DiagnosticInfo | undefined> {
|
||||
return {
|
||||
encryption: {
|
||||
getLatestVersion: this.getLatestVersion(),
|
||||
hasAccount: this.hasAccount(),
|
||||
hasRootKeyEncryptionSource: this.hasRootKeyEncryptionSource(),
|
||||
getUserVersion: this.getUserVersion(),
|
||||
upgradeAvailable: await this.upgradeAvailable(),
|
||||
accountUpgradeAvailable: this.accountUpgradeAvailable(),
|
||||
passcodeUpgradeAvailable: await this.passcodeUpgradeAvailable(),
|
||||
hasPasscode: this.hasPasscode(),
|
||||
isPasscodeLocked: await this.isPasscodeLocked(),
|
||||
needsNewRootKeyBasedItemsKey: this.needsNewRootKeyBasedItemsKey(),
|
||||
...(await this.itemsEncryption.getDiagnostics()),
|
||||
...(await this.rootKeyEncryption.getDiagnostics()),
|
||||
},
|
||||
void this.mutator.setItemsDirty(unsyncedKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function DecryptItemsKeyByPromptingUser(
|
||||
| 'aborted'
|
||||
> {
|
||||
if (!keyParams) {
|
||||
keyParams = encryptor.getKeyEmbeddedKeyParams(itemsKey)
|
||||
keyParams = encryptor.getKeyEmbeddedKeyParamsFromItemsKey(itemsKey)
|
||||
}
|
||||
|
||||
if (!keyParams) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ContentType, ProtocolVersion } from '@standardnotes/common'
|
||||
import {
|
||||
DecryptedParameters,
|
||||
EncryptedParameters,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
isErrorDecryptingParameters,
|
||||
@@ -9,26 +8,30 @@ import {
|
||||
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 { Uuids } from '@standardnotes/utils'
|
||||
|
||||
import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics'
|
||||
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'
|
||||
|
||||
export class ItemsEncryptionService extends AbstractService {
|
||||
private removeItemsObserver!: () => void
|
||||
@@ -39,13 +42,14 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
private payloadManager: PayloadManagerInterface,
|
||||
private storageService: StorageServiceInterface,
|
||||
private operatorManager: OperatorManager,
|
||||
private keys: KeySystemKeyManagerInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
|
||||
this.removeItemsObserver = this.itemManager.addObserver([ContentType.ItemsKey], ({ changed, inserted }) => {
|
||||
if (changed.concat(inserted).length > 0) {
|
||||
void this.decryptErroredPayloads()
|
||||
void this.decryptErroredItemPayloads()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -54,6 +58,8 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
;(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()
|
||||
@@ -70,12 +76,17 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
return this.storageService.savePayloads(payloads)
|
||||
}
|
||||
|
||||
public getItemsKeys() {
|
||||
public getItemsKeys(): ItemsKeyInterface[] {
|
||||
return this.itemManager.getDisplayableItemsKeys()
|
||||
}
|
||||
|
||||
public itemsKeyForPayload(payload: EncryptedPayloadInterface): ItemsKeyInterface | undefined {
|
||||
return this.getItemsKeys().find(
|
||||
public itemsKeyForEncryptedPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
const itemsKeys = this.getItemsKeys()
|
||||
const keySystemItemsKeys = this.itemManager.getItems<KeySystemItemsKeyInterface>(ContentType.KeySystemItemsKey)
|
||||
|
||||
return [...itemsKeys, ...keySystemItemsKeys].find(
|
||||
(key) => key.uuid === payload.items_key_id || key.duplicateOf === payload.items_key_id,
|
||||
)
|
||||
}
|
||||
@@ -84,8 +95,20 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
return findDefaultItemsKey(this.getItemsKeys())
|
||||
}
|
||||
|
||||
private keyToUseForItemEncryption(): ItemsKeyInterface | StandardException {
|
||||
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) {
|
||||
@@ -107,9 +130,11 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
return result
|
||||
}
|
||||
|
||||
private keyToUseForDecryptionOfPayload(payload: EncryptedPayloadInterface): ItemsKeyInterface | undefined {
|
||||
private keyToUseForDecryptionOfPayload(
|
||||
payload: EncryptedPayloadInterface,
|
||||
): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined {
|
||||
if (payload.items_key_id) {
|
||||
const itemsKey = this.itemsKeyForPayload(payload)
|
||||
const itemsKey = this.itemsKeyForEncryptedPayload(payload)
|
||||
return itemsKey
|
||||
}
|
||||
|
||||
@@ -117,20 +142,24 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
return defaultKey
|
||||
}
|
||||
|
||||
public async encryptPayloadWithKeyLookup(payload: DecryptedPayloadInterface): Promise<EncryptedParameters> {
|
||||
const key = this.keyToUseForItemEncryption()
|
||||
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)
|
||||
return this.encryptPayload(payload, key, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayload(
|
||||
payload: DecryptedPayloadInterface,
|
||||
key: ItemsKeyInterface,
|
||||
): Promise<EncryptedParameters> {
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters> {
|
||||
if (isEncryptedPayload(payload)) {
|
||||
throw Error('Attempting to encrypt already encrypted payload.')
|
||||
}
|
||||
@@ -141,18 +170,22 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
throw Error('Attempting to encrypt payload with no UuidGenerator.')
|
||||
}
|
||||
|
||||
return encryptPayload(payload, key, this.operatorManager)
|
||||
return encryptPayload(payload, key, this.operatorManager, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayloads(
|
||||
payloads: DecryptedPayloadInterface[],
|
||||
key: ItemsKeyInterface,
|
||||
): Promise<EncryptedParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key)))
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
signingKeyPair?: PkcKeyPair,
|
||||
): Promise<EncryptedOutputParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key, signingKeyPair)))
|
||||
}
|
||||
|
||||
public async encryptPayloadsWithKeyLookup(payloads: DecryptedPayloadInterface[]): Promise<EncryptedParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayloadWithKeyLookup(payload)))
|
||||
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>(
|
||||
@@ -173,7 +206,7 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
|
||||
public async decryptPayload<C extends ItemContent = ItemContent>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
key: ItemsKeyInterface,
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
|
||||
if (!payload.content) {
|
||||
return {
|
||||
@@ -193,21 +226,24 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
|
||||
public async decryptPayloads<C extends ItemContent = ItemContent>(
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
key: ItemsKeyInterface,
|
||||
key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
|
||||
return Promise.all(payloads.map((payload) => this.decryptPayload<C>(payload, key)))
|
||||
}
|
||||
|
||||
public async decryptErroredPayloads(): Promise<void> {
|
||||
const payloads = this.payloadManager.invalidPayloads.filter((i) => i.content_type !== ContentType.ItemsKey)
|
||||
if (payloads.length === 0) {
|
||||
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(payloads)
|
||||
const resultParams = await this.decryptPayloadsWithKeyLookup(erroredItemPayloads)
|
||||
|
||||
const decryptedPayloads = resultParams.map((params) => {
|
||||
const original = SureFindPayload(payloads, params.uuid)
|
||||
const original = SureFindPayload(erroredItemPayloads, params.uuid)
|
||||
if (isErrorDecryptingParameters(params)) {
|
||||
return new EncryptedPayload({
|
||||
...original.ejected(),
|
||||
@@ -247,15 +283,4 @@ export class ItemsEncryptionService extends AbstractService {
|
||||
return key.keyVersion === version
|
||||
})
|
||||
}
|
||||
|
||||
override async getDiagnostics(): Promise<DiagnosticInfo | undefined> {
|
||||
const keyForItems = this.keyToUseForItemEncryption()
|
||||
return {
|
||||
itemsEncryption: {
|
||||
itemsKeysIds: Uuids(this.getItemsKeys()),
|
||||
defaultItemsKeyId: this.getDefaultItemsKey()?.uuid,
|
||||
keyToUseForItemEncryptionId: keyForItems instanceof StandardException ? undefined : keyForItems.uuid,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import {
|
||||
ApplicationIdentifier,
|
||||
ProtocolVersionLatest,
|
||||
@@ -17,15 +18,19 @@ import {
|
||||
CreateAnyKeyParams,
|
||||
SNRootKey,
|
||||
isErrorDecryptingParameters,
|
||||
EncryptedParameters,
|
||||
DecryptedParameters,
|
||||
ErrorDecryptingParameters,
|
||||
findDefaultItemsKey,
|
||||
ItemsKeyMutator,
|
||||
encryptPayload,
|
||||
decryptPayload,
|
||||
EncryptedOutputParameters,
|
||||
DecryptedParameters,
|
||||
KeySystemKeyManagerInterface,
|
||||
} from '@standardnotes/encryption'
|
||||
import {
|
||||
ContentTypeUsesKeySystemRootKeyEncryption,
|
||||
ContentTypesUsingRootKeyEncryption,
|
||||
ContentTypeUsesRootKeyEncryption,
|
||||
CreateDecryptedItemFromPayload,
|
||||
DecryptedPayload,
|
||||
DecryptedPayloadInterface,
|
||||
@@ -34,25 +39,29 @@ import {
|
||||
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 { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics'
|
||||
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
|
||||
@@ -60,10 +69,13 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
public memoizedRootKeyParams?: SNRootKeyParams
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManagerInterface,
|
||||
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,
|
||||
) {
|
||||
@@ -71,7 +83,13 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.itemManager as unknown) = undefined
|
||||
;(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()
|
||||
@@ -144,7 +162,7 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
if (this.hasAccount()) {
|
||||
return this.getSureUserVersion()
|
||||
} else if (this.hasPasscode()) {
|
||||
const passcodeParams = await this.getSureRootKeyWrapperKeyParams()
|
||||
const passcodeParams = this.getSureRootKeyWrapperKeyParams()
|
||||
return passcodeParams.version
|
||||
}
|
||||
|
||||
@@ -170,7 +188,7 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
return undefined
|
||||
}
|
||||
|
||||
const keyParams = await this.getSureRootKeyParams()
|
||||
const keyParams = this.getSureRootKeyParams()
|
||||
|
||||
return CreateNewRootKey({
|
||||
...rawKey,
|
||||
@@ -193,11 +211,8 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
})
|
||||
}
|
||||
|
||||
public async getRootKeyWrapperKeyParams(): Promise<SNRootKeyParams | undefined> {
|
||||
const rawKeyParams = await this.storageService.getValue(
|
||||
StorageKey.RootKeyWrapperKeyParams,
|
||||
StorageValueModes.Nonwrapped,
|
||||
)
|
||||
public getRootKeyWrapperKeyParams(): SNRootKeyParams | undefined {
|
||||
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyWrapperKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return undefined
|
||||
@@ -206,11 +221,11 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
return CreateAnyKeyParams(rawKeyParams as AnyKeyParamsContent)
|
||||
}
|
||||
|
||||
public async getSureRootKeyWrapperKeyParams() {
|
||||
return this.getRootKeyWrapperKeyParams() as Promise<SNRootKeyParams>
|
||||
public getSureRootKeyWrapperKeyParams() {
|
||||
return this.getRootKeyWrapperKeyParams() as SNRootKeyParams
|
||||
}
|
||||
|
||||
public async getRootKeyParams(): Promise<SNRootKeyParams | undefined> {
|
||||
public getRootKeyParams(): SNRootKeyParams | undefined {
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
return this.getRootKeyWrapperKeyParams()
|
||||
} else if (this.keyMode === KeyMode.RootKeyOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
@@ -222,22 +237,22 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
}
|
||||
}
|
||||
|
||||
public async getSureRootKeyParams(): Promise<SNRootKeyParams> {
|
||||
return this.getRootKeyParams() as Promise<SNRootKeyParams>
|
||||
public getSureRootKeyParams(): SNRootKeyParams {
|
||||
return this.getRootKeyParams() as SNRootKeyParams
|
||||
}
|
||||
|
||||
public async computeRootKey(password: string, keyParams: SNRootKeyParams): Promise<RootKeyInterface> {
|
||||
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(
|
||||
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)
|
||||
}
|
||||
@@ -291,8 +306,8 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
}
|
||||
}
|
||||
|
||||
private async recomputeAccountKeyParams(): Promise<SNRootKeyParams | undefined> {
|
||||
const rawKeyParams = await this.storageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
private recomputeAccountKeyParams(): SNRootKeyParams | undefined {
|
||||
const rawKeyParams = this.storageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
||||
|
||||
if (!rawKeyParams) {
|
||||
return
|
||||
@@ -308,10 +323,12 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
*/
|
||||
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)
|
||||
@@ -371,7 +388,7 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
if (this.keyMode === KeyMode.WrapperOnly || this.keyMode === KeyMode.RootKeyPlusWrapper) {
|
||||
if (this.keyMode === KeyMode.WrapperOnly) {
|
||||
this.setRootKeyInstance(wrappingKey)
|
||||
await this.reencryptItemsKeys()
|
||||
await this.reencryptApplicableItemsAfterUserRootKeyChange()
|
||||
} else {
|
||||
await this.wrapAndPersistRootKey(wrappingKey)
|
||||
}
|
||||
@@ -487,35 +504,65 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
}
|
||||
|
||||
private getItemsKeys() {
|
||||
return this.itemManager.getDisplayableItemsKeys()
|
||||
return this.items.getDisplayableItemsKeys()
|
||||
}
|
||||
|
||||
public async encrypPayloadWithKeyLookup(payload: DecryptedPayloadInterface): Promise<EncryptedParameters> {
|
||||
const key = this.getRootKey()
|
||||
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)
|
||||
return this.encryptPayload(payload, key, signingKeyPair)
|
||||
}
|
||||
|
||||
public async encryptPayloadsWithKeyLookup(payloads: DecryptedPayloadInterface[]): Promise<EncryptedParameters[]> {
|
||||
return Promise.all(payloads.map((payload) => this.encrypPayloadWithKeyLookup(payload)))
|
||||
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): Promise<EncryptedParameters> {
|
||||
return encryptPayload(payload, key, this.operatorManager)
|
||||
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) {
|
||||
return Promise.all(payloads.map((payload) => this.encryptPayload(payload, key)))
|
||||
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> {
|
||||
const key = this.getRootKey()
|
||||
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 {
|
||||
@@ -530,7 +577,7 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
|
||||
public async decryptPayload<C extends ItemContent = ItemContent>(
|
||||
payload: EncryptedPayloadInterface,
|
||||
key: RootKeyInterface,
|
||||
key: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<DecryptedParameters<C> | ErrorDecryptingParameters> {
|
||||
return decryptPayload(payload, key, this.operatorManager)
|
||||
}
|
||||
@@ -543,25 +590,63 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
|
||||
public async decryptPayloads<C extends ItemContent = ItemContent>(
|
||||
payloads: EncryptedPayloadInterface[],
|
||||
key: RootKeyInterface,
|
||||
key: RootKeyInterface | KeySystemRootKeyInterface,
|
||||
): Promise<(DecryptedParameters<C> | ErrorDecryptingParameters)[]> {
|
||||
return Promise.all(payloads.map((payload) => this.decryptPayload<C>(payload, key)))
|
||||
}
|
||||
|
||||
/**
|
||||
* When the root key changes (non-null only), we must re-encrypt all items
|
||||
* keys with this new root key (by simply re-syncing).
|
||||
*/
|
||||
public async reencryptItemsKeys(): Promise<void> {
|
||||
const itemsKeys = this.getItemsKeys()
|
||||
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
|
||||
}
|
||||
|
||||
if (itemsKeys.length > 0) {
|
||||
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.itemManager.setItemsDirty(itemsKeys)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,14 +684,13 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
})
|
||||
|
||||
for (const key of defaultKeys) {
|
||||
await this.itemManager.changeItemsKey(key, (mutator) => {
|
||||
await this.mutator.changeItemsKey(key, (mutator) => {
|
||||
mutator.isDefault = false
|
||||
})
|
||||
}
|
||||
|
||||
const itemsKey = (await this.itemManager.insertItem(itemTemplate)) as ItemsKeyInterface
|
||||
|
||||
await this.itemManager.changeItemsKey(itemsKey, (mutator) => {
|
||||
const itemsKey = await this.mutator.insertItem<ItemsKeyInterface>(itemTemplate)
|
||||
await this.mutator.changeItemsKey(itemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
|
||||
@@ -618,10 +702,10 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
const newDefaultItemsKey = await this.createNewDefaultItemsKey()
|
||||
|
||||
const rollback = async () => {
|
||||
await this.itemManager.setItemToBeDeleted(newDefaultItemsKey)
|
||||
await this.mutator.setItemToBeDeleted(newDefaultItemsKey)
|
||||
|
||||
if (currentDefaultItemsKey) {
|
||||
await this.itemManager.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
mutator.isDefault = true
|
||||
})
|
||||
}
|
||||
@@ -629,19 +713,4 @@ export class RootKeyEncryptionService extends AbstractService<RootKeyServiceEven
|
||||
|
||||
return rollback
|
||||
}
|
||||
|
||||
override async getDiagnostics(): Promise<DiagnosticInfo | undefined> {
|
||||
return {
|
||||
rootKeyEncryption: {
|
||||
hasRootKey: this.rootKey != undefined,
|
||||
keyMode: KeyMode[this.keyMode],
|
||||
hasRootKeyWrapper: await this.hasRootKeyWrapper(),
|
||||
hasAccount: this.hasAccount(),
|
||||
hasRootKeyEncryptionSource: this.hasRootKeyEncryptionSource(),
|
||||
hasPasscode: this.hasPasscode(),
|
||||
getEncryptionSourceVersion: this.hasRootKeyEncryptionSource() && (await this.getEncryptionSourceVersion()),
|
||||
getUserVersion: this.getUserVersion(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user