最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Yubikey and Performing admin task in Active Directory in C# - Stack Overflow

programmeradmin0浏览0评论

I have an app I built that helps me with some tasks such as adding users to groups, removing, creating users, deleting users, and unlocking users in Active Directory. Which all require some admin privileges.

Currently, the way it authenticates the admin account connection to the domain is password-based authentication.

The user gets prompted to provide a username and password and using PrincipalContext I authenticate the session. Throughout the app I reference the context variable to perform these tasks.

using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _myDomainName, adminUsername, adminPassword))

I want to check the login to the app to use Smart Card. I was able to use that, but it seems like PrincipalContext only support password-based authentication there is not certification-based authentication as far as I can tell. This lead to context variable to not be able to reference my admin account in the session, causing "Access Denied" errors.

Currently I authenticate the user like this:

static X509Certificate2 GetAdminCertificate()
{
    Console.Write("Enter admin username: ");
    adminUsername = Console.ReadLine().Trim();
    /*
    Console.Write("Enter your smart card PIN: ");
    string smartCardPin = PasswordManager.GetPassword().Trim();
    */

    using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
    {
        store.Open(OpenFlags.ReadOnly);
        
        X509Certificate2Collection yubiKeyCerts = new X509Certificate2Collection();

        foreach (var cert in store.Certificates)
        {
            // First, check if the subject name matches the admin username
            string subjectName = cert.GetNameInfo(X509NameType.SimpleName, false);

            if (!subjectName.Equals(adminUsername, StringComparison.OrdinalIgnoreCase))
            {
                // Skip this certificate if the subject does not match
                continue;
            }

            try
            {
                if (cert.HasPrivateKey)
                {
                    // Access the private key
                    var privateKey = cert.PrivateKey;

                    if (privateKey is RSACng rsaCng)
                    {
                        if (rsaCng.KeyExchangeAlgorithm.Equals("RSA", StringComparison.OrdinalIgnoreCase))
                        {
                            // Match the certificate with admin username
                            if (subjectName.Equals(adminUsername, StringComparison.OrdinalIgnoreCase))
                            {
                                // Attempt to sign data with the RSA key to validate access
                                byte[] dataToSign = new byte[] { 0x01 }; // Dummy data
                                byte[] signedData = rsaCng.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                                yubiKeyCerts.Add(cert);
                            }  // end of if-statement
                        }  // end of if-statement 
                    }  // end of if-statement
                }  // end of if-statement
            }  // end of try
            catch (Exception ex)
            {
                Console.WriteLine($"Error obtaining certification: {ex.Message}");
                continue;
            }  // end of catch
        }  // end of foreach

        if (yubiKeyCerts.Count == 0)
        {
            Console.WriteLine("No smart card detected or no valid certificates found on the connected smart card.");
            return null;
        }

        // X509Certificate2 selectedCert = null;
        if (yubiKeyCerts.Count > 0)
        {
            selectecdCert = X509Certificate2UI.SelectFromCollection(
                yubiKeyCerts,
                "Select a YubiKey certificate",
                "Please select your admin certificate from the YubiKey",
                X509SelectionFlag.SingleSelection
            )[0];
        }

        adminUsername = selectecdCert.GetNameInfo(X509NameType.SimpleName, false);
        // adminPassword = selectecdCert.Verify();

        return selectecdCert;
    }  // end of using x509Store
}  // end of GetAdminCertificate

(too many if statements - I know)

The app connects to the domain with follow:

