Skip to content

Commit

Permalink
Add ease of use helpers in X.509
Browse files Browse the repository at this point in the history
  • Loading branch information
bartonjs authored Jul 21, 2022
1 parent 5044106 commit 0a7d601
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ internal void Revoke(X509Certificate2 certificate, DateTimeOffset revocationTime
_revocationList = new List<(byte[], DateTimeOffset)>();
}

byte[] serial = certificate.GetSerialNumber();
Array.Reverse(serial);
byte[] serial = certificate.SerialNumberBytes.ToArray();
_revocationList.Add((serial, revocationTime));
_crl = null;
}
Expand Down Expand Up @@ -219,8 +218,7 @@ private void RebuildRootWithRevocation(X509Extension cdpExtension, X509Extension
req.CertificateExtensions.Add(cdpExtension);
req.CertificateExtensions.Add(aiaExtension);

byte[] serial = _cert.GetSerialNumber();
Array.Reverse(serial);
byte[] serial = _cert.SerialNumberBytes.ToArray();

X509Certificate2 dispose = _cert;

Expand Down Expand Up @@ -762,9 +760,7 @@ private X509Extension CreateAkidExtension()
}

// authorityCertSerialNumber [2] CertificateSerialNumber (INTEGER)
byte[] serial = _cert.GetSerialNumber();
Array.Reverse(serial);
writer.WriteInteger(serial, s_context2);
writer.WriteInteger(_cert.SerialNumberBytes.Span, s_context2);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,19 @@ public static void X509CertTest()
Assert.Equal(certSubjectObsolete, cert.GetIssuerName());
#pragma warning restore CS0618

int snlen = cert.GetSerialNumber().Length;
Assert.Equal(16, snlen);

byte[] serialNumber = new byte[snlen];
Buffer.BlockCopy(cert.GetSerialNumber(), 0,
serialNumber, 0,
snlen);

Assert.Equal(0xF6, serialNumber[0]);
Assert.Equal(0xB3, serialNumber[snlen / 2]);
Assert.Equal(0x2A, serialNumber[snlen - 1]);

byte[] serial1 = cert.GetSerialNumber();
byte[] serial2 = cert.GetSerialNumber();
Assert.NotSame(serial1, serial2);
AssertExtensions.SequenceEqual(serial1, serial2);

// Big-endian value
byte[] expectedSerial = "2A98A8770374E7B34195EBE04D9B17F6".HexToByteArray();
AssertExtensions.SequenceEqual(expectedSerial, cert.SerialNumberBytes.Span);

// GetSerialNumber() returns in little-endian order.
Array.Reverse(expectedSerial);
AssertExtensions.SequenceEqual(expectedSerial, serial1);

Assert.Equal("1.2.840.113549.1.1.1", cert.GetKeyAlgorithm());

int pklen = cert.GetPublicKey().Length;
Expand Down Expand Up @@ -377,6 +378,7 @@ public static void UseAfterDispose()
Assert.ThrowsAny<CryptographicException>(() => c.GetKeyAlgorithmParametersString());
Assert.ThrowsAny<CryptographicException>(() => c.GetPublicKey());
Assert.ThrowsAny<CryptographicException>(() => c.GetSerialNumber());
Assert.ThrowsAny<CryptographicException>(() => c.SerialNumberBytes);
Assert.ThrowsAny<CryptographicException>(() => c.Issuer);
Assert.ThrowsAny<CryptographicException>(() => c.Subject);
Assert.ThrowsAny<CryptographicException>(() => c.NotBefore);
Expand Down Expand Up @@ -433,6 +435,7 @@ public static void X509Certificate2WithT61String()
Assert.Equal(certSubject, cert.Issuer);

Assert.Equal("9E7A5CCC9F951A8700", cert.GetSerialNumber().ByteArrayToHex());
Assert.Equal("00871A959FCC5C7A9E", cert.SerialNumberBytes.ByteArrayToHex());
Assert.Equal("1.2.840.113549.1.1.1", cert.GetKeyAlgorithm());

