Class: PWKeep::Storage

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

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Storage

Returns a new instance of Storage.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/pwkeep/storage.rb', line 13

def initialize(options)
  @options = options
 
  @options[:cipher] ||= 'AES-256-CTR'
  @options[:keysize] ||= 2048
  @options[:iterations] ||= 2000
  @options[:digest] ||= 'sha512'

  unless @options[:path].class == Pathname
    @options[:path] = Pathname.new(@options[:path].to_s).expand_path
  end
  
  if path.exist? 
    @lockfile = Lockfile.new path.join(".lock").to_s, :retries => 0
    @lockfile.lock
    ObjectSpace.define_finalizer(self, proc { @lockfile.unlock })
  end
end

Instance Method Details

#createObject



32
33
34
35
36
37
38
# File 'lib/pwkeep/storage.rb', line 32

def create
  return if path.exist?
  path.mkdir
  @lockfile = Lockfile.new path.join(".lock").to_s, :retries => 0
  @lockfile.lock
  ObjectSpace.define_finalizer(self, proc { @lockfile.unlock })
end

#decrypt_system(file) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pwkeep/storage.rb', line 73

def decrypt_system(file)
  unless @key
    raise PWKeep::Exception, "Private key required"
  end
  # found it, decrypt and load json
  # the file contains crypto name, iv len, iv, data
  header = nil
  data = nil
  file.open('rb') { |io|
    header = io.read @options[:keysize]/8
    data = io.read
  }

  # header
  cipher = @key.private_decrypt(header,4).unpack('Z*')[0]
  cipher = OpenSSL::Cipher.new cipher
  # re-unpack now that we know the size of the rest of the fields...
  header = @key.private_decrypt(header,4).unpack("Z*a#{cipher.iv_len}a#{cipher.key_len}")

  cipher.decrypt
  cipher.iv = header[1]
  cipher.key = header[2]

  # perform decrypt
  cipher.update(data) + cipher.final
end

#delete(system) ⇒ Object



158
159
160
161
162
163
164
165
# File 'lib/pwkeep/storage.rb', line 158

def delete(system)
  data = load_system(system)
  unless data[:system] == system
    raise "System not found"
  end
 
  path.join(system_to_hash(system)).delete!
end

#encrypt_system(file, data) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/pwkeep/storage.rb', line 100

def encrypt_system(file, data)
  unless @key
    raise PWKeep::Exception, "Private key required"
  end

  # encrypt data
  cipher = OpenSSL::Cipher::Cipher.new @options[:cipher]
  cipher.encrypt

  # use one time key and iv
  iv = cipher.random_iv
  key = cipher.random_key

  header = [cipher.name, iv, key].pack("Z*a#{cipher.iv_len}a#{cipher.key_len}")
  blob = cipher.update(data) + cipher.final

  # store system name to make search work
  file.open('wb') do |io|
      io.write @key.public_encrypt header, 4
      io.write blob
  end

  true
end

#keypair_create(password) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/pwkeep/storage.rb', line 40

def keypair_create(password)
  # ensure it does not exist
  @key = OpenSSL::PKey::RSA.new @options[:keysize]
  cipher = OpenSSL::Cipher.new @options[:keycipher]

  path.join('private.pem').open 'w' do |io| io.write @key.export(cipher, password) end
end

#keypair_load(password) ⇒ Object



48
49
50
51
# File 'lib/pwkeep/storage.rb', line 48

def keypair_load(password)
  key_pem = path.join('private.pem').read
  @key = OpenSSL::PKey::RSA.new key_pem, password
end

#list_all_systemsObject



167
168
169
170
171
172
173
174
# File 'lib/pwkeep/storage.rb', line 167

def list_all_systems
  systems = []
  path.entries.each do |s|
      next unless s.fnmatch? "system-*"
      systems << JSON.load(decrypt_system(path.join(s)))["system"]
  end
  systems
end

#load_system(system) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/pwkeep/storage.rb', line 125

def load_system(system)
  unless @key
    raise PWKeep::Exception, "Private key required"
  end

  system_h = system_to_hash(system)
  raise "Cannot find #{system}" unless path.join(system_h).exist?

  data = decrypt_system(path.join(system_h))

  unless data[0] == "{" and data[-1] == "}" 
    raise PWKeep::Exception, "Corrupted data file"
  end

  JSON.load(data).deep_symbolize_keys
end

#master_key_loadObject



53
54
55
56
57
58
59
60
# File 'lib/pwkeep/storage.rb', line 53

def master_key_load
  unless @key
    raise PWKeep::Exception, "RSA private key required"
  end

  # load the key
  @master_key = @key.private_decrypt(path.join('master.key').open('rb') { |io| io.read },4)
end

#pathObject



9
10
11
# File 'lib/pwkeep/storage.rb', line 9

def path
  @options[:path]
end

#save_system(system, data) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/pwkeep/storage.rb', line 142

def save_system(system, data)
  unless @key
    raise PWKeep::Exception, "Private key required"
  end

  # write system
  system_h = system_to_hash(system)

  data = { :system => system, :data => data, :stored_at => Time.now }
  encrypt_system(path.join(system_h), data)
end

#system_to_hash(system) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/pwkeep/storage.rb', line 62

def system_to_hash(system)
  d = Digest.const_get(@options[:digest].upcase).new

  system_h = system.downcase
  (0..@options[:iterations]).each do
      system_h = d.update(system_h).digest
      d.reset
  end
  "system-#{Base64.urlsafe_encode64(system_h)}"
end

#valid?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'lib/pwkeep/storage.rb', line 154

def valid?
  path.join('private.pem').exist? 
end