diff --git a/src/Fluxzy.Core/Archiving/SslInfo.cs b/src/Fluxzy.Core/Archiving/SslInfo.cs
index 6c7c2bab..56698a7f 100644
--- a/src/Fluxzy.Core/Archiving/SslInfo.cs
+++ b/src/Fluxzy.Core/Archiving/SslInfo.cs
@@ -60,13 +60,12 @@ public SslInfo(SslStream sslStream, bool dumpCertificate)
///
internal SslInfo(FluxzyClientProtocol clientProtocol, bool dumpCertificate)
{
-//#if NET6_0
-// CipherAlgorithm = ((System.Net.Security.TlsCipherSuite) clientProtocol.SessionParameters.CipherSuite).ToString();
-//#endif
-
NegotiatedApplicationProtocol = clientProtocol.GetApplicationProtocol().ToString();
SslProtocol = clientProtocol.GetSChannelProtocol();
+ NegotiatedCipherSuite = (TlsCipherSuite) clientProtocol.SessionParameters.CipherSuite;
+ (CipherAlgorithm, HashAlgorithm) = DeriveAlgorithmsFromCipherSuite(NegotiatedCipherSuite);
+
if (BcCertificateHelper.TryReadDetailedInfo(clientProtocol.SessionParameters.LocalCertificate,
out var localSubject, out var localIssuer,
out var localNotBefore, out var localNotAfter, out var localSerial)) {
@@ -172,5 +171,51 @@ public SslInfo(
public DateTime? LocalCertificateNotAfter { get; }
public string? LocalCertificateSerialNumber { get; }
+
+ private static (CipherAlgorithmType Cipher, HashAlgorithmType Hash) DeriveAlgorithmsFromCipherSuite(
+ TlsCipherSuite cipherSuite)
+ {
+ var name = cipherSuite.ToString();
+
+ // TLS 1.2 and earlier use TLS__WITH__; TLS 1.3 uses TLS__.
+ var withIdx = name.IndexOf("_WITH_", StringComparison.Ordinal);
+ var body = withIdx >= 0
+ ? name.Substring(withIdx + "_WITH_".Length)
+ : name.StartsWith("TLS_", StringComparison.Ordinal) ? name.Substring(4) : name;
+
+ var lastUnderscore = body.LastIndexOf('_');
+ var hashName = lastUnderscore >= 0 ? body.Substring(lastUnderscore + 1) : string.Empty;
+ var cipherPart = lastUnderscore >= 0 ? body.Substring(0, lastUnderscore) : body;
+
+ var hash = hashName switch {
+ "SHA" => HashAlgorithmType.Sha1,
+ "SHA256" => HashAlgorithmType.Sha256,
+ "SHA384" => HashAlgorithmType.Sha384,
+ "SHA512" => HashAlgorithmType.Sha512,
+ "MD5" => HashAlgorithmType.Md5,
+ _ => HashAlgorithmType.None
+ };
+
+ var cipher = CipherAlgorithmType.None;
+
+ if (cipherPart.Contains("AES_128"))
+ cipher = CipherAlgorithmType.Aes128;
+ else if (cipherPart.Contains("AES_256"))
+ cipher = CipherAlgorithmType.Aes256;
+ else if (cipherPart.Contains("AES_192"))
+ cipher = CipherAlgorithmType.Aes192;
+ else if (cipherPart.Contains("3DES"))
+ cipher = CipherAlgorithmType.TripleDes;
+ else if (cipherPart.Contains("DES"))
+ cipher = CipherAlgorithmType.Des;
+ else if (cipherPart.Contains("RC4"))
+ cipher = CipherAlgorithmType.Rc4;
+ else if (cipherPart.Contains("RC2"))
+ cipher = CipherAlgorithmType.Rc2;
+ else if (cipherPart.Contains("NULL"))
+ cipher = CipherAlgorithmType.Null;
+
+ return (cipher, hash);
+ }
}
}
diff --git a/test/Fluxzy.Tests/UnitTests/Handlers/CipherAndHashInSslInfoTests.cs b/test/Fluxzy.Tests/UnitTests/Handlers/CipherAndHashInSslInfoTests.cs
new file mode 100644
index 00000000..123e185c
--- /dev/null
+++ b/test/Fluxzy.Tests/UnitTests/Handlers/CipherAndHashInSslInfoTests.cs
@@ -0,0 +1,76 @@
+// Copyright 2021 - Haga Rakotoharivelo - https://github.com/haga-rak
+
+using System;
+using System.Net.Http;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Threading.Tasks;
+using Fluxzy.Clients.DotNetBridge;
+using Fluxzy.Core;
+using Fluxzy.Writers;
+using Xunit;
+
+namespace Fluxzy.Tests.UnitTests.Handlers
+{
+ ///
+ /// Confirms that exposes the negotiated cipher suite,
+ /// cipher algorithm and hash algorithm regardless of the SSL provider.
+ /// The OsDefault path populates them from SslStream; the BouncyCastle
+ /// constructor currently does not (see SslInfo(FluxzyClientProtocol, bool)).
+ ///
+ public class CipherAndHashInSslInfoTests
+ {
+ [Theory]
+ [InlineData(SslProvider.OsDefault)]
+ [InlineData(SslProvider.BouncyCastle)]
+ public async Task NegotiatedCipherSuite_IsPopulated(SslProvider sslProvider)
+ {
+ var sslInfo = await GetSslInfoForGoogle(sslProvider);
+
+ Assert.NotNull(sslInfo);
+ Assert.NotEqual(default(TlsCipherSuite), sslInfo.NegotiatedCipherSuite);
+ }
+
+ [Theory]
+ [InlineData(SslProvider.OsDefault)]
+ [InlineData(SslProvider.BouncyCastle)]
+ public async Task CipherAlgorithm_IsPopulated(SslProvider sslProvider)
+ {
+ var sslInfo = await GetSslInfoForGoogle(sslProvider);
+
+ Assert.NotNull(sslInfo);
+ Assert.NotEqual(CipherAlgorithmType.None, sslInfo.CipherAlgorithm);
+ }
+
+ // OsDefault is intentionally excluded: SslStream.HashAlgorithm is obsolete and returns
+ // None for TLS 1.3 on OpenSSL (Linux/macOS), even though the negotiated suite carries
+ // a hash. The BouncyCastle path derives Sha256/Sha384 from the suite name and so
+ // exposes a useful value where .NET no longer does.
+ [Theory]
+ [InlineData(SslProvider.BouncyCastle)]
+ public async Task HashAlgorithm_IsPopulated(SslProvider sslProvider)
+ {
+ var sslInfo = await GetSslInfoForGoogle(sslProvider);
+
+ Assert.NotNull(sslInfo);
+ Assert.NotEqual(HashAlgorithmType.None, sslInfo.HashAlgorithm);
+ }
+
+ private static async Task GetSslInfoForGoogle(SslProvider sslProvider)
+ {
+ await using var tcpProvider = ITcpConnectionProvider.Default;
+
+ using var handler = new FluxzyDefaultHandler(sslProvider, tcpProvider, new EventOnlyArchiveWriter());
+
+ using var httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(15) };
+
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com/");
+
+ var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
+ response.EnsureSuccessStatusCode();
+
+ var fluxzyResponse = Assert.IsType(response);
+ return fluxzyResponse.Exchange.Connection?.SslInfo;
+ }
+ }
+}