static void Main(string[] args)
{
      ActiveDirectoryManager ADManager = new ActiveDirectoryManager();
      AccountCreationManager ACManager;
      AccountDeactivationManager ACCDeactivationManager = new AccountDeactivationManager();
      PasswordManager PWDManager = null;
      ADGroupActionManager ADGroupManager = null;
      AuditLogManager auditLogManager = null;

      configuration = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("Appsettings.json", optional: false, reloadOnChange: true)
          .Build();

      EmailNotifcationManager emailManager = new EmailNotifcationManager(configuration);
      string _myDomainName = configuration["AccountCreationSettings:myDomainName"];

      do
      {
          X509Certificate2 certificate = GetAdminCertificate();

          if (certificate == null)
          {
              Console.WriteLine("No valid smart card certificate found.");
              return;
          }

          try
          {
              using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _myDomainName))                                                          
              // Check if the the password/user are correct
              {
                  // Throw error if the password/username is incorrect        
                  if (context.ConnectedServer != null)                                                                                                                
                  {
                      isAuthenticated = true;
                      Console.WriteLine($"Connected to Active Directory as: {adminUsername}.".Pastel(Color.GreenYellow));

                      auditLogManager = new AuditLogManager(adminUsername, configuration);
                      ADGroupManager = new ADGroupActionManager(auditLogManager);
                      PWDManager = new PasswordManager(auditLogManager);
                      ACManager = new AccountCreationManager(auditLogManager, configuration);

                      bool exit = false;

                      // Loop the menu
                      while (!exit)                                                                                                                                           
                      {
                          DisplayMainMenu();
                          string choice = Console.ReadLine();
                          exit = HandleMainMenuChoice(choice, context, ADManager, ADGroupManager, PWDManager, ACManager, ACCDeactivationManager);
                      }   // end of while-loop
                  }    // end of if statement

                  context.Dispose();
              }   // end of using
          }   // end of try
          catch (DirectoryServicesCOMException)                                                                                                                                         
          {
              // Error out if password/username are incorrect
              Console.WriteLine("Error: Unable to connect to the Active Directory server. Please check your credentials and try again.".Pastel(Color.IndianRed));
          }
          catch (Exception ex)
          {
              Console.WriteLine($"An error occurred: {ex.Message}".Pastel(Color.IndianRed));
          }    // end of Catch
      } while (!isAuthenticated || string.IsNullOrEmpty(adminUsername));                                                                                                                                              
        // Repeat until a valid password is entered
}  // end of Main Method

Example of a snippet of code I get Access denied. As you see I reference the context variable here to use the admin session to perform these tasks.

UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);                                              

// Check for user in AD
if (user != null)
{
     // Check for group in AD
     GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName);                                                                        

     if (group != null)
     {
         // If the user is not in the group add him
         if (!group.Members.Contains(user))                                                                                                               
         {
             // Add the user to the group
             group.Members.Add(user);
             // Apply changes
             group.Save();                                                                                                                       

             group.Dispose();

             Console.WriteLine($"User '{username}' added to group '{groupName}' successfully.".Pastel(Color.LimeGreen));

             string logEntry = ($"\"{user.DisplayName}\" has been added to \"{groupName}\" group in Active Directory\n");
             emailActionLog.Add(logEntry);
             auditLogManager.Log(logEntry);
         }   // end of inner-2 if-statement
         else
         {
             Console.WriteLine($"User '{username}' is already a member of group '{groupName}'.".Pastel(Color.DarkGoldenrod));
         }   // end of inner-2 else-statement
     }   // end of inner if-statement
     else
     {
         Console.WriteLine($"Group '{groupName}' not found in Active Directory.".Pastel(Color.IndianRed));
     }   // end of outer else-statement
}

My main question, is there a way I could Authenticate my admin account with a certification-based logon?

I have an app I built that helps me with some tasks such as adding users to groups, removing, creating users, deleting users, and unlocking users in Active Directory. Which all require some admin privileges.

Currently, the way it authenticates the admin account connection to the domain is password-based authentication.

The user gets prompted to provide a username and password and using PrincipalContext I authenticate the session. Throughout the app I reference the context variable to perform these tasks.

using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _myDomainName, adminUsername, adminPassword))

I want to check the login to the app to use Smart Card. I was able to use that, but it seems like PrincipalContext only support password-based authentication there is not certification-based authentication as far as I can tell. This lead to context variable to not be able to reference my admin account in the session, causing "Access Denied" errors.

