c# .net ssl

ssl web api c#



¿Qué versión de TLS se negoció? (4)

Al reunir algunas ideas aquí y allá, hice un método simple para probar cada protocolo disponible, forzando un tipo específico de conexión en cada intento. Al final, obtengo una lista con los resultados para usar según lo necesito.

Ps: La prueba es válida solo si sabe que el sitio web está en línea; puede hacer una prueba previa para verificar esto.

public static IEnumerable<T> GetValues<T>() { return Enum.GetValues(typeof(T)).Cast<T>(); } private Dictionary<SecurityProtocolType, bool> ProcessProtocols(string address) { var protocolResultList = new Dictionary<SecurityProtocolType, bool>(); var defaultProtocol = ServicePointManager.SecurityProtocol; ServicePointManager.Expect100Continue = true; foreach (var protocol in GetValues<SecurityProtocolType>()) { try { ServicePointManager.SecurityProtocol = protocol; var request = WebRequest.Create(address); var response = request.GetResponse(); protocolResultList.Add(protocol, true); } catch { protocolResultList.Add(protocol, false); } } ServicePointManager.SecurityProtocol = defaultProtocol; return protocolResultList; }

Espero que esto sea útil

Tengo mi aplicación ejecutándose en .NET 4.7. Por defecto, intentará usar TLS1.2. ¿Es posible saber qué versión de TLS se negoció al realizar, por ejemplo, una solicitud HTTP como se muestra a continuación?

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(decodedUri); if (requestPayload.Length > 0) { using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(requestPayload, 0, requestPayload.Length); } }

Solo necesito esta información para fines de registro / depuración, por lo que no es importante tener esta información antes de escribir en la secuencia de solicitud o recibir la respuesta. No deseo analizar los registros de seguimiento de red para esta información, y tampoco quiero crear una segunda conexión (usando SslStream o similar).


La única forma en que puedo entender es usar SslStream para hacer una conexión de prueba, luego verificar la propiedad SslProtocol .

TcpClient client = new TcpClient(decodedUri.DnsSafeHost, 443); SslStream sslStream = new SslStream(client.GetStream()); // use this overload to ensure SslStream has the same scope of enabled protocol as HttpWebRequest sslStream.AuthenticateAsClient(decodedUri.Host, null, (SslProtocols)ServicePointManager.SecurityProtocol, true); // Check sslStream.SslProtocol here client.Close(); sslStream.Close();

He comprobado que sslStream.SslProtocl siempre será igual que el TlsStream.m_worker.SslProtocol que usó HttpWebRequest ''s Connection .


La solución a continuación es sin duda un "truco" en el sentido de que utiliza la reflexión, pero actualmente cubre la mayoría de las situaciones en las que podría encontrarse con una HttpWebRequest. Volverá nulo si no se pudo determinar la versión de Tls. También verifica la versión de Tls en la misma solicitud, antes de que haya escrito algo en la secuencia de solicitud. Si aún no se ha producido el protocolo de enlace Tls cuando se llama al método, lo activará.

Su uso de muestra se vería así:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("..."); request.Method = "POST"; if (requestPayload.Length > 0) { using (Stream requestStream = request.GetRequestStream()) { SslProtocols? protocol = GetSslProtocol(requestStream); requestStream.Write(requestPayload, 0, requestPayload.Length); } }

Y el método:

