2015-10-07 22 views
12

Zidentyfikowaliśmy problem związany z budowaniem aplikacji używających C: \ Windows \ System32 \ CertEnroll.dll jako punktu odniesienia.Problemy podczas kompilacji w systemie Windows 10

Poniższy kod działa poprawnie po skompilowaniu przy użyciu VS 2015 w systemie Windows 7, a następnie uruchomiony na komputerze z systemem 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); 
      } 
     } 
    } 
} 

Podczas próby skompilowania tego w systemie Windows 10, a następnie spróbuj uruchomić go na komputerze z systemem Windows 7, spowoduje to następujący błąd.

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

miałem kilka Ludzie tutaj powielać go i chciałbym, aby uzyskać więcej wejście przed skontaktowaniem się z firmą Microsoft, co się tu dzieje.

Domyślam się, że moje pytanie brzmi: czy ktoś inny może to potwierdzić, czy też jest potwierdzone, że złamali oni wsteczną kompatybilność?

+0

Czy próbowałeś uruchomić to zarówno w trybie 64-bitowym, jak i 32-bitowym? Wygląda na to, że MS znacząco zmieniło interfejs między tymi dwoma. –

+0

Do tej pory moje testy były tylko x64 – spowser

+1

Z podwyższonego wiersza polecenia, można uruchomić 'regsvr32 c: \ Windows \ System32 \ CertEnroll.dll' i sprawdzić, czy to ma znaczenie? Może to być spowodowane zepsutą rejestracją, w przeciwnym razie spróbuj uruchomić pod 32-bitowym i sprawdź, czy otrzymujesz ten sam błąd. –

Odpowiedz

5

Są kroków od Microsoft, aby rozwiązać ten problem

Jeśli używasz systemu Windows 10 wyłącznie jako środowiska kompilacji, wówczas plik wykonywalny działałby na niższych wersjach systemu operacyjnego, ale jeśli naprawdę chcesz mieć projekt, który możesz skompilować w dowolnym miejscu i uruchomić w dowolnym miejscu, jedynym rozwiązaniem jest utworzenie własnej biblioteki DLL, dołącz do folderu projektu. Najpierw musisz wygenerować go na systemie Windows 7 i odwołać się do tej biblioteki DLL.

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

To generuje plik CertEnroll_Interop.dll które można skopiować do folderu projektu, a następnie przejdź do swojego projektu. Oczywiście potrzebowałbyś użycia instrukcji "using CertEnroll_Interop;".

Możesz zbudować projekt na Windows 10 i uruchomić go na Windows 7 i Windows 8.1 i dowolnej innej kombinacji.

+0

Uwaga: Należy użyć przełącznika/out, tj. Tlbimp.exe/out: CertEnroll_Interop c: \ Windows \ System32 \ CertEnroll.dll W przeciwnym razie uważa, że ​​CertEnroll_Interop jest nazwą biblioteki typów i narzeka, że ​​nie może Znajdź to. –

9

W jakiś sposób implementacja interfejsu na CertEnroll.dll zmieniła się pomiędzy "waniliowymi" Windows 2008 i Windows 2008 R2. Sądzę, że tak samo jest z niektórymi wersjami Windows 7. Aby go uruchomić (w połowie) działa, musisz utworzyć instancję klasy za pomocą Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>); Spowoduje to, że system sprawdzi odnośniki w pliku HKLM: \ SOFTWARE \ Classes \ Interface \, aby uzyskać odpowiednią klasę.

Przykład pracy:

(część tego kodu użyto od 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(); 
    } 
} 

Uwagi

Dlaczego więc piszę "w połowie drogi"? Występuje problem z plikiem certenroll.dll V. 6, który powoduje niepowodzenie kompilacji na certyfikacie cert.InitializeFromPrivateKey. W certenroll.dll V 6.0, drugi parametr musi być typu "CX509PrivateKey", natomiast w maszynach Win10 z Certenroll.dll V 10, to IX509PrivateKey:

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

Więc można by pomyśleć: Tak, po prostu "obsada" privateKey w powyższym przykładzie do CX509PrivateKey w Activator.CreateInstance. Problem polega na tym, że zostanie skompilowany, ale na wanilii Win2k8 nie da ci klasy (CX509 ...), ale interfejs (IX509 ...), a tym samym rzutowanie kończy się niepowodzeniem i zwraca wartość null.

Rozwiązaliśmy ten problem, kompilując funkcję certenrollment w oddzielnym projekcie na komputerze z certenroll.dll V 10. Kompiluje się dobrze i działa również w Win2k8. To nigdy nie-mniej nieco irytujące mieć go w oddzielnym projekcie, ponieważ nie będzie budować na naszej buildserver z certenroll.dll V 6.

+0

Skąd pochodzi GetMachineDN? –

0

Miałem ten sam problem, moja maszyna programistyczna działa na systemie Windows 10 i systemie Windows 8.1.

Ale ponieważ C# ma zdolność odbijania i typy dynamiczne, teraz najpierw przeanalizuję, które typy metody przyjmuje jako parametr (oddzieliłem go od kodu rzeczywistego certyfikatu, tworząc metodę).

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

Następnie użyj typu dynamicznego w zależności od typu drugiego parametru.

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