using System.Text; using System.Net; using Org.BouncyCastle.Security; using Org.BouncyCastle.Tls; using Org.BouncyCastle.Tls.Crypto.Impl.BC; namespace XFESystem.Tls; /// Actual managed TLS over the supplied platform stream; never a host HTTP adapter. public sealed class TlsClientConnection : IDisposable { private readonly TlsClientProtocol protocol; private readonly VerifiedClient client; public Stream Stream => protocol.Stream; public string Protocol => client.Protocol; public bool CertificateValidated => client.CertificateValidated; public TlsClientConnection(Stream platformStream, string host, TlsCertificateValidator certificates, ITlsSecurityContext platform) { ArgumentNullException.ThrowIfNull(platformStream); ArgumentNullException.ThrowIfNull(certificates); ArgumentNullException.ThrowIfNull(platform); if (string.IsNullOrWhiteSpace(host) || host.Length > 253 || host.Any(character => character > 127 || char.IsControl(character)) || Uri.CheckHostName(host) == UriHostNameType.Unknown) throw new ArgumentException("TLS requires an ASCII DNS name or IP address without a port or credentials.", nameof(host)); host = host.TrimEnd('.'); _ = TlsSecurity.ReadUtc(platform); var random = TlsSecurity.CreateRandom(platform); client = new(host, certificates, platform, random); protocol = new TlsClientProtocol(platformStream); try { protocol.Connect(client); if (!client.CertificateValidated) throw new IOException("The TLS handshake did not validate the peer certificate."); } catch { TryClose(); throw; } } private void TryClose() { try { protocol.Close(); } catch { } } public void Dispose() => TryClose(); private sealed class VerifiedClient(string host, TlsCertificateValidator certificates, ITlsSecurityContext platform, SecureRandom random) : DefaultTlsClient(new BcTlsCrypto(random)), TlsAuthentication { public bool CertificateValidated { get; private set; } public string Protocol { get; private set; } = ""; public override int GetHandshakeTimeoutMillis() => 10_000; public override bool RequiresExtendedMasterSecret() => true; protected override ProtocolVersion[] GetSupportedVersions() => [ProtocolVersion.TLSv13, ProtocolVersion.TLSv12]; protected override int[] GetSupportedCipherSuites() => [ CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 ]; protected override IList GetSniServerNames() => IPAddress.TryParse(host, out _) ? null! : [new ServerName(NameType.host_name, Encoding.ASCII.GetBytes(host))]; protected override IList GetProtocolNames() => [ProtocolName.Http_1_1]; protected override IList GetSupportedGroups(IList namedGroupRoles) => [NamedGroup.x25519, NamedGroup.secp256r1, NamedGroup.secp384r1]; public override TlsAuthentication GetAuthentication() => this; public void NotifyServerCertificate(TlsServerCertificate serverCertificate) { var chain = serverCertificate.Certificate.GetCertificateList().Select(certificate => certificate.GetEncoded()).ToArray(); certificates.Validate(host, chain, TlsSecurity.ReadUtc(platform)); CertificateValidated = true; } public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) => null!; public override void NotifyHandshakeComplete() { base.NotifyHandshakeComplete(); Protocol = m_context.SecurityParameters.NegotiatedVersion.ToString(); } } }