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 = [],
content = [],
trash = [],
vaultKey = [],
vaultKey,
settings = [],
attachments = [],
} = serverResponse;

View File

@@ -159,6 +159,45 @@ class UserManager {
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() {
let token = await this.tokenManager.getAccessToken();
if (!token) return;

View File

@@ -12,36 +12,25 @@ export default class Attachments extends Collection {
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 {{
* iv: string,
* salt: string,
* length: number,
* alg: string,
* hash: string,
* hashType: string,
* filename: string,
* type: string
* type: string,
* salt: string,
* key: {}
* }} attachment
* @param {string} noteId Optional as attachments will be parsed at extraction time
* @returns
*/
add(attachment, noteId) {
if (!attachment)
return console.error("attachment or noteId cannot be null");
async add(attachment, noteId) {
if (!attachment) return console.error("attachment cannot be undefined");
if (attachment.remote) return this._collection.addItem(attachment);
if (!attachment.hash) throw new Error("Please provide attachment hash.");
const oldAttachment = this.all.find(
@@ -55,21 +44,23 @@ export default class Attachments extends Collection {
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;
if (
!iv ||
!salt ||
!length ||
!alg ||
!hash ||
!hashType ||
!filename ||
!type
!type ||
!salt ||
!key
)
throw new Error("Could not add attachment: all properties are required.");
const encryptedKey = await this._encryptKey(key);
const attachmentItem = {
id: id(),
noteIds: noteId ? [noteId] : [],
@@ -77,6 +68,7 @@ export default class Attachments extends Collection {
salt,
length,
alg,
key: encryptedKey,
metadata: {
hash,
hashType,
@@ -91,6 +83,17 @@ export default class Attachments extends Collection {
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) {
const attachment = this.all.find((a) => a.metadata.hash === hash);
if (!attachment || !deleteItem(attachment.noteIds, noteId)) return;
@@ -253,4 +256,28 @@ export default class Attachments extends Collection {
get all() {
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 {
constructor(context) {
this.storage = context;
@@ -46,4 +48,10 @@ export default class 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);
}
}