diff --git a/lastpass/parser.py b/lastpass/parser.py index b4261a8..ef5257c 100644 --- a/lastpass/parser.py +++ b/lastpass/parser.py @@ -50,7 +50,11 @@ def parse_ACCT(chunk, encryption_key): id = read_item(io) name = decode_aes256_plain_auto(read_item(io), encryption_key) group = decode_aes256_plain_auto(read_item(io), encryption_key) - url = decode_hex(read_item(io)) + url_encoded = read_item(io) + try: + url = decode_aes256_plain_auto(url_encoded, encryption_key) + except ValueError: + url = decode_hex(url_encoded) notes = decode_aes256_plain_auto(read_item(io), encryption_key) skip_item(io, 2) username = decode_aes256_plain_auto(read_item(io), encryption_key) @@ -72,10 +76,13 @@ def parse_ACCT(chunk, encryption_key): def parse_PRIK(chunk, encryption_key): """Parse PRIK chunk which contains private RSA key""" - decrypted = decode_aes256('cbc', - encryption_key[:16], - decode_hex(chunk.payload), - encryption_key) + if chunk.payload[:1] in (b'!', '!'): + decrypted = decode_aes256_base64_auto(chunk.payload, encryption_key) + else: + decrypted = decode_aes256('cbc', + encryption_key[:16], + decode_hex(chunk.payload), + encryption_key) hex_key = re.match(br'^LastPassPrivateKey<(?P.*)>LastPassPrivateKey$', decrypted).group('hex_key') rsa_key = RSA.importKey(decode_hex(hex_key)) diff --git a/tests/test_parser.py b/tests/test_parser.py index 597a542..f5616a0 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,8 +1,10 @@ # coding: utf-8 -from base64 import b64decode +from base64 import b64decode, b64encode import codecs +import struct import unittest from io import BytesIO +from Crypto.Cipher import AES from lastpass.blob import Blob from lastpass.chunk import Chunk from lastpass import parser @@ -177,6 +179,33 @@ def test_parse_PRIK_parses_private_key(self): "62999647203566877832873138065626190040996517274" + "418161068665712298519808863")) + def test_parse_PRIK_parses_v2_private_key(self): + legacy_ciphertext = parser.decode_hex(self.encoded_rsa_key) + plaintext = parser.decode_aes256( + 'cbc', + self.rsa_key_encryption_key[:16], + legacy_ciphertext, + self.rsa_key_encryption_key) + iv = b'\0' * AES.block_size + padding_length = AES.block_size - len(plaintext) % AES.block_size + padded = plaintext + struct.pack('B', padding_length) * padding_length + ciphertext = AES.new( + self.rsa_key_encryption_key, + AES.MODE_CBC, + iv).encrypt(padded) + encoded = b'!' + b64encode(iv) + b'|' + b64encode(ciphertext) + + legacy_key = parser.parse_PRIK( + Chunk(b'PRIK', self.encoded_rsa_key), + self.rsa_key_encryption_key) + v2_key = parser.parse_PRIK( + Chunk(b'PRIK', encoded), + self.rsa_key_encryption_key) + + self.assertEqual(v2_key.n, legacy_key.n) + self.assertEqual(v2_key.e, legacy_key.e) + self.assertEqual(v2_key.d, legacy_key.d) + def test_parse_secure_note_server_returns_parsed_values(self): type = b'type' url = b'url'