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

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

Overview

Provides access to the Windows Credential Manager.

This module allows for storing, retrieving, and deleting credentials in the Windows Credential Manager.

See Also:

Defined Under Namespace

Classes: CredentialStruct

Constant Summary collapse

CRED_TYPE_GENERIC =

CRED_TYPE values

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

1
CRED_PERSIST_LOCAL_MACHINE =
2
CRED_PERSIST_ENTERPRISE =
3
CRED_MAX_CREDENTIAL_BLOB_SIZE =

Max credential size (512KB)

512 * 1024

Class Method Summary collapse

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
75
76
77
# File 'documented/common/gui/windows_credential_manager.rb', line 67

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:

Delete a credential

delete_credential("example.com")

Parameters:

  • target_name (String)

    the target name for the credential

Returns:

  • (Boolean)

    true if the credential was deleted successfully, false otherwise



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'documented/common/gui/windows_credential_manager.rb', line 188

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:

Retrieve a credential

password = retrieve_credential("example.com")

Parameters:

  • target_name (String)

    the target name for the credential

Returns:

  • (String, nil)

    the password if found, nil if not found



144
145
146
147
148
149
150
151
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
# File 'documented/common/gui/windows_credential_manager.rb', line 144

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:

Store a credential

store_credential("example.com", "user", "password123")

Parameters:

  • target_name (String)

    the target name for the credential

  • username (String)

    the username associated with the credential

  • password (String)

    the password for 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)

    true if the credential was stored successfully, false otherwise



89
90
91
92
93
94
95
96
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
# File 'documented/common/gui/windows_credential_manager.rb', line 89

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