class TheFox::OSP::Database

Attributes

has_changed[RW]

Public Class Methods

new(file_path, osp) click to toggle source
# File lib/osp/database.rb, line 12
def initialize(file_path, osp)
  @file_path = file_path
  @osp = osp
  @load_callback_method = nil
  @write_callback_method = nil
  @has_changed = false
  
  @data = {
    'meta' => {
      'version' => 1,
      'created_at' => DateTime.now.to_s,
      'updated_at' => DateTime.now.to_s,
    },
    'hosts' => Hash.new,
  }
end

Public Instance Methods

add_host(host) click to toggle source
# File lib/osp/database.rb, line 170
def add_host(host)
  @data['hosts'][host.name] = host
  update
  @has_changed = true
end
csv() click to toggle source

Return all hosts and passwords as CSV string.

# File lib/osp/database.rb, line 189
def csv
  "HOST,PASSWORD\r\n"
  @data['hosts']
    .map{ |id, host|
      "#{host.name},#{host.password}"
    }
    .join("\r\n")
end
hosts() click to toggle source
# File lib/osp/database.rb, line 166
def hosts
  @data['hosts']
end
load() click to toggle source
# File lib/osp/database.rb, line 39
def load
  load_callback(1000, 'Check for existing database file.')
  
  if @file_path.exist?
    load_callback(1050, "Use database file: #{@file_path}")
    
    load_callback(1100, "Read file '#{@file_path}'.")
    db_meta = File.binread(@file_path)
    
    load_callback(1200, 'Process database metadata.')
    db_meta = Base64.strict_decode64(db_meta)
    db_meta = MessagePack.unpack(db_meta)
    
    db_e = Base64.strict_decode64(db_meta['db'])
    mac = OpenSSL::Digest::SHA256.digest(db_e)
    if db_meta['mac'] == mac
      load_callback(1300, 'Setup database decryption.')
      dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
      iv = Base64.strict_decode64(db_meta['iv'])
      
      aes = OpenSSL::Cipher::AES.new(256, 'CBC')
      aes.decrypt
      aes.key = dk_sha256
      aes.iv = iv
      
      begin
        load_callback(1350, 'Decrypt database.')
        db_b64 = aes.update(db_e)
        db_b64 << aes.final
      rescue Exception #=> e
        raise 'Incorrect email and password combination.'
      end
      
      load_callback(1400, 'Build database.')
      @data = MessagePack.unpack(Base64.strict_decode64(db_b64))
      
      @data['hosts'] = @data['hosts'].map{ |name, host|
        host_o = TheFox::OSP::Host.from_h(host)
        host_o.osp = @osp
        [name, host_o]
      }.to_h
      
      load_callback(9000, 'Database startup done.')
    else
      raise 'Database integrity check failed.'
    end
  else
    load_callback(9500, 'Database startup done.')
  end
end
load_callback(*o) click to toggle source
# File lib/osp/database.rb, line 33
def load_callback(*o)
  if !@load_callback_method.nil?
    @load_callback_method.call(*o)
  end
end
load_callback_method=(m) click to toggle source
# File lib/osp/database.rb, line 29
def load_callback_method=(m)
  @load_callback_method = m
end
remove_host(host) click to toggle source
# File lib/osp/database.rb, line 176
def remove_host(host)
  if @data['hosts'].has_key?(host.name)
    @data['hosts'].delete(host.name)
    update
    @has_changed = true
    true
  else
    false
  end
end
update() click to toggle source
# File lib/osp/database.rb, line 162
def update
  @data['meta']['updated_at'] = DateTime.now.to_s
end
write() click to toggle source
# File lib/osp/database.rb, line 100
def write
  write_callback(1000, 'Check database for changes.')
  
  if @has_changed
    tmp = Pathname.new("#{@file_path}~").expand_path
    
    # http://stackoverflow.com/questions/9049789/aes-encryption-key-versus-iv
    # http://keepass.info/help/base/security.html
    # https://gist.github.com/byu/99651
    
    write_callback(1100, 'Make temp database.')
    db_c = @data.clone
    db_c['hosts'] = db_c['hosts'].map{ |name, host| [name, host.clone.to_h] }.to_h
    
    write_callback(1200, 'Setup database encryption.')
    dk_sha256 = OpenSSL::Digest::SHA256.digest(@osp.dk)
    iv = OpenSSL::Cipher::AES.new(256, 'CBC').random_iv
    
    aes = OpenSSL::Cipher::AES.new(256, 'CBC')
    aes.encrypt
    aes.key = dk_sha256
    aes.iv = iv
    
    write_callback(1250, 'Encrypt database.')
    db_e = aes.update(Base64.strict_encode64(db_c.to_msgpack))
    db_e << aes.final
    
    mac = OpenSSL::Digest::SHA256.digest(db_e)
    
    db_out = {
      'version' => 1,
      'iv' => Base64.strict_encode64(iv),
      'db' => Base64.strict_encode64(db_e),
      'mac' => mac,
    }
    db_out = db_out.to_msgpack
    db_out = Base64.strict_encode64(db_out)
    
    write_callback(1300, "Write temp file to '#{tmp}'.")
    File.write(tmp, 'tmp')
    tmp.chmod(0600)
    File.binwrite(tmp, db_out)
    
    backup_dts = Time.now.strftime('%Y%m%d-%H%M%S')
    backup = Pathname.new("#{@file_path}~backup_#{backup_dts}_" << Digest::SHA256.file(tmp.to_s).hexdigest[0..7])
    
    write_callback(1350, "Backup temp file to '#{backup}'.")
    File.write(backup, 'tmp')
    backup.chmod(0600)
    FileUtils.cp(tmp, backup)
    
    write_callback(1390, "Finally, move temp file to '#{@file_path}'.")
    File.write(@file_path, 'tmp')
    @file_path.chmod(0600)
    tmp.rename(@file_path)
    
    @has_changed = false
  else
    write_callback(9500, 'Nothing changed, nothing written.')
  end
end
write_callback(*o) click to toggle source
# File lib/osp/database.rb, line 94
def write_callback(*o)
  unless @write_callback_method.nil?
    @write_callback_method.call(*o)
  end
end
write_callback_method=(m) click to toggle source
# File lib/osp/database.rb, line 90
def write_callback_method=(m)
  @write_callback_method = m
end