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