Class: Aspera::Keychain::EncryptedHash

Inherits:
Base
  • Object
show all
Defined in:
lib/aspera/keychain/encrypted_hash.rb

Overview

Manage secrets in a simple Hash

Defined Under Namespace

Classes: OsslCipher

Constant Summary

Constants inherited from Base

Base::CONTENT_KEYS

Instance Method Summary collapse

Methods inherited from Base

#ids, #validate_set

Constructor Details

#initialize(file:, password:) ⇒ EncryptedHash

Returns a new instance of EncryptedHash.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/aspera/keychain/encrypted_hash.rb', line 28

def initialize(file:, password:)
  super()
  Aspera.assert_type(file, String) { 'path to vault file' }
  @path = file
  Log.dump(:vault_file, @path)
  @all_secrets = {}
  @cipher_name = DEFAULT_CIPHER_NAME
  @kdf_params = nil
  # true when the vault was created by v4.26.0 (password padded with zeros, no KDF)
  @legacy_key = false
  vault_encrypted_data = nil
  Log.dump(:vault_file, File.expand_path(@path))
  if File.exist?(@path)
    vault_file = File.read(@path)
    if vault_file.start_with?('---')
      vault_info = YAML.parse(vault_file).to_ruby
      sorted_keys = vault_info.keys.sort
      Aspera.assert(sorted_keys == FILE_KEYS || sorted_keys == FILE_KEYS_KDF) { "Invalid vault file: #{@path}: #{sorted_keys}" }
      @cipher_name = vault_info['cipher']
      @kdf_params  = vault_info['kdf']
      # vault created before PBKDF2 was introduced (v4.26.0 format: no kdf field)
      @legacy_key = @kdf_params.nil?
      vault_encrypted_data = vault_info['data']
    else
      # legacy vault file (binary, pre-YAML format)
      @cipher_name = LEGACY_CIPHER_NAME
      @legacy_key = true
      vault_encrypted_data = File.read(@path, mode: 'rb')
    end
  end
  # setting password also creates the cipher
  @cipher = cipher(password)
  @all_secrets = Yaml.safe_load(@cipher.decrypt(vault_encrypted_data)) || {} if !vault_encrypted_data.nil?
end

Instance Method Details

#all ⇒ Object



69
70
71
72
73
74
75
76
77
78
# File 'lib/aspera/keychain/encrypted_hash.rb', line 69

def all
  result = []
  @all_secrets.each do |label, values|
    normal = values.symbolize_keys
    normal[:label] = label
    CONTENT_KEYS.each { |k| normal[k] = '' unless normal.key?(k) }
    result.push(normal)
  end
  return result
end

#change_password(password) ⇒ Object



102
103
104
105
106
107
# File 'lib/aspera/keychain/encrypted_hash.rb', line 102

def change_password(password)
  # Generate new KDF params so a password change also re-salts the vault
  @kdf_params = nil
  @cipher = cipher(password)
  save
end

#delete(label:) ⇒ Object



97
98
99
100
# File 'lib/aspera/keychain/encrypted_hash.rb', line 97

def delete(label:)
  @all_secrets.delete(label)
  save
end

#get(label:, exception: true) ⇒ Object



90
91
92
93
94
95
# File 'lib/aspera/keychain/encrypted_hash.rb', line 90

def get(label:, exception: true)
  Aspera.assert(@all_secrets.key?(label)) { "Label not found: #{label}" } if exception
  result = @all_secrets[label].clone
  result[:label] = label if result.is_a?(Hash)
  return result
end

#info ⇒ Object



63
64
65
66
67
# File 'lib/aspera/keychain/encrypted_hash.rb', line 63

def info
  return {
    file: @path
  }
end

#set(options) ⇒ Object

set a secret

Parameters:

  • options (Hash) —

    with keys :label, :username, :password, :url, :description



82
83
84
85
86
87
88
# File 'lib/aspera/keychain/encrypted_hash.rb', line 82

def set(options)
  validate_set(options)
  label = options.delete(:label)
  Aspera.assert(!@all_secrets.key?(label)) { "secret #{label} already exist, delete first" }
  @all_secrets[label] = options.symbolize_keys
  save
end