2015-10-07 37 views
12

Tôi đã xác định được sự cố liên quan đến việc tạo ứng dụng sử dụng C: \ Windows \ System32 \ CertEnroll.dll làm tham chiếu.Sự cố biên dịch trong Windows 10

Mã sau hoạt động tốt khi được biên dịch bằng VS 2015 trên Windows 7 và sau đó chạy trên máy tính 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); 
      } 
     } 
    } 
} 

Khi bạn cố gắng biên dịch trong Windows 10 rồi thử chạy trên máy tính Windows 7, nó sẽ ném lỗi sau.

"Unable to cast COM object of type 'System.__ComObject' to interface type 'CERTENROLLLib.CX509PrivateKey'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{728AB362-217D-11DA-B2A4-000E7BBB2B09}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))."

Tôi đã có nhiều người ở đây sao chép và tôi muốn nhận thêm dữ liệu đầu vào trước khi liên hệ với Microsoft về những gì đang diễn ra tại đây.

Tôi đoán câu hỏi của tôi là: Ai đó có thể xác nhận điều này hoặc nếu xác nhận rằng họ đã phá vỡ sự tương thích ngược?

+0

Bạn đã thử chạy chế độ này ở cả chế độ 64 bit và 32 bit chưa? Có vẻ như MS đã thay đổi giao diện đáng kể giữa hai người. –

+0

Cho đến nay các thử nghiệm của tôi chỉ có x64 – spowser

+1

Từ một dấu kiểm nhắc lệnh nâng lên, bạn có thể chạy 'regsvr32 c: \ Windows \ System32 \ CertEnroll.dll' và xem liệu có khác biệt gì không? Nó có thể là do đăng ký bị hỏng, nếu không hãy thử chạy dưới 32 bit và xem bạn có gặp lỗi tương tự hay không. –

Trả lời

5

Đây là những bước từ Microsoft để giải quyết vấn đề này

Nếu bạn sử dụng Windows 10 chỉ như môi trường xây dựng của bạn thì thực thi sẽ chạy trên các hệ điều hành downlevel, tuy nhiên nếu bạn thực sự muốn có một dự án mà bạn có thể biên dịch ở bất cứ đâu và chạy bất cứ đâu thì giải pháp duy nhất là tạo DLL interop của riêng bạn bao gồm trong thư mục dự án. Bạn sẽ phải tạo nó trên Windows 7 trước và tham chiếu đến DLL đó.

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

này tạo ra một tập tin CertEnroll_Interop.dll mà bạn có thể sao chép vào thư mục dự án của bạn và sau đó duyệt đến trong dự án của bạn. Tất nhiên bạn sẽ cần sử dụng câu lệnh “using CertEnroll_Interop;”.

Bạn có thể tạo dự án trên Windows 10 và chạy trên Windows 7 và Windows 8.1 và bất kỳ kết hợp nào khác.

+0

Lưu ý: Bạn cần sử dụng nút chuyển/ra, tức là Tlbimp.exe/out: CertEnroll_Interop c: \ Windows \ System32 \ CertEnroll.dll Nếu không thì CertEnroll_Interop là tên thư viện kiểu và than phiền rằng nó không thể tìm nó. –

9

Bằng cách nào đó việc triển khai giao diện trên CertEnroll.dll đã thay đổi giữa "vanilla" Windows 2008 và Windows 2008 R2. Tôi đoán nó là như nhau với một số Windows 7 xây dựng. Để làm cho nó (nửa chừng) hoạt động, bạn phải khởi tạo các lớp với Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>); Điều này sẽ làm cho hệ thống tra cứu các tham chiếu trong HKLM: \ SOFTWARE \ Classes \ Interface \ để có được lớp phù hợp với bạn.

làm việc Ví dụ:

(Một phần của mã này đã được sử dụng từ https://stackoverflow.com/a/13806300/5243037)

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's obsolete...</param> 
/// <returns>Created certificate</returns> 
/// <remarks>Base from https://stackoverflow.com/a/13806300/5243037 </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(); 
    } 
} 

chú

Vậy tại sao tôi viết "nửa đường"? Đã xảy ra sự cố với certenroll.dll V. 6, điều này làm cho bản dựng không thành công trên cert.InitializeFromPrivateKey. Trong certenroll.dll V 6.0, tham số thứ hai phải là kiểu "CX509PrivateKey", trong khi đó trên máy với Certenroll.dll V 10 Win10, nó IX509PrivateKey:

error CS1503: Argument 2: cannot convert from 'CERTENROLLLib.IX509PrivateKey' to 'CERTENROLLLib.CX509PrivateKey'

Vì vậy, bạn sẽ nghĩ: Yea, đơn giản là "diễn viên" privateKey trong ví dụ trên để CX509PrivateKey trên Activator.CreateInstance. Vấn đề ở đây là, nó sẽ biên dịch, nhưng trên vanilla Win2k8 nó sẽ không cung cấp cho bạn lớp (CX509 ...) mà là Giao diện (IX509 ...), và do đó dàn diễn viên thất bại và trả về null.

Chúng tôi đã giải quyết vấn đề này bằng cách biên dịch chức năng kiểm định trong một dự án riêng biệt trên máy với certenroll.dll V 10. Nó cũng biên dịch tốt và hoạt động trong Win2k8. Không bao giờ-the-ít một chút khó chịu để có nó trong một dự án riêng biệt, kể từ khi xây dựng sẽ thất bại trên buildserver của chúng tôi với certenroll.dll V 6.

+0

GetMachineDN ở đâu? –

0

Tôi gặp sự cố tương tự, máy phát triển của tôi đang chạy trên windows 10 và cửa sổ máy chủ xây dựng 8.1.

Nhưng vì C# có khả năng phản xạ và kiểu động, bây giờ tôi phân tích loại Phương thức InitializeFromPrivateKey lấy làm tham số (tôi tách nó ra khỏi mã chứng chỉ thực sự bằng cách tạo phương thức).

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); 
    } 

Và sau đó sử dụng loại động phụ thuộc vào loại tham số thứ hai.

 dynamic privateKeyCorrectType; 
     if (IsCompiledOnWin10AndAbove()) // win 10 and above compiled 
     { 
      privateKeyCorrectType= privateKey; 
     } 
     else // below win 10 compiled 
     { 
      privateKeyCorrectType= (CX509PrivateKey)privateKey; 
     } 
     cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKeyCorrectType, ""); 
Các vấn đề liên quan