-
Notifications
You must be signed in to change notification settings - Fork 34
[PB-4610] Opaque PoC #1690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[PB-4610] Opaque PoC #1690
Changes from 13 commits
7909f63
496bc7a
1839209
9489a85
6a1dcf1
628aa90
04077cc
44db502
1bc9d59
bf329f2
916fc9c
74868ca
76922ee
10cefd0
c65e17c
080b94e
872dd0c
c974d3f
945e306
5d32592
3fee78b
e1223b0
0ab63be
4fba4dd
ee02788
739f848
994b85e
5d0350e
8bcdaa8
b9be060
b97e446
e54ff76
c1dda59
cac4356
baf3538
7b8bca5
8a70388
435b604
19f663a
6a35e91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ import { twoFactorRegexPattern } from 'app/core/services/validation.service'; | |
| import { RootState } from 'app/store'; | ||
| import { useAppDispatch } from 'app/store/hooks'; | ||
| import { userActions } from 'app/store/slices/user'; | ||
| import authService, { authenticateUser, is2FANeeded } from '../../services/auth.service'; | ||
| import authService, { authenticateUser } from '../../services/auth.service'; | ||
|
|
||
| import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; | ||
| import { WarningCircle } from '@phosphor-icons/react'; | ||
|
|
@@ -29,6 +29,7 @@ import TextInput from '../TextInput/TextInput'; | |
| import { AuthMethodTypes } from 'app/payment/types'; | ||
| import vpnAuthService from 'app/auth/services/vpnAuth.service'; | ||
| import envService from 'app/core/services/env.service'; | ||
| import { logInOpaque, is2FAorOpaqueNeeded } from '../../services/auth.opaque'; | ||
|
|
||
| const showNotification = ({ text, isError }: { text: string; isError: boolean }) => { | ||
| notificationsService.show({ | ||
|
|
@@ -168,7 +169,7 @@ export default function LogIn(): JSX.Element { | |
| const { email, password } = formData; | ||
|
|
||
| try { | ||
| const isTfaEnabled = await is2FANeeded(email); | ||
| const { tfaEnabled: isTfaEnabled, opaqueLogin } = await is2FAorOpaqueNeeded(email); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we hide these things under the same functions? Otherwise we mix component-related logic with authentication low-level details, which is far from ideal in terms of maintenance and testability. Let's hide these things under the auth layer.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
|
||
| if (!isTfaEnabled || showTwoFactor) { | ||
| const loginType: 'desktop' | 'web' = isUniversalLinkMode ? 'desktop' : 'web'; | ||
|
|
@@ -181,7 +182,7 @@ export default function LogIn(): JSX.Element { | |
| loginType, | ||
| }; | ||
|
|
||
| const { token, user, mnemonic } = await authenticateUser(authParams); | ||
| const { token, user, mnemonic } = await (opaqueLogin ? logInOpaque(authParams) : authenticateUser(authParams)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| handleSuccessfulAuth(token, user, mnemonic); | ||
| } else { | ||
| setShowTwoFactor(true); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { generateUserSecrets, encryptUserKeysAndMnemonic, decryptUserKeysAndMnemonic } from './auth.crypto'; | ||
|
|
||
| describe('Test auth crypto functions', () => { | ||
| it('test enc/dec of user sercrets', async () => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use When-then legends and define clearly what each case is doing / testing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| const { keys, mnemonic } = await generateUserSecrets(); | ||
| const exportKey = 'Srp6AzybbyludWuaVwGoHa1C2H0Qtv7JR0sKGLSWe8Ho8_q9hezfYD2RYb9IUrW999pH4VlABgDLse484zAapg'; | ||
|
|
||
| const { encMnemonic, encKeys } = await encryptUserKeysAndMnemonic(keys, mnemonic, exportKey); | ||
|
|
||
| const { keys: dec_keys, mnemonic: dec_mnemonic } = await decryptUserKeysAndMnemonic( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using snake_case, the convention is camelCase
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| encMnemonic, | ||
| encKeys, | ||
| exportKey, | ||
| ); | ||
|
|
||
| expect(keys).toStrictEqual(dec_keys); | ||
| expect(mnemonic).toEqual(dec_mnemonic); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { symmetric, utils, deriveKey } from 'internxt-crypto'; | ||
| import { UserKeys } from '@internxt/sdk'; | ||
|
|
||
| import { generateNewKeys } from 'app/crypto/services/pgp.service'; | ||
| export async function encryptUserKeysAndMnemonic( | ||
| userKeys: UserKeys, | ||
| mnemonic: string, | ||
| exportKey: string, | ||
| ): Promise<{ encMnemonic: string; encKeys: UserKeys }> { | ||
| const cryptoKey = await symmetric.deriveSymmetricCryptoKey(exportKey); | ||
| const key = utils.UTF8ToUint8(userKeys.ecc.privateKey); | ||
| const encPrivateKey = await symmetric.encryptSymmetrically(cryptoKey, key, 'user-private-key'); | ||
| const keyKyber = utils.base64ToUint8Array(userKeys.kyber.privateKey); | ||
| const encPrivateKyberKey = await symmetric.encryptSymmetrically(cryptoKey, keyKyber, 'user-private-kyber-key'); | ||
| const mnemonicArray = utils.UTF8ToUint8(mnemonic); | ||
| const mnemonicCipher = await symmetric.encryptSymmetrically(cryptoKey, mnemonicArray, 'user-mnemonic'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extract the constants to a constants file to reuse it in the different necessary points
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| const encMnemonic = utils.ciphertextToBase64(mnemonicCipher); | ||
|
|
||
| const encKeys: UserKeys = { | ||
| ecc: { | ||
| privateKey: utils.ciphertextToBase64(encPrivateKey), | ||
| publicKey: userKeys.ecc.publicKey, | ||
| }, | ||
| kyber: { | ||
| privateKey: utils.ciphertextToBase64(encPrivateKyberKey), | ||
| publicKey: userKeys.kyber.publicKey, | ||
| }, | ||
| }; | ||
| return { encMnemonic, encKeys }; | ||
| } | ||
|
|
||
| export async function decryptUserKeysAndMnemonic( | ||
| encMnemonic: string, | ||
| encKeys: UserKeys, | ||
| exportKey: string, | ||
| ): Promise<{ keys: UserKeys; mnemonic: string }> { | ||
| const cryptoKey = await symmetric.deriveSymmetricCryptoKey(exportKey); | ||
| const encKey = utils.base64ToCiphertext(encKeys.ecc.privateKey); | ||
| const privateKey = await symmetric.decryptSymmetrically(cryptoKey, encKey, 'user-private-key'); | ||
| const encKyberKey = utils.base64ToCiphertext(encKeys.kyber.privateKey); | ||
| const privateKyberKey = await symmetric.decryptSymmetrically(cryptoKey, encKyberKey, 'user-private-kyber-key'); | ||
| const encMnemonicArray = utils.base64ToCiphertext(encMnemonic); | ||
| const mnemonicArray = await symmetric.decryptSymmetrically(cryptoKey, encMnemonicArray, 'user-mnemonic'); | ||
| const mnemonic = utils.uint8ToUTF8(mnemonicArray); | ||
|
|
||
| const keys: UserKeys = { | ||
| ecc: { | ||
| privateKey: utils.uint8ToUTF8(privateKey), | ||
| publicKey: encKeys.ecc.publicKey, | ||
| }, | ||
| kyber: { | ||
| privateKey: utils.uint8ArrayToBase64(privateKyberKey), | ||
| publicKey: encKeys.kyber.publicKey, | ||
| }, | ||
| }; | ||
| return { keys, mnemonic }; | ||
| } | ||
|
|
||
| export const encryptSessionKey = async ( | ||
| password: string, | ||
| sessionKey: string, | ||
| ): Promise<{ sessionKeyEnc: string; salt: string }> => { | ||
| const { key, salt } = await deriveKey.getKeyFromPassword(password); | ||
| const cryptoKey = await symmetric.importSymmetricCryptoKey(key); | ||
| const sessionKeyArray = utils.base64ToUint8Array(sessionKey); | ||
| const sessionKeyEncCipher = await symmetric.encryptSymmetrically(cryptoKey, sessionKeyArray, 'UserSessionKey'); | ||
| const sessionKeyEnc = utils.ciphertextToBase64(sessionKeyEncCipher); | ||
| return { sessionKeyEnc, salt }; | ||
| }; | ||
|
|
||
| export const decryptSessionKey = async (password: string, sessionKeyEnc: string, salt: string): Promise<string> => { | ||
| const keyBytes = await deriveKey.getKeyFromPasswordAndSalt(password, salt); | ||
| const key = await symmetric.importSymmetricCryptoKey(keyBytes); | ||
| const sessionKeyCipher = utils.base64ToCiphertext(sessionKeyEnc); | ||
| const sessionKeyArray = await symmetric.decryptSymmetrically(key, sessionKeyCipher, 'UserSessionKey'); | ||
| const sessionKey = utils.uint8ArrayToBase64(sessionKeyArray); | ||
| const urlSafeSessionKey = sessionKey.replace(/\+/g, '-').replace(/\//g, '_').replace(/=$/, '').replace(/=$/, ''); | ||
| return urlSafeSessionKey; | ||
| }; | ||
|
|
||
| export const generateUserSecrets = async (): Promise<{ keys: UserKeys; mnemonic: string }> => { | ||
| const mnemonic = utils.genMnemonic(256); | ||
| const { privateKeyArmored, publicKeyArmored, publicKyberKeyBase64, privateKyberKeyBase64 } = await generateNewKeys(); | ||
| const keys: UserKeys = { | ||
| ecc: { privateKey: privateKeyArmored, publicKey: publicKeyArmored }, | ||
| kyber: { | ||
| privateKey: privateKyberKeyBase64, | ||
| publicKey: publicKyberKeyBase64, | ||
| }, | ||
| }; | ||
| return { keys, mnemonic }; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in the auth service, let's not create 'helper' files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done