Подтвердить что ты не робот

Составление проблем в Windows 10

Я идентифицировал проблему, связанную с созданием приложений, использующих C:\Windows\System32\CertEnroll.dll в качестве ссылки.

Следующий код отлично работает при компиляции с использованием VS 2015 в Windows 7, а затем запускается на компьютере под управлением Windows 7.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CERTENROLLLib;

namespace CertTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                CX509PrivateKey key = new CX509PrivateKey();
                key.ContainerName = Guid.NewGuid().ToString();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

Когда вы пытаетесь скомпилировать это в Windows 10, а затем попробуйте запустить его на компьютере под управлением Windows 7, он выдает следующую ошибку.

"Невозможно передать COM-объект типа" System.__ ComObject "в тип интерфейса" CERTENROLLLib.CX509PrivateKey ". Эта операция завершилась неудачно, поскольку вызов QueryInterface для COM-компонента для интерфейса с IID '{728AB362-217D-11DA-B2A4 -000E7BBB2B09} 'не удалось из-за следующей ошибки: такой интерфейс не поддерживается (исключение из HRESULT: 0x80004002 (E_NOINTERFACE)).

У меня было несколько человек, которые реплицировали его, и я хотел бы получить больше информации, прежде чем обращаться в Microsoft о том, что происходит здесь.

Я думаю, мой вопрос: может ли кто-нибудь подтвердить это или подтвердил, что они нарушили совместимость?

4b9b3361

Ответ 1

Это шаги Microsoft для решения этой проблемы.

Если вы используете Windows 10 исключительно как среду сборки, тогда исполняемый файл будет работать на нижних уровнях ОС, однако, если вы действительно хотите иметь проект, который можно скомпилировать в любом месте и запускать в любом месте, единственным решением является создание вашего собственного взаимодействия DLL, которую вы включаете в папку проекта. Вы должны сначала сгенерировать его в Windows 7 и ссылаться на эту DLL.

Tlbimp.exe CertEnroll_Interop c:\Windows\System32\CertEnroll.dll

Это создает файл CertEnroll_Interop.dll, который вы можете скопировать в папку проекта, а затем просмотреть в своем проекте. Конечно, вам понадобится использовать "using CertEnroll_Interop"; утверждение.

Вы можете создать проект в Windows 10 и запустить его в Windows 7 и Windows 8.1 и любой другой комбинации.

Ответ 2

Как-то реализация интерфейса в CertEnroll.dll изменилась между "vanilla" Windows 2008 и Windows 2008 R2. Я думаю, это то же самое с некоторыми сборками Windows 7. Чтобы заставить его (наполовину) работать, вы должны создать экземпляры классов с помощью Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>); Это заставит систему искать ссылки в HKLM:\SOFTWARE\Classes\Interface \, чтобы получить правильный класс для вас.

Рабочий пример:

(Часть этого кода использовалась fooobar.com/questions/139742/...)

using System;
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using CERTENROLLLib;


