using System.Security.Cryptography; using XFESystem.Tls; namespace XFESystem.NativeTls.Tests; public sealed class TlsCertificateTests { [Fact] public void ValidExplicitRootSanAndUtcAreAccepted() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(); var validated = new TlsCertificateValidator([fixture.RootBc]).Validate("TEST.XFE.INVALID", [leaf.RawData], fixture.Now); Assert.Equal(leaf.RawData, validated.GetEncoded()); } [Theory] [InlineData("other.xfe.invalid")] [InlineData("sub.test.xfe.invalid")] [InlineData("test.xfe.invalid.attacker.invalid")] [InlineData("127.0.0.1")] public void WrongSubjectAlternativeNameIsRejected(string host) { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(); Assert.Throws(() => new TlsCertificateValidator([fixture.RootBc]).Validate(host, [leaf.RawData], fixture.Now)); } [Fact] public void UnknownRootIsRejected() { using var fixture = new CertificateFixtures(); using var stranger = new CertificateFixtures(); using var leaf = fixture.Leaf(); Assert.ThrowsAny(() => new TlsCertificateValidator([stranger.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } [Fact] public void WrongSigningKeyIsRejectedEvenWhenIssuerNameMatchesTrustedRoot() { using var fixture = new CertificateFixtures(); using var wrongKey = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var leaf = fixture.Leaf(signingKey: wrongKey); Assert.ThrowsAny(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } [Theory] [InlineData(-3, -2)] [InlineData(2, 3)] public void ExpiredAndNotYetValidCertificatesAreRejected(int before, int after) { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(before: fixture.Now.AddDays(before), after: fixture.Now.AddDays(after)); Assert.ThrowsAny(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } [Fact] public void SubjectCommonNameDoesNotReplaceMissingSan() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(dns: null); Assert.Throws(() => new TlsCertificateValidator([fixture.RootBc]).Validate("never-used-for-name-match.invalid", [leaf.RawData], fixture.Now)); } [Theory] [InlineData(true, true, true)] [InlineData(false, false, true)] [InlineData(false, true, false)] public void CaLeafWrongExtendedUsageAndWrongSigningUsageAreRejected(bool ca, bool serverAuth, bool signingUsage) { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(ca: ca, serverAuth: serverAuth, signingUsage: signingUsage); Assert.ThrowsAny(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } [Fact] public void UnknownCriticalExtensionIsRejectedByPkix() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(unknownCriticalExtension: true); Assert.ThrowsAny(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } [Theory] [InlineData("*.xfe.invalid", "test.xfe.invalid", true)] [InlineData("*.xfe.invalid", "nested.test.xfe.invalid", false)] [InlineData("*.xfe.invalid", "xfe.invalid", false)] [InlineData("*.invalid", "test.invalid", false)] [InlineData("*.xfe.invalid", "xn--test.xfe.invalid", false)] public void WildcardMatchesExactlyOneCompleteNonIdnaLabel(string name, string host, bool accepted) { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(dns: name); var validation = () => new TlsCertificateValidator([fixture.RootBc]).Validate(host, [leaf.RawData], fixture.Now); if (accepted) Assert.NotNull(validation()); else Assert.Throws(validation); } [Fact] public void IpIdentityRequiresIpSan() { using var fixture = new CertificateFixtures(); using var ipLeaf = fixture.Leaf(dns: null, ip: "127.0.0.1"); using var dnsLeaf = fixture.Leaf(dns: "127.0.0.1"); var validator = new TlsCertificateValidator([fixture.RootBc]); Assert.NotNull(validator.Validate("127.0.0.1", [ipLeaf.RawData], fixture.Now)); Assert.Throws(() => validator.Validate("127.0.0.1", [dnsLeaf.RawData], fixture.Now)); } [Fact] public void BoundsEmptyTrustAndNonUtcInputsAreRejected() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(); Assert.Throws(() => new TlsCertificateValidator([])); var validator = new TlsCertificateValidator([fixture.RootBc]); Assert.Throws(() => validator.Validate("test.xfe.invalid", [], fixture.Now)); Assert.Throws(() => validator.Validate("test.xfe.invalid", [new byte[131_073]], fixture.Now)); Assert.Throws(() => validator.Validate("test.xfe.invalid", [leaf.RawData], DateTime.SpecifyKind(fixture.Now, DateTimeKind.Unspecified))); } [Fact] public void ExpiredTrustAnchorIsRejectedEvenWhenLeafIsStillValid() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(before: fixture.Now.AddDays(9), after: fixture.Now.AddDays(12)); Assert.ThrowsAny(() => new TlsCertificateValidator([fixture.RootBc]).Validate("test.xfe.invalid", [leaf.RawData], fixture.Now.AddDays(11))); } [Fact] public void PemRootBundleIsParsedWithoutHostCertificateStore() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(); var validator = TlsCertificateValidator.FromPem(fixture.Root.ExportCertificatePem()); Assert.NotNull(validator.Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); } }