Han habido varias consultas en el foro MSDN sobre como poder consultar un recurso web aunque el servidor que utiliza el protocolo https tenga el certificado caducado.
En esta entrada pondré un ejemplo muy simple que se puede utilizar para este menester.
Lo primero que he realizado es un proyecto web que utiliza una clase para enviar una petición xml, por ejemplo si quisiera consumir un servicio web XML en php que no dispone de una descripción pública del interfaz “WSD”
La clase se llama Conector y utiliza un método estático que utiliza la clase HttpWebResponse para hacer la petición Http y devuelve el resultado obtenido de la petición como un string.
EDITADO (25.09.2009):
Gracias al comentario de eccho me he dado cuenta que la clase CertificatePolicy está en desuso “eso pasa por sacar temas del baúl sin hacer el repaso necesario de las librerías”. hora se recomienda el uso de ServerCertificateValidationCallback, que obtiene o establece la devolución de la llamada para validar un certificado de servidor.
public static string PostXml(string url, string xml, string certificado, string user, string pass) { string resultado = ""; byte[] bytes = System.Text.Encoding.UTF8.GetBytes(xml); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pass)) request.Credentials = new NetworkCredential(user, pass); else request.Credentials = new NetworkCredential(); if (!string.IsNullOrEmpty(certificado)) { try { X509Certificate certificate = X509Certificate.CreateFromSignedFile(certificado); request.ClientCertificates.Add(certificate); ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallbac( customXertificateValidation); } catch (Exception) { throw new Exception("Error al generar el certificado de cliente. " + certificado); } } request.Method = "POST"; request.ContentLength = bytes.Length; request.ContentType = "text/xml"; using (System.IO.Stream requestStream = request.GetRequestStream()) { requestStream.Write(bytes, 0, bytes.Length); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode != HttpStatusCode.OK) { string message = String.Format("POST failed. Received HTTP {0}", response.StatusCode); throw new ApplicationException(message); } //Mostramos la respuesta de la Página using (System.IO.StreamReader sr = new System.IO.StreamReader( response.GetResponseStream(), System.Text.Encoding.Default)) { resultado = sr.ReadToEnd(); } } return resultado; }
A este método se le pasan varios parámetros:
- url –> url a donde haremos la petición-
- xml –> texto XML que espera el servidor.
- certificado –> ruta física donde tenemos guardado el fichero de certificación.
- user –> usuario si necesitamos credenciales de red
- pas –> contraseña del usuario de red.
Cabe destacar que para poder conectar con el servidor HTTPS necesitamos el certificado de cliente correcto. Para conseguirlo lo podemos descargar directamente de la web.
- Accedemos al recurso https “puede que el certificado esté caducado y de un error” seleccionamos Vaya a este sitio web (no recomendado)
- Seleccionamos ver el certificado3. guardamos el certificado en el disco.
- Una vez guardado el certificado ya podemos hacer la llamada al recurso web.
protected void Button1_Click(object sender, EventArgs e) { string respuesta = Conector.PostXml("https://test2.com/", "<test><dato></dato></test>", "c\certificado.cer","winUser","PassUser"); }
Pero si el certificado no es correcto o está caducado no nos permite hacer la consulta !!!
Entonces lo que tenemos que hacer es personalizar la validación del certificado para que nos devuelva el valor que nos interese en cada caso. Utilizaremos el delegado ServerCertificateValidationCallback para lanzar la validación personalizada del certificado y en nuestro caso devolver true.
ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback( customXertificateValidation); public static bool customXertificateValidation(object sender, System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors error){ return true;}
Esta clase se podría personalizar más para que dependiendo del error devuelva verdadero o falso, gracias a la enumeración con los tipos de error.
public enum CertificateProblem : long { CertEXPIRED = 0x800B0101, CertVALIDITYPERIODNESTING = 0x800B0102, CertROLE = 0x800B0103, CertPATHLENCONST = 0x800B0104, CertCRITICAL = 0x800B0105, CertPURPOSE = 0x800B0106, CertISSUERCHAINING = 0x800B0107, CertMALFORMED = 0x800B0108, CertUNTRUSTEDROOT = 0x800B0109, CertCHAINING = 0x800B010A, CertREVOKED = 0x800B010C, CertUNTRUSTEDTESTROOT = 0x800B010D, CertREVOCATION_FAILURE = 0x800B010E, CertCN_NO_MATCH = 0x800B010F, CertWRONG_USAGE = 0x800B0110, CertUNTRUSTEDCA = 0x800B0112 }
Con esto tenemos todo lo necesario para que nuestro ejemplo esté funcionando perfectamente.
Espero que sea de utilidad.
Nos vemos en el CodeCamp 09