por - web api c# tutorial
WebApi HttpClient no envĂa certificado de cliente (3)
Estoy tratando de asegurar mi servicio RESTful WebApi con ssl y autenticación de cliente usando certificados de cliente.
Probar; Generé un certificado autofirmado y lo coloqué en la máquina local, la carpeta de autoridades de certificación de raíz de confianza y generé certificados de "servidor" y "cliente". Estándar https para el servidor funciona sin problemas.
Sin embargo, tengo algo de código en el servidor para validar el certificado, esto nunca se llama cuando me conecto usando mi cliente de prueba que suministra el certificado de mi cliente y al cliente de prueba se le devuelve un estado 403 Prohibido.
Esto indica que el servidor está fallando mi certificado antes de que llegue a mi código de validación. Sin embargo, si enciendo fiddler, sabe que se requiere un certificado de cliente y me pide que proporcione uno a Mis documentos / Fiddler2. Le di el mismo certificado de cliente que uso en mi cliente de prueba y mi servidor ahora funciona y ¡recibí el certificado de cliente que esperaba! Esto implica que el cliente WebApi no está enviando correctamente el certificado, el código de mi cliente a continuación es más o menos igual que otros ejemplos que he encontrado.
static async Task RunAsync()
{
try
{
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(GetClientCert());
handler.ServerCertificateValidationCallback += Validate;
handler.UseProxy = false;
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri("https://hostname:10001/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var response = await client.GetAsync("api/system/");
var str = await response.Content.ReadAsStringAsync();
Console.WriteLine(str);
}
} catch(Exception ex)
{
Console.Write(ex.Message);
}
}
¿Alguna idea de por qué funcionaría en Fiddler pero no en mi cliente de prueba?
Edición: Aquí está el código para GetClientCert ()
private static X509Certificate GetClientCert()
{
X509Store store = null;
try
{
store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "Integration Client Certificate", true);
if (certs.Count == 1)
{
var cert = certs[0];
return cert;
}
}
finally
{
if (store != null)
store.Close();
}
return null;
}
Concedido, el código de prueba no maneja un certificado nulo, pero estoy depurando para asegurar que se encuentre el certificado correcto.
¿Está su código de arriba ejecutándose en la misma cuenta de usuario que Fiddler está ejecutando? Si no, puede tener acceso al archivo de certificado pero no a la clave privada correcta. Del mismo modo, ¿qué GetClientCert()
su función GetClientCert()
? Específicamente, ¿tiene la propiedad PrivateKey
establecida?
En el código que está utilizando store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
.
Los certificados de cliente no se recogen en LocalMachine
, en su lugar, debe usar StoreLocation.CurrentUser
.
Verificando MMC -> File -> Add or Remove Snap-ins -> Certificates -> My user account
verá el certificado que usa el Fiddler. Si lo elimina de My user account
y solo lo tiene importado en Computer account
, verá que Fiddler tampoco puede recogerlo.
Una nota al margen es cuando encuentra certificados que también debe abordar para la cultura.
Ejemplo:
var certificateSerialNumber= "83 c6 62 0a 73 c7 b1 aa 41 06 a3 ce 62 83 ae 25".ToUpper().Replace(" ", string.Empty);
//0 certs
var certs = store.Certificates.Find(X509FindType.FindBySerialNumber, certificateSerialNumber, true);
//null
var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString() == certificateSerialNumber);
//1 cert
var cert1 = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x =>
x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));
Hay 2 tipos de certificados. El primero es el archivo .cer público que le envía el propietario del servidor. Este archivo es sólo una larga cadena de caracteres. El segundo es el certificado de almacén de claves, este es el certificado autofirmado que crea y envía el archivo cer al servidor al que está llamando y lo instalan. Dependiendo de la cantidad de seguridad que tenga, es posible que deba agregar uno o ambos al Cliente (Controlador en su caso). Solo he visto el certificado de almacén de claves utilizado en un servidor donde la seguridad es MUY segura. Este código obtiene ambos certificados de la carpeta bin / deployed:
#region certificate Add
// KeyStore is our self signed cert
// TrustStore is cer file sent to you.
// Get the path where the cert files are stored (this should handle running in debug mode in Visual Studio and deployed code) -- Not tested with deployed code
string executableLocation = Path.GetDirectoryName(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
#region Add the TrustStore certificate
// Get the cer file location
string pfxLocation = executableLocation + "//Certificates//TheirCertificate.cer";
// Add the certificate
X509Certificate2 theirCert = new X509Certificate2();
theirCert.Import(pfxLocation, "Password", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(theirCert);
#endregion
#region Add the KeyStore
// Get the location
pfxLocation = executableLocation + "//Certificates//YourCert.pfx";
// Add the Certificate
X509Certificate2 YourCert = new X509Certificate2();
YourCert.Import(pfxLocation, "PASSWORD", X509KeyStorageFlags.DefaultKeySet);
handler.ClientCertificates.Add(YourCert);
#endregion
#endregion
Además, debe manejar los errores de certificación (nota: esto es MALO: dice que TODOS los problemas de certificación están bien), debe cambiar este código para manejar cuestiones de certificación específicas como la falta de coincidencia de nombres. está en mi lista para hacer Hay muchos ejemplos de cómo hacer esto.
Este código en la parte superior de tu método.
// Ignore Certificate errors need to fix to only handle
ServicePointManager.ServerCertificateValidationCallback = MyCertHandler;
Método en algún lugar de tu clase
private bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error)
{
// Ignore errors
return true;
}