feat: add 3-layer-encryption for attachments

This commit is contained in:
thecodrr
2021-10-25 11:35:00 +05:00
parent 69defdb367
commit 51794e001e
4 changed files with 94 additions and 20 deletions

View File

@@ -78,7 +78,7 @@ class Merger {
notebooks = [], notebooks = [],
content = [], content = [],
trash = [], trash = [],
vaultKey = [], vaultKey,
settings = [], settings = [],
attachments = [], attachments = [],
} = serverResponse; } = serverResponse;

View File

@@ -159,6 +159,45 @@ class UserManager {
return { key, salt: user.salt }; return { key, salt: user.salt };
} }
async getAttachmentsKey() {
let user = await this.getUser();
if (!user) return;
let token = await this.tokenManager.getAccessToken();
if (!token) return;
if (!user.attachmentsKey) {
user = await http.get(`${constants.API_HOST}${ENDPOINTS.user}`, token);
}
const userEncryptionKey = await this.getEncryptionKey();
if (!userEncryptionKey) return;
if (!user.attachmentsKey) {
const key = await this._storage.generateRandomKey();
const encryptedKey = await this._storage.encrypt(
userEncryptionKey,
JSON.stringify(key)
);
user.attachmentsKey = encryptedKey;
await http.patch.json(
`${constants.API_HOST}${ENDPOINTS.user}`,
user,
token
);
await this.setUser(user);
return key;
}
const plainData = await this._storage.decrypt(
userEncryptionKey,
user.attachmentsKey
);
return JSON.parse(plainData);
}
async sendVerificationEmail() { async sendVerificationEmail() {
let token = await this.tokenManager.getAccessToken(); let token = await this.tokenManager.getAccessToken();
if (!token) return; if (!token) return;

View File

@@ -12,36 +12,25 @@ export default class Attachments extends Collection {
this.key = null; this.key = null;
} }
async _getEncryptionKey() {
if (!this.key) this.key = await this._db.user.getEncryptionKey();
if (!this.key)
throw new Error(
"Failed to get user encryption key. Cannot cache attachments."
);
return this.key;
}
/** /**
* *
* @param {{ * @param {{
* iv: string, * iv: string,
* salt: string,
* length: number, * length: number,
* alg: string, * alg: string,
* hash: string, * hash: string,
* hashType: string, * hashType: string,
* filename: string, * filename: string,
* type: string * type: string,
* salt: string,
* key: {}
* }} attachment * }} attachment
* @param {string} noteId Optional as attachments will be parsed at extraction time * @param {string} noteId Optional as attachments will be parsed at extraction time
* @returns * @returns
*/ */
add(attachment, noteId) { async add(attachment, noteId) {
if (!attachment) if (!attachment) return console.error("attachment cannot be undefined");
return console.error("attachment or noteId cannot be null");
if (attachment.remote) return this._collection.addItem(attachment); if (attachment.remote) return this._collection.addItem(attachment);
if (!attachment.hash) throw new Error("Please provide attachment hash."); if (!attachment.hash) throw new Error("Please provide attachment hash.");
const oldAttachment = this.all.find( const oldAttachment = this.all.find(
@@ -55,21 +44,23 @@ export default class Attachments extends Collection {
return this._collection.updateItem(oldAttachment); return this._collection.updateItem(oldAttachment);
} }
const { iv, salt, length, alg, hash, hashType, filename, type } = const { iv, length, alg, hash, hashType, filename, salt, type, key } =
attachment; attachment;
if ( if (
!iv || !iv ||
!salt ||
!length || !length ||
!alg || !alg ||
!hash || !hash ||
!hashType || !hashType ||
!filename || !filename ||
!type !type ||
!salt ||
!key
) )
throw new Error("Could not add attachment: all properties are required."); throw new Error("Could not add attachment: all properties are required.");
const encryptedKey = await this._encryptKey(key);
const attachmentItem = { const attachmentItem = {
id: id(), id: id(),
noteIds: noteId ? [noteId] : [], noteIds: noteId ? [noteId] : [],
@@ -77,6 +68,7 @@ export default class Attachments extends Collection {
salt, salt,
length, length,
alg, alg,
key: encryptedKey,
metadata: { metadata: {
hash, hash,
hashType, hashType,
@@ -91,6 +83,17 @@ export default class Attachments extends Collection {
return this._collection.addItem(attachmentItem); return this._collection.addItem(attachmentItem);
} }
async generateKey() {
await this._getEncryptionKey();
return await this._db.storage.generateRandomKey();
}
async decryptKey(key) {
const encryptionKey = await this._getEncryptionKey();
const plainData = await this._db.storage.decrypt(encryptionKey, key);
return JSON.parse(plainData);
}
async delete(hash, noteId) { async delete(hash, noteId) {
const attachment = this.all.find((a) => a.metadata.hash === hash); const attachment = this.all.find((a) => a.metadata.hash === hash);
if (!attachment || !deleteItem(attachment.noteIds, noteId)) return; if (!attachment || !deleteItem(attachment.noteIds, noteId)) return;
@@ -253,4 +256,28 @@ export default class Attachments extends Collection {
get all() { get all() {
return this._collection.getItems(); return this._collection.getItems();
} }
/**
* @private
*/
async _encryptKey(key) {
const encryptionKey = await this._getEncryptionKey();
const encryptedKey = await this._db.storage.encrypt(
encryptionKey,
JSON.stringify(key)
);
return encryptedKey;
}
/**
* @private
*/
async _getEncryptionKey() {
if (!this.key) this.key = await this._db.user.getAttachmentsKey();
if (!this.key)
throw new Error(
"Failed to get user encryption key. Cannot cache attachments."
);
return this.key;
}
} }

View File

@@ -1,3 +1,5 @@
import { randomBytes } from "../utils/random";
export default class Storage { export default class Storage {
constructor(context) { constructor(context) {
this.storage = context; this.storage = context;
@@ -46,4 +48,10 @@ export default class Storage {
getCryptoKey(name) { getCryptoKey(name) {
return this.storage.getCryptoKey(name); return this.storage.getCryptoKey(name);
} }
async generateRandomKey() {
const passwordBytes = randomBytes(124);
const password = passwordBytes.toString("base64");
return await this.storage.generateCryptoKey(password, false);
}
} }