/// <summary>
///     Creates a self-signed certificate in the computer certificate store MY.
///     Issuer and Subject are computername and its domain.
/// </summary>
/// <param name="friendlyName">Friendly-name of the certificate</param>
/// <param name="password">Password which will be used by creation. I think it obsolete...</param>
/// <returns>Created certificate</returns>
/// <remarks>Base from /questions/139742/how-to-create-a-self-signed-certificate-using-c/829185#829185 </remarks>
public static X509Certificate2 CreateSelfSignedCertificate(string friendlyName, string password)
{
    // create DN for subject and issuer
    var dnHostName = new CX500DistinguishedName();
    // DN will be in format CN=machinename, DC=domain, DC=local for machinename.domain.local
    dnHostName.Encode(GetMachineDn());
    var dnSubjectName = dnHostName;

    //var privateKey = new CX509PrivateKey();
    var typeName = "X509Enrollment.CX509PrivateKey";
    var type = Type.GetTypeFromProgID(typeName);
    if (type == null)
    {
        throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
    }
    var privateKey = Activator.CreateInstance(type) as IX509PrivateKey;
    if (privateKey == null)
    {
        throw new Exception("Your certlib does not know an implementation of " + typeName +
                            " (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
    }
    privateKey.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider";
    privateKey.ProviderType = X509ProviderType.XCN_PROV_RSA_AES;
    // key-bitness
    privateKey.Length = 2048;
    privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
    privateKey.MachineContext = true;
    // Don't allow export of private key
    privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE;

    // use is not limited
    privateKey.Create();

    // Use the stronger SHA512 hashing algorithm
    var hashobj = new CObjectId();
    hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
        ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
        AlgorithmFlags.AlgorithmFlagsNone, "SHA512");

    // add extended key usage if you want - look at MSDN for a list of possible OIDs
    var oid = new CObjectId();
    oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
    var oidlist = new CObjectIds { oid };
    var eku = new CX509ExtensionEnhancedKeyUsage();
    eku.InitializeEncode(oidlist);

    // add all IPs of current machine as dns-names (SAN), so a user connecting to our wcf 
    // service by IP still claim-trusts this server certificate
    var objExtensionAlternativeNames = new CX509ExtensionAlternativeNames();
    {
        var altNames = new CAlternativeNames();
        var dnsHostname = new CAlternativeName();
        dnsHostname.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, Environment.MachineName);
        altNames.Add(dnsHostname);
        foreach (var ipAddress in Dns.GetHostAddresses(Dns.GetHostName()))
        {
            if ((ipAddress.AddressFamily == AddressFamily.InterNetwork ||
                 ipAddress.AddressFamily == AddressFamily.InterNetworkV6) && !IPAddress.IsLoopback(ipAddress))
            {
                var dns = new CAlternativeName();
                dns.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, ipAddress.ToString());
                altNames.Add(dns);
            }
        }
        objExtensionAlternativeNames.InitializeEncode(altNames);
    }

    // Create the self signing request
    //var cert = new CX509CertificateRequestCertificate();
    typeName = "X509Enrollment.CX509CertificateRequestCertificate";
    type = Type.GetTypeFromProgID(typeName);
    if (type == null)
    {
        throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
    }
    var cert = Activator.CreateInstance(type) as IX509CertificateRequestCertificate;
    if (cert == null)
    {
        throw new Exception("Your certlib does not know an implementation of " + typeName +
                            " (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
    }
    cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
    cert.Subject = dnSubjectName;
    cert.Issuer = dnHostName; // the issuer and the subject are the same
    cert.NotBefore = DateTime.Now.AddDays(-1);
    // this cert expires immediately. Change to whatever makes sense for you
    cert.NotAfter = DateTime.Now.AddYears(1);
    cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
    cert.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames);
    cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
    cert.Encode(); // encode the certificate

    // Do the final enrollment process
    //var enroll = new CX509Enrollment();
    typeName = "X509Enrollment.CX509Enrollment";
    type = Type.GetTypeFromProgID(typeName);
    if (type == null)
    {
        throw new Exception(typeName + " is not available on your system: 0x80040154 (REGDB_E_CLASSNOTREG)");
    }
    var enroll = Activator.CreateInstance(type) as IX509Enrollment;
    if (enroll == null)
    {
        throw new Exception("Your certlib does not know an implementation of " + typeName +
                            " (in HKLM:\\SOFTWARE\\Classes\\Interface\\)!");
    }
    // Use private key to initialize the certrequest...
    enroll.InitializeFromRequest(cert);
    enroll.CertificateFriendlyName = friendlyName; // Optional: add a friendly name
    var csr = enroll.CreateRequest(); // Output the request in base64 and install it back as the response
    enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, csr,
        EncodingType.XCN_CRYPT_STRING_BASE64, password);

    // This will fail on Win2k8, some strange "Parameter is empty" error... Thus we search the
    // certificate by serial number with the managed X509Store-class
    // // output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
    //var base64Encoded = enroll.CreatePFX(password, PFXExportOptions.PFXExportChainNoRoot, EncodingType.XCN_CRYPT_STRING_BASE64);
    //return new X509Certificate2(Convert.FromBase64String(base64Encoded), password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
    var certFs = LoadCertFromStore(cert.SerialNumber);
    if (!certFs.HasPrivateKey) throw new InvalidOperationException("Created certificate has no private key!");

    return certFs;
}


/// <summary>
///     Converts Domain.local into CN=Domain, CN=local
/// </summary>
private static string GetDomainDn()
{
    var fqdnDomain = IPGlobalProperties.GetIPGlobalProperties().DomainName;
    if (string.IsNullOrWhiteSpace(fqdnDomain)) return null;
    var context = new DirectoryContext(DirectoryContextType.Domain, fqdnDomain);
    var d = Domain.GetDomain(context);
    var de = d.GetDirectoryEntry();
    return de.Properties["DistinguishedName"].Value.ToString();
}