Currently I authenticate the user like this:

static X509Certificate2 GetAdminCertificate()
{
    Console.Write("Enter admin username: ");
    adminUsername = Console.ReadLine().Trim();
    /*
    Console.Write("Enter your smart card PIN: ");
    string smartCardPin = PasswordManager.GetPassword().Trim();
    */

    using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
    {
        store.Open(OpenFlags.ReadOnly);
        
        X509Certificate2Collection yubiKeyCerts = new X509Certificate2Collection();

        foreach (var cert in store.Certificates)
        {
            // First, check if the subject name matches the admin username
            string subjectName = cert.GetNameInfo(X509NameType.SimpleName, false);

            if (!subjectName.Equals(adminUsername, StringComparison.OrdinalIgnoreCase))
            {
                // Skip this certificate if the subject does not match
                continue;
            }

            try
            {
                if (cert.HasPrivateKey)
                {
                    // Access the private key
                    var privateKey = cert.PrivateKey;

                    if (privateKey is RSACng rsaCng)
                    {
                        if (rsaCng.KeyExchangeAlgorithm.Equals("RSA", StringComparison.OrdinalIgnoreCase))
                        {
                            // Match the certificate with admin username
                            if (subjectName.Equals(adminUsername, StringComparison.OrdinalIgnoreCase))
                            {
                                // Attempt to sign data with the RSA key to validate access
                                byte[] dataToSign = new byte[] { 0x01 }; // Dummy data
                                byte[] signedData = rsaCng.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                                yubiKeyCerts.Add(cert);
                            }  // end of if-statement
                        }  // end of if-statement 
                    }  // end of if-statement
                }  // end of if-statement
            }  // end of try
            catch (Exception ex)
            {
                Console.WriteLine($"Error obtaining certification: {ex.Message}");
                continue;
            }  // end of catch
        }  // end of foreach

        if (yubiKeyCerts.Count == 0)
        {
            Console.WriteLine("No smart card detected or no valid certificates found on the connected smart card.");
            return null;
        }

        // X509Certificate2 selectedCert = null;
        if (yubiKeyCerts.Count > 0)
        {
            selectecdCert = X509Certificate2UI.SelectFromCollection(
                yubiKeyCerts,
                "Select a YubiKey certificate",
                "Please select your admin certificate from the YubiKey",
                X509SelectionFlag.SingleSelection
            )[0];
        }

        adminUsername = selectecdCert.GetNameInfo(X509NameType.SimpleName, false);
        // adminPassword = selectecdCert.Verify();

        return selectecdCert;
    }  // end of using x509Store
}  // end of GetAdminCertificate

(too many if statements - I know)

The app connects to the domain with follow:

