Add info on the item failing the parse when importing from LastPass (#9068)

* Added more error log info into methods called on openVault from LastPass Direct importer

* Adding error messages to help identify errors on the ParseAccounts and ParseShar methods from the lastpass importer parser.ts

* removed unintended throw

* Revert "Added more error log info into methods called on openVault from LastPass Direct importer"

This reverts commit dc83722c2f.

* Cleaned up error messages on LastPass's parser.ts

* throwing the error message if it exists on the Error object

* Using const on name instead let on parseShar
This commit is contained in:
aj-rosado 2024-05-20 11:46:20 +01:00 committed by GitHub
parent adf7a38f87
commit 4e1173d5cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 233 additions and 219 deletions

View File

@ -32,213 +32,220 @@ export class Parser {
folder: SharedFolder,
options: ParserOptions,
): Promise<Account> {
const placeholder = "decryption failed";
const reader = new BinaryReader(chunk.payload);
let id: string;
try {
const placeholder = "decryption failed";
const reader = new BinaryReader(chunk.payload);
// Read all items
// 0: id
const id = Utils.fromBufferToUtf8(this.readItem(reader));
// Read all items
// 0: id
id = Utils.fromBufferToUtf8(this.readItem(reader));
// 1: name
const name = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 1: name
const name = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 2: group
const group = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 2: group
const group = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 3: url
let url = Utils.fromBufferToUtf8(
this.decodeHexLoose(Utils.fromBufferToUtf8(this.readItem(reader))),
);
// 3: url
let url = Utils.fromBufferToUtf8(
this.decodeHexLoose(Utils.fromBufferToUtf8(this.readItem(reader))),
);
// Ignore "group" accounts. They have no credentials.
if (url == "http://group") {
return null;
}
// 4: extra (notes)
const notes = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 5: fav (is favorite)
const isFavorite = Utils.fromBufferToUtf8(this.readItem(reader)) === "1";
// 6: sharedfromaid (?)
this.skipItem(reader);
// 7: username
let username = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 8: password
let password = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 9: pwprotect (?)
this.skipItem(reader);
// 10: genpw (?)
this.skipItem(reader);
// 11: sn (is secure note)
const isSecureNote = Utils.fromBufferToUtf8(this.readItem(reader)) === "1";
// Parse secure note
if (options.parseSecureNotesToAccount && isSecureNote) {
let type = "";
// ParseSecureNoteServer
for (const i of notes.split("\n")) {
const keyValue = i.split(":", 2);
if (keyValue.length < 2) {
continue;
}
switch (keyValue[0]) {
case "NoteType":
type = keyValue[1];
break;
case "Hostname":
url = keyValue[1];
break;
case "Username":
username = keyValue[1];
break;
case "Password":
password = keyValue[1];
break;
}
}
// Only the some secure notes contain account-like information
if (!AllowedSecureNoteTypes.has(type)) {
// Ignore "group" accounts. They have no credentials.
if (url == "http://group") {
return null;
}
// 4: extra (notes)
const notes = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 5: fav (is favorite)
const isFavorite = Utils.fromBufferToUtf8(this.readItem(reader)) === "1";
// 6: sharedfromaid (?)
this.skipItem(reader);
// 7: username
let username = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 8: password
let password = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 9: pwprotect (?)
this.skipItem(reader);
// 10: genpw (?)
this.skipItem(reader);
// 11: sn (is secure note)
const isSecureNote = Utils.fromBufferToUtf8(this.readItem(reader)) === "1";
// Parse secure note
if (options.parseSecureNotesToAccount && isSecureNote) {
let type = "";
// ParseSecureNoteServer
for (const i of notes.split("\n")) {
const keyValue = i.split(":", 2);
if (keyValue.length < 2) {
continue;
}
switch (keyValue[0]) {
case "NoteType":
type = keyValue[1];
break;
case "Hostname":
url = keyValue[1];
break;
case "Username":
username = keyValue[1];
break;
case "Password":
password = keyValue[1];
break;
}
}
// Only the some secure notes contain account-like information
if (!AllowedSecureNoteTypes.has(type)) {
return null;
}
}
// 12: last_touch_gmt (?)
this.skipItem(reader);
// 13: autologin (?)
this.skipItem(reader);
// 14: never_autofill (?)
this.skipItem(reader);
// 15: realm (?)
this.skipItem(reader);
// 16: id_again (?)
this.skipItem(reader);
// 17: custom_js (?)
this.skipItem(reader);
// 18: submit_id (?)
this.skipItem(reader);
// 19: captcha_id (?)
this.skipItem(reader);
// 20: urid (?)
this.skipItem(reader);
// 21: basic_auth (?)
this.skipItem(reader);
// 22: method (?)
this.skipItem(reader);
// 23: action (?)
this.skipItem(reader);
// 24: groupid (?)
this.skipItem(reader);
// 25: deleted (?)
this.skipItem(reader);
// 26: attachkey (?)
this.skipItem(reader);
// 27: attachpresent (?)
this.skipItem(reader);
// 28: individualshare (?)
this.skipItem(reader);
// 29: notetype (?)
this.skipItem(reader);
// 30: noalert (?)
this.skipItem(reader);
// 31: last_modified_gmt (?)
this.skipItem(reader);
// 32: hasbeenshared (?)
this.skipItem(reader);
// 33: last_pwchange_gmt (?)
this.skipItem(reader);
// 34: created_gmt (?)
this.skipItem(reader);
// 35: vulnerable (?)
this.skipItem(reader);
// 36: pwch (?)
this.skipItem(reader);
// 37: breached (?)
this.skipItem(reader);
// 38: template (?)
this.skipItem(reader);
// 39: totp (?)
const totp = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 3 more left. Don't even bother skipping them.
// 40: trustedHostnames (?)
// 41: last_credential_monitoring_gmt (?)
// 42: last_credential_monitoring_stat (?)
// Adjust the path to include the group and the shared folder, if any.
const path = this.makeAccountPath(group, folder);
const account = new Account();
account.id = id;
account.name = name;
account.username = username;
account.password = password;
account.url = url;
account.path = path;
account.notes = notes;
account.totp = totp;
account.isFavorite = isFavorite;
account.isShared = folder != null;
return account;
} catch (err) {
throw new Error(
"Error parsing accounts on item with ID:" + id + " errorMessage: " + err.message,
);
}
// 12: last_touch_gmt (?)
this.skipItem(reader);
// 13: autologin (?)
this.skipItem(reader);
// 14: never_autofill (?)
this.skipItem(reader);
// 15: realm (?)
this.skipItem(reader);
// 16: id_again (?)
this.skipItem(reader);
// 17: custom_js (?)
this.skipItem(reader);
// 18: submit_id (?)
this.skipItem(reader);
// 19: captcha_id (?)
this.skipItem(reader);
// 20: urid (?)
this.skipItem(reader);
// 21: basic_auth (?)
this.skipItem(reader);
// 22: method (?)
this.skipItem(reader);
// 23: action (?)
this.skipItem(reader);
// 24: groupid (?)
this.skipItem(reader);
// 25: deleted (?)
this.skipItem(reader);
// 26: attachkey (?)
this.skipItem(reader);
// 27: attachpresent (?)
this.skipItem(reader);
// 28: individualshare (?)
this.skipItem(reader);
// 29: notetype (?)
this.skipItem(reader);
// 30: noalert (?)
this.skipItem(reader);
// 31: last_modified_gmt (?)
this.skipItem(reader);
// 32: hasbeenshared (?)
this.skipItem(reader);
// 33: last_pwchange_gmt (?)
this.skipItem(reader);
// 34: created_gmt (?)
this.skipItem(reader);
// 35: vulnerable (?)
this.skipItem(reader);
// 36: pwch (?)
this.skipItem(reader);
// 37: breached (?)
this.skipItem(reader);
// 38: template (?)
this.skipItem(reader);
// 39: totp (?)
const totp = await this.cryptoUtils.decryptAes256PlainWithDefault(
this.readItem(reader),
encryptionKey,
placeholder,
);
// 3 more left. Don't even bother skipping them.
// 40: trustedHostnames (?)
// 41: last_credential_monitoring_gmt (?)
// 42: last_credential_monitoring_stat (?)
// Adjust the path to include the group and the shared folder, if any.
const path = this.makeAccountPath(group, folder);
const account = new Account();
account.id = id;
account.name = name;
account.username = username;
account.password = password;
account.url = url;
account.path = path;
account.notes = notes;
account.totp = totp;
account.isFavorite = isFavorite;
account.isShared = folder != null;
return account;
}
async parseShar(
@ -246,30 +253,37 @@ export class Parser {
encryptionKey: Uint8Array,
rsaKey: Uint8Array,
): Promise<SharedFolder> {
const reader = new BinaryReader(chunk.payload);
let id: string;
try {
const reader = new BinaryReader(chunk.payload);
// Id
const id = Utils.fromBufferToUtf8(this.readItem(reader));
// Id
id = Utils.fromBufferToUtf8(this.readItem(reader));
// Key
const folderKey = this.readItem(reader);
const rsaEncryptedFolderKey = Utils.fromHexToArray(Utils.fromBufferToUtf8(folderKey));
const decFolderKey = await this.cryptoFunctionService.rsaDecrypt(
rsaEncryptedFolderKey,
rsaKey,
"sha1",
);
const key = Utils.fromHexToArray(Utils.fromBufferToUtf8(decFolderKey));
// Key
const folderKey = this.readItem(reader);
const rsaEncryptedFolderKey = Utils.fromHexToArray(Utils.fromBufferToUtf8(folderKey));
const decFolderKey = await this.cryptoFunctionService.rsaDecrypt(
rsaEncryptedFolderKey,
rsaKey,
"sha1",
);
const key = Utils.fromHexToArray(Utils.fromBufferToUtf8(decFolderKey));
// Name
const encryptedName = this.readItem(reader);
const name = await this.cryptoUtils.decryptAes256Base64(encryptedName, key);
// Name
const encryptedName = this.readItem(reader);
const name = await this.cryptoUtils.decryptAes256Base64(encryptedName, key);
const folder = new SharedFolder();
folder.id = id;
folder.name = name;
folder.encryptionKey = key;
return folder;
const folder = new SharedFolder();
folder.id = id;
folder.name = name;
folder.encryptionKey = key;
return folder;
} catch (err) {
throw new Error(
"Error parsing shared folder with ID:" + id + " errorMessage: " + err.message,
);
}
}
async parseEncryptedPrivateKey(encryptedPrivateKey: string, encryptionKey: Uint8Array) {