Class: OnePass::OpVault

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

Overview

Handle all the 1Password OpVault operations

Constant Summary collapse

FIELD_TYPES =
{
  password: 'P',
  text: 'T',
  email: 'E',
  number: 'N',
  radio: 'R',
  telephone: 'TEL',
  checkbox: 'C',
  url: 'U'
}.freeze
DESIGNATION_TYPES =
{
  username: 'username',
  password: 'password',
  none: ''
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(vault_path) ⇒ OpVault

Returns a new instance of OpVault.



26
27
28
29
30
31
# 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

Instance Method Details

#check_and_set_profile(profile_path) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 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) ⇒ Object



33
34
35
36
37
38
# 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) ⇒ Object

Raises:

  • (ArgumentError.new)


75
76
77
78
79
# 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) ⇒ Object



98
99
100
101
102
103
104
# 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) ⇒ Object



137
138
139
140
141
142
143
# 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) ⇒ Object



106
107
108
109
110
111
112
113
114
115
# 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) ⇒ Object



117
118
119
120
121
122
123
124
125
# 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) ⇒ Object



94
95
96
# 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) ⇒ Object



163
164
165
166
167
168
# 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) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
# 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) ⇒ Object



157
158
159
160
161
# 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_itemsObject



81
82
83
84
85
86
87
88
89
90
91
92
# 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

#lockObject



71
72
73
# File 'lib/OnePass/op_vault.rb', line 71

def lock
  @master_key = @master_mac_key = nil
end

#master_keys(derived_key, derived_mac_key) ⇒ Object



127
128
129
130
# 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) ⇒ Object



132
133
134
135
# 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) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
# 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?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/OnePass/op_vault.rb', line 67

def unlocked?
  !!@master_key && !!@overview_key
end