Class: OnePass::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/OnePass.rb

Instance Method Summary collapse

Constructor Details

#initialize(master_password, path = nil) ⇒ Manager

Returns a new instance of Manager.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/OnePass.rb', line 43

def initialize(master_password, path = nil)
  path ||= "#{ENV["HOME"]}/Library/Application Support/1Password 4/Data/OnePassword.sqlite"
  raise "Can't find sqlite db at #{path}" unless File.exist? path

  db_filename = File.basename(path)
  dir_path = File.dirname(path)

  # 1Password keeps the sqlite db open in exclusive mode. So we copy it to
  # a tempdir and use that.
  #
  # Note that Dir.mktmpdir will clean up after itself when
  # passed a block.
  Dir.mktmpdir('OnePass') do |tmpdir|
    FileUtils.cp_r("#{dir_path}/.", tmpdir)
    sqlite_file = File.join(tmpdir, db_filename)

    # roll the main db forward using write-ahead-log.
    db = SQLite3::Database.new(sqlite_file)
    db.execute "VACUUM;"

    # read profile data
    @overviews = []
    @masters = []
    db.execute "SELECT id,master_key_data,overview_key_data,salt,iterations FROM profiles" do |profile|

      # derive the key from the password
      derived_key = OpenSSL::PKCS5.pbkdf2_hmac(master_password, profile[3], profile[4], 64, OpenSSL::Digest::SHA512.new)
      derived_encryption_key = derived_key[0..31]
      derived_mac_key = derived_key[32..-1]

      # try to unlock profile data. return fail if failed login
      overview_key_data = OnePass::Opdata.new(profile[2], derived_encryption_key, derived_mac_key)
      overview_key = OpenSSL::Digest::SHA512.new.digest(overview_key_data.data)
      overview_encryption_key, overview_mac_key = overview_key[0..31], overview_key[32..-1]

      # load overview opdata into object based format. overviews are stored decrypted for use later.
      # the encrypted data for the keys is included, but is not decrypted unless requested later
      db.execute "SELECT items.key_data, items.overview_data, item_details.data FROM items INNER JOIN item_details ON items.id=item_details.item_id WHERE items.profile_id=#{profile[0]};" do |row|
        overview = OnePass::Opdata.new(row[1], overview_encryption_key, overview_mac_key)
        json = JSON.parse(overview.data).merge({profile: profile[0], key_data: row[0], data: row[2]})
        @overviews << json
      end

      # decrypt the master key for use later
      master_key_data = OnePass::Opdata.new(profile[1], derived_encryption_key, derived_mac_key)
      master_key = OpenSSL::Digest::SHA512.new.digest(master_key_data.data)
      @masters[profile[0]] = {enc_key: master_key[0..31], mac_key: master_key[32..-1]}
    end

    db.close

    # tmpdir removed when block exits
  end
end

Instance Method Details

#decrypt(overview) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/OnePass.rb', line 107

def decrypt(overview)
  key_data = overview[:key_data][0..-33]
  mac = overview[:key_data][-32..-1]
  profile = overview[:profile]
  if OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @masters[profile][:mac_key], key_data) != mac
    raise VerifyException.new("The item's encryption key couldn't be verified.")
  end
  cipher = OpenSSL::Cipher::AES.new(256, :CBC)
  cipher.decrypt
  cipher.padding = 0
  cipher.iv = key_data[0..15]
  cipher.key = @masters[profile][:enc_key]
  key_data = cipher.update(key_data[16..-1]) + cipher.final
  return JSON.parse(OnePass::Opdata.new(overview[:data],key_data[0..31],key_data[32..-1]).data)["password"]
end

#load_all_regex(re) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/OnePass.rb', line 98

def load_all_regex(re)
  all = []
  @overviews.each do |overview|
    all << overview if /#{re}/.match(overview["title"])
  end
  return all unless all.empty?
  return nil
end