class OnePass::OpVault

Handle all the 1Password OpVault operations

Constants

DESIGNATION_TYPES
FIELD_TYPES

Public Class Methods

new(vault_path) click to toggle source
# File lib/OnePass/op_vault.rb, line 26
def initialize(vault_path)
  check_and_set_vault vault_path
  check_and_set_profile File.join(@vault_path, 'default', 'profile.js')
  @items = {}
  @item_index = {}
end

Public Instance Methods

check_and_set_profile(profile_path) click to toggle source
# File lib/OnePass/op_vault.rb, line 40
def check_and_set_profile(profile_path)
  unless File.exist? profile_path
    raise ArgumentError.new, 'Vault profile does not exist'
  end

  profile = File.read profile_path
  unless profile.start_with?('var profile=') && profile.end_with?(';')
    raise 'Vault profile format incorrect'
  end

  @profile = JSON.parse profile[12..-2]
rescue
  raise 'Unable to parse vault profile'
end
check_and_set_vault(vault_path) click to toggle source
# File lib/OnePass/op_vault.rb, line 33
def check_and_set_vault(vault_path)
  @vault_path = File.expand_path(vault_path)
  unless File.exist? @vault_path
    raise ArgumentError.new, 'Vault file does not exist'
  end
end
check_hmac(data, hmac_key, desired_hmac) click to toggle source
# File lib/OnePass/op_vault.rb, line 75
def check_hmac(data, hmac_key, desired_hmac)
  digest = OpenSSL::Digest::SHA256.new
  computed_hmac = OpenSSL::HMAC.digest digest, hmac_key, data
  raise ArgumentError.new, 'Invalid HMAC' if computed_hmac != desired_hmac
end
decrypt_data(key, iv, data) click to toggle source
# File lib/OnePass/op_vault.rb, line 98
def decrypt_data(key, iv, data)
  cipher = OpenSSL::Cipher::AES256.new(:CBC).decrypt
  cipher.key = key
  cipher.iv = iv
  cipher.padding = 0
  cipher.update(data) + cipher.final
end
decrypt_keys(encrypted_key, derived_key, derived_mac_key) click to toggle source
# File lib/OnePass/op_vault.rb, line 137
def decrypt_keys(encrypted_key, derived_key, derived_mac_key)
  key_base = decrypt_opdata encrypted_key, derived_key, derived_mac_key
  keys = OpenSSL::Digest::SHA512.new.digest key_base

  # => key, hmac
  [keys[0..31], keys[32..63]]
end
decrypt_opdata(cipher_text, cipher_key, cipher_mac_key) click to toggle source
# File lib/OnePass/op_vault.rb, line 106
def decrypt_opdata(cipher_text, cipher_key, cipher_mac_key)
  key_data = cipher_text[0..-33]
  mac_data = cipher_text[-32..-1]

  check_hmac key_data, cipher_mac_key, mac_data
  plaintext = decrypt_data cipher_key, key_data[16..31], key_data[32..-1]

  plaintext_size = key_data[8..15].unpack('Q')[0]
  plaintext[-plaintext_size..-1]
end
derive_keys(master_password, salt, iterations) click to toggle source
# File lib/OnePass/op_vault.rb, line 117
def derive_keys(master_password, salt, iterations)
  digest = OpenSSL::Digest::SHA512.new
  derived_key = OpenSSL::PKCS5.pbkdf2_hmac(
    master_password, salt, iterations, digest.digest_length, digest
  )

  # => key, hmac
  [derived_key[0..31], derived_key[32..63]]
end
find(search) click to toggle source
# File lib/OnePass/op_vault.rb, line 94
def find(search)
  @items.values_at *@item_index.values_at(*@item_index.keys.grep(search))
end
item_detail(item) click to toggle source
# File lib/OnePass/op_vault.rb, line 163
def item_detail(item)
  data = Base64.decode64(item['d'])
  item_key, item_mac_key = item_keys item
  detail = decrypt_opdata data, item_key, item_mac_key
  { 'uuid' => item['uuid'] }.merge JSON.parse detail
end
item_keys(item) click to toggle source
# File lib/OnePass/op_vault.rb, line 145
def item_keys(item)
  item_key = Base64.decode64 item['k']
  key_data = item_key[0..-33]
  key_hmac = item_key[-32..-1]

  check_hmac key_data, @master_mac_key, key_hmac
  plaintext = decrypt_data @master_key, key_data[0..15], key_data[16..-1]

  # => key, hmac
  [plaintext[0..31], plaintext[32..63]]
end
item_overview(item) click to toggle source
# File lib/OnePass/op_vault.rb, line 157
def item_overview(item)
  data = Base64.decode64(item['o'])
  overview = decrypt_opdata data, @overview_key, @overview_mac_key
  { 'uuid' => item['uuid'] }.merge JSON.parse overview
end
load_items() click to toggle source
# File lib/OnePass/op_vault.rb, line 81
def load_items
  file_glob = File.join(@vault_path, 'default', 'band_*.js')
  Dir.glob(file_glob) do |file|
    band = JSON.parse File.read(file)[3..-3]
    @items.merge! band
  end

  @items.each_pair do |uuid, item|
    overview = item_overview item
    @item_index[overview['title']] = uuid
  end
end
lock() click to toggle source
# File lib/OnePass/op_vault.rb, line 71
def lock
  @master_key = @master_mac_key = nil
end
master_keys(derived_key, derived_mac_key) click to toggle source
# File lib/OnePass/op_vault.rb, line 127
def master_keys(derived_key, derived_mac_key)
  encrypted = Base64.decode64(@profile['masterKey'])
  decrypt_keys encrypted, derived_key, derived_mac_key
end
overview_keys(derived_key, derived_mac_key) click to toggle source
# File lib/OnePass/op_vault.rb, line 132
def overview_keys(derived_key, derived_mac_key)
  encrypted = Base64.decode64(@profile['overviewKey'])
  decrypt_keys encrypted, derived_key, derived_mac_key
end
unlock(master_password = nil) { || ... } click to toggle source
# File lib/OnePass/op_vault.rb, line 55
def unlock(master_password = nil)
  master_password = yield if block_given?

  salt = Base64.decode64(@profile['salt'])
  iterations = @profile['iterations']
  key, mac_key = derive_keys master_password, salt, iterations
  @master_key, @master_mac_key = master_keys key, mac_key
  @overview_key, @overview_mac_key = overview_keys key, mac_key
rescue
  raise 'Incorrect password'
end
unlocked?() click to toggle source
# File lib/OnePass/op_vault.rb, line 67
def unlocked?
  !!@master_key && !!@overview_key
end