/// <summary>
///     Gets machine and domain name in X.500-format: CN=PC,DN=MATESO,DN=local
/// </summary>
private static string GetMachineDn()
{
    var machine = "CN=" + Environment.MachineName;
    var dom = GetDomainDn();
    return machine + (string.IsNullOrWhiteSpace(dom) ? "" : ", " + dom);
}

/// <summary>
///     Load a certificate by serial number from computer.my-store
/// </summary>
/// <param name="serialNumber">Base64-encoded certificate serial number</param>
private static X509Certificate2 LoadCertFromStore(string serialNumber)
{
    var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
    store.Open(OpenFlags.OpenExistingOnly | OpenFlags.MaxAllowed);
    try
    {
        // serialnumber from certenroll.dll v6 is a base64 encoded byte array, which is reversed.
        // serialnumber from certenroll.dll v10 is a base64 encoded byte array, which is NOT reversed.
        var serialBytes = Convert.FromBase64String(serialNumber);
        var serial = BitConverter.ToString(serialBytes.ToArray()).Replace("-", "");
        var serialReversed = BitConverter.ToString(serialBytes.Reverse().ToArray()).Replace("-", "");

        var serverCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, serial, false);
        if (serverCerts.Count == 0)
        {
            serverCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, serialReversed, false);
        }
        if (serverCerts.Count == 0)
        {
            throw new KeyNotFoundException("No certificate with serial number <" + serial + "> or reversed serial <" + serialReversed + "> found!");
        }
        if (serverCerts.Count > 1)
        {
            throw new Exception("Found multiple certificates with serial <" + serial + "> or reversed serial <" + serialReversed + ">!");
        }

        return serverCerts[0];
    }
    finally
    {
        store.Close();
    }
}

Примечания

Так почему я написал "на полпути"? Существует проблема с certenroll.dll V. 6, которая приводит к сбою сборки в cert.InitializeFromPrivateKey. В certenroll.dll V 6.0 второй параметр должен иметь тип "CX509PrivateKey", тогда как на машинах Win10 с Certenroll.dll V 10 он IX509PrivateKey:

error CS1503: Аргумент 2: невозможно преобразовать из 'CERTENROLLLib.IX509PrivateKey' в 'CERTENROLLLib.CX509PrivateKey'

Итак, вы могли бы подумать: "Да, просто" бросил "privateKey в приведенном выше примере на CX509PrivateKey на Activator.CreateInstance. Проблема здесь в том, что она будет компилироваться, но на vanilla Win2k8 он не даст вам класс (CX509...), а интерфейс (IX509...), и, таким образом, сбой завершается и возвращает null.

Мы решили эту проблему, собрав функцию certenrollment в отдельном проекте на машине с certenroll.dll V 10. Он компилируется отлично и работает в Win2k8. Это, тем не менее, немного раздражает, чтобы иметь его в отдельном проекте, так как сборка завершится неудачно на нашем сервере сборки с помощью certenroll.dll V 6.

Ответ 3

У меня была такая же проблема, моя машина разработки работает на Windows 10 и на сервере сборки 8.1.

Но так как С# обладает способностью к отражению и динамическим типам, теперь я сначала анализирую, какие типы использует метод InitializeFromPrivateKey как параметры (я отделил его от кода сертификата на самом деле, создав метод).

    private static bool IsCompiledOnWin10AndAbove()
    {
        var typeOfMethod = typeof(IX509CertificateRequestPkcs10);
        var methodType = typeOfMethod.GetMethod("InitializeFromPrivateKey", new Type[] { typeof(X509CertificateEnrollmentContext), typeof(CX509PrivateKey), typeof(string) });
        var methodeParameters = methodType.GetParameters();
        return methodeParameters[1].ParameterType != typeof(CX509PrivateKey);
    }

И затем используйте динамический тип, зависящий от того, какой тип имеет второй параметр.

        dynamic privateKeyCorrectType;
        if (IsCompiledOnWin10AndAbove()) // win 10 and above compiled
        {
            privateKeyCorrectType= privateKey;
        }
        else // below win 10 compiled
        {
            privateKeyCorrectType= (CX509PrivateKey)privateKey;
        }
        cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKeyCorrectType, "");