Module: Lich::Common::GUI::WindowsCredentialManager

Extended by:
FFI::Library
Defined in:
documented/common/gui/windows_credential_manager.rb

Overview

Provides an interface to the Windows Credential Manager. This module allows storing, retrieving, and deleting credentials on Windows.

Examples:

Storing a credential

WindowsCredentialManager.store_credential("target", "user", "pass")

Defined Under Namespace

Classes: CredentialStruct

Constant Summary collapse

CRED_TYPE_GENERIC =

CRED_TYPE values Represents a generic credential type.

1
CRED_TYPE_DOMAIN_PASSWORD =
2
CRED_TYPE_DOMAIN_CERTIFICATE =
3
CRED_TYPE_DOMAIN_VISIBLE_PASSWORD =
4
CRED_TYPE_GENERIC_CERTIFICATE =
5
CRED_TYPE_DOMAIN_EXTENDED =
6
CRED_PERSIST_SESSION =

CRED_PERSIST values Represents a credential that is valid for the current session.

1
CRED_PERSIST_LOCAL_MACHINE =
2
CRED_PERSIST_ENTERPRISE =
3
CRED_MAX_CREDENTIAL_BLOB_SIZE =

Max credential size (512KB) Maximum size for a credential blob, set to 512KB.

512 * 1024

Class Method Summary collapse

Class Method Details

.available?Boolean

Checks if the Windows Credential Manager is available.

Examples:

Checking availability

if WindowsCredentialManager.available?
  puts "Credential Manager is available"
end

Returns:

  • (Boolean)

    Returns true if available, false otherwise.



75
76
77
78
79
80
81
82
83
84
85
# File 'documented/common/gui/windows_credential_manager.rb', line 75

def available?
  return false unless OS.windows?

  # Simple test: try to allocate credential structure
  begin
    FFI::MemoryPointer.new(CredentialStruct)
    true
  rescue
    false
  end
end

.delete_credential(target_name) ⇒ Boolean

Deletes a credential from the Windows Credential Manager.

Examples:

Deleting a credential

WindowsCredentialManager.delete_credential("target")

Parameters:

  • target_name (String)

    The target name for the credential to delete.

Returns:

  • (Boolean)

    Returns true if the credential was deleted successfully, false otherwise.

Raises:

  • (StandardError)

    Raises an error if deletion fails.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'documented/common/gui/windows_credential_manager.rb', line 196

def delete_credential(target_name)
  return false unless available?

  begin
    target_name_wide = string_to_wide(target_name)

    # Call CredDeleteW
    result = CredDeleteW(target_name_wide, CRED_TYPE_GENERIC, 0)

    if result
      true
    else
      error_code = GetLastError
      Lich.log "error: CredDeleteW failed with error code #{error_code}"
      false
    end
  rescue StandardError => e
    Lich.log "error: Failed to delete credential: #{e.message}"
    false
  end
end

.retrieve_credential(target_name) ⇒ String?

Retrieves a credential from the Windows Credential Manager.

Examples:

Retrieving a credential

password = WindowsCredentialManager.retrieve_credential("target")

Parameters:

  • target_name (String)

    The target name for the credential to retrieve.

Returns:

  • (String, nil)

    Returns the password if found, nil otherwise.

Raises:

  • (StandardError)

    Raises an error if retrieval fails.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'documented/common/gui/windows_credential_manager.rb', line 152

def retrieve_credential(target_name)
  return nil unless available?

  begin
    target_name_wide = string_to_wide(target_name)
    cred_ptr = FFI::MemoryPointer.new(:pointer)

    # Call CredReadW
    result = CredReadW(target_name_wide, CRED_TYPE_GENERIC, 0, cred_ptr)

    if result
      cred = cred_ptr.read_pointer
      credential = CredentialStruct.new(cred)

      # Extract password blob
      blob_ptr = credential[:credential_blob]
      blob_size = credential[:credential_blob_size]
      password_blob = blob_ptr.read_bytes(blob_size)
      # Password blob is stored as UTF-8 bytes; convert safely with encoding handling
      password = password_blob.b.force_encoding('UTF-8')

      # Free credential structure
      CredFree(cred)

      password
    else
      error_code = GetLastError
      # ERROR_NOT_FOUND = 1168, so only log unexpected errors
      Lich.log "error: CredReadW failed with error code #{error_code}" unless error_code == 1168
      nil
    end
  rescue StandardError
    # Don't log as error for "not found" - it's expected on first run
    # Only actual failures (encoding, access, API errors) should log
    nil
  end
end

.store_credential(target_name, username, password, comment = nil, persist = CRED_PERSIST_LOCAL_MACHINE) ⇒ Boolean

Stores a credential in the Windows Credential Manager.

Examples:

Storing a credential

WindowsCredentialManager.store_credential("target", "user", "pass")

Parameters:

  • target_name (String)

    The target name for the credential.

  • username (String)

    The username associated with the credential.

  • password (String)

    The password associated with the credential.

  • comment (String, nil) (defaults to: nil)

    An optional comment for the credential.

  • persist (Integer) (defaults to: CRED_PERSIST_LOCAL_MACHINE)

    The persistence type for the credential (default: CRED_PERSIST_LOCAL_MACHINE).

Returns:

  • (Boolean)

    Returns true if the credential was stored successfully, false otherwise.

Raises:

  • (StandardError)

    Raises an error if storing fails.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'documented/common/gui/windows_credential_manager.rb', line 97

def store_credential(target_name, username, password, comment = nil, persist = CRED_PERSIST_LOCAL_MACHINE)
  return false unless available?

  begin
    credential = CredentialStruct.new

    # Convert strings to wide characters (UTF-16LE)
    target_name_wide = string_to_wide(target_name)
    username_wide = string_to_wide(username)
    # Store password as UTF-8 bytes (as binary data, not text)
    password_bytes = password.to_s.encode('UTF-8')
    password_blob = password_bytes.b
    comment_wide = comment ? string_to_wide(comment) : FFI::Pointer.new(:pointer, 0)

    Lich.log "debug: Storing credential - target: #{target_name}, user: #{username}, pass_size: #{password_blob.bytesize}, persist: #{persist}"

    # Allocate memory for credential blob (password data)
    blob_ptr = FFI::MemoryPointer.new(:uint8, password_blob.bytesize)
    blob_ptr.put_bytes(0, password_blob)

    # Fill credential structure
    credential[:flags] = 0
    credential[:type] = CRED_TYPE_GENERIC
    credential[:target_name] = target_name_wide
    credential[:comment] = comment_wide
    credential[:credential_blob_size] = password_blob.size
    credential[:credential_blob] = blob_ptr
    credential[:persist] = persist
    credential[:attribute_count] = 0
    credential[:attributes] = FFI::Pointer.new(:pointer, 0)
    credential[:user_name] = username_wide

    # Call CredWriteW - pass pointer to the credential struct
    result = CredWriteW(credential, 0)

    if result
      Lich.log "debug: Credential stored successfully"
      true
    else
      error_code = GetLastError
      Lich.log "error: CredWriteW failed with error code #{error_code}"
      false
    end
  rescue StandardError => e
    Lich.log "error: Failed to store credential: #{e.class.name}: #{e.message}"
    false
  end
end