Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,37 @@
using System.Linq;
using Xunit;

namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests
namespace System.Security.Cryptography.X509Certificates.Tests.Common
{
// This class represents only a portion of what is required to be a proper Certificate Authority.
//
// Please do not use it as the basis for any real Public/Private Key Infrastructure (PKI) system
// without understanding all of the portions of proper CA management that you're skipping.
//
// At minimum, read the current baseline requirements of the CA/Browser Forum.

[Flags]
public enum PkiOptions
{
None = 0,

IssuerRevocationViaCrl = 1 << 0,
IssuerRevocationViaOcsp = 1 << 1,
EndEntityRevocationViaCrl = 1 << 2,
EndEntityRevocationViaOcsp = 1 << 3,

CrlEverywhere = IssuerRevocationViaCrl | EndEntityRevocationViaCrl,
OcspEverywhere = IssuerRevocationViaOcsp | EndEntityRevocationViaOcsp,
AllIssuerRevocation = IssuerRevocationViaCrl | IssuerRevocationViaOcsp,
AllEndEntityRevocation = EndEntityRevocationViaCrl | EndEntityRevocationViaOcsp,
AllRevocation = CrlEverywhere | OcspEverywhere,

IssuerAuthorityHasDesignatedOcspResponder = 1 << 16,
RootAuthorityHasDesignatedOcspResponder = 1 << 17,
NoIssuerCertDistributionUri = 1 << 18,
NoRootCertDistributionUri = 1 << 18,
}

internal sealed class CertificateAuthority : IDisposable
{
private static readonly Asn1Tag s_context0 = new Asn1Tag(TagClass.ContextSpecific, 0);
Expand All @@ -35,7 +58,7 @@ internal sealed class CertificateAuthority : IDisposable

private static readonly X509KeyUsageExtension s_eeKeyUsage =
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature,
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment,
critical: false);

private static readonly X509EnhancedKeyUsageExtension s_ocspResponderEku =
Expand All @@ -46,6 +69,14 @@ internal sealed class CertificateAuthority : IDisposable
},
critical: false);

private static readonly X509EnhancedKeyUsageExtension s_tlsServerEku =
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1", null)
},
false);

private static readonly X509EnhancedKeyUsageExtension s_tlsClientEku =
new X509EnhancedKeyUsageExtension(
new OidCollection
Expand Down Expand Up @@ -137,15 +168,16 @@ internal X509Certificate2 CreateSubordinateCA(
ekuExtension: null);
}

internal X509Certificate2 CreateEndEntity(string subject, RSA publicKey)
internal X509Certificate2 CreateEndEntity(string subject, RSA publicKey, X509Extension altName)
{
return CreateCertificate(
subject,
publicKey,
TimeSpan.FromSeconds(2),
s_eeConstraints,
s_eeKeyUsage,
s_tlsClientEku);
s_tlsServerEku,
altName: altName);
}

