Windows无智能卡证书认证:LSALOGONUSER实现本地账户登录
2024-12-15 12:47:26
使用LSALOGONUSER进行基于证书的身份验证(无智能卡)
在Windows系统中,LSALOGONUSER函数是进行用户身份验证的关键组件。通常,基于证书的身份验证会用到智能卡,但本文将探讨如何在没有智能卡的情况下,利用LSALOGONUSER函数实现基于证书的身份验证。尤其针对本地账户而非域用户的场景,我们将深入分析问题、查找原因,并提供解决方案。
问题分析:LSALOGONUSER 失败的原因
提供的代码片段尝试通过个人证书存储中的证书进行LSALOGONUSER身份验证。代码可以成功获取证书的私钥容器和CSP信息,但在调用 LsaLogonUser
函数时,返回错误代码0x0000052E,子状态码为0x000004F0。这意味着登录失败,且子状态码通常表示账户限制或证书映射问题。
具体来说,这个错误可能由以下原因引起:
- 证书映射: 本地账户没有在系统中映射到证书的主题名称 (Subject Name) 或使用者可选名称 (Subject Alternative Name)。
- 权限问题: 执行 LSALOGONUSER 的进程没有获得必要的权限,比如
SeTcbPrivilege
(充当操作系统的一部分)。 - 证书问题: 证书的密钥用法 (Key Usage) 或增强型密钥用法 (Enhanced Key Usage) 扩展不适合用于身份验证。
- CSP 问题: 加密服务提供程序 (CSP) 与 LSALOGONUSER 不兼容,或者没有正确配置。
- 账户限制: 本地账户可能被禁用或存在其他登录限制。
解决方案:
1. 检查并设置 SeTcbPrivilege
权限
LSALOGONUSER函数需要 SeTcbPrivilege
权限才能成功执行。必须确保调用进程拥有此权限。
代码示例 (C++):
#include <Windows.h>
#include <iostream>
#include <sddl.h>
#include <AclAPI.h>
#include <atlsecurity.h>
bool SetTcbPrivilege() {
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
std::cerr << "OpenProcessToken failed with error: " << GetLastError() << std::endl;
return false;
}
ATL::CTokenPrivileges privs;
if(!privs.AddPrivilege(SE_TCB_NAME)){
std::cerr << "AddPrivilege failed with error: " << GetLastError() << std::endl;
CloseHandle(hToken);
return false;
}
if(!privs.EnableAllPrivileges()){
std::cerr << "EnableAllPrivileges failed with error: " << GetLastError() << std::endl;
CloseHandle(hToken);
return false;
}
CloseHandle(hToken);
return true;
}
int main() {
if (SetTcbPrivilege()) {
std::cout << "SeTcbPrivilege set successfully." << std::endl;
// 执行其他需要 SeTcbPrivilege 的操作,例如调用 LsaLogonUser
}
else
{
std::cerr << "Failed to set SeTcbPrivilege." << std::endl;
}
return 0;
}
操作步骤:
- 编译并运行以上代码,确保进程获得
SeTcbPrivilege
权限。 - 也可以通过本地安全策略编辑器手动设置,将进程的运行用户添加到 “替换进程级令牌” 策略中。
2. 证书映射:为本地用户创建证书映射
对于本地用户,必须手动创建证书到本地用户的映射关系。 这可以通过 certutil
命令完成。
命令行指令:
certutil -user -addstore My mycert.cer
certutil -user -dump mycert.cer
#从输出中获取证书的 SHA1 哈希值
certutil -user -setreg chain\ClientAuthEKU "1.3.6.1.5.5.7.3.2"
certutil -user -setreg chain\StrongPrivateKeyProtection "1"
certutil -user -mapname "CN=testuser" "YourLocalUsername"
操作步骤:
-
将
mycert.cer
替换为实际证书文件的路径。 -
将
CN=testuser
替换为证书中实际的主题名称。 -
将
"YourLocalUsername"
替换为要映射到的本地账户名。 -
使用
certutil -user -dump mycert.cer
查看是否添加成功,注意检查是否包含如下几行:Subject: CN=testuser
检查是否与您设置的主题名称一致。dwFlags = CERT_STORE_LOCAL_MACHINE_GROUP_POLICY_FLAG | CERT_STORE_PROV_SYSTEM_W_FLAG (200008)
这行标志确保证书存储在本地计算机,并且设置了相应的系统标志位。Provider Name: Microsoft Enhanced RSA and AES Cryptographic Provider
这行显示了用于证书的CSP,确保证书是使用微软增强型RSA和AES加密提供程序创建的。
请确保
certutil -user -addstore My mycert.cer
和certutil -user -mapname "CN=testuser" "YourLocalUsername"
都已经成功运行并且证书主题名称与你的本地用户名称一一对应。同时确保证书属性
Key Usage
包含Digital Signature, Key Encipherment (a0)
或者包含Digital Signature
(80), 证书扩展属性Enhanced Key Usage
包含Client Authentication (1.3.6.1.5.5.7.3.2)
.
3. 构造 KERB_CERTIFICATE_UNLOCK_LOGON
结构体
确保 KERB_CERTIFICATE_UNLOCK_LOGON
结构体的成员正确填充。尤其注意 LogonId
和 CertificateBuffer
字段。
代码示例 (C++):
#include <Windows.h>
#include <iostream>
#include <security.h>
#include <ntsecapi.h>
#include <Wincrypt.h>
#include <sddl.h>
#pragma comment(lib, "Secur32.lib")
#pragma comment(lib, "crypt32.lib")
using namespace std;
BOOL ConstructAuthCertInfo(KERB_CERTIFICATE_UNLOCK_LOGON& rkiulIn, LPBYTE* ppbAuthInfo, ULONG* pulAuthInfoLen) {
HCRYPTSTORE hStoreHandle = NULL;
PCCERT_CONTEXT pCertContext = NULL;
LPCTSTR pszStoreName = _T("MY");
HCRYPTPROV hProv = NULL;
DWORD dwKeySpec;
wstring containerName;
wstring WProvName;
BOOL bFreeHandle = FALSE;
DWORD cbCertEncoded = 0;
// 打开证书存储区
hStoreHandle = CertOpenSystemStore(NULL, pszStoreName);
if (!hStoreHandle) {
std::cerr << "CertOpenSystemStore failed with error: " << GetLastError() << std::endl;
return FALSE;
}
//查找证书
pCertContext = CertFindCertificateInStore(
hStoreHandle,
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR_W,
L"testuser", //使用者
NULL);
if (!pCertContext) {
std::cerr << "CertFindCertificateInStore failed with error: " << GetLastError() << std::endl;
CertCloseStore(hStoreHandle, 0);
return FALSE;
}
// 获取证书的编码字节
if (!CertGetCertificateContextProperty(pCertContext, CERT_CONTEXT_PROP_ID, NULL, &cbCertEncoded)) {
std::cerr << "CertGetCertificateContextProperty (get size) failed with error: " << GetLastError() << std::endl;
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStoreHandle, 0);
return FALSE;
}
rkiulIn.CertificateLength = cbCertEncoded;
rkiulIn.CertificateBuffer = (PUCHAR)malloc(cbCertEncoded);
if (rkiulIn.CertificateBuffer == NULL)
{
std::cerr << "Memory allocation for CertificateBuffer failed." << std::endl;
CertFreeCertificateContext(pCertContext);
CertCloseStore(hStoreHandle, 0);
return FALSE;
}
if (!CertGetCertificateContextProperty(pCertContext, CERT_CONTEXT_PROP_ID, r