2015-10-07 23 views
12

Ho identificato un problema relativo alla creazione di app che utilizzano C: \ Windows \ System32 \ CertEnroll.dll come riferimento.Problemi di compilazione in Windows 10

Il seguente codice funziona correttamente quando viene compilato utilizzando VS 2015 su Windows 7 e quindi eseguito su un computer 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); 
      } 
     } 
    } 
} 

Quando si tenta e compilare questa operazione in Windows 10 e quindi provare ed eseguirlo su una macchina Windows 7, getta il seguente errore.

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

Ho avuto diverse persone qui replicare e mi piacerebbe avere più input prima di contattare Microsoft su quello che sta succedendo qui.

Credo che la mia domanda sia: qualcuno può confermarlo o se è confermato che ha rotto la compatibiltà?

+0

Hai provato a eseguire questo in modalità sia a 64 bit che a 32 bit? Sembra che MS abbia cambiato l'interfaccia in modo significativo tra i due. –

+0

Finora i miei test sono stati solo x64 – spowser

+1

Da un prompt dei comandi con privilegi elevati, è possibile eseguire 'regsvr32 c: \ Windows \ System32 \ CertEnroll.dll 'e vedere se questo fa alcuna differenza? Potrebbe essere a causa di una registrazione errata, altrimenti provare a eseguire sotto 32 bit e vedere se si ottiene lo stesso errore. –

risposta

5

Questi sono i passi da Microsoft per risolvere il problema

Se si utilizza Windows 10 esclusivamente come ambiente di costruzione, l'eseguibile verrà eseguito su sistemi operativi di livello inferiore, tuttavia se si desidera semplicemente un progetto che è possibile compilare ovunque ed eseguire ovunque, l'unica soluzione è creare la propria DLL di interoperabilità. includere nella cartella del progetto. Dovresti prima generarlo su Windows 7 e fare riferimento a quella DLL.

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

Questo genera un file CertEnroll_Interop.dll che è possibile copiare nella cartella del progetto e quindi individuare nel progetto. Naturalmente è necessario utilizzare l'istruzione "using CertEnroll_Interop;".

È possibile creare il progetto su Windows 10 e farlo girare su Windows 7 e Windows 8.1 e qualsiasi altra combinazione.

+0

Nota: è necessario utilizzare l'opzione/out, ad esempio Tlbimp.exe/out: CertEnroll_Interop c: \ Windows \ System32 \ CertEnroll.dll In caso contrario, CertEnroll_Interop è il nome della libreria dei tipi e si lamenta che non può Trovalo. –

9

In qualche modo l'implementazione dell'interfaccia su CertEnroll.dll è cambiata tra "vanilla" Windows 2008 e Windows 2008 R2. Immagino sia lo stesso con alcuni build di Windows 7. Per ottenerlo (a metà strada), devi istanziare le classi con Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>); In questo modo il sistema cercherà i riferimenti in HKLM: \ SOFTWARE \ Classes \ Interface \ per ottenere la classe giusta per te.

Esempio di lavoro:

(Parte di questo codice è stato utilizzato da 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(); 
    } 
} 

Osservazioni

Allora, perché ho scritto "a metà strada"? C'è un problema con certenroll.dll V. 6, che causa il fallimento della build su cert.InitializeFromPrivateKey. In CertEnroll.dll V 6.0, il secondo parametro deve essere di tipo "CX509PrivateKey", mentre su macchine Win10 con CertEnroll.dll V 10, è IX509PrivateKey:

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

Così si potrebbe pensare: Sì, semplicemente "cast" la privateKey nell'esempio sopra a CX509PrivateKey su Activator.CreateInstance. Il problema qui è che si compilerà, ma su Win2k8 vaniglia non ti darà la classe (CX509 ...) ma l'interfaccia (IX509 ...), e quindi il cast fallisce e restituisce null.

Abbiamo risolto questo problema compilando la funzione certenrollment in un progetto separato su una macchina con certenroll.dll V 10. Compila bene e funziona anche su Win2k8. E 'mai-la-meno un po' fastidioso per averlo in un progetto separato, dal momento che la costruzione fallirà sul nostro buildserver con CertEnroll.dll V 6.

+0

Da dove proviene GetMachineDN? –

0

Ho avuto lo stesso problema, il mio computer di sviluppo è in esecuzione su Windows 10 e Windows 8.1 sul build server.

Ma poiché C# ha la capacità di riflessione tipi e dinamici, ora prima analizzare quali tipi di metodo InitializeFromPrivateKey prende come parametri (ho separato dal codice certificato effettivamente creando un metodo).

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

E quindi utilizzare un tipo dinamico a seconda del tipo del secondo parametro.

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