feat: add @standardnotes/encryption package (#1199)

* feat: add @standardnotes/encryption package

* fix: mobile dependency on encryption package

* fix: order of build & lint in pr workflows

* fix: web dependency on encryption package

* fix: remove encryption package composite configuration

* fix: import order
This commit is contained in:
Karol Sójko
2022-07-05 10:06:03 +02:00
committed by GitHub
parent 60273785c2
commit e5771fcbde
70 changed files with 4682 additions and 27 deletions

View File

@@ -0,0 +1,95 @@
import {
DecryptedPayloadInterface,
EncryptedPayloadInterface,
isDecryptedPayload,
ItemsKeyContent,
RootKeyInterface,
} from '@standardnotes/models'
import {
ChallengePrompt,
ChallengeReason,
ChallengeServiceInterface,
ChallengeValidation,
} from '@standardnotes/services'
import { EncryptionProvider } from '../../Service/Encryption/EncryptionProvider'
import { SNRootKeyParams } from '../RootKey/RootKeyParams'
import { KeyRecoveryStrings } from './KeyRecoveryStrings'
export async function DecryptItemsKeyWithUserFallback(
itemsKey: EncryptedPayloadInterface,
encryptor: EncryptionProvider,
challengor: ChallengeServiceInterface,
): Promise<DecryptedPayloadInterface<ItemsKeyContent> | 'failed' | 'aborted'> {
const decryptionResult = await encryptor.decryptSplitSingle<ItemsKeyContent>({
usesRootKeyWithKeyLookup: {
items: [itemsKey],
},
})
if (isDecryptedPayload(decryptionResult)) {
return decryptionResult
}
const secondDecryptionResult = await DecryptItemsKeyByPromptingUser(itemsKey, encryptor, challengor)
if (secondDecryptionResult === 'aborted' || secondDecryptionResult === 'failed') {
return secondDecryptionResult
}
return secondDecryptionResult.decryptedKey
}
export async function DecryptItemsKeyByPromptingUser(
itemsKey: EncryptedPayloadInterface,
encryptor: EncryptionProvider,
challengor: ChallengeServiceInterface,
keyParams?: SNRootKeyParams,
): Promise<
| {
decryptedKey: DecryptedPayloadInterface<ItemsKeyContent>
rootKey: RootKeyInterface
}
| 'failed'
| 'aborted'
> {
if (!keyParams) {
keyParams = encryptor.getKeyEmbeddedKeyParams(itemsKey)
}
if (!keyParams) {
return 'failed'
}
const challenge = challengor.createChallenge(
[new ChallengePrompt(ChallengeValidation.None, undefined, undefined, true)],
ChallengeReason.Custom,
true,
KeyRecoveryStrings.KeyRecoveryLoginFlowPrompt(keyParams),
KeyRecoveryStrings.KeyRecoveryPasswordRequired,
)
const response = await challengor.promptForChallengeResponse(challenge)
if (!response) {
return 'aborted'
}
const password = response.values[0].value as string
const rootKey = await encryptor.computeRootKey(password, keyParams)
const secondDecryptionResult = await encryptor.decryptSplitSingle<ItemsKeyContent>({
usesRootKey: {
items: [itemsKey],
key: rootKey,
},
})
challengor.completeChallenge(challenge)
if (isDecryptedPayload(secondDecryptionResult)) {
return { decryptedKey: secondDecryptionResult, rootKey }
}
return 'failed'
}

View File

@@ -0,0 +1,32 @@
import { KeyParamsOrigination } from '@standardnotes/common'
import { SNRootKeyParams } from '../RootKey/RootKeyParams'
export const KeyRecoveryStrings = {
KeyRecoveryLoginFlowPrompt: (keyParams: SNRootKeyParams) => {
const dateString = keyParams.createdDate?.toLocaleString()
switch (keyParams.origination) {
case KeyParamsOrigination.EmailChange:
return `Enter your account password as it was when you changed your email on ${dateString}.`
case KeyParamsOrigination.PasswordChange:
return `Enter your account password after it was changed on ${dateString}.`
case KeyParamsOrigination.Registration:
return `Enter your account password as it was when you registered ${dateString}.`
case KeyParamsOrigination.ProtocolUpgrade:
return `Enter your account password as it was when you upgraded your encryption version on ${dateString}.`
case KeyParamsOrigination.PasscodeChange:
return `Enter your application passcode after it was changed on ${dateString}.`
case KeyParamsOrigination.PasscodeCreate:
return `Enter your application passcode as it was when you created it on ${dateString}.`
default:
throw Error('Unhandled KeyParamsOrigination case for KeyRecoveryLoginFlowPrompt')
}
},
KeyRecoveryLoginFlowReason: 'Your account password is required to revalidate your session.',
KeyRecoveryLoginFlowInvalidPassword: 'Incorrect credentials entered. Please try again.',
KeyRecoveryRootKeyReplaced: 'Your credentials have successfully been updated.',
KeyRecoveryPasscodeRequiredTitle: 'Passcode Required',
KeyRecoveryPasscodeRequiredText: 'You must enter your passcode in order to save your new credentials.',
KeyRecoveryPasswordRequired: 'Your account password is required to recover an encryption key.',
KeyRecoveryKeyRecovered: 'Your key has successfully been recovered.',
KeyRecoveryUnableToRecover: 'Unable to recover your key with the attempted password. Please try again.',
}