c# - tag - Problemas de compilación en Windows 10
summary c# visual studio (3)
De alguna manera, la implementación de la interfaz en CertEnroll.dll cambió entre "vainilla" Windows 2008 y Windows 2008 R2. Supongo que es lo mismo con algunas versiones de Windows 7. Para que funcione (a medias), debe crear una instancia de las clases con Activator.CreateInstance(Type.GetTypeFromProgID(<TypeName>)
; esto hará que el sistema busque las referencias en HKLM: / SOFTWARE / Classes / Interface / para obtener la clase correcta para ti
Ejemplo de trabajo:
(Parte de este código se usó en 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();
}
}
Observaciones
Entonces, ¿por qué escribí "a mitad de camino"? Hay un problema con certenroll.dll V. 6, que hace que la compilación falle en cert.InitializeFromPrivateKey. En certenroll.dll V 6.0, el segundo parámetro debe ser del tipo "CX509PrivateKey", mientras que en las máquinas Win10 con Certenroll.dll V 10, es IX509PrivateKey:
error CS1503: Argumento 2: no se puede convertir de ''CERTENROLLLib.IX509PrivateKey'' a ''CERTENROLLLib.CX509PrivateKey''
Así que usted pensaría: Sí, simplemente "lance" privateKey en el ejemplo anterior a CX509PrivateKey en Activator.CreateInstance. El problema aquí es que se compilará, pero en vainilla Win2k8 no le dará la clase (CX509 ...) sino la interfaz (IX509 ...) y, por lo tanto, la conversión falla y devuelve el valor nulo.
Hemos resuelto este problema al compilar la función de certificación en un proyecto independiente en una máquina con certenroll.dll V 10. Se compila bien y funciona en Win2k8, también. Es, sin embargo, un poco molesto tenerlo en un proyecto separado, ya que la compilación fallará en nuestro servidor de compilación con certenroll.dll V 6.
He identificado un problema relacionado con la creación de aplicaciones que utilizan C: / Windows / System32 / CertEnroll.dll como referencia.
El siguiente código funciona bien cuando se compila con VS 2015 en Windows 7 y luego se ejecuta en una máquina con 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);
}
}
}
}
Cuando intenta y compila esto en Windows 10 y luego intenta ejecutarlo en una máquina con Windows 7, produce el siguiente error.
"No se puede convertir el objeto COM del tipo ''System .__ ComObject'' en el tipo de interfaz ''CERTENROLLLib.CX509PrivateKey''. Esta operación falló porque la llamada QueryInterface en el componente COM para la interfaz con IID ''{728AB362-217D-11DA-B2A2-000E7BBB2B09} ''falló debido al siguiente error: No se admite dicha interfaz (excepción de HRESULT: 0x80004002 (E_NOINTERFACE)) ".
He tenido varias personas aquí que lo replican y me gustaría obtener más información antes de contactar a Microsoft sobre lo que está sucediendo aquí.
Supongo que mi pregunta es: ¿Alguien más puede confirmar esto o si se confirma que rompieron la compatibilidad hacia atrás?
Estos son los pasos de Microsoft para resolver este problema.
Si usa Windows 10 únicamente como su entorno de compilación, el ejecutable se ejecutará en sistemas operativos de nivel inferior, sin embargo, si realmente desea tener un proyecto que pueda compilar en cualquier lugar y ejecutar en cualquier lugar, entonces la única solución es crear su propia DLL de interoperabilidad. incluir en la carpeta del proyecto. Tendría que generarlo primero en Windows 7 y hacer referencia a esa DLL.
Tlbimp.exe CertEnroll_Interop c: / Windows / System32 / CertEnroll.dll
Esto genera un archivo CertEnroll_Interop.dll que puede copiar en la carpeta de su proyecto y luego buscarlo en su proyecto. Por supuesto, necesitaría el uso de la declaración "using CertEnroll_Interop;".
Puede construir el proyecto en Windows 10 y ejecutarlo en Windows 7 y Windows 8.1 y cualquier otra combinación.
Tuve el mismo problema, mi máquina de desarrollo se ejecuta en Windows 10 y el servidor de compilación de Windows 8.1.
Pero como c # tiene la capacidad de tipos de reflexión y dinámicos, primero analizo qué tipos toma el método InitializeFromPrivateKey como parámetros (lo separé del código del certificado en realidad creando un método).
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);
}
Y luego use un tipo dinámico dependiendo de qué tipo es el segundo parámetro.
dynamic privateKeyCorrectType;
if (IsCompiledOnWin10AndAbove()) // win 10 and above compiled
{
privateKeyCorrectType= privateKey;
}
else // below win 10 compiled
{
privateKeyCorrectType= (CX509PrivateKey)privateKey;
}
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKeyCorrectType, "");