static void Main(string[] args)
{
      ActiveDirectoryManager ADManager = new ActiveDirectoryManager();
      AccountCreationManager ACManager;
      AccountDeactivationManager ACCDeactivationManager = new AccountDeactivationManager();
      PasswordManager PWDManager = null;
      ADGroupActionManager ADGroupManager = null;
      AuditLogManager auditLogManager = null;

      configuration = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("Appsettings.json", optional: false, reloadOnChange: true)
          .Build();

      EmailNotifcationManager emailManager = new EmailNotifcationManager(configuration);
      string _myDomainName = configuration["AccountCreationSettings:myDomainName"];

      do
      {
          X509Certificate2 certificate = GetAdminCertificate();

          if (certificate == null)
          {
              Console.WriteLine("No valid smart card certificate found.");
              return;
          }

          try
          {
              using (PrincipalContext context = new PrincipalContext(ContextType.Domain, _myDomainName))                                                          
              // Check if the the password/user are correct
              {
                  // Throw error if the password/username is incorrect        
                  if (context.ConnectedServer != null)                                                                                                                
                  {
                      isAuthenticated = true;
                      Console.WriteLine($"Connected to Active Directory as: {adminUsername}.".Pastel(Color.GreenYellow));

                      auditLogManager = new AuditLogManager(adminUsername, configuration);
                      ADGroupManager = new ADGroupActionManager(auditLogManager);
                      PWDManager = new PasswordManager(auditLogManager);
                      ACManager = new AccountCreationManager(auditLogManager, configuration);

                      bool exit = false;

                      // Loop the menu
                      while (!exit)                                                                                                                                           
                      {
                          DisplayMainMenu();
                          string choice = Console.ReadLine();
                          exit = HandleMainMenuChoice(choice, context, ADManager, ADGroupManager, PWDManager, ACManager, ACCDeactivationManager);
                      }   // end of while-loop
                  }    // end of if statement

                  context.Dispose();
              }   // end of using
          }   // end of try
          catch (DirectoryServicesCOMException)                                                                                                                                         
          {
              // Error out if password/username are incorrect
              Console.WriteLine("Error: Unable to connect to the Active Directory server. Please check your credentials and try again.".Pastel(Color.IndianRed));
          }
          catch (Exception ex)
          {
              Console.WriteLine($"An error occurred: {ex.Message}".Pastel(Color.IndianRed));
          }    // end of Catch
      } while (!isAuthenticated || string.IsNullOrEmpty(adminUsername));                                                                                                                                              
        // Repeat until a valid password is entered
}  // end of Main Method

Example of a snippet of code I get Access denied. As you see I reference the context variable here to use the admin session to perform these tasks.

UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);                                              

// Check for user in AD
if (user != null)
{
     // Check for group in AD
     GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName);                                                                        

     if (group != null)
     {
         // If the user is not in the group add him
         if (!group.Members.Contains(user))                                                                                                               
         {
             // Add the user to the group
             group.Members.Add(user);
             // Apply changes
             group.Save();                                                                                                                       

             group.Dispose();

             Console.WriteLine($"User '{username}' added to group '{groupName}' successfully.".Pastel(Color.LimeGreen));

             string logEntry = ($"\"{user.DisplayName}\" has been added to \"{groupName}\" group in Active Directory\n");
             emailActionLog.Add(logEntry);
             auditLogManager.Log(logEntry);
         }   // end of inner-2 if-statement
         else
         {
             Console.WriteLine($"User '{username}' is already a member of group '{groupName}'.".Pastel(Color.DarkGoldenrod));
         }   // end of inner-2 else-statement
     }   // end of inner if-statement
     else
     {
         Console.WriteLine($"Group '{groupName}' not found in Active Directory.".Pastel(Color.IndianRed));
     }   // end of outer else-statement
}

My main question, is there a way I could Authenticate my admin account with a certification-based logon?

Share Improve this question edited Nov 20, 2024 at 5:17 marc_s 756k184 gold badges1.4k silver badges1.5k bronze badges asked Nov 19, 2024 at 21:52 MaizMaiz 211 silver badge6 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

You may either;

  1. Setup a Certification Authority (CA) and issue smart card certificates OR

  2. Get smart card certificates from a 3rd party certificate provider

In first option, if you setup a Microsoft CA, it can issue smart card certificates that you can directly use for logging on to AD (Active Directory) domain, it will see the CA as trusted root authority and automatically map UPN (user principal name) part of the certificate (which should be on CN field) to the AD user.

In the second option you should distribute to root CA of the provider to the client PCs and follow the procedure at https://learn.microsoft/en-us/troubleshoot/windows-server/certificates-and-public-key-infrastructure-pki/import-third-party-ca-to-enterprise-ntauth-store. This procedure is required for smart card logons.

In both options the client PCs that your application will run should be AD domain members.

In your application, I think you should check UPN information at CN (common name) field and check that root certificate of the smart card certificate chain is trusted by the Operating System and this root CA certificate exists in NTAUTH store as described in the above link. Then UPN may should be mapped to the AD user.

发布评论

评论列表(0)

  1. 暂无评论