【问题标题】:Accessing certificates of another user via impersonation通过模拟访问另一个用户的证书
【发布时间】:2021-01-09 01:16:03
【问题描述】:

我需要在 IIS 中运行 ASP.NET Core 2.2 的 Web 服务器上对 PDF 文档进行数字签名。 Web 应用程序与服务用户一起运行,模拟应在代码中完成。问题是,我无法通过 X509Store 类访问用户证书。我尝试创建一个仅模拟不同用户并在控制台中输出证书的最小示例。我以管理员身份运行它,但没有找到任何证书。

我在“计算机配置 > Windows 设置 > 安全设置 > 本地策略 > 用户权限管理”下的“本地组策略编辑器”(gpedit.msc) 中授予的权限:

  • 作为操作系统的一部分:添加了 ServiceUser
  • 允许本地登录:添加了 UserToBeImpersonated
  • 作为批处理作业登录:添加了 UserToBeImpersonated
  • 创建令牌对象:添加了 ServiceUser
  • 替换进程级别令牌:添加了 ServiceUser
  • 调整进程的内存配额:添加了 ServiceUser

对于模拟,我使用了来自 https://github.com/mj1856/SimpleImpersonation 的 SimpleImpersonation 并将其扩展为还加载用户配置文件,如下所示:

public static void RunAsUser(UserCredentials credentials, LogonType logonType, Action action)
{
    // this method tells Windows to dynamically determine where to look for the HKEY_CURRENT_USER registry hive,
    // rather than using the cached location from when the process was initially invoked
    RegDisablePredefinedCache();

    using (var tokenHandle = credentials.Impersonate(logonType))
    using (var profileToken = credentials.ImpersonateUserProfile(tokenHandle.DangerousGetHandle()))
    {
        RunImpersonated(tokenHandle, _ => action());
    }
}       

internal UserProfileToken ImpersonateUserProfile(IntPtr tokenHandle)
{
    var tokenDuplicate = IntPtr.Zero;

    // Not sure if the token needs to be duplicated or not
    if (DuplicateToken(tokenHandle, 2, ref tokenDuplicate) == 0)
        HandleError(tokenHandle);

    // Load User profile
    var profileInfo = new ProfileInfo();
    profileInfo.dwSize = Marshal.SizeOf(profileInfo);
    profileInfo.lpUserName = _username;
    profileInfo.dwFlags = 1;

    // LoadUserProfile() failed
    if (!LoadUserProfile(tokenDuplicate, ref profileInfo))
        HandleError(tokenDuplicate);

    // LoadUserProfile() failed - HKCU handle was not loaded
    if (profileInfo.hProfile == IntPtr.Zero)
        HandleError(tokenDuplicate);

    return new UserProfileToken() { ProfileInfo = profileInfo, Token = tokenDuplicate };
}

public class UserProfileToken : IDisposable
{
    public ProfileInfo ProfileInfo { get; set; }

    public IntPtr Token { get; set; }

    ~UserProfileToken()
    {
        Dispose();
    }

    private bool isDisposed = false;

    public void Dispose()
    {
        if (isDisposed) return;
        isDisposed = true;

        UnloadUserProfile(Token, ProfileInfo.hProfile);
    }
}
       
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool LoadUserProfile(IntPtr hToken, ref ProfileInfo lpProfileInfo);

[DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool UnloadUserProfile(IntPtr hToken, IntPtr lpProfileInfo);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
public static extern int RegDisablePredefinedCache();

测试的主要代码如下:

var credentials = new Impersonator.UserCredentials(domain, username, password);
Impersonator.RunAsUser(credentials, Impersonator.LogonType.Interactive, () =>
{
    Console.WriteLine($"Current user: {WindowsIdentity.GetCurrent().Name}");
    
    var store = new X509Store(StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    if (store.Certificates.Count == 0)
    {
        Console.WriteLine("No Certificates found.");
    }
    else
    {
        var certCount = 0;
        foreach (var cert in store.Certificates)
        {
            Console.WriteLine($"[{certCount}] Subject: {cert.Subject}");
            Console.WriteLine($"[{certCount}] Issuer: {cert.Issuer}");

            certCount++;
        }
    }
});

很遗憾,当相同的代码(没有模拟)在目标用户机器上显示两个证书时,找不到任何证书。

我在服务器上发现的另一个有趣的事情是C:\Users下的用户配置文件目录没有用户名而是一些中文?人物。在系统设置中检查用户配置文件时,用户名是正确的。

【问题讨论】:

    标签: c# asp.net-core certificate x509 impersonation


    【解决方案1】:

    似乎模拟(加载配置文件)不会触发自动注册以从证书颁发机构接收证书。这必须单独完成,如下所示:

    using CERTENROLLLib;
    
    public static void EnrollUserCertificateByTemplate(string templateName, string subjectName, string friendlyName = null)
        {
            var enrollment = new CX509Enrollment();
    
            // Set target store
            enrollment.InitializeFromTemplateName(X509CertificateEnrollmentContext.ContextUser, templateName);
    
            var request = enrollment.Request;
            var innerRequest = request.GetInnerRequest(InnerRequestLevel.LevelInnermost);
            var innerRequestPkcs10 = innerRequest as IX509CertificateRequestPkcs10;
    
            // Set the subject name
            var distinguishedName = new CX500DistinguishedName();
            distinguishedName.Encode(subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);            
            innerRequestPkcs10.Subject = distinguishedName;
    
            // Set the friendly name
            if (friendlyName != null) enrollment.CertificateFriendlyName = friendlyName;
    
            // Enroll for the certificate into MY store if it is successfully issued by CA
            enrollment.Enroll();
        }
    

    此外,您还需要检查用户证书存储中是否已经存在有效证书。否则,您每次模拟时都添加一个证书(并执行上面的代码)。

    我需要做的另一件事是授予执行用户管理权限。我的问题中发布的所有其他权限都可以删除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-13
      • 1970-01-01
      • 1970-01-01
      • 2020-07-11
      • 2019-09-30
      • 1970-01-01
      相关资源
      最近更新 更多