using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Math; using Org.BouncyCastle.X509; using XFESystem.Tls; namespace XFESystem.NativeTls.Tests; public sealed class TlsTrustStoreImportTests { private static X509Certificate ReplacePublicKey(X509Certificate certificate, SubjectPublicKeyInfo info) { var original = (Asn1Sequence)certificate.CertificateStructure.TbsCertificate.ToAsn1Object(); var fields = original.Cast().ToArray(); // This fixture is v3, so its explicit version occupies index zero. Assert.IsAssignableFrom(fields[0]); fields[6] = info; var tbs = TbsCertificateStructure.GetInstance(new DerSequence(fields)); var structure = new X509CertificateStructure(tbs, certificate.CertificateStructure.SignatureAlgorithm, certificate.CertificateStructure.Signature); return new X509Certificate(structure); } private static X509Certificate RsaRoot(X509Certificate original, BigInteger modulus, BigInteger exponent, Asn1Encodable? parameters = null) => ReplacePublicKey(original, new SubjectPublicKeyInfo(new AlgorithmIdentifier(PkcsObjectIdentifiers.RsaEncryption, parameters ?? DerNull.Instance), new DerSequence(new DerInteger(modulus), new DerInteger(exponent)))); [Fact] public void RootImportDoesNotRunRsaKeyFactoryForEveryAnchor() { using var fixture = new CertificateFixtures(); // Import must remain a cheap structural check, even for a large bundle. var root = new ObservedCertificate(RsaRoot(fixture.RootBc, BigInteger.One.ShiftLeft(2203).Subtract(BigInteger.One), BigInteger.ValueOf(65537))); for (int count = 0; count < 121; count++) _ = new TlsCertificateValidator([root]); Assert.Equal(0, root.KeyFactoryCalls); } [Theory] [InlineData(1024, 65537)] [InlineData(16385, 65537)] [InlineData(2048, 1)] [InlineData(2048, 2)] [InlineData(2048, 0)] [InlineData(2048, -3)] public void RootImportRejectsWeakStrengthAndInvalidExponent(int bits, long exponent) { using var fixture = new CertificateFixtures(); var root = RsaRoot(fixture.RootBc, BigInteger.One.ShiftLeft(bits - 1).Add(BigInteger.One), BigInteger.ValueOf(exponent)); Assert.Throws(() => new TlsCertificateValidator([root])); } [Fact] public void RootImportRejectsEvenModulusAndInvalidAlgorithmParameters() { using var fixture = new CertificateFixtures(); var odd = BigInteger.One.ShiftLeft(2047).Add(BigInteger.One); var even = RsaRoot(fixture.RootBc, odd.Add(BigInteger.One), BigInteger.ValueOf(65537)); var invalidParameters = RsaRoot(fixture.RootBc, odd, BigInteger.ValueOf(65537), DerInteger.ValueOf(7)); Assert.Throws(() => new TlsCertificateValidator([even])); Assert.Throws(() => new TlsCertificateValidator([invalidParameters])); } [Fact] public void NegativeAsn1ModulusCannotBeNormalizedIntoAnAcceptedPositiveKey() { using var fixture = new CertificateFixtures(); var negative = BigInteger.One.ShiftLeft(2203).Subtract(BigInteger.One).Negate(); var root = RsaRoot(fixture.RootBc, negative, BigInteger.ValueOf(65537)); Assert.Throws(() => new TlsCertificateValidator([root])); } [Fact] public void UnsupportedRootKeyAlgorithmIsNotAdmitted() { using var fixture = new CertificateFixtures(); var root = ReplacePublicKey(fixture.RootBc, new SubjectPublicKeyInfo( new AlgorithmIdentifier(new DerObjectIdentifier("1.3.6.1.4.1.55555.77.1")), new DerBitString([1, 2, 3]))); Assert.ThrowsAny(() => new TlsCertificateValidator([root])); } [Fact] public void SelectedAnchorStillRunsFullRsaValidationAndRejectsPrimeModulus() { using var fixture = new CertificateFixtures(); using var leaf = fixture.Leaf(); // 2^2203-1 is a known Mersenne prime, so it meets the structural bit // policy but cannot be an RSA modulus. The real BC factory must reject. var root = new ObservedCertificate(RsaRoot(fixture.RootBc, BigInteger.One.ShiftLeft(2203).Subtract(BigInteger.One), BigInteger.ValueOf(65537))); var validator = new TlsCertificateValidator([root]); Assert.Equal(0, root.KeyFactoryCalls); Assert.ThrowsAny(() => validator.Validate("test.xfe.invalid", [leaf.RawData], fixture.Now)); Assert.True(root.KeyFactoryCalls > 0); Assert.Contains("not composite", root.KeyFactoryError?.Message ?? "", StringComparison.OrdinalIgnoreCase); } private sealed class ObservedCertificate(X509Certificate source) : X509Certificate(source.CertificateStructure) { public int KeyFactoryCalls; public Exception? KeyFactoryError; public override AsymmetricKeyParameter GetPublicKey() { KeyFactoryCalls++; try { return base.GetPublicKey(); } catch (Exception error) { KeyFactoryError = error; throw; } } } }