Hello S-1-1-0, @Crypt32 is again on a failboatboard with new blog post. Today I will share information about a little-known portion in configuration of Microsoft ADCS Certification Authority – serial number generation algorithm.

This article assumes big-endian encoding

Certificate serial number requirements

Every X.509 conforming CA generates a unique serial number for each issued certificate, which in fact is a long integer. As per RFC 5280 §4.1.2.2, serial numbers MUST be unique, not greater than 20 bytes long non-negative integer and at least 1 bit must be enabled in first byte. If first byte is zero, this byte is truncated unless it is the only byte. This means the first byte must be in range 0x0÷0x7f with a whole integer value up to 20 bytes. 0x00 0x00 0x01 serial number is truncated to 0x01. Various CA engines implement different serial number generation algorithms. Some allow generation of sequential serial numbers starting with 0x00, 0x01, 0x02, etc. (excluding 0x80÷0xff range for most significant byte), others may use cryptographically random serial numbers, GUID-based or custom conforming algorithm.

Microsoft ADCS Certification Authority implementation

I found only one Microsoft article that talks about this topic: Configure Serial Number Generation. However I found this article hard to understand and it misses some interesting details. In this post, I’m outlining all information I was able to get from different sources, including Microsoft in a more clear way.

Serial number configuration is stored in registry

HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\CAName\HighSerial

and it is a REG_DWORD value by default. In certain cases its type is changed to REG_SZ (string). Default value is 0.

Option 1: HighSerial = REG_DWORD:0x0

This option enables default serial number algorithm. Generated serial numbers are 10 (20 hex chars) bytes long:

Length Description
4 GetTickCount() from system time
2 Zero-based CA certificate index
4 RequestID from database

Example: 15 fb a3 bb 00 00 00 00 00 02

Option 2: HighSerial = REG_DWORD:0x1÷0x7f

This option adds cryptographic random component inside serial number. Generated serial numbers are 19 (38 hex chars) bytes long:

Length Description
1 Registry value of HighSerial
4 RequestID from database
8 CryptGenRandom()
2 Zero-based CA certificate index
4 RequestID from database

If registry value is in range 0x80÷0xfffffffe, only least-significant byte is accounted and is inserted as first byte of serial number. Sign bit is set to 0 (to force positive integer). If HighSerial is between 0x01-0x0f, random bits are inserted in most-significant 2-4 bits. See remarks section for more details.

Example: 11 00 00 01 10 2e cf 52 a1 b2 24 d1 aa 00 00 00 00 00 02

Option 3: HighSerial = REG_DWORD:0xFFFFFFFF

Similar to default option where first component (GetTickCount()) is replaced with CryptGenRandom() function call. Generated serial numbers are 19 (38 hex chars) bytes long:

Length Description
8 CryptGenRandom()
2 Zero-based CA certificate index
4 RequestID from database

When the CA attempts to generate its first serial number, 8 bytes of CryptGenRandom() data are produced and the registry is rewritten as a REG_SZ value to save the random data as a constant hex string. See Option 4 for more details.

Example: 21 71 fa f5 14 f6 85 37 00 00 00 00 00 02

Option 4: HighSerial = REG_SZ:OCTET_STRING

This option uses REG_SZ value type instead of REG_DWORD. Generated serial numbers are from 7 to 19 bytes long depending on a number of octets in HighSerial registry value. Since value data is octet string it must have even number of hex digits.

Length Description
1÷13 HighSerial registry data
2 Zero-based CA certificate index
4 RequestID from database

If HighSerial registry value exceeds 13 octets, value is truncated to least-significant 13 bytes and truncated digits are ignored.

Example: 11 02 03 04 05 06 07 08 09 00 00 00 00 01 12

Remarks

Remark 1

In all cases, the high byte of the serial number is manipulated to always have the sign bit clear, and to always have some bits set in the high nibble for compatibility with various PKI implementations with limited robustness. This means that:

  • if first byte of serial number is in range 0x80÷0xff, the most-significant bit is set to 0.
  • if first byte is 0x0 (all bits are zeroes), arbitrary bits are inserted to make it non-zero
  • at least one bit is set in most-significant 2-4 bits of first byte to avoid any kind of 0x0* pattern for the first byte

Remark 2

It is evident that ADCS uses database Request ID as a part of serial number. By reading last 4 bytes, you can estimate the size (issued certificate volume) of ADCS CA. When you delete rows from CA database, deleted request IDs are never re-used, thus inclusion of Request ID in serial number guarantees its uniqueness for a particular CA. When a CA reaches 2,147,483,647 issued certificates, the CA will die even if all previously issued certificates are deleted from its database. Request ID is never used twice.

Remark 3

I’ve received the same question several times on whether it is possible to configure truly sequential serial numbers (starting with 01 and up) with Microsoft ADCS. The answer is NO, it is not possible.

3 Comments

  1. Avatar Marke on November 6, 2020 at 1:51 pm

    As usual, great deep dive article, thank you!

    I was looking for an answer on your website how to address the challenge.
    Auto-enrollment is enabled for a particular template of the certificate.
    Everything works correctly, users are not able to “export private key” as on the template has not been selected the option “Allow private key to be exported”.
    But… the user can request the certificate again manually, and can change the option to Allow private key to be exported.
    How to prevent users to manually request the certificate and select the option Allow private key to be exported?

    Thank you for your articles,
    Cheers!

    • ThePKIGuy ThePKIGuy on November 6, 2020 at 1:55 pm

      This can not be achieved. The private key exportable option is defined on the client and the CA has no way to enforce it. During AutoEnrollment Windows will use whatever is defined in the template. But when a user performs an enrollment manually, they can override anything that the CA isn’t able to see (they couldn’t change the keysize for instance as the CA sees the Public Key). The only way you can ever enforce this is to use Key Attestation and ensure the key is generated in a device that won’t allow the key to be exported. By the way, this checkbox/setting provides very little security. There are many programs out there that can be easily downloaded and export a key even if it is not marked as exportable.

      • Avatar Marke on November 6, 2020 at 2:53 pm

        Thank you very much Mark.
        So the only way is to teach users to do not do this and mention about this in the security policies/procedures.

Leave a Comment





This site uses Akismet to reduce spam. Learn how your comment data is processed.