class Tome::Tome
Constants
- FILE_VERSION
Public Class Methods
create!(tome_filename, master_password, stretch = 100_000)
click to toggle source
# File lib/tome/tome.rb, line 16 def self.create!(tome_filename, master_password, stretch = 100_000) if tome_filename.nil? || tome_filename.empty? raise ArgumentError end if master_password.nil? || master_password.empty? raise MasterPasswordError end save_tome(tome_filename, new_tome(stretch), {}, master_password) return Tome.new(tome_filename, master_password) end
exists?(tome_filename)
click to toggle source
# File lib/tome/tome.rb, line 12 def self.exists?(tome_filename) return !load_tome(tome_filename).nil? end
new(tome_filename, master_password)
click to toggle source
# File lib/tome/tome.rb, line 29 def initialize(tome_filename, master_password) if tome_filename.nil? || tome_filename.empty? raise ArgumentError end if master_password.nil? || master_password.empty? raise MasterPasswordError end @tome_filename = tome_filename @master_password = master_password # TODO: This is suboptimal. We are loading the store # twice for most operations because of this authentication. authenticate() end
Private Class Methods
load_tome(tome_filename)
click to toggle source
# File lib/tome/tome.rb, line 173 def self.load_tome(tome_filename) if tome_filename.nil? || tome_filename.empty? raise ArgumentError end return nil if !File.exist?(tome_filename) contents = File.open(tome_filename, 'rb') { |file| file.read } return nil if contents.length == 0 tome = YAML.load(contents) return nil if !tome validate_tome(tome) return tome end
new_tome(stretch)
click to toggle source
# File lib/tome/tome.rb, line 310 def self.new_tome(stretch) { :store => {}, :salt => Crypt.new_salt, :iv => Crypt.new_iv, :stretch => stretch } end
save_tome(tome_filename, tome, store, master_password)
click to toggle source
# File lib/tome/tome.rb, line 250 def self.save_tome(tome_filename, tome, store, master_password) if tome.nil? || store.nil? || master_password.nil? || master_password.empty? raise ArgumentError end store_yaml = YAML.dump(store) padded_store_yaml = Padding.pad(store_yaml, 1024, 4096) new_salt = Crypt.new_salt new_iv = Crypt.new_iv encrypted_store = Crypt.encrypt( :value => padded_store_yaml, :password => master_password, :salt => new_salt, :iv => new_iv, :stretch => tome[:stretch] ) contents = tome.merge({ :version => FILE_VERSION, :store => encrypted_store, :salt => new_salt, :iv => new_iv }) File.open(tome_filename, 'wb') do |file| YAML.dump(contents, file) end end
validate_tome(tome)
click to toggle source
# File lib/tome/tome.rb, line 191 def self.validate_tome(tome) if tome[:version].nil? || tome[:version].class != Fixnum raise FileFormatError, 'The tome database is invalid (missing or invalid version).' end if tome[:version] > FILE_VERSION raise FileFormatError, "The tome database comes from a newer version of tome (v#{tome[:version]} > v#{FILE_VERSION}). Try updating tome." end if tome[:version] < FILE_VERSION raise FileFormatError, "The tome database is incompatible with this version of tome (v#{tome[:version]} < v#{FILE_VERSION})." end # TODO: Check version number, do file format migration if necessary. if tome[:salt].nil? || tome[:salt].class != String || tome[:salt].empty? raise FileFormatError, 'The tome database is invalid (missing or invalid salt).' end if tome[:iv].nil? || tome[:iv].class != String || tome[:iv].empty? raise FileFormatError, 'The tome database is invalid (missing or invalid IV).' end if tome[:stretch].nil? || tome[:stretch].class != Fixnum || tome[:stretch] < 0 raise FileFormatError, 'The tome database is invalid (missing or invalid key stretch).' end if tome[:store].nil? || tome[:store].class != String || tome[:store].empty? raise FileFormatError, 'The tome database is invalid (missing or invalid store).' end end
Public Instance Methods
delete(id)
click to toggle source
# File lib/tome/tome.rb, line 76 def delete(id) if id.nil? || id.empty? raise ArgumentError end return writable_store do |store| delete_by_id(store, id) end end
each_password() { |id, info| ... }
click to toggle source
# File lib/tome/tome.rb, line 96 def each_password if !block_given? raise ArgumentError end readable_store do |store| store.each { |id, info| yield id, info[:password] } end end
find(pattern)
click to toggle source
# File lib/tome/tome.rb, line 66 def find(pattern) if pattern.nil? || pattern.empty? raise ArgumentError end return readable_store do |store| get_by_pattern(store, pattern) end end
get(id)
click to toggle source
# File lib/tome/tome.rb, line 56 def get(id) if id.nil? || id.empty? raise ArgumentError end return readable_store do |store| get_by_id(store, id) end end
master_password=(master_password)
click to toggle source
# File lib/tome/tome.rb, line 108 def master_password=(master_password) return writable_store do |store| @master_password = master_password true end end
rename(old_id, new_id)
click to toggle source
# File lib/tome/tome.rb, line 86 def rename(old_id, new_id) if old_id.nil? || old_id.empty? || new_id.nil? || new_id.empty? raise ArgumentError end return writable_store do |store| rename_by_id(store, old_id, new_id) end end
set(id, password)
click to toggle source
# File lib/tome/tome.rb, line 46 def set(id, password) if id.nil? || id.empty? || password.nil? || password.empty? raise ArgumentError end return writable_store do |store| set_by_id(store, id, password) end end
Private Instance Methods
authenticate()
click to toggle source
# File lib/tome/tome.rb, line 319 def authenticate # Force a read. # If the master password is invalid, the access exception will propagate. readable_store { } end
delete_by_id(store, id)
click to toggle source
# File lib/tome/tome.rb, line 137 def delete_by_id(store, id) same = store.reject! { |key, info| key.casecmp(id) == 0 }.nil? return !same end
find_by_pattern(store, pattern)
click to toggle source
# File lib/tome/tome.rb, line 145 def find_by_pattern(store, pattern) return {} if pattern.nil? # TODO: Better matching. Should allow separated # substring matching. Exact match > solid substrings > separated substrings. exact = store.select { |key, info| key.casecmp(pattern) == 0 } return exact if !exact.empty? return store.select { |key, info| key =~ /#{pattern}/i } end
get_by_id(store, id)
click to toggle source
# File lib/tome/tome.rb, line 133 def get_by_id(store, id) store[id] end
get_by_pattern(store, pattern)
click to toggle source
# File lib/tome/tome.rb, line 125 def get_by_pattern(store, pattern) find_by_pattern(store, pattern).map { |key, value| { key => value[:password] } }.inject { |hash, item| hash.merge!(item) } || {} end
load_store(tome)
click to toggle source
# File lib/tome/tome.rb, line 223 def load_store(tome) if tome.nil? raise ArgumentError end begin padded_store_yaml = Crypt.decrypt( :value => tome[:store], :password => @master_password, :stretch => tome[:stretch], :salt => tome[:salt], :iv => tome[:iv] ) rescue OpenSSL::Cipher::CipherError raise MasterPasswordError end begin store_yaml = Padding.unpad(padded_store_yaml) rescue Exception raise MasterPasswordError end store = YAML.load(store_yaml) return store || {} end
readable_store() { |store| ... }
click to toggle source
# File lib/tome/tome.rb, line 281 def readable_store() tome = Tome.load_tome(@tome_filename) store = load_store(tome) begin result = yield store ensure store = nil GC.start end return result end
rename_by_id(store, old_id, new_id)
click to toggle source
# File lib/tome/tome.rb, line 162 def rename_by_id(store, old_id, new_id) if store[old_id].nil? return false else values = store[old_id] store.delete(old_id) store[new_id] = values return true end end
set_by_id(store, id, password)
click to toggle source
# File lib/tome/tome.rb, line 116 def set_by_id(store, id, password) created = !store.include?(id) store[id] = {} store[id][:password] = password return created end
writable_store() { |store| ... }
click to toggle source
# File lib/tome/tome.rb, line 295 def writable_store() tome = Tome.load_tome(@tome_filename) store = load_store(tome) begin result = yield store Tome.save_tome(@tome_filename, tome, store, @master_password) ensure store = nil GC.start end return result end