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 @@ -166,7 +166,8 @@ static void MergeStatusCodes(CFTypeRef key, CFTypeRef value, void* context)
*pStatus |= PAL_X509ChainNotValidForUsage;
else if (CFEqual(keyString, CFSTR("AnchorTrusted")))
*pStatus |= PAL_X509ChainUntrustedRoot;
else if (CFEqual(keyString, CFSTR("BasicConstraints")))
else if (CFEqual(keyString, CFSTR("BasicConstraints")) || CFEqual(keyString, CFSTR("BasicConstraintsCA")) ||
CFEqual(keyString, CFSTR("BasicConstraintsPathLen")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this means that we need more tests for this status code. Can you add them in DynamicChainTests along with this PR?

*pStatus |= PAL_X509ChainInvalidBasicConstraints;
else if (CFEqual(keyString, CFSTR("UsageConstraints")))
*pStatus |= PAL_X509ChainExplicitDistrust;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,86 @@ void CheckChain()
}
}

[Fact]
public static void BasicConstraints_ExceedMaximumPathLength()
{
X509Extension[] rootExtensions = new [] {
new X509BasicConstraintsExtension(
certificateAuthority: true,
hasPathLengthConstraint: true,
pathLengthConstraint: 0,
critical: true)
};

X509Extension[] intermediateExtensions = new [] {
new X509BasicConstraintsExtension(
certificateAuthority: true,
hasPathLengthConstraint: true,
pathLengthConstraint: 0,
critical: true)
};

TestDataGenerator.MakeTestChain4(
out X509Certificate2 endEntityCert,
out X509Certificate2 intermediateCert1,
out X509Certificate2 intermediateCert2,
out X509Certificate2 rootCert,
rootExtensions: rootExtensions,
intermediateExtensions: intermediateExtensions);

using (endEntityCert)
using (intermediateCert1)
using (intermediateCert2)
using (rootCert)
using (ChainHolder chainHolder = new ChainHolder())
{
X509Chain chain = chainHolder.Chain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(rootCert);
chain.ChainPolicy.ExtraStore.Add(intermediateCert1);
chain.ChainPolicy.ExtraStore.Add(intermediateCert2);

Assert.False(chain.Build(endEntityCert));
Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags());
}
}

[Fact]
public static void BasicConstraints_ViolatesCaFalse()
{
X509Extension[] intermediateExtensions = new [] {
new X509BasicConstraintsExtension(
certificateAuthority: false,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: true)
};

TestDataGenerator.MakeTestChain3(
out X509Certificate2 endEntityCert,
out X509Certificate2 intermediateCert,
out X509Certificate2 rootCert,
intermediateExtensions: intermediateExtensions);

using (endEntityCert)
using (intermediateCert)
using (rootCert)
using (ChainHolder chainHolder = new ChainHolder())
{
X509Chain chain = chainHolder.Chain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(rootCert);
chain.ChainPolicy.ExtraStore.Add(intermediateCert);

Assert.False(chain.Build(endEntityCert));
Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags());
}
}

[Fact]
public static void TestInvalidAia()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

namespace System.Security.Cryptography.X509Certificates.Tests
{
internal static class TestDataGenerator
{
internal static void MakeTestChain3(
out X509Certificate2 endEntityCert,
out X509Certificate2 intermediateCert,
out X509Certificate2 rootCert)
out X509Certificate2 rootCert,
IEnumerable<X509Extension> endEntityExtensions = null,
IEnumerable<X509Extension> intermediateExtensions = null,
IEnumerable<X509Extension> rootExtensions = null)
{
using (RSA rootKey = RSA.Create())
using (RSA intermediateKey = RSA.Create())
Expand All @@ -23,41 +28,94 @@ internal static void MakeTestChain3(
};

Span<X509Certificate2> certs = new X509Certificate2[keys.Length];
MakeTestChain(keys, certs);
MakeTestChain(
keys,
certs,
endEntityExtensions,
intermediateExtensions,
rootExtensions);

endEntityCert = certs[0];
intermediateCert = certs[1];
rootCert = certs[2];
}
}


internal static void MakeTestChain4(
out X509Certificate2 endEntityCert,
out X509Certificate2 intermediateCert1,
out X509Certificate2 intermediateCert2,
out X509Certificate2 rootCert,
IEnumerable<X509Extension> endEntityExtensions = null,
IEnumerable<X509Extension> intermediateExtensions = null,
IEnumerable<X509Extension> rootExtensions = null)
{
using (RSA rootKey = RSA.Create())
using (RSA intermediateKey = RSA.Create())
using (RSA endEntityKey = RSA.Create())
{
ReadOnlySpan<RSA> keys = new[]
{
rootKey,
intermediateKey,
intermediateKey,
endEntityKey,
};

Span<X509Certificate2> certs = new X509Certificate2[keys.Length];
MakeTestChain(
keys,
certs,
endEntityExtensions,
intermediateExtensions,
rootExtensions);

endEntityCert = certs[0];
intermediateCert1 = certs[1];
intermediateCert2 = certs[2];
rootCert = certs[3];
}
}

