lib/lastpass/parser.rb in lastpass-1.2.0 vs lib/lastpass/parser.rb in lastpass-1.2.1
- old
+ new
@@ -4,10 +4,18 @@
module LastPass
class Parser
# OpenSSL constant
RSA_PKCS1_OAEP_PADDING = 4
+ # Secure note types that contain account-like information
+ ALLOWED_SECURE_NOTE_TYPES = {
+ "Server" => true,
+ "Email Account" => true,
+ "Database" => true,
+ "Instant Messenger" => true,
+ }
+
# Splits the blob into chucks grouped by kind.
def self.extract_chunks blob
chunks = []
StringIO.open blob.bytes do |stream|
@@ -26,27 +34,26 @@
#
# TODO: Make a test case that covers secure note account
def self.parse_ACCT chunk, encryption_key
StringIO.open chunk.payload do |io|
id = read_item io
- name = decode_aes256_auto read_item(io), encryption_key
- group = decode_aes256_auto read_item(io), encryption_key
+ 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
- notes = decode_aes256_auto read_item(io), encryption_key
+ notes = decode_aes256_plain_auto read_item(io), encryption_key
2.times { skip_item io }
- username = decode_aes256_auto read_item(io), encryption_key
- password = decode_aes256_auto read_item(io), encryption_key
+ username = decode_aes256_plain_auto read_item(io), encryption_key
+ password = decode_aes256_plain_auto read_item(io), encryption_key
2.times { skip_item io }
secure_note = read_item io
# Parse secure note
if secure_note == "1"
17.times { skip_item io }
secure_note_type = read_item io
- # Only "Server" secure note stores account information
- if secure_note_type != "Server"
+ if !ALLOWED_SECURE_NOTE_TYPES.key? secure_note_type
return nil
end
url, username, password = parse_secure_note_server notes
end
@@ -93,14 +100,14 @@
# When the key is blank, then there's a RSA encrypted key, which has to
# be decrypted first before use.
key = if key.empty?
decode_hex rsa_key.private_decrypt(encrypted_key, RSA_PKCS1_OAEP_PADDING)
else
- decode_hex decode_aes256_auto(key, encryption_key)
+ decode_hex decode_aes256_plain_auto(key, encryption_key)
end
- name = decode_aes256_auto encrypted_name, key
+ name = decode_aes256_base64_auto encrypted_name, key
# TODO: Return an object, not a hash
{id: id, name: name, encryption_key: key}
end
end
@@ -187,30 +194,32 @@
def self.decode_base64 data
# TODO: Check for input validity!
Base64.decode64 data
end
- # Guesses AES encoding/cipher from the length of the data.
- # Possible combinations are:
- # - ciphers: AES-256 EBC, AES-256 CBC
- # - encodings: plain, base64
- def self.decode_aes256_auto data, encryption_key
+ # Guesses AES cipher (EBC or CBD) from the length of the plain data.
+ def self.decode_aes256_plain_auto data, encryption_key
length = data.length
- length16 = length % 16
- length64 = length % 64
if length == 0
""
- elsif length16 == 0
- decode_aes256_ecb_plain data, encryption_key
- elsif length64 == 0 || length64 == 24 || length64 == 44
- decode_aes256_ecb_base64 data, encryption_key
- elsif length16 == 1
+ elsif data[0] == "!" && length % 16 == 1 && length > 32
decode_aes256_cbc_plain data, encryption_key
- elsif length64 == 6 || length64 == 26 || length64 == 50
+ else
+ decode_aes256_ecb_plain data, encryption_key
+ end
+ end
+
+ # Guesses AES cipher (EBC or CBD) from the length of the base64 encoded data.
+ def self.decode_aes256_base64_auto data, encryption_key
+ length = data.length
+
+ if length == 0
+ ""
+ elsif data[0] == "!"
decode_aes256_cbc_base64 data, encryption_key
else
- raise RuntimeError, "'#{data.inspect}' doesn't seem to be AES-256 encrypted"
+ decode_aes256_ecb_base64 data, encryption_key
end
end
# Decrypts AES-256 ECB bytes.
def self.decode_aes256_ecb_plain data, encryption_key