Module: Lich::Common::GUI::MasterPasswordManager

Defined in:
documented/common/gui/master_password_manager.rb

Overview

Manages the storage and retrieval of the master password.

This module provides methods to store, retrieve, validate, and delete the master password using the system's keychain.

See Also:

Constant Summary collapse

KEYCHAIN_SERVICE =
'lich5.master_password'
VALIDATION_ITERATIONS =
100_000
VALIDATION_KEY_LENGTH =
32
VALIDATION_SALT_PREFIX =
'lich5-master-password-validation-v1'

Class Method Summary collapse

Class Method Details

.create_validation_test(master_password) ⇒ Hash

Creates a validation test for the given master password.

Examples:

Create a validation test

validation_test = MasterPasswordManager.create_validation_test("my_secret_password")

Parameters:

  • master_password (String)

    the master password to validate

Returns:

  • (Hash)

    a hash containing the validation salt and hash



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'documented/common/gui/master_password_manager.rb', line 88

def self.create_validation_test(master_password)
  random_salt = SecureRandom.random_bytes(16)
  full_salt = VALIDATION_SALT_PREFIX + random_salt

  validation_key = OpenSSL::PKCS5.pbkdf2_hmac(
    master_password, full_salt, VALIDATION_ITERATIONS,
    VALIDATION_KEY_LENGTH, OpenSSL::Digest.new('SHA256')
  )

  validation_hash = OpenSSL::Digest::SHA256.digest(validation_key)

  {
    'validation_salt'    => Base64.strict_encode64(random_salt),
    'validation_hash'    => Base64.strict_encode64(validation_hash),
    'validation_version' => 1
  }
end

.delete_master_passwordBoolean

Deletes the master password from the keychain.

Returns:

  • (Boolean)

    true if the password was deleted successfully, false otherwise

Raises:

  • (StandardError)

    if an error occurs during deletion



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'documented/common/gui/master_password_manager.rb', line 136

def self.delete_master_password
  return false unless keychain_available?

  if OS.mac?
    delete_macos_keychain
  elsif OS.linux?
    delete_linux_keychain
  elsif OS.windows?
    delete_windows_keychain
  else
    false
  end
rescue StandardError => e
  Lich.log "error: Failed to delete master password: #{e.message}"
  false
end

.keychain_available?Boolean

Checks if the keychain is available on the current operating system.

Returns:

  • (Boolean)

    true if the keychain is available, false otherwise



30
31
32
33
34
35
36
37
38
39
40
# File 'documented/common/gui/master_password_manager.rb', line 30

def self.keychain_available?
  if OS.mac?
    macos_keychain_available?
  elsif OS.linux?
    linux_keychain_available?
  elsif OS.windows?
    windows_keychain_available?
  else
    false
  end
end

.retrieve_master_passwordString?

Retrieves the master password from the keychain.

Returns:

  • (String, nil)

    the stored master password, or nil if not found

Raises:

  • (StandardError)

    if an error occurs during retrieval



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'documented/common/gui/master_password_manager.rb', line 66

def self.retrieve_master_password
  return nil unless keychain_available?

  if OS.mac?
    retrieve_macos_keychain
  elsif OS.linux?
    retrieve_linux_keychain
  elsif OS.windows?
    retrieve_windows_keychain
  else
    nil
  end
rescue StandardError => e
  Lich.log "error: Failed to retrieve master password: #{e.message}"
  nil
end

.store_master_password(master_password) ⇒ Boolean

Stores the master password in the keychain.

Parameters:

  • master_password (String)

    the master password to store

Returns:

  • (Boolean)

    true if the password was stored successfully, false otherwise

Raises:

  • (StandardError)

    if an error occurs during storage



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'documented/common/gui/master_password_manager.rb', line 46

def self.store_master_password(master_password)
  return false unless keychain_available?

  if OS.mac?
    store_macos_keychain(master_password)
  elsif OS.linux?
    store_linux_keychain(master_password)
  elsif OS.windows?
    store_windows_keychain(master_password)
  else
    false
  end
rescue StandardError => e
  Lich.log "error: Failed to store master password: #{e.message}"
  false
end

.validate_master_password(entered_password, validation_test) ⇒ Boolean

Validates the entered master password against the stored validation test.

Parameters:

  • entered_password (String)

    the password entered by the user

  • validation_test (Hash)

    the validation test data

Returns:

  • (Boolean)

    true if the password is valid, false otherwise

Raises:

  • (StandardError)

    if an error occurs during validation



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'documented/common/gui/master_password_manager.rb', line 111

def self.validate_master_password(entered_password, validation_test)
  return false unless validation_test.is_a?(Hash)
  return false unless validation_test['validation_salt'] && validation_test['validation_hash']

  begin
    random_salt = Base64.strict_decode64(validation_test['validation_salt'])
    stored_hash = Base64.strict_decode64(validation_test['validation_hash'])
    full_salt = VALIDATION_SALT_PREFIX + random_salt

    validation_key = OpenSSL::PKCS5.pbkdf2_hmac(
      entered_password, full_salt, VALIDATION_ITERATIONS,
      VALIDATION_KEY_LENGTH, OpenSSL::Digest.new('SHA256')
    )

    computed_hash = OpenSSL::Digest::SHA256.digest(validation_key)
    secure_compare(computed_hash, stored_hash)
  rescue StandardError => e
    Lich.log "error: Validation failed: #{e.message}"
    false
  end
end