internal static void MakeTestChain(
ReadOnlySpan<RSA> keys,
Span<X509Certificate2> certs,
string eeName = "CN=End-Entity",
OidCollection eeEku = null)
IEnumerable<X509Extension> endEntityExtensions,
IEnumerable<X509Extension> intermediateExtensions,
IEnumerable<X509Extension> rootExtensions)
{
if (keys.Length < 2)
throw new ArgumentException(nameof(keys));
if (keys.Length != certs.Length)
throw new ArgumentException(nameof(certs));
if (string.IsNullOrEmpty(eeName))
throw new ArgumentOutOfRangeException(nameof(eeName));

var caUnlimited = new X509BasicConstraintsExtension(true, false, 0, true);
var eeConstraint = new X509BasicConstraintsExtension(false, false, 0, true);

var caUsage = new X509KeyUsageExtension(
X509KeyUsageFlags.CrlSign |
X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.DigitalSignature,
false);

var eeUsage = new X509KeyUsageExtension(
rootExtensions ??= new X509Extension[] {
new X509BasicConstraintsExtension(true, false, 0, true),
new X509KeyUsageExtension(
X509KeyUsageFlags.CrlSign |
X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.DigitalSignature,
false)
};

intermediateExtensions ??= new X509Extension[] {
new X509BasicConstraintsExtension(true, false, 0, true),
new X509KeyUsageExtension(
X509KeyUsageFlags.CrlSign |
X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.DigitalSignature,
false)
};

endEntityExtensions ??= new X509Extension[] {
new X509BasicConstraintsExtension(false, false, 0, true),
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature |
X509KeyUsageFlags.NonRepudiation |
X509KeyUsageFlags.KeyEncipherment,
false);
false)
};

TimeSpan notBeforeInterval = TimeSpan.FromDays(30);
TimeSpan notAfterInterval = TimeSpan.FromDays(90);
Expand All @@ -76,14 +134,20 @@ internal static void MakeTestChain(
hashAlgorithm,
signaturePadding);

rootReq.CertificateExtensions.Add(caUnlimited);
rootReq.CertificateExtensions.Add(caUsage);
foreach (X509Extension extension in rootExtensions)
{
rootReq.CertificateExtensions.Add(extension);
}

X509SignatureGenerator lastGenerator = X509SignatureGenerator.CreateForRSA(keys[rootIndex], RSASignaturePadding.Pkcs1);
X500DistinguishedName lastSubject = rootReq.SubjectName;

X509Certificate2 lastWithKey = rootReq.CreateSelfSigned(
certs[rootIndex] = rootReq.Create(
lastSubject,
lastGenerator,
eeStart - (notBeforeInterval * rootIndex),
eeEnd + (notAfterInterval * rootIndex));

certs[rootIndex] = new X509Certificate2(lastWithKey.RawData);
eeEnd + (notAfterInterval * rootIndex),
CreateSerial());

int presentationNumber = 0;

Expand All @@ -97,41 +161,41 @@ internal static void MakeTestChain(
hashAlgorithm,
signaturePadding);

intermediateReq.CertificateExtensions.Add(caUnlimited);
intermediateReq.CertificateExtensions.Add(caUsage);

// Leave serialBuf[0] as 0 to avoid a realloc in the signer
RandomNumberGenerator.Fill(serialBuf.AsSpan(1));
foreach (X509Extension extension in intermediateExtensions)
{
intermediateReq.CertificateExtensions.Add(extension);
}

certs[i] = intermediateReq.Create(
lastWithKey,
lastSubject,
lastGenerator,
eeStart - (notBeforeInterval * i),
eeEnd + (notAfterInterval * i),
serialBuf);
CreateSerial());

lastWithKey.Dispose();
lastWithKey = certs[i].CopyWithPrivateKey(keys[i]);
lastSubject = intermediateReq.SubjectName;
lastGenerator = X509SignatureGenerator.CreateForRSA(keys[i], RSASignaturePadding.Pkcs1);
}

CertificateRequest eeReq = new CertificateRequest(
eeName,
"CN=End-Entity",
keys[0],
hashAlgorithm,
signaturePadding);

eeReq.CertificateExtensions.Add(eeConstraint);
eeReq.CertificateExtensions.Add(eeUsage);

if (eeEku != null)
foreach (X509Extension extension in endEntityExtensions)
{
eeReq.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(eeEku, false));
eeReq.CertificateExtensions.Add(extension);
}

// Leave serialBuf[0] as 0 to avoid a realloc in the signer
RandomNumberGenerator.Fill(serialBuf.AsSpan(1));
certs[0] = eeReq.Create(lastSubject, lastGenerator, eeStart, eeEnd, CreateSerial());
}

certs[0] = eeReq.Create(lastWithKey, eeStart, eeEnd, serialBuf);
lastWithKey.Dispose();
private static byte[] CreateSerial()
{
byte[] bytes = new byte[8];
RandomNumberGenerator.Fill(bytes);
return bytes;
}
}
}