NetEventSource.Error(null, $"Failed to build chain for {target.Subject}");
}
- int count = chain.ChainElements.Count - (TrimRootCertificate ? 1 : 2);
- foreach (X509ChainStatus status in chain.ChainStatus)
+ int count = chain.ChainElements.Count - 1;
+#pragma warning disable 0162 // Disable unreachable code warning. TrimRootCertificate is const bool = false on some platforms
+ if (TrimRootCertificate)
{
- if (status.Status.HasFlag(X509ChainStatusFlags.PartialChain))
+ count--;
+ foreach (X509ChainStatus status in chain.ChainStatus)
{
- // The last cert isn't a root cert
- count++;
- break;
+ if (status.Status.HasFlag(X509ChainStatusFlags.PartialChain))
+ {
+ // The last cert isn't a root cert
+ count++;
+ break;
+ }
}
}
+#pragma warning restore 0162
// Count can be zero for a self-signed certificate, or a cert issued directly from a root.
if (count > 0 && chain.ChainElements.Count > 1)
static SslStreamNetworkStreamTest()
{
TestHelper.CleanupCertificates(nameof(SslStreamNetworkStreamTest));
- (_serverCert, _serverChain) = TestHelper.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest));
+ (_serverCert, _serverChain) = TestHelper.GenerateCertificates("localhost", nameof(SslStreamNetworkStreamTest), longChain: true);
}
[ConditionalFact]
}
}
- [Fact]
- public async Task SslStream_UntrustedCaWithCustomCallback_OK()
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task SslStream_UntrustedCaWithCustomCallback_OK(bool usePartialChain)
{
+ var rnd = new Random();
+ int split = rnd.Next(0, _serverChain.Count - 1);
+
var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost" };
clientOptions.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) =>
{
- chain.ChainPolicy.CustomTrustStore.Add(_serverChain[_serverChain.Count -1]);
+ // add our custom root CA
+ chain.ChainPolicy.CustomTrustStore.Add(_serverChain[_serverChain.Count - 1]);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ // Add only one CA to verify that peer did send intermediate CA cert.
+ // In case of partial chain, we need to make missing certs available.
+ if (usePartialChain)
+ {
+ for (int i = split; i < _serverChain.Count - 1; i++)
+ {
+ chain.ChainPolicy.ExtraStore.Add(_serverChain[i]);
+ }
+ }
bool result = chain.Build((X509Certificate2)certificate);
Assert.True(result);
};
var serverOptions = new SslServerAuthenticationOptions();
- serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_serverCert, _serverChain);
+ X509Certificate2Collection serverChain;
+ if (usePartialChain)
+ {
+ // give first few certificates without root CA
+ serverChain = new X509Certificate2Collection();
+ for (int i = 0; i < split; i++)
+ {
+ serverChain.Add(_serverChain[i]);
+ }
+ }
+ else
+ {
+ serverChain = _serverChain;
+ }
+
+ serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_serverCert, serverChain);
(Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
using (clientStream)
clientOptions.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) =>
{
+ // Add only root CA to verify that peer did send intermediate CA cert.
chain.ChainPolicy.CustomTrustStore.Add(_serverChain[_serverChain.Count -1]);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
// This should work and we should be able to trust the chain.
}
else
{
- errorMessage = "UntrustedRoot";
+ // On Windows we hand whole chain to OS so they can always see the root CA.
+ errorMessage = PlatformDetection.IsWindows ? "UntrustedRoot" : "PartialChain";
}
var serverOptions = new SslServerAuthenticationOptions();
catch { };
}
- internal static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null)
+ internal static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false)
{
+ const int keySize = 2048;
if (PlatformDetection.IsWindows && testName != null)
{
CleanupCertificates(testName);
out X509Certificate2 endEntity,
subjectName: targetName,
testName: testName,
- keySize: 2048,
+ keySize: keySize,
extensions: extensions);
+ if (longChain)
+ {
+ using (RSA intermedKey2 = RSA.Create(keySize))
+ using (RSA intermedKey3 = RSA.Create(keySize))
+ {
+ X509Certificate2 intermedPub2 = intermediate.CreateSubordinateCA(
+ $"CN=\"A SSL Test CA 2\", O=\"testName\"",
+ intermedKey2);
+
+ X509Certificate2 intermedCert2 = intermedPub2.CopyWithPrivateKey(intermedKey2);
+ intermedPub2.Dispose();
+ CertificateAuthority intermediateAuthority2 = new CertificateAuthority(intermedCert2, null, null, null);
+
+ X509Certificate2 intermedPub3 = intermediateAuthority2.CreateSubordinateCA(
+ $"CN=\"A SSL Test CA 3\", O=\"testName\"",
+ intermedKey3);
+
+ X509Certificate2 intermedCert3 = intermedPub3.CopyWithPrivateKey(intermedKey3);
+ intermedPub3.Dispose();
+ CertificateAuthority intermediateAuthority3 = new CertificateAuthority(intermedCert3, null, null, null);
+
+ RSA eeKey = (RSA)endEntity.PrivateKey;
+ endEntity = intermediateAuthority3.CreateEndEntity(
+ $"CN=\"A SSL Test\", O=\"testName\"",
+ eeKey,
+ extensions);
+
+ endEntity = endEntity.CopyWithPrivateKey(eeKey);
+
+ chain.Add(intermedCert3);
+ chain.Add(intermedCert2);
+ }
+ }
+
chain.Add(intermediate.CloneIssuerCert());
chain.Add(root.CloneIssuerCert());