Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ease of use helpers in X.509 #72500

Merged
merged 4 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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());
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
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,7 @@ 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));
}

[Fact]
Expand All @@ -89,6 +94,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 +140,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 +165,7 @@ private static void EncodeDecode(

ext = new X509SubjectKeyIdentifierExtension(new AsnEncodedData(rawData), critical);
Assert.Equal(expectedIdentifier, ext.SubjectKeyIdentifier);
Assert.Equal(expectedIdentifier, Convert.ToHexString(ext.SubjectKeyIdentifierBytes.Span));
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
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 @@ -43,6 +43,7 @@ public static void TestSerialBytes()
Assert.Equal(expectedSerialBytes, serial);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2595,6 +2595,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 @@ -2632,6 +2634,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 @@ -3149,6 +3152,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,20 @@ public override void CopyFrom(AsnEncodedData asnEncodedData)
_decoded = false;
}

public static X509BasicConstraintsExtension CreateForCertificateAuthority(int? pathLengthConstraint = null)
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
{
return new X509BasicConstraintsExtension(
true,
pathLengthConstraint.HasValue,
pathLengthConstraint.GetValueOrDefault(),
true);
}

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();
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
}
}

public virtual string GetSerialNumberString()
{
ThrowIfInvalid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ namespace System.Security.Cryptography.X509Certificates
{
public sealed class X509SubjectKeyIdentifierExtension : X509Extension
{
private byte[]? _subjectKeyIdentifierBytes;
private string? _subjectKeyIdentifierString;
private bool _decoded;

public X509SubjectKeyIdentifierExtension()
: base(Oids.SubjectKeyIdentifierOid)
{
_subjectKeyIdentifier = null;
_decoded = true;
}

Expand Down Expand Up @@ -51,12 +54,26 @@ public string? SubjectKeyIdentifier
{
if (!_decoded)
{
byte[] subjectKeyIdentifierValue;
X509Pal.Instance.DecodeX509SubjectKeyIdentifierExtension(RawData, out subjectKeyIdentifierValue);
_subjectKeyIdentifier = subjectKeyIdentifierValue.ToHexStringUpper();
_decoded = true;
Decode(RawData);
}

return _subjectKeyIdentifierString;
}
}

public ReadOnlyMemory<byte> SubjectKeyIdentifierBytes
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
{
get
{
// Rather than check _decoded, this property checks for a null _subjectKeyIdentifierBytes so that
// usingthe default constructor, not calling CopyFrom, and then calling this property will throw
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
// instead of using Nullable to talk about that degenerate state.
if (_subjectKeyIdentifierBytes is null)
{
Decode(RawData);
}
return _subjectKeyIdentifier;

return _subjectKeyIdentifierBytes;
}
}

Expand All @@ -66,6 +83,13 @@ public override void CopyFrom(AsnEncodedData asnEncodedData)
_decoded = false;
}

private void Decode(byte[] rawData)
{
X509Pal.Instance.DecodeX509SubjectKeyIdentifierExtension(rawData, out _subjectKeyIdentifierBytes);
_subjectKeyIdentifierString = _subjectKeyIdentifierBytes.ToHexStringUpper();
_decoded = true;
}

private static byte[] EncodeExtension(ReadOnlySpan<byte> subjectKeyIdentifier)
{
if (subjectKeyIdentifier.Length == 0)
Expand Down Expand Up @@ -121,8 +145,5 @@ private static byte[] GenerateSubjectKeyIdentifierFromPublicKey(PublicKey key, X
throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, algorithm), nameof(algorithm));
}
}

private string? _subjectKeyIdentifier;
private bool _decoded;
}
}