Search code examples
c#certificateserial-number

Why does CertificateRequest.Create add a leading zero byte to the serial number sometimes?


Example:

//generate specific serial number for testing purposes
byte[] result = "A0-41-A1-32-BD-9E-58-98-4B-CC-9E-E6-27-17-B4-10"
   .Split('-')
   .Select(item => Convert.ToByte(item, 16))
   .ToArray();

using (X509Certificate2 cert = certRequest.Create(
        caCert,
        DateTimeOffset.UtcNow.AddDays(-1),
        DateTimeOffset.UtcNow.AddYears(50),
        serialNumber))
    {
        // display serial number for testing purposes
        Console.WriteLine(cert.SerialNumber);
    }

Input serial number: "A0-41-A1-32-BD-9E-58-98-4B-CC-9E-E6-27-17-B4-10"

Certificates actual serial number: "00-A0-41-A1-32-BD-9E-58-98-4B-CC-9E-E6-27-17-B4-10"

Expected serial number to be the same as the input without a leading zero byte.

Method doc: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.certificaterequest.create?view=netframework-4.7.2


Solution

  • https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.2

    The serial number MUST be a positive integer assigned by the CA to each certificate. It MUST be unique for each certificate issued by a given CA (i.e., the issuer name and serial number identify a unique certificate). CAs MUST force the serialNumber to be a non-negative integer. ...

    (emphasis mine)

    The serial number is stored as a DER integer, which is a minimum bytes signed big-endian value.

    • { 0x79 } is positive
    • { 0x80 } is negative
    • { 0x00, 0x80 } is positive

    If you give it a value where serialNumber[0] > 0 && serialNumber[0] < 0x80 then the bytes are left as-is.

    If you give it a value where serialNumber[0] == 0 && serialNumber[1] < 0x79 then the leading byte is removed (repeat until you hit the value { 0x00 } or the point where the zero is necessary).

    These two statements are (slightly obliquely) embedded in The value is interpreted as an unsigned integer of arbitrary size in big-endian byte ordering.

    To personify the method, it says "I was given this large value, 0xA041A132BD9E58984BCC9EE62717B410, which I know to be large positive number. Now I need to write it down as a DER integer. The equivalent byte stream is { 0x00, 0xA0, 0x41, 0xA1, 0x32, 0xBD, 0x9E, 0x58, 0x98, 0x4B, 0xCC, 0x9E, 0xE6, 0x27, 0x17, 0xB4, 0x10 }. Done."