Merge pull request #49 from ente-io/intermediate-key

Intermediate key
This commit is contained in:
Abhinav-grd 2021-04-03 10:31:36 +05:30 committed by GitHub
commit c95fa68326
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 131 additions and 34 deletions

View file

@ -9,10 +9,12 @@ import { Formik, FormikHelpers } from 'formik';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { useRouter } from 'next/router';
import * as Yup from 'yup';
import { keyAttributes } from 'types';
import { KeyAttributes } from 'types';
import { setKey, SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker, { generateIntermediateKeyAttributes } from 'utils/crypto';
import { logoutUser } from 'services/userService';
import { isFirstLogin } from 'utils/storage';
import { Spinner } from 'react-bootstrap';
const Image = styled.img`
width: 200px;
@ -26,7 +28,7 @@ interface formValues {
export default function Credentials() {
const router = useRouter();
const [keyAttributes, setKeyAttributes] = useState<keyAttributes>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
const [loading, setLoading] = useState(false);
useEffect(() => {
@ -57,7 +59,7 @@ export default function Credentials() {
passphrase,
keyAttributes.kekSalt,
keyAttributes.opsLimit,
keyAttributes.memLimit,
keyAttributes.memLimit
);
try {
@ -66,6 +68,14 @@ export default function Credentials() {
keyAttributes.keyDecryptionNonce,
kek
);
if (isFirstLogin()) {
const intermediateKeyAttributes = await generateIntermediateKeyAttributes(
passphrase,
keyAttributes,
key
);
setData(LS_KEYS.KEY_ATTRIBUTES, intermediateKeyAttributes);
}
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
key
);
@ -128,7 +138,7 @@ export default function Credentials() {
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(
touched.passphrase &&
errors.passphrase
errors.passphrase
)}
disabled={loading}
autoFocus={true}
@ -138,7 +148,11 @@ export default function Credentials() {
</Form.Control.Feedback>
</Form.Group>
<Button block type="submit" disabled={loading}>
{constants.VERIFY_PASSPHRASE}
{loading ? (
<Spinner animation="border" />
) : (
constants.VERIFY_PASSPHRASE
)}
</Button>
<br />
<div>

View file

@ -29,7 +29,8 @@ import ConfirmDialog, { CONFIRM_ACTION } from 'components/ConfirmDialog';
import FullScreenDropZone from 'components/FullScreenDropZone';
import Sidebar from 'components/Sidebar';
import UploadButton from './components/UploadButton';
import { checkConnectivity } from 'utils/common/utilFunctions';
import { checkConnectivity } from 'utils/common';
import { isFirstLogin, setIsFirstLogin } from 'utils/storage';
import { logoutUser } from 'services/userService';
const DATE_CONTAINER_HEIGHT = 45;
const IMAGE_CONTAINER_HEIGHT = 200;
@ -170,7 +171,8 @@ export default function Gallery(props: Props) {
return;
}
const main = async () => {
setIsFirstLoad((await getCollectionUpdationTime()) == 0);
setIsFirstLoad(isFirstLogin());
setIsFirstLogin(false);
const data = await localFiles();
const collections = await getLocalCollections();
const nonEmptyCollections = getNonEmptyCollections(

View file

@ -12,7 +12,9 @@ import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { useRouter } from 'next/router';
import { getKey, SESSION_KEYS, setKey } from 'utils/storage/sessionStorage';
import { B64EncryptionResult } from 'services/uploadService';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker from 'utils/crypto';
import { generateIntermediateKeyAttributes } from 'utils/crypto';
import { Spinner } from 'react-bootstrap';
const Image = styled.img`
width: 200px;
@ -25,7 +27,7 @@ interface formValues {
confirm: string;
}
interface KEK {
export interface KEK {
key: string;
opsLimit: number;
memLimit: number;
@ -99,7 +101,15 @@ export default function Generate() {
getData(LS_KEYS.USER).name,
keyAttributes
);
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
setData(
LS_KEYS.KEY_ATTRIBUTES,
await generateIntermediateKeyAttributes(
passphrase,
keyAttributes,
key
)
);
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
key
@ -196,7 +206,11 @@ export default function Generate() {
disabled={loading}
style={{ marginTop: '28px' }}
>
{constants.SET_PASSPHRASE}
{loading ? (
<Spinner animation="border" />
) : (
constants.SET_PASSPHRASE
)}
</Button>
</Form>
)}

View file

@ -16,6 +16,7 @@ import {
clearFiles,
isTokenValid,
} from 'services/userService';
import { setIsFirstLogin } from 'utils/storage';
const Image = styled.img`
width: 350px;
@ -41,7 +42,6 @@ export default function Verify() {
if (!user?.email) {
router.push('/');
} else if (user.token) {
console.log(user.token);
if (await isTokenValid()) {
router.push('/credentials');
} else {
@ -71,6 +71,7 @@ export default function Verify() {
keyAttributes && setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
subscription && setData(LS_KEYS.SUBSCRIPTION, subscription);
clearFiles();
setIsFirstLogin(true);
if (resp.data.keyAttributes?.encryptedKey) {
router.push('/credentials');
} else {

View file

@ -7,7 +7,7 @@ import HTTPService from './HTTPService';
import { B64EncryptionResult } from './uploadService';
import { getActualKey, getToken } from 'utils/common/key';
import { user } from './userService';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker from 'utils/crypto';
import { ErrorHandler } from 'utils/common/errorUtil';
const ENDPOINT = getEndpoint();

View file

@ -1,9 +1,9 @@
import { getToken } from 'utils/common/key';
import { file } from './fileService';
import HTTPService from './HTTPService';
import { getEndpoint, getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil';
import { getFileExtension, runningInBrowser } from 'utils/common/utilFunctions';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import { getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil';
import { getFileExtension, runningInBrowser } from 'utils/common';
import CryptoWorker from 'utils/crypto';
const TYPE_HEIC = 'heic';

View file

@ -4,7 +4,7 @@ import localForage from 'utils/storage/localForage';
import { collection } from './collectionService';
import { DataStream, MetadataObject } from './uploadService';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker from 'utils/crypto';
import { getToken } from 'utils/common/key';
import { selectedState } from 'pages/gallery';
import { ErrorHandler } from 'utils/common/errorUtil';

View file

@ -4,9 +4,9 @@ import EXIF from 'exif-js';
import { fileAttribute } from './fileService';
import { collection } from './collectionService';
import { FILE_TYPE } from 'pages/gallery';
import { checkConnectivity } from 'utils/common/utilFunctions';
import { checkConnectivity } from 'utils/common';
import { ErrorHandler } from 'utils/common/errorUtil';
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker from 'utils/crypto';
import * as convert from 'xml-js';
import { ENCRYPTION_CHUNK_SIZE } from 'types';
import { getToken } from 'utils/common/key';

View file

@ -1,5 +1,5 @@
import HTTPService from './HTTPService';
import { keyAttributes } from 'types';
import { KeyAttributes } from 'types';
import { getEndpoint } from 'utils/common/apiUtil';
import { clearKeys } from 'utils/storage/sessionStorage';
import router from 'next/router';
@ -29,7 +29,7 @@ export const verifyOtt = (email: string, ott: string) => {
export const putAttributes = (
token: string,
name: string,
keyAttributes: keyAttributes
keyAttributes: KeyAttributes
) => {
console.log('name ' + name);
return HTTPService.put(

View file

@ -1,9 +1,12 @@
export interface keyAttributes {
export interface KeyAttributes {
kekSalt: string;
encryptedKey: string;
keyDecryptionNonce: string;
opsLimit: number;
memLimit: number;
publicKey: string;
encryptedSecretKey: string;
secretKeyDecryptionNonce: string;
}
export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024;

View file

@ -1,3 +1,4 @@
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { errorCodes } from './errorUtil';
export function checkConnectivity() {

View file

@ -1,4 +1,4 @@
import CryptoWorker from 'utils/crypto/cryptoWorker';
import CryptoWorker from 'utils/crypto';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';

View file

@ -1,8 +0,0 @@
import * as Comlink from 'comlink';
import { runningInBrowser } from 'utils/common/utilFunctions';
const CryptoWorker: any =
runningInBrowser() &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
export default CryptoWorker;

38
src/utils/crypto/index.ts Normal file
View file

@ -0,0 +1,38 @@
import { KEK } from 'pages/generate';
import { B64EncryptionResult } from 'services/uploadService';
import { KeyAttributes } from 'types';
import * as Comlink from 'comlink';
import { runningInBrowser } from 'utils/common';
const CryptoWorker: any =
runningInBrowser() &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
export async function generateIntermediateKeyAttributes(
passphrase,
keyAttributes,
key
): Promise<KeyAttributes> {
const cryptoWorker = await new CryptoWorker();
const intermediateKekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
const intermediateKek: KEK = await cryptoWorker.deriveIntermediateKey(
passphrase,
intermediateKekSalt
);
const encryptedKeyAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(
key,
intermediateKek.key
);
return {
kekSalt: intermediateKekSalt,
encryptedKey: encryptedKeyAttributes.encryptedData,
keyDecryptionNonce: encryptedKeyAttributes.nonce,
publicKey: keyAttributes.publicKey,
encryptedSecretKey: keyAttributes.encryptedSecretKey,
secretKeyDecryptionNonce: keyAttributes.secretKeyDecryptionNonce,
opsLimit: intermediateKek.opsLimit,
memLimit: intermediateKek.memLimit,
};
}
export default CryptoWorker;

View file

@ -294,6 +294,25 @@ export async function deriveSensitiveKey(passphrase: string, salt: string) {
throw null;
}
export async function deriveIntermediateKey(passphrase: string, salt: string) {
await sodium.ready;
const key = await toB64(
sodium.crypto_pwhash(
sodium.crypto_secretbox_KEYBYTES,
await fromString(passphrase),
await fromB64(salt),
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
sodium.crypto_pwhash_ALG_DEFAULT
)
);
return {
key: key,
opsLimit: sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
memLimit: sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
};
}
export async function generateMasterKey() {
await sodium.ready;
return await toB64(sodium.crypto_kdf_keygen());

View file

@ -0,0 +1,8 @@
import { getData, LS_KEYS, setData } from './localStorage';
export const isFirstLogin = () =>
getData(LS_KEYS.IS_FIRST_LOGIN)?.status ?? false;
export function setIsFirstLogin(status) {
setData(LS_KEYS.IS_FIRST_LOGIN, { status });
}

View file

@ -1,4 +1,4 @@
import { runningInBrowser } from 'utils/common/utilFunctions';
import { runningInBrowser } from 'utils/common';
const localForage: LocalForage = runningInBrowser() && require('localforage');
if (runningInBrowser()) {

View file

@ -3,6 +3,7 @@ export enum LS_KEYS {
SESSION = 'session',
KEY_ATTRIBUTES = 'keyAttributes',
SUBSCRIPTION = 'subscription',
IS_FIRST_LOGIN = 'isFirstLogin',
}
export const setData = (key: LS_KEYS, value: object) => {

View file

@ -1,4 +1,4 @@
import { runningInBrowser } from 'utils/common/utilFunctions';
import { runningInBrowser } from 'utils/common';
import englishConstants from './englishConstants';
/** Enums of supported locale */

View file

@ -84,6 +84,10 @@ export class Crypto {
return libsodium.deriveSensitiveKey(passphrase, salt);
}
async deriveIntermediateKey(passphrase, salt) {
return libsodium.deriveIntermediateKey(passphrase, salt);
}
async decryptB64(data, nonce, key) {
return libsodium.decryptB64(data, nonce, key);
}