Search code examples
c#certificatebouncycastlepublic-key

The domain account is locked out when certificate key is accessed with local account


When I access X509Certificate2.PublicKey or X509Certificate2.PrivateKey initialized from an object that was generated with BouncyCastle, I'm getting my domain account locked out (if I do it multiple times). It happens if I run the program on behalf of a local account with the same name but different password. Here is the code:

using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace TestCertificateConversion
{
    class Program
    {
        static void Main(string[] args)
        {
            var certString = GetCertificateString();
            var textReader = new StringReader(certString);
            var pemReader = new PemReader(textReader);
            var bcCert = pemReader.ReadObject() as Org.BouncyCastle.X509.X509Certificate;
            var netCert = DotNetUtilities.ToX509Certificate(bcCert);
            var netCert2 = new X509Certificate2(netCert);
            var publicKey = netCert2.PublicKey; // this locks out domain account
        }

        private static string GetCertificateString()
        {
            return @"-----BEGIN CERTIFICATE-----
MIICvDCCAaSgAwIBAgIQANDHl0sFjYUG3j76dYTadzANBgkqhkiG9w0BAQsFADAQ
MQ4wDAYDVQQDDAVwYWNlbTAgFw0xNjAxMDExMjQ4MzdaGA8yMjAwMDEwMTIyNTg0
N1owEDEOMAwGA1UEAwwFcGFjZW0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC5AKAkYnerRUmeAX0Z+aZsX39LXTVZiUd8U6waw7Hzd9E0YA50tHWizfEI
w7IBZwXS0aiXwHqJvrslc3NNs0grwu/iYQl+FGdudKmgXVE7Riu0uFAHo6eFr0dO
o0IP3LS+dPSWarXEBLbymaXRiPJDyHLefvslcSM9UQ2BHOe7dnHh9K1h+XMKTw3Z
/3szAyABBX9hsJU/mc9XjrMNXHJXALSxTfLIPzDrfh+aJtlopWpnb6vQcXwKksyk
4hyVUfw1klhglJgN0LgBGU7Ps3oxCbOqns7fB+tzkBV1E5Q97otgvMR14qLZgc8k
NQrdMl57GaWQJl6mAP1NR1gZt2f1AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBAAEz3vJOfqao9QXPWSz8YCjbWG1FeVG0NdYpd422dC2V
Vrzlo5zrkRv5XPhBOY3o81OhUe7iByiiM9suYfXLNxxd29TBGB5amO8Yv1ZX0hS/
zvVF6QS0+zZvOiujVhfHGiJxKypqgaigI6NM80ZDKPzsRPwFLIJiZYwQ7eQUlrpt
WGgFkZC23/mSOkY6VMmO5zugeMoiXRyFq33uWLlaAr+zJtRh1IPRmkA1lJv0bkC1
SslO0oSDoT2lcvZkQ5odFKX5i1z7T/wioQqG62i8nsDSz+iZOqUyDx7bL8fIEHog
qgwizgr2aAPLO/VQKU9pRTyRNFl/GL5bi7w8NN+rLxE=
-----END CERTIFICATE-----";
        }
    }
}

I'm not sure what I'm doing wrong, are there any security settings I might need to change to prevent it from locking out domain accounts?


Solution

  • I checked the .net source code and found what causes an authentication problem in X509Certificate2.PublicKey. It is a creation of a new OID object:

    public PublicKey PublicKey {
        [SecuritySafeCritical]
        get {
            if (m_safeCertContext.IsInvalid)
                throw new CryptographicException(SR.GetString(SR.Cryptography_InvalidHandle), "m_safeCertContext");
    
            if (m_publicKey == null) {
                string friendlyName = this.GetKeyAlgorithm();
                byte[] parameters = this.GetKeyAlgorithmParameters();
                byte[] keyValue = this.GetPublicKey();
                Oid oid = new Oid(friendlyName, OidGroup.PublicKeyAlgorithm, true); // this line
                m_publicKey = new PublicKey(oid, new AsnEncodedData(oid, parameters), new AsnEncodedData(oid, keyValue));
            }
    
            return m_publicKey;
        }
    }
    

    The OID constructor is called with lookupFriendlyName set to 'true', which leads to FindOidInfoWithFallback function:

    // Try to find OID info within a specific group, and if that doesn't work fall back to all
    // groups for compatibility with previous frameworks
    internal static string FindOidInfoWithFallback(uint key, string value, OidGroup group)
    {
        string info = FindOidInfo(key, value, group);
    
        // If we couldn't find it in the requested group, then try again in all groups
        if (info == null && group != OidGroup.All)
        {
            info = FindOidInfo(key, value, OidGroup.All);
        }
    
        return info;
    }
    

    The first FindOidInfo returns null and then it is called second time with OidGroup.All. Eventually it results in cryptAPI call:

    CAPIMethods.CryptFindOIDInfo(dwKeyType, pvKey, dwGroupId);
    

    From documentation:

    The CryptFindOIDInfo function performs a lookup in the active directory to retrieve the friendly names of OIDs under the following conditions:

    • The key type in the dwKeyType parameter is set to CRYPT_OID_INFO_OID_KEY or CRYPT_OID_INFO_NAME_KEY.
    • No group identifier is specified in the dwGroupId parameter or the GroupID refers to EKU OIDs, policy OIDs or template OIDs.

    It then attempts to authentication with local user account and as a result I'm getting my domain account locked. From the comments to the code I see that the second FindOidInfo call was added for compatibility with older frameworks and potentially I can remove it. Unfortunately there is no easy was to change the code since it is in the framework itself. I may try to inherit the X509Certificate2 object and rewrite PublicKey and PrivateKey, but I don't really like that idea.