public static SslProtocols? GetSslProtocol(Stream stream) { if (stream == null) return null; if (typeof(SslStream).IsAssignableFrom(stream.GetType())) { var ssl = stream as SslStream; return ssl.SslProtocol; } var flags = BindingFlags.NonPublic | BindingFlags.Instance; if (stream.GetType().FullName == "System.Net.ConnectStream") { var connection = stream.GetType().GetProperty("Connection", flags).GetValue(stream); var netStream = connection.GetType().GetProperty("NetworkStream", flags).GetValue(connection) as Stream; return GetSslProtocol(netStream); } if (stream.GetType().FullName == "System.Net.TlsStream") { // type SslState var ssl = stream.GetType().GetField("m_Worker", flags).GetValue(stream); if (ssl.GetType().GetProperty("IsAuthenticated", flags).GetValue(ssl) as bool? != true) { // we''re not authenticated yet. see: https://referencesource.microsoft.com/#System/net/System/Net/_TLSstream.cs,115 var processAuthMethod = stream.GetType().GetMethod("ProcessAuthentication", flags); processAuthMethod.Invoke(stream, new object[] { null }); } var protocol = ssl.GetType().GetProperty("SslProtocol", flags).GetValue(ssl) as SslProtocols?; return protocol; } return null; }


Puede usar Reflection para llegar al valor de la propiedad TlsStream->SslState->SslProtocol .
Esta información se puede extraer del flujo devuelto por HttpWebRequest.GetRequestStream() y HttpWebRequest.GetResponseStream() .

ExtractSslProtocol() también maneja el GzipStream o DeflateStream comprimido que se devuelven cuando se activa WebRequest AutomaticDecompression .

La validación ocurrirá en ServerCertificateValidationCallback , que se llama cuando la solicitud se inicializa con request.GetRequestStream()

Nota : SecurityProtocolType.Tls13 se incluye en .Net Framework 4.8+ y .Net Core 3.0+ .

using System.Net; using System.Net.Security; using System.Reflection; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; //(...) ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; ServicePointManager.ServerCertificateValidationCallback += TlsValidationCallback; HttpWebRequest request = WebRequest.CreateHttp(decodedUri); using (Stream requestStream = request.GetRequestStream()) { //Here the request stream is already validated SslProtocols sslProtocol = ExtractSslProtocol(requestStream); if (sslProtocol < SslProtocols.Tls12) { // Refuse/close the connection } } //(...) private SslProtocols ExtractSslProtocol(Stream stream) { if (stream is null) return SslProtocols.None; BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; Stream metaStream = stream; if (stream.GetType().BaseType == typeof(GZipStream)) { metaStream = (stream as GZipStream).BaseStream; } else if (stream.GetType().BaseType == typeof(DeflateStream)) { metaStream = (stream as DeflateStream).BaseStream; } var connection = metaStream.GetType().GetProperty("Connection", bindingFlags).GetValue(metaStream); if (!(bool)connection.GetType().GetProperty("UsingSecureStream", bindingFlags).GetValue(connection)) { // Not a Https connection return SslProtocols.None; } var tlsStream = connection.GetType().GetProperty("NetworkStream", bindingFlags).GetValue(connection); var tlsState = tlsStream.GetType().GetField("m_Worker", bindingFlags).GetValue(tlsStream); return (SslProtocols)tlsState.GetType().GetProperty("SslProtocol", bindingFlags).GetValue(tlsState); }

RemoteCertificateValidationCallback tiene información útil sobre los protocolos de seguridad utilizados. (consulte: Parámetros de seguridad de la capa de transporte (TLS) (IANA) y RFC 5246 ).
Los tipos de protocolos de seguridad utilizados pueden ser lo suficientemente informativos, ya que cada versión de protocolo admite un subconjunto de algoritmos de Hashing y Cifrado.
Tls 1.2, presenta HMAC-SHA256 y desprecia los cifrados IDEA y DES (todas las variantes se enumeran en los documentos vinculados).

Aquí, OIDExtractor un OIDExtractor , que enumera los algoritmos en uso.
Tenga en cuenta que tanto TcpClient () como WebRequest () llegarán aquí.

private bool TlsValidationCallback(object sender, X509Certificate CACert, X509Chain CAChain, SslPolicyErrors sslPolicyErrors) { List<Oid> oidExtractor = CAChain .ChainElements .Cast<X509ChainElement>() .Select(x509 => new Oid(x509.Certificate.SignatureAlgorithm.Value)) .ToList(); // Inspect the oidExtractor list if (sslPolicyErrors == SslPolicyErrors.None) return true; X509Certificate2 certificate = new X509Certificate2(CACert); //If you needed/have to pass a certificate, add it here. //X509Certificate2 cert = new X509Certificate2(@"[localstorage]/[ca.cert]"); //CAChain.ChainPolicy.ExtraStore.Add(cert); CAChain.Build(certificate); foreach (X509ChainStatus CACStatus in CAChain.ChainStatus) { if ((CACStatus.Status != X509ChainStatusFlags.NoError) & (CACStatus.Status != X509ChainStatusFlags.UntrustedRoot)) return false; } return true; }

ACTUALIZACIÓN 2:
El secur32.dll -> QueryContextAttributesW() , permite consultar el contexto de seguridad de conexión de una secuencia inicializada.

[DllImport("secur32.dll", CharSet = CharSet.Auto, ExactSpelling=true, SetLastError=false)] private static extern int QueryContextAttributesW(SSPIHandle contextHandle, [In] ContextAttribute attribute, [In] [Out] ref SecPkgContext_ConnectionInfo ConnectionInfo);

Como puede ver en la documentación, este método devuelve un void* buffer que hace referencia a una estructura SecPkgContext_ConnectionInfo :

//[SuppressUnmanagedCodeSecurity] private struct SecPkgContext_ConnectionInfo { public SchProtocols dwProtocol; public ALG_ID aiCipher; public int dwCipherStrength; public ALG_ID aiHash; public int dwHashStrength; public ALG_ID aiExch; public int dwExchStrength; }

El miembro SchProtocols dwProtocol es el SslProtocol.

Cuál es el truco.
El TlsStream.Context.m_SecurityContext._handle que hace referencia al controlador de contexto de conexión no es público.
Por lo tanto, puede obtenerlo, nuevamente, solo a través de la reflexión o a través de las clases derivadas System.Net.Security.SslStream ( System.Net.Security.SslStream y System.Net.Security.NegotiateStream ) devueltas por TcpClient.GetStream() .

Desafortunadamente, el Stream devuelto por WebRequest / WebResponse no se puede transmitir a estas clases. Los tipos de conexiones y secuencias solo se mencionan a través de propiedades y campos no públicos.

Estoy publicando la documentación reunida, tal vez te ayude a encontrar otro camino para llegar a ese controlador de contexto.

Las declaraciones, estructuras, listas de enumeradores están en QueryContextAttributesW (PASTEBIN) .

Microsoft TechNet
Estructuras de autenticación

MSDN
Crear una conexión segura usando Schannel

Obteniendo información sobre las conexiones de Schannel

Consulta de los atributos de un contexto de canal

QueryContextAttributes (Schannel)

Base de código (parcial)

Fuente de referencia .NET

Internals.cs

estructura interna SSPIHandle {}

enumeración interna ContextAttribute {}

ACTUALIZACIÓN 1:

Vi en su comentario a otra respuesta que la solución que usa TcpClient() no es aceptable para usted. Lo dejo aquí de todos modos, así que los comentarios de Ben Voigt en este serán útiles para cualquier persona interesada. Además, 3 posibles soluciones son mejores que 2.

Algunos detalles de implementación sobre el uso de TcpClient() SslStream en el contexto proporcionado.

Si se requiere información de protocolo antes de inicializar una WebRequest, se puede establecer una conexión TcpClient () en el mismo contexto utilizando las mismas herramientas requeridas para una conexión TLS. A saber, el ServicePointManager.SecurityProtocol para definir los protocolos compatibles y el ServicePointManager.ServerCertificateValidationCallback para validar el certificado del servidor.

Tanto TcpClient () como WebRequest pueden usar esta configuración:
- habilite todos los protocolos y deje que Tls Handshake determine cuál se usará.
- defina un delegado RemoteCertificateValidationCallback() para validar los X509Certificates el servidor pasa en una X509Chain .

En la práctica, el Apretón de manos de Tls es el mismo cuando se establece una conexión TcpClient o WebRequest.
Este enfoque le permite saber qué protocolo Tls negociará su HttpWebRequest con el mismo servidor.

Configure un TcpClient() para recibir y evaluar el SslStream .
El indicador checkCertificateRevocation está establecido en false , por lo que el proceso no perderá tiempo buscando la lista de revocación.
La devolución de llamada de validación del certificado es la misma especificada en ServicePointManager

TlsInfo TLSInfo; IPHostEntry DnsHost = await Dns.GetHostEntryAsync(HostURI.Host); using (TcpClient client = new TcpClient(DnsHost.HostName, 443)) { using (SslStream sslstream = new SslStream(client.GetStream(), false, TlsValidationCallback, null)) { sslstream.AuthenticateAsClient(DnsHost.HostName, null, (SslProtocols)ServicePointManager.SecurityProtocol, false); TLSInfo = new TlsInfo(sslstream); } } //The HttpWebRequest goes on from here. HttpWebRequest httpRequest = WebRequest.CreateHttp(HostURI); //(...)

La clase TlsInfo recopila cierta información sobre la conexión segura establecida:
- Versión del protocolo Tls
- Algoritmos de cifrado y hash
- El certificado del servidor utilizado en el protocolo de enlace SSL

public class TlsInfo { public TlsInfo(SslStream SecureStream) { this.ProtocolVersion = SecureStream.SslProtocol; this.CipherAlgorithm = SecureStream.CipherAlgorithm; this.HashAlgorithm = SecureStream.HashAlgorithm; this.RemoteCertificate = SecureStream.RemoteCertificate; } public SslProtocols ProtocolVersion { get; set; } public CipherAlgorithmType CipherAlgorithm { get; set; } public HashAlgorithmType HashAlgorithm { get; set; } public X509Certificate RemoteCertificate { get; set; } }