lib/lastpass/parser.rb in lastpass-1.0.1 vs lib/lastpass/parser.rb in lastpass-1.1.0

- old
+ new

@@ -1,36 +1,87 @@ # Copyright (C) 2013 Dmitry Yakimenko (detunized@gmail.com). # Licensed under the terms of the MIT license. See LICENCE for details. module LastPass class Parser + # OpenSSL constant + RSA_PKCS1_OAEP_PADDING = 4 + # Splits the blob into chucks grouped by kind. def self.extract_chunks blob - chunks = Hash.new { |hash, key| hash[key] = [] } + chunks = [] StringIO.open blob.bytes do |stream| while !stream.eof? - chunk = read_chunk stream - chunks[chunk.id] << chunk + chunks.push read_chunk stream end end chunks end # Parses an account chunk, decrypts and creates an Account object. - # TODO: See if this should be part of Account class. - def self.parse_account chunk, encryption_key + 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 url = decode_hex read_item io 3.times { skip_item io } username = decode_aes256_auto read_item(io), encryption_key password = decode_aes256_auto read_item(io), encryption_key Account.new id, name, username, password, url, group + end + end + + # Parse PRIK chunk which contains private RSA key + def self.parse_PRIK chunk, encryption_key + decrypted = decode_aes256 "cbc", + encryption_key[0, 16], + decode_hex(chunk.payload), + encryption_key + + /^LastPassPrivateKey<(?<hex_key>.*)>LastPassPrivateKey$/ =~ decrypted + asn1_encoded_all = OpenSSL::ASN1.decode decode_hex hex_key + asn1_encoded_key = OpenSSL::ASN1.decode asn1_encoded_all.value[2].value + + rsa_key = OpenSSL::PKey::RSA.new + rsa_key.n = asn1_encoded_key.value[1].value + rsa_key.e = asn1_encoded_key.value[2].value + rsa_key.d = asn1_encoded_key.value[3].value + rsa_key.p = asn1_encoded_key.value[4].value + rsa_key.q = asn1_encoded_key.value[5].value + rsa_key.dmp1 = asn1_encoded_key.value[6].value + rsa_key.dmq1 = asn1_encoded_key.value[7].value + rsa_key.iqmp = asn1_encoded_key.value[8].value + + rsa_key + end + + # TODO: Fake some data and make a test + def self.parse_SHAR chunk, encryption_key, rsa_key + StringIO.open chunk.payload do |io| + id = read_item io + encrypted_key = decode_hex read_item io + encrypted_name = read_item io + 2.times { skip_item io } + key = read_item io + + # Shared folder encryption key might come already in pre-decrypted form, + # where it's only AES encrypted with the regular encryption key. + # 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) + end + + name = decode_aes256_auto encrypted_name, key + + # TODO: Return an object, not a hash + {id: id, name: name, encryption_key: key} end end # Reads one chunk from a stream and creates a Chunk object with the data read. def self.read_chunk stream