using System.Security.Cryptography;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using XFESystem.Tls;
namespace XFESystem.NativeTls.Tests;
public sealed class TlsPlatformIntegrationTests
{
[Fact]
public void PlatformContextUsesOnlyTheSuppliedUtcAndRandomProviders()
{
int reads = 0, fills = 0;
DateTime now = new(2031, 2, 3, 4, 5, 6, DateTimeKind.Utc);
var platform = new TlsPlatformSecurityContext(() => { reads++; return now; }, destination => { fills++; destination.Fill(0xA7); });
Assert.Equal(now, platform.UtcNow);
Span<byte> bytes = stackalloc byte[17]; platform.FillRandom(bytes);
Assert.True(bytes.SequenceEqual(Enumerable.Repeat((byte)0xA7, 17).ToArray()));
Assert.Equal(1, reads); Assert.Equal(1, fills);
}
[Fact]
public void FailedPlatformRandomClearsPartialOutputAndPropagatesFailure()
{
var platform = new TlsPlatformSecurityContext(() => DateTime.UtcNow, destination =>
{
destination.Fill(0xA7); throw new InvalidOperationException("Kernel entropy source failed its health check.");
});
byte[] bytes = Enumerable.Repeat((byte)0x55, 32).ToArray();
Assert.Throws<InvalidOperationException>(() => platform.FillRandom(bytes));
Assert.All(bytes, value => Assert.Equal(0, value));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MissingOrNonUtcPlatformTimeDoesNotFallBackToHost(bool unspecified)
{
var platform = new TlsPlatformSecurityContext(() => unspecified ? DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified) : DateTime.UnixEpoch,
destination => throw new InvalidOperationException("No source"));
Assert.Throws<InvalidOperationException>(() => platform.UtcNow);
}
[Theory]
[InlineData(".xfe.invalid", null, true)]
[InlineData(".other.invalid", null, false)]
[InlineData(null, ".xfe.invalid", false)]
[InlineData(".xfe.invalid", ".other.invalid", true)]
public void ExplicitAnchorNameConstraintsReallyRestrictSelectedPath(string? permitted, string? excluded, bool accepted)
{
using var fixture = new CertificateFixtures(permitted, excluded); using var leaf = fixture.Leaf();
var validator = new TlsCertificateValidator([fixture.RootBc]);
Action validate = () => validator.Validate("test.xfe.invalid", [leaf.RawData], fixture.Now);
if (accepted) validate(); else Assert.ThrowsAny<Exception>(validate);
}
[Fact]
public void ExplicitRootCannotRestrictChainToClientAuthentication()
{
using var fixture = new CertificateFixtures(serverAuthRoot: false); using var leaf = fixture.Leaf();
Assert.Throws<InvalidOperationException>(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now));
}
[Fact]
public void ExplicitRootMustPermitCertificateSigning()
{
using var fixture = new CertificateFixtures(certificateSigning: false);
Assert.Throws<ArgumentException>(() => new TlsCertificateValidator([fixture.RootBc]));
}
[Theory]
[InlineData("SHA1WITHECDSA", false)]
[InlineData("SHA256WITHECDSA", true)]
public void ActualSignedPathCannotUseWeakCertificateDigest(string algorithm, bool accepted)
{
using var fixture = new CertificateFixtures();
using var leafKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var generator = new X509V3CertificateGenerator();
generator.SetSerialNumber(Org.BouncyCastle.Math.BigInteger.ValueOf(491));
generator.SetIssuerDN(fixture.RootBc.SubjectDN); generator.SetSubjectDN(new X509Name("CN=test.xfe.invalid"));
generator.SetNotBefore(fixture.Now.AddHours(-1)); generator.SetNotAfter(fixture.Now.AddHours(1));
generator.SetPublicKey(PublicKeyFactory.CreateKey(leafKey.ExportSubjectPublicKeyInfo()));
generator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
generator.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DigitalSignature));
generator.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeID.id_kp_serverAuth));
generator.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.DnsName, "test.xfe.invalid")));
var leaf = generator.Generate(new Asn1SignatureFactory(algorithm, PrivateKeyFactory.CreateKey(fixture.RootKey.ExportPkcs8PrivateKey())));
Action validate = () => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.GetEncoded()], fixture.Now);
if (accepted) validate(); else Assert.Throws<InvalidOperationException>(validate);
}
}
using System.Security.Cryptography;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using XFESystem.Tls;
namespace XFESystem.NativeTls.Tests;
public sealed class TlsPlatformIntegrationTests
{
[Fact]
public void PlatformContextUsesOnlyTheSuppliedUtcAndRandomProviders()
{
int reads = 0, fills = 0;
DateTime now = new(2031, 2, 3, 4, 5, 6, DateTimeKind.Utc);
var platform = new TlsPlatformSecurityContext(() => { reads++; return now; }, destination => { fills++; destination.Fill(0xA7); });
Assert.Equal(now, platform.UtcNow);
Span<byte> bytes = stackalloc byte[17]; platform.FillRandom(bytes);
Assert.True(bytes.SequenceEqual(Enumerable.Repeat((byte)0xA7, 17).ToArray()));
Assert.Equal(1, reads); Assert.Equal(1, fills);
}
[Fact]
public void FailedPlatformRandomClearsPartialOutputAndPropagatesFailure()
{
var platform = new TlsPlatformSecurityContext(() => DateTime.UtcNow, destination =>
{
destination.Fill(0xA7); throw new InvalidOperationException("Kernel entropy source failed its health check.");
});
byte[] bytes = Enumerable.Repeat((byte)0x55, 32).ToArray();
Assert.Throws<InvalidOperationException>(() => platform.FillRandom(bytes));
Assert.All(bytes, value => Assert.Equal(0, value));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void MissingOrNonUtcPlatformTimeDoesNotFallBackToHost(bool unspecified)
{
var platform = new TlsPlatformSecurityContext(() => unspecified ? DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified) : DateTime.UnixEpoch,
destination => throw new InvalidOperationException("No source"));
Assert.Throws<InvalidOperationException>(() => platform.UtcNow);
}
[Theory]
[InlineData(".xfe.invalid", null, true)]
[InlineData(".other.invalid", null, false)]
[InlineData(null, ".xfe.invalid", false)]
[InlineData(".xfe.invalid", ".other.invalid", true)]
public void ExplicitAnchorNameConstraintsReallyRestrictSelectedPath(string? permitted, string? excluded, bool accepted)
{
using var fixture = new CertificateFixtures(permitted, excluded); using var leaf = fixture.Leaf();
var validator = new TlsCertificateValidator([fixture.RootBc]);
Action validate = () => validator.Validate("test.xfe.invalid", [leaf.RawData], fixture.Now);
if (accepted) validate(); else Assert.ThrowsAny<Exception>(validate);
}
[Fact]
public void ExplicitRootCannotRestrictChainToClientAuthentication()
{
using var fixture = new CertificateFixtures(serverAuthRoot: false); using var leaf = fixture.Leaf();
Assert.Throws<InvalidOperationException>(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now));
}
[Fact]
public void ExplicitRootMustPermitCertificateSigning()
{
using var fixture = new CertificateFixtures(certificateSigning: false);
Assert.Throws<ArgumentException>(() => new TlsCertificateValidator([fixture.RootBc]));
}
[Theory]
[InlineData("SHA1WITHECDSA", false)]
[InlineData("SHA256WITHECDSA", true)]
public void ActualSignedPathCannotUseWeakCertificateDigest(string algorithm, bool accepted)
{
using var fixture = new CertificateFixtures();
using var leafKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var generator = new X509V3CertificateGenerator();
generator.SetSerialNumber(Org.BouncyCastle.Math.BigInteger.ValueOf(491));
generator.SetIssuerDN(fixture.RootBc.SubjectDN); generator.SetSubjectDN(new X509Name("CN=test.xfe.invalid"));
generator.SetNotBefore(fixture.Now.AddHours(-1)); generator.SetNotAfter(fixture.Now.AddHours(1));
generator.SetPublicKey(PublicKeyFactory.CreateKey(leafKey.ExportSubjectPublicKeyInfo()));
generator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
generator.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.DigitalSignature));
generator.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeID.id_kp_serverAuth));
generator.AddExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.DnsName, "test.xfe.invalid")));
var leaf = generator.Generate(new Asn1SignatureFactory(algorithm, PrivateKeyFactory.CreateKey(fixture.RootKey.ExportPkcs8PrivateKey())));
Action validate = () => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.GetEncoded()], fixture.Now);
if (accepted) validate(); else Assert.Throws<InvalidOperationException>(validate);
}
}