internal X509Certificate2 CreateOcspSigner(string subject, RSA publicKey)
Expand Down Expand Up @@ -219,7 +251,8 @@ private X509Certificate2 CreateCertificate(
X509BasicConstraintsExtension basicConstraints,
X509KeyUsageExtension keyUsage,
X509EnhancedKeyUsageExtension ekuExtension,
bool ocspResponder = false)
bool ocspResponder = false,
X509Extension altName = null)
{
if (_cdpExtension == null && CdpUri != null)
{
Expand Down Expand Up @@ -262,6 +295,11 @@ private X509Certificate2 CreateCertificate(
request.CertificateExtensions.Add(ekuExtension);
}

if (altName != null)
{
request.CertificateExtensions.Add(altName);
}

byte[] serial = new byte[sizeof(long)];
RandomNumberGenerator.Fill(serial);

Expand Down Expand Up @@ -793,5 +831,130 @@ private enum CertStatus
OK,
Revoked,
}

internal static void BuildPrivatePki(
PkiOptions pkiOptions,
out RevocationResponder responder,
out CertificateAuthority rootAuthority,
out CertificateAuthority intermediateAuthority,
out X509Certificate2 endEntityCert,
string testName = null,
bool registerAuthorities = true,
bool pkiOptionsInSubject = false,
string subjectName = null)
{
bool rootDistributionViaHttp = !pkiOptions.HasFlag(PkiOptions.NoRootCertDistributionUri);
bool issuerRevocationViaCrl = pkiOptions.HasFlag(PkiOptions.IssuerRevocationViaCrl);
bool issuerRevocationViaOcsp = pkiOptions.HasFlag(PkiOptions.IssuerRevocationViaOcsp);
bool issuerDistributionViaHttp = !pkiOptions.HasFlag(PkiOptions.NoIssuerCertDistributionUri);
bool endEntityRevocationViaCrl = pkiOptions.HasFlag(PkiOptions.EndEntityRevocationViaCrl);
bool endEntityRevocationViaOcsp = pkiOptions.HasFlag(PkiOptions.EndEntityRevocationViaOcsp);

Assert.True(
issuerRevocationViaCrl || issuerRevocationViaOcsp ||
endEntityRevocationViaCrl || endEntityRevocationViaOcsp,
"At least one revocation mode is enabled");

// All keys created in this method are smaller than recommended,
// but they only live for a few seconds (at most),
// and never communicate out of process.
const int KeySize = 1024;

using (RSA rootKey = RSA.Create(KeySize))
using (RSA intermedKey = RSA.Create(KeySize))
using (RSA eeKey = RSA.Create(KeySize))
{
var rootReq = new CertificateRequest(
BuildSubject("A Revocation Test Root", testName, pkiOptions, pkiOptionsInSubject),
rootKey,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);

X509BasicConstraintsExtension caConstraints =
new X509BasicConstraintsExtension(true, false, 0, true);

rootReq.CertificateExtensions.Add(caConstraints);
var rootSkid = new X509SubjectKeyIdentifierExtension(rootReq.PublicKey, false);
rootReq.CertificateExtensions.Add(
rootSkid);

DateTimeOffset start = DateTimeOffset.UtcNow;
DateTimeOffset end = start.AddMonths(3);

// Don't dispose this, it's being transferred to the CertificateAuthority
X509Certificate2 rootCert = rootReq.CreateSelfSigned(start.AddDays(-2), end.AddDays(2));
responder = RevocationResponder.CreateAndListen();

string certUrl = $"{responder.UriPrefix}cert/{rootSkid.SubjectKeyIdentifier}.cer";
string cdpUrl = $"{responder.UriPrefix}crl/{rootSkid.SubjectKeyIdentifier}.crl";
string ocspUrl = $"{responder.UriPrefix}ocsp/{rootSkid.SubjectKeyIdentifier}";

rootAuthority = new CertificateAuthority(
rootCert,
rootDistributionViaHttp ? certUrl : null,
issuerRevocationViaCrl ? cdpUrl : null,
issuerRevocationViaOcsp ? ocspUrl : null);

// Don't dispose this, it's being transferred to the CertificateAuthority
X509Certificate2 intermedCert;

{
X509Certificate2 intermedPub = rootAuthority.CreateSubordinateCA(
BuildSubject("A Revocation Test CA", testName, pkiOptions, pkiOptionsInSubject),
intermedKey);

intermedCert = intermedPub.CopyWithPrivateKey(intermedKey);
intermedPub.Dispose();
}

X509SubjectKeyIdentifierExtension intermedSkid =
intermedCert.Extensions.OfType<X509SubjectKeyIdentifierExtension>().Single();

certUrl = $"{responder.UriPrefix}cert/{intermedSkid.SubjectKeyIdentifier}.cer";
cdpUrl = $"{responder.UriPrefix}crl/{intermedSkid.SubjectKeyIdentifier}.crl";
ocspUrl = $"{responder.UriPrefix}ocsp/{intermedSkid.SubjectKeyIdentifier}";

intermediateAuthority = new CertificateAuthority(
intermedCert,
issuerDistributionViaHttp ? certUrl : null,
endEntityRevocationViaCrl ? cdpUrl : null,
endEntityRevocationViaOcsp ? ocspUrl : null);

X509Extension altName = null;

if (!String.IsNullOrEmpty(subjectName))
{
SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder();
builder.AddDnsName(subjectName);
altName = builder.Build();
}

endEntityCert = intermediateAuthority.CreateEndEntity(
BuildSubject(subjectName ?? "A Revocation Test Cert", testName, pkiOptions, pkiOptionsInSubject),
eeKey,
altName);
endEntityCert = endEntityCert.CopyWithPrivateKey(eeKey);
}

if (registerAuthorities)
{
responder.AddCertificateAuthority(rootAuthority);
responder.AddCertificateAuthority(intermediateAuthority);
}
}

private static string BuildSubject(
string cn,
string testName,
PkiOptions pkiOptions,
bool includePkiOptions)
{
if (includePkiOptions)
{
return $"CN=\"{cn}\", O=\"{testName}\", OU=\"{pkiOptions}\"";
}

return $"CN=\"{cn}\", O=\"{testName}\"";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Threading.Tasks;
using System.Web;

namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests
namespace System.Security.Cryptography.X509Certificates.Tests.Common
{
internal sealed class RevocationResponder : IDisposable
{
Expand Down Expand Up @@ -292,7 +292,7 @@ private static void DecodeOcspRequest(

if (!versionReader.TryReadInt32(out int version) || version != 0)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
throw new CryptographicException("ASN1 corrupted data");
}

versionReader.ThrowIfNotEmpty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,6 @@ private static TlsAlertMessage GetAlertMessageFromChain(X509Chain chain)
return TlsAlertMessage.CertificateUnknown;
}

Debug.Fail("GetAlertMessageFromChain was called but none of the chain elements had errors.");
return TlsAlertMessage.BadCertificate;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ namespace System.Net.Security.Tests

public class SslStreamNetworkStreamTest
{
private readonly X509Certificate2 _serverCert;
private readonly X509CertificateCollection _serverChain;

public SslStreamNetworkStreamTest()
{
(_serverCert, _serverChain) = TestHelper.GenerateCertificates("localhost");
}

[Fact]
public async Task SslStream_SendReceiveOverNetworkStream_Ok()
{
Expand Down Expand Up @@ -261,6 +269,69 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
}
}

[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
public async Task SslStream_UntrustedCaWithCustomCallback_OK()
{
var options = new SslClientAuthenticationOptions() { TargetHost = "localhost" };
options.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) =>
{
chain.ChainPolicy.ExtraStore.AddRange(_serverChain);
chain.ChainPolicy.CustomTrustStore.Add(_serverChain[_serverChain.Count -1]);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;

bool result = chain.Build((X509Certificate2)certificate);
Assert.True(result);

return result;
};

(Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
using (clientStream)
using (serverStream)
using (SslStream client = new SslStream(clientStream))
using (SslStream server = new SslStream(serverStream))
{
Task t1 = client.AuthenticateAsClientAsync(options, default);
Task t2 = server.AuthenticateAsServerAsync(_serverCert);

await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
}
}

[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
public async Task SslStream_UntrustedCaWithCustomCallback_Throws()
{
var options = new SslClientAuthenticationOptions() { TargetHost = "localhost" };
options.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) =>
{
chain.ChainPolicy.ExtraStore.AddRange(_serverChain);
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.
Assert.True(chain.Build((X509Certificate2)certificate));
// Reject it in custom callback to simulate for example pinning.
return false;
};

(Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
using (clientStream)
using (serverStream)
using (SslStream client = new SslStream(clientStream))
using (SslStream server = new SslStream(serverStream))
{
Task t1 = client.AuthenticateAsClientAsync(options, default);
Task t2 = server.AuthenticateAsServerAsync(_serverCert);

await Assert.ThrowsAsync<AuthenticationException>(() => t1);
// Server side should finish since we run custom callback after handshake is done.
await t2;
}
}

private static bool ValidateServerCertificate(
object sender,
X509Certificate retrievedServerPublicCertificate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
Link="Common\System\Net\VirtualNetwork\VirtualNetwork.cs" />
<Compile Include="$(CommonTestPath)System\Net\VirtualNetwork\VirtualNetworkStream.cs"
Link="Common\System\Net\VirtualNetwork\VirtualNetworkStream.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\CertificateAuthority.cs"
Link="CommonTest\System\Security\Cryptography\509Certificates\CertificateAuthority.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\RevocationResponder.cs"
Link="CommonTest\System\Security\Cryptography\509Certificates\RevocationResponder.cs" />
<Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs"
Link="Common\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Expand Down
Loading