Assert.Equal(74, cert.GetPublicKey().Length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ private static void VerifyDefaultConstructor(X509Certificate2 c)
Assert.ThrowsAny<CryptographicException>(() => c.GetKeyAlgorithmParametersString());
Assert.ThrowsAny<CryptographicException>(() => c.GetPublicKey());
Assert.ThrowsAny<CryptographicException>(() => c.GetSerialNumber());
Assert.ThrowsAny<CryptographicException>(() => c.SerialNumberBytes);
Assert.ThrowsAny<CryptographicException>(() => ignored = c.Issuer);
Assert.ThrowsAny<CryptographicException>(() => ignored = c.Subject);
Assert.ThrowsAny<CryptographicException>(() => ignored = c.RawData);
Expand Down Expand Up @@ -155,6 +156,7 @@ public static void TestCopyConstructor_Pal()
Assert.Equal(c1.GetKeyAlgorithmParametersString(), c2.GetKeyAlgorithmParametersString());
Assert.Equal(c1.GetPublicKey(), c2.GetPublicKey());
Assert.Equal(c1.GetSerialNumber(), c2.GetSerialNumber());
AssertExtensions.SequenceEqual(c1.SerialNumberBytes.Span, c1.SerialNumberBytes.Span);
Assert.Equal(c1.Issuer, c2.Issuer);
Assert.Equal(c1.Subject, c2.Subject);
Assert.Equal(c1.RawData, c2.RawData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ public static void Encode(

byte[] expectedDer = expectedDerString.HexToByteArray();
Assert.Equal(expectedDer, ext.RawData);
Assert.Equal(critical, ext.Critical);

if (certificateAuthority)
{
ext = X509BasicConstraintsExtension.CreateForCertificateAuthority(
hasPathLengthConstraint ? pathLengthConstraint : null);

AssertExtensions.SequenceEqual(expectedDer, ext.RawData);
Assert.True(ext.Critical, "ext.Critical");
}
else if (!hasPathLengthConstraint)
{
ext = X509BasicConstraintsExtension.CreateForEndEntity(critical);

AssertExtensions.SequenceEqual(expectedDer, ext.RawData);
Assert.Equal(critical, ext.Critical);
}
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public static void DefaultConstructor()

string skid = e.SubjectKeyIdentifier;
Assert.Null(skid);

Assert.Throws<CryptographicException>(() => e.SubjectKeyIdentifierBytes);
}

[Theory]
Expand Down Expand Up @@ -55,6 +57,8 @@ public static void EncodeFromBytes(bool fromSpan)

string skid = e.SubjectKeyIdentifier;
Assert.Equal("01020304", skid);

AssertExtensions.SequenceEqual(sk, e.SubjectKeyIdentifierBytes.Span);
}

[Fact]
Expand All @@ -69,6 +73,11 @@ public static void EncodeFromString()
e = new X509SubjectKeyIdentifierExtension(new AsnEncodedData(rawData), false);
string skid = e.SubjectKeyIdentifier;
Assert.Equal("01ABCD", skid);
Assert.Equal(skid, Convert.ToHexString(e.SubjectKeyIdentifierBytes.Span));

ReadOnlyMemory<byte> ski1 = e.SubjectKeyIdentifierBytes;
ReadOnlyMemory<byte> ski2 = e.SubjectKeyIdentifierBytes;
Assert.True(ski1.Span == ski2.Span, "Two calls to SubjectKeyIdentifierBytes return the same buffer");
}

[Fact]
Expand All @@ -89,6 +98,7 @@ public static void EncodeFromPublicKey()
e = new X509SubjectKeyIdentifierExtension(new AsnEncodedData(rawData), false);
string skid = e.SubjectKeyIdentifier;
Assert.Equal("5971A65A334DDA980780FF841EBE87F9723241F2", skid);
Assert.Equal(skid, Convert.ToHexString(e.SubjectKeyIdentifierBytes.Span));
}

[Fact]
Expand Down Expand Up @@ -134,8 +144,9 @@ public static void DecodeFromBER()
ext = new X509SubjectKeyIdentifierExtension(new AsnEncodedData(rawData), false);
string skid = ext.SubjectKeyIdentifier;
Assert.Equal("5971A65A334DDA980780FF841EBE87F9723241F2", skid);
Assert.Equal(skid, Convert.ToHexString(ext.SubjectKeyIdentifierBytes.Span));
}

