2015-10-07 99 views
12

我發現了一個與構建使用C:\ Windows \ System32 \ CertEnroll.dll作爲參考的應用程序相關的問題。在Windows 10中編譯的問題

以下代碼在Windows 7上使用VS 2015進行編譯,然後在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機器上運行它時,它會引發以下錯誤。

"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))."

我有幾個人在這裏複製它,我想在與微軟聯繫這裏發生了什麼之前獲得更多的輸入。

我想我的問題是:其他人可以證實這一點,或者如果它證實他們破壞了後向兼容性?

+0

你有沒有試過在64位和32位模式下運行這個?看起來MS更改了兩者之間的界面。 –

+0

到目前爲止,我的測試只有x64 – spowser

+1

從提升的命令提示符下,你可以運行'regsvr32 c:\ Windows \ System32 \ CertEnroll.dll'並查看是否有任何區別?這可能是因爲註冊失敗,否則請嘗試在32位以下運行,看看是否出現相同的錯誤。 –

回答

5

這是來自微軟的步驟來解決此問題

如果您僅將Windows 10用作構建環境,那麼可執行文件將在低級操作系統上運行,但是如果您真的只想擁有一個可以在任何位置編譯和運行的項目,那麼唯一的解決方案就是創建自己的interop DLL包含在項目文件夾中。您必須先在Windows 7上生成它並引用該DLL。

Tlbimp.exe將CertEnroll_Interop C:\ WINDOWS \ SYSTEM32 \的CertEnroll.dll

這會生成CertEnroll_Interop.dll文件,您可以複製到項目文件夾,然後瀏覽到您的項目。當然,你需要使用「使用CertEnroll_Interop;」語句。

您可以在Windows 10上構建項目並使其在Windows 7和Windows 8.1以及任何其他組合上運行。

+0

注意:您需要使用/ out開關,即 Tlbimp.exe/out:CertEnroll_Interop c:\ Windows \ System32 \ CertEnroll.dll 否則它認爲CertEnroll_Interop是類型庫名稱,並且抱怨它不能找到它。 –

9

不知怎的,CertEnroll.dll上的接口實現在「vanilla」Windows 2008和Windows 2008 R2之間發生了變化。我想這與一些Windows 7版本是一樣的。爲了讓它(半途)工作,你必須用Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>)實例化類; 這將導致系統查找HKLM:\ SOFTWARE \ Classes \ Interface \中的引用,爲您獲得合適的課程。

工作實例:

(此代碼的一部分是從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(); 
    } 
} 

備註

那麼我爲什麼寫 「半途而廢」? certenroll.dll V. 6存在問題,導致構建在cert.InitializeFromPrivateKey上失敗。在的CertEnroll.dll V 6.0,第二個參數必須是類型 「CX509PrivateKey」,而在Win10的機器用的CertEnroll.dll V 10,這是IX509PrivateKey:

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

所以,你會想:是的,簡單的 「投」在上面的例子中的privateKey對Activator.CreateInstance的CX509PrivateKey。這裏的問題是,它會編譯,但在香草Win2k8它不會給你類(CX509 ...),但接口(IX509 ...),因此轉換失敗,並返回null。

我們通過在certenroll.dll V 10的機器上編譯certenrollment函數解決了這個問題。它編譯得很好,並且也在Win2k8中工作。這是從來沒有-的少有點討厭把它在一個單獨的項目,因爲該版本將在我們buildserver失敗的CertEnroll.dll V 6

+0

GetMachineDN從哪裏來? –

0

我有同樣的問題,我的開發機器運行在Windows 10和生成服務器的Windows 8.1。但由於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); 
    } 

然後根據第二個參數是哪個類型使用動態類型。

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