返回

Windows无智能卡证书认证:LSALOGONUSER实现本地账户登录

windows

使用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;

}

操作步骤:

  1. 编译并运行以上代码,确保进程获得 SeTcbPrivilege 权限。
  2. 也可以通过本地安全策略编辑器手动设置,将进程的运行用户添加到 “替换进程级令牌” 策略中。

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"

操作步骤:

  1. mycert.cer 替换为实际证书文件的路径。

  2. CN=testuser 替换为证书中实际的主题名称。

  3. "YourLocalUsername" 替换为要映射到的本地账户名。

  4. 使用 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 结构体的成员正确填充。尤其注意 LogonIdCertificateBuffer 字段。

代码示例 (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