private static void EncodeDecode(
byte[] certBytes,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
Expand All @@ -158,6 +169,11 @@ private static void EncodeDecode(

ext = new X509SubjectKeyIdentifierExtension(new AsnEncodedData(rawData), critical);
Assert.Equal(expectedIdentifier, ext.SubjectKeyIdentifier);
Assert.Equal(expectedIdentifier, Convert.ToHexString(ext.SubjectKeyIdentifierBytes.Span));

ReadOnlyMemory<byte> ski1 = ext.SubjectKeyIdentifierBytes;
ReadOnlyMemory<byte> ski2 = ext.SubjectKeyIdentifierBytes;
Assert.True(ski1.Span == ski2.Span, "Two calls to SubjectKeyIdentifierBytes return the same buffer");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public static void TestSerial()
Assert.Equal(expectedSerialHex, serialHex);
serialHex = c.SerialNumber;
Assert.Equal(expectedSerialHex, serialHex);

Assert.Equal(expectedSerialHex, c.SerialNumberBytes.ByteArrayToHex());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,35 @@ public static void TestSubject()
public static void TestSerialBytes()
{
byte[] expectedSerialBytes = "b00000000100dd9f3bd08b0aaf11b000000033".HexToByteArray();
string expectedSerialString = "33000000B011AF0A8BD03B9FDD0001000000B0";
const string ExpectedSerialString = "33000000B011AF0A8BD03B9FDD0001000000B0";

using (var c = new X509Certificate2(TestData.MsCertificate))
{
byte[] serial = c.GetSerialNumber();
Assert.Equal(expectedSerialBytes, serial);

Assert.Equal(expectedSerialString, c.SerialNumber);
Assert.Equal(ExpectedSerialString, c.SerialNumber);
Assert.Equal(ExpectedSerialString, c.SerialNumberBytes.ByteArrayToHex());

ReadOnlyMemory<byte> serial1 = c.SerialNumberBytes;
ReadOnlyMemory<byte> serial2 = c.SerialNumberBytes;
Assert.True(serial1.Span == serial2.Span, "Two calls to SerialNumberBytes return the same buffer");
}
}

[Fact]
public static void SerialNumberBytes_LifetimeIndependentOfCert()
{
const string ExpectedSerialString = "33000000B011AF0A8BD03B9FDD0001000000B0";

ReadOnlyMemory<byte> serial;

using (X509Certificate2 cert = new X509Certificate2(TestData.MsCertificate))
{
serial = cert.SerialNumberBytes;
}

Assert.Equal(ExpectedSerialString, serial.ByteArrayToHex());
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2614,6 +2614,8 @@ public X509BasicConstraintsExtension(System.Security.Cryptography.AsnEncodedData
public bool HasPathLengthConstraint { get { throw null; } }
public int PathLengthConstraint { get { throw null; } }
public override void CopyFrom(System.Security.Cryptography.AsnEncodedData asnEncodedData) { }
public static System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension CreateForCertificateAuthority(int? pathLengthConstraint = default(int?)) { throw null; }
public static System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension CreateForEndEntity(bool critical = false) { throw null; }
}
public partial class X509Certificate : System.IDisposable, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable
{
Expand Down Expand Up @@ -2651,6 +2653,7 @@ public X509Certificate(string fileName, string? password) { }
public X509Certificate(string fileName, string? password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags) { }
public System.IntPtr Handle { get { throw null; } }
public string Issuer { get { throw null; } }
public System.ReadOnlyMemory<byte> SerialNumberBytes { get { throw null; } }
public string Subject { get { throw null; } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public static System.Security.Cryptography.X509Certificates.X509Certificate CreateFromCertFile(string filename) { throw null; }
Expand Down Expand Up @@ -3178,6 +3181,7 @@ public X509SubjectKeyIdentifierExtension(System.Security.Cryptography.X509Certif
public X509SubjectKeyIdentifierExtension(System.Security.Cryptography.X509Certificates.PublicKey key, System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierHashAlgorithm algorithm, bool critical) { }
public X509SubjectKeyIdentifierExtension(string subjectKeyIdentifier, bool critical) { }
public string? SubjectKeyIdentifier { get { throw null; } }
public System.ReadOnlyMemory<byte> SubjectKeyIdentifierBytes { get { throw null; } }
public override void CopyFrom(System.Security.Cryptography.AsnEncodedData asnEncodedData) { }
}
public enum X509SubjectKeyIdentifierHashAlgorithm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,50 @@ public override void CopyFrom(AsnEncodedData asnEncodedData)
_decoded = false;
}

/// <summary>
/// Creates an instance of <see cref="X509BasicConstraintsExtension"/> appropriate for
/// a certificate authority, optionally including a path length constraint value.
/// </summary>
/// <param name="pathLengthConstraint">
/// The longest valid length of a certificate chain between the certificate containing
/// this extension and an end-entity certificate.
/// The default is <see langword="null" />, an unconstrained length.
/// </param>
/// <returns>
/// The configured basic constraints extension.
/// </returns>
/// <remarks>
/// Following the guidance from IETF RFC 3280, the extension returned from this method
/// will have the <see cref="X509Extension.Critical"/> property set to <see langword="true" />.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="pathLengthConstraint"/> is a non-null value less than zero.
/// </exception>
public static X509BasicConstraintsExtension CreateForCertificateAuthority(int? pathLengthConstraint = null)
{
return new X509BasicConstraintsExtension(
true,
pathLengthConstraint.HasValue,
pathLengthConstraint.GetValueOrDefault(),
true);
}

/// <summary>
/// Creates an instance of <see cref="X509BasicConstraintsExtension"/> appropriate for
/// an end-entity certificate, optionally marking the extension as critical.
/// </summary>
/// <param name="critical">
/// <see langword="true" /> to mark the extension as critical; <see langword="false" /> otherwise.
/// The default is <see langword="false" />.
/// </param>
/// <returns>
/// The configured basic constraints extension.
/// </returns>
public static X509BasicConstraintsExtension CreateForEndEntity(bool critical = false)
{
return new X509BasicConstraintsExtension(false, false, 0, critical);
}

private static byte[] EncodeExtension(bool certificateAuthority, bool hasPathLengthConstraint, int pathLengthConstraint)
{
if (hasPathLengthConstraint && pathLengthConstraint < 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,21 @@ public virtual byte[] GetSerialNumber()
return serialNumber;
}

/// <summary>
/// Gets a value whose contents represent the big-endian representation of the
/// certificate's serial number.
/// </summary>
/// <value>The big-endian representation of the certificate's serial number.</value>
public ReadOnlyMemory<byte> SerialNumberBytes
{
get
{
ThrowIfInvalid();

return GetRawSerialNumber();
}
}

public virtual string GetSerialNumberString()
{
ThrowIfInvalid();
Expand Down
Loading

0 comments on commit 0a7d601

Please sign in to comment.