helpers.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { setRecoveryKey } from "@ente/accounts/api/user";
  2. import { logError } from "@ente/shared/sentry";
  3. import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
  4. import { getToken } from "@ente/shared/storage/localStorage/helpers";
  5. import { SESSION_KEYS, setKey } from "@ente/shared/storage/sessionStorage";
  6. import { getActualKey } from "@ente/shared/user";
  7. import { KeyAttributes } from "@ente/shared/user/types";
  8. import isElectron from "is-electron";
  9. import ComlinkCryptoWorker from ".";
  10. import ElectronAPIs from "../electron";
  11. import { addLogLine } from "../logging";
  12. const LOGIN_SUB_KEY_LENGTH = 32;
  13. const LOGIN_SUB_KEY_ID = 1;
  14. const LOGIN_SUB_KEY_CONTEXT = "loginctx";
  15. const LOGIN_SUB_KEY_BYTE_LENGTH = 16;
  16. export async function decryptAndStoreToken(
  17. keyAttributes: KeyAttributes,
  18. masterKey: string,
  19. ) {
  20. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  21. const user = getData(LS_KEYS.USER);
  22. let decryptedToken = null;
  23. const { encryptedToken } = user;
  24. if (encryptedToken && encryptedToken.length > 0) {
  25. const secretKey = await cryptoWorker.decryptB64(
  26. keyAttributes.encryptedSecretKey,
  27. keyAttributes.secretKeyDecryptionNonce,
  28. masterKey,
  29. );
  30. const urlUnsafeB64DecryptedToken = await cryptoWorker.boxSealOpen(
  31. encryptedToken,
  32. keyAttributes.publicKey,
  33. secretKey,
  34. );
  35. const decryptedTokenBytes = await cryptoWorker.fromB64(
  36. urlUnsafeB64DecryptedToken,
  37. );
  38. decryptedToken = await cryptoWorker.toURLSafeB64(decryptedTokenBytes);
  39. setData(LS_KEYS.USER, {
  40. ...user,
  41. token: decryptedToken,
  42. encryptedToken: null,
  43. });
  44. }
  45. }
  46. // We encrypt the masterKey, with an intermediate key derived from the
  47. // passphrase (with Interactive mem and ops limits) to avoid saving it to local
  48. // storage in plain text. This means that on the web user will always have to
  49. // enter their passphrase to access their masterKey.
  50. export async function generateAndSaveIntermediateKeyAttributes(
  51. passphrase: string,
  52. existingKeyAttributes: KeyAttributes,
  53. key: string,
  54. ): Promise<KeyAttributes> {
  55. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  56. const intermediateKekSalt = await cryptoWorker.generateSaltToDeriveKey();
  57. const intermediateKek = await cryptoWorker.deriveInteractiveKey(
  58. passphrase,
  59. intermediateKekSalt,
  60. );
  61. const encryptedKeyAttributes = await cryptoWorker.encryptToB64(
  62. key,
  63. intermediateKek.key,
  64. );
  65. const intermediateKeyAttributes = Object.assign(existingKeyAttributes, {
  66. kekSalt: intermediateKekSalt,
  67. encryptedKey: encryptedKeyAttributes.encryptedData,
  68. keyDecryptionNonce: encryptedKeyAttributes.nonce,
  69. opsLimit: intermediateKek.opsLimit,
  70. memLimit: intermediateKek.memLimit,
  71. });
  72. setData(LS_KEYS.KEY_ATTRIBUTES, intermediateKeyAttributes);
  73. return intermediateKeyAttributes;
  74. }
  75. export const generateLoginSubKey = async (kek: string) => {
  76. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  77. const kekSubKeyString = await cryptoWorker.generateSubKey(
  78. kek,
  79. LOGIN_SUB_KEY_LENGTH,
  80. LOGIN_SUB_KEY_ID,
  81. LOGIN_SUB_KEY_CONTEXT,
  82. );
  83. const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString);
  84. // use first 16 bytes of generated kekSubKey as loginSubKey
  85. const loginSubKey = await cryptoWorker.toB64(
  86. kekSubKey.slice(0, LOGIN_SUB_KEY_BYTE_LENGTH),
  87. );
  88. return loginSubKey;
  89. };
  90. export const saveKeyInSessionStore = async (
  91. keyType: SESSION_KEYS,
  92. key: string,
  93. fromDesktop?: boolean,
  94. ) => {
  95. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  96. const sessionKeyAttributes =
  97. await cryptoWorker.generateKeyAndEncryptToB64(key);
  98. setKey(keyType, sessionKeyAttributes);
  99. addLogLine("fromDesktop", fromDesktop);
  100. if (
  101. isElectron() &&
  102. !fromDesktop &&
  103. keyType === SESSION_KEYS.ENCRYPTION_KEY
  104. ) {
  105. ElectronAPIs.setEncryptionKey(key);
  106. }
  107. };
  108. export async function encryptWithRecoveryKey(key: string) {
  109. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  110. const hexRecoveryKey = await getRecoveryKey();
  111. const recoveryKey = await cryptoWorker.fromHex(hexRecoveryKey);
  112. const encryptedKey = await cryptoWorker.encryptToB64(key, recoveryKey);
  113. return encryptedKey;
  114. }
  115. export const getRecoveryKey = async () => {
  116. let recoveryKey: string = null;
  117. try {
  118. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  119. const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
  120. const {
  121. recoveryKeyEncryptedWithMasterKey,
  122. recoveryKeyDecryptionNonce,
  123. } = keyAttributes;
  124. const masterKey = await getActualKey();
  125. if (recoveryKeyEncryptedWithMasterKey) {
  126. recoveryKey = await cryptoWorker.decryptB64(
  127. recoveryKeyEncryptedWithMasterKey,
  128. recoveryKeyDecryptionNonce,
  129. masterKey,
  130. );
  131. } else {
  132. recoveryKey = await createNewRecoveryKey();
  133. }
  134. recoveryKey = await cryptoWorker.toHex(recoveryKey);
  135. return recoveryKey;
  136. } catch (e) {
  137. console.log(e);
  138. logError(e, "getRecoveryKey failed");
  139. throw e;
  140. }
  141. };
  142. // Used only for legacy users for whom we did not generate recovery keys during
  143. // sign up
  144. async function createNewRecoveryKey() {
  145. const masterKey = await getActualKey();
  146. const existingAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
  147. const cryptoWorker = await ComlinkCryptoWorker.getInstance();
  148. const recoveryKey = await cryptoWorker.generateEncryptionKey();
  149. const encryptedMasterKey = await cryptoWorker.encryptToB64(
  150. masterKey,
  151. recoveryKey,
  152. );
  153. const encryptedRecoveryKey = await cryptoWorker.encryptToB64(
  154. recoveryKey,
  155. masterKey,
  156. );
  157. const recoveryKeyAttributes = {
  158. masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData,
  159. masterKeyDecryptionNonce: encryptedMasterKey.nonce,
  160. recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData,
  161. recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce,
  162. };
  163. await setRecoveryKey(getToken(), recoveryKeyAttributes);
  164. const updatedKeyAttributes = Object.assign(
  165. existingAttributes,
  166. recoveryKeyAttributes,
  167. );
  168. setData(LS_KEYS.KEY_ATTRIBUTES, updatedKeyAttributes);
  169. return recoveryKey;
  170. }