Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 49 additions & 4 deletions src/Fluxzy.Core/Archiving/SslInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,12 @@ public SslInfo(SslStream sslStream, bool dumpCertificate)
/// <param name="dumpCertificate"></param>
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)) {
Expand Down Expand Up @@ -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_<kex>_WITH_<cipher>_<hash>; TLS 1.3 uses TLS_<cipher>_<hash>.
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Confirms that <see cref="SslInfo"/> exposes the negotiated cipher suite,
/// cipher algorithm and hash algorithm regardless of the SSL provider.
/// The OsDefault path populates them from <c>SslStream</c>; the BouncyCastle
/// constructor currently does not (see <c>SslInfo(FluxzyClientProtocol, bool)</c>).
/// </summary>
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<SslInfo?> 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<FluxzyHttpResponseMessage>(response);
return fluxzyResponse.Exchange.Connection?.SslInfo;
}
}
}
Loading