1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-06-15 23:00:36 +02:00

All: Fixes #2591: Handle invalid UTF-8 data when encrypting

This commit is contained in:
Laurent Cozic
2020-03-06 19:11:51 +00:00
parent 40eba9e95e
commit c6c4e950db
3 changed files with 62 additions and 12 deletions

View File

@ -28,7 +28,7 @@ class EncryptionService {
this.chunkSize_ = 5000;
this.loadedMasterKeys_ = {};
this.activeMasterKeyId_ = null;
this.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL;
this.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL_1A;
this.defaultMasterKeyEncryptionMethod_ = EncryptionService.METHOD_SJCL_2;
this.logger_ = new Logger();
@ -274,6 +274,12 @@ class EncryptionService {
return true;
}
wrapSjclError(sjclError) {
const error = new Error(sjclError.message);
error.stack = sjclError.stack;
return error;
}
async encrypt(method, key, plainText) {
if (!method) throw new Error('Encryption method is required');
if (!key) throw new Error('Encryption key is required');
@ -294,8 +300,28 @@ class EncryptionService {
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
throw new Error(`SJCL error: ${error.message}`);
throw this.wrapSjclError(error);
}
}
// 2020-03-06: Added method to fix https://github.com/laurent22/joplin/issues/2591
// Also took the opportunity to change number of key derivations, per Isaac Potoczny's suggestion
if (method === EncryptionService.METHOD_SJCL_1A) {
try {
// We need to escape the data because SJCL uses encodeURIComponent to process the data and it only
// accepts UTF-8 data, or else it throws an error. And the notes might occasionally contain
// invalid UTF-8 data. Fixes https://github.com/laurent22/joplin/issues/2591
return sjcl.json.encrypt(key, escape(plainText), {
v: 1, // version
iter: 101, // Since the master key already uses key derivations and is secure, additional iteration here aren't necessary, which will make decryption faster. SJCL enforces an iter strictly greater than 100
ks: 128, // Key size - "128 bits should be secure enough"
ts: 64, // ???
mode: 'ocb2', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
// "adata":"", // Associated Data - not needed?
cipher: 'aes',
});
} catch (error) {
throw this.wrapSjclError(error);
}
}
@ -312,8 +338,7 @@ class EncryptionService {
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
throw new Error(`SJCL error: ${error.message}`);
throw this.wrapSjclError(error);
}
}
@ -330,8 +355,7 @@ class EncryptionService {
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
throw new Error(`SJCL error: ${error.message}`);
throw this.wrapSjclError(error);
}
}
@ -347,8 +371,7 @@ class EncryptionService {
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
throw new Error(`SJCL error: ${error.message}`);
throw this.wrapSjclError(error);
}
}
@ -363,7 +386,13 @@ class EncryptionService {
if (!this.isValidEncryptionMethod(method)) throw new Error(`Unknown decryption method: ${method}`);
try {
return sjcl.json.decrypt(key, cipherText);
const output = sjcl.json.decrypt(key, cipherText);
if (method === EncryptionService.METHOD_SJCL_1A) {
return unescape(output);
} else {
return output;
}
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
throw new Error(error.message);
@ -618,7 +647,7 @@ class EncryptionService {
}
isValidEncryptionMethod(method) {
return [EncryptionService.METHOD_SJCL, EncryptionService.METHOD_SJCL_2, EncryptionService.METHOD_SJCL_3, EncryptionService.METHOD_SJCL_4].indexOf(method) >= 0;
return [EncryptionService.METHOD_SJCL, EncryptionService.METHOD_SJCL_1A, EncryptionService.METHOD_SJCL_2, EncryptionService.METHOD_SJCL_3, EncryptionService.METHOD_SJCL_4].indexOf(method) >= 0;
}
async itemIsEncrypted(item) {
@ -640,6 +669,7 @@ EncryptionService.METHOD_SJCL = 1;
EncryptionService.METHOD_SJCL_2 = 2;
EncryptionService.METHOD_SJCL_3 = 3;
EncryptionService.METHOD_SJCL_4 = 4;
EncryptionService.METHOD_SJCL_1A = 5;
EncryptionService.fsDriver_ = null;