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