From d79691ed341e4b0ca2e316d766cc2a5a2c103ae1 Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Fri, 19 Dec 2025 15:28:39 -0700 Subject: [PATCH 1/6] Allow serial number 0 for root CA certificates --- .github/workflows/codespell.yml | 2 +- certs/include.am | 1 + certs/test-serial0/ee_normal.pem | 21 +++++ certs/test-serial0/ee_serial0.pem | 21 +++++ certs/test-serial0/generate_certs.sh | 88 +++++++++++++++++++ certs/test-serial0/include.am | 11 +++ certs/test-serial0/root_serial0.pem | 21 +++++ certs/test-serial0/root_serial0_key.pem | 28 ++++++ .../test-serial0/selfsigned_nonca_serial0.pem | 21 +++++ tests/api.c | 26 +++--- tests/api/test_asn.c | 66 ++++++++++++++ tests/api/test_asn.h | 4 +- wolfcrypt/src/asn.c | 56 +++++++++--- 13 files changed, 335 insertions(+), 31 deletions(-) create mode 100644 certs/test-serial0/ee_normal.pem create mode 100644 certs/test-serial0/ee_serial0.pem create mode 100755 certs/test-serial0/generate_certs.sh create mode 100644 certs/test-serial0/include.am create mode 100644 certs/test-serial0/root_serial0.pem create mode 100644 certs/test-serial0/root_serial0_key.pem create mode 100644 certs/test-serial0/selfsigned_nonca_serial0.pem diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 32351f8334..f6dfdd8acb 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -27,4 +27,4 @@ jobs: # The exclude_file contains lines of code that should be ignored. This is useful for individual lines which have non-words that can safely be ignored. exclude_file: '.codespellexcludelines' # To skip files entirely from being processed, add it to the following list: - skip: '*.cproject,*.der,*.mtpj,*.pem,*.vcxproj,.git,*.launch,*.scfg,*.revoked,./examples/asn1/dumpasn1.cfg,./examples/asn1/oid_names.h' + skip: '*.cproject,*.csr,*.der,*.mtpj,*.pem,*.vcxproj,.git,*.launch,*.scfg,*.revoked,./examples/asn1/dumpasn1.cfg,./examples/asn1/oid_names.h' diff --git a/certs/include.am b/certs/include.am index b19881d31f..8d7089c370 100644 --- a/certs/include.am +++ b/certs/include.am @@ -155,6 +155,7 @@ include certs/ocsp/include.am include certs/statickeys/include.am include certs/test/include.am include certs/test-pathlen/include.am +include certs/test-serial0/include.am include certs/intermediate/include.am include certs/falcon/include.am include certs/rsapss/include.am diff --git a/certs/test-serial0/ee_normal.pem b/certs/test-serial0/ee_normal.pem new file mode 100644 index 0000000000..75ff1796cf --- /dev/null +++ b/certs/test-serial0/ee_normal.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDeDCCAmCgAwIBAgIBZDANBgkqhkiG9w0BAQsFADBEMR4wHAYDVQQDDBVUZXN0 +IFJvb3QgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDELMAkGA1UE +BhMCVVMwHhcNMjYwMzE5MjA0NjM2WhcNMzYwMzE2MjA0NjM2WjBAMRowGAYDVQQD +DBFFbmQgRW50aXR5IE5vcm1hbDEVMBMGA1UECgwMd29sZlNTTCBUZXN0MQswCQYD +VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdhKjg6wGtt +Ivkud3c5BHbytPU1OyvguGwbvoqJszxz7U8ibiEE9k7MHtvrc7vh+mYHhl6py/Z7 +9U9BGvS5dQi6MFUSbc1PR5y/mnYbN3/TdwSIv+/faJzvbru6C6fIe8dXo1wSIUyQ +dxP2JbUAERwVsPylIQZypYjJi8E3ku/chJmNLUUfAgal23LQdT6KbYP5kErAMqLt +5z7flt/fNouiWisk8Cf7DuPVA2fOHkwmq7prqkut0MF5N6DtEw0OWXpHsZw0JDlj +a8lKVFZ7hkIfl0m7Ij3pXUjRQ0SMd2CfWao5yOW5X0hHCcepnRQJJw+Hi6ZZjra9 +b370bbfT82ECAwEAAaN5MHcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTSS9S9Yn+wHfdPhvEd +NVQzSE/1sDAfBgNVHSMEGDAWgBRh4Nck2fNzW7ljbZ7aY0JNA1WHwDANBgkqhkiG +9w0BAQsFAAOCAQEAG72+EoRdjiudzxfP2SvJ7p1o493NOGhQXjDCZyxyjBkP5s+A +rEbyjA2QEJU3vl/wx5fuxhbcyhSiBUhq8gPjLFkahKHGdDouMhB+b4VXi0sU+HmC +p0DFDckn0YAuejDuzkiBP9DFLmXBhL6xniLtDujEY6k6gXf+nEO3cTj5gf0Zofrr +I4s3sn+kYzp0ltrA3bLAsJD+SSVM6sRPZJSa9IKqyInlJDqyh1CG6U6Dk8TTj4Tr +V3l1KwxiECev0CFul+J7OZE0fdkcEkscZ7ySrsA5cHiqtB9xzcCWMRMR/LYjcUDo +R6o7yosbg6SzoDO78VUImqtwQhKXrvDy7kMwPw== +-----END CERTIFICATE----- diff --git a/certs/test-serial0/ee_serial0.pem b/certs/test-serial0/ee_serial0.pem new file mode 100644 index 0000000000..4d955150fc --- /dev/null +++ b/certs/test-serial0/ee_serial0.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDejCCAmKgAwIBAgIBADANBgkqhkiG9w0BAQsFADBEMR4wHAYDVQQDDBVUZXN0 +IFJvb3QgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDELMAkGA1UE +BhMCVVMwHhcNMjYwMzE5MjA0NjM2WhcNMzYwMzE2MjA0NjM2WjBCMRwwGgYDVQQD +DBNFbmQgRW50aXR5IFNlcmlhbCAwMRUwEwYDVQQKDAx3b2xmU1NMIFRlc3QxCzAJ +BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzQSr4Wfu +rt6+d27Y3Nr9+W8Yxy60KrIhT512OX275xfdCDMzzEC/w6ECIeKHR5PopjmzCvUx ++j0GaQ5vrPmhoVHvBfgWnYNrZvlxRq8ZCHqyQ15Emd44n/14W91dA4n2rqLUXvuF +D2vHX1tQ7D1ZyjkUkJOeRypni2JdFhsUBE1e7WGFFYBUJY6TMPugKAM/jIPk5C0E +h7TJmQDCQNfmbxF8BVboDKu1riVqiwQ3T+3RLQoaNL4/C3MRhfKKLXafuX4dt4n+ +8mZ9ATqNPycbRGwa01JNpB3wzHecSeRjlBiY16Aahr+R4vCnXnbslfjI8MM7Q/oU +bLV+mjnt9mP/OQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFDfbdXod3raDcV6l +4WVRx3AYleBpMB8GA1UdIwQYMBaAFGHg1yTZ83NbuWNtntpjQk0DVYfAMA0GCSqG +SIb3DQEBCwUAA4IBAQCBooXTFwajQmHYPLnyeBgfUpdSchiPCUuXj/31QyhVZ4NE +6tdlPyFTA8f+SE1sXxIYElF+CAh6mMqKz5pFxpkjjWBMEG7IIliFpQfmgftJDthW +f+R3MD1WlNdrMLmQm/MIv95CGCMqaClX4SkN0N75FSdVT0lDv2Ly1OOYcVmTawN0 +MeRgTdJy1qhAhbhncBTKRXBpk+dydOp1RClmoKI0nUGL6x4NUcjNPoIb29lu2MLh +KqjfC8Gy4Z7z00lGPbGvzMlBjRP0qJ8h1ENPjUbPUY56/i0azh+h0QQedLudeN5+ +uYn0tWZDeLq932X8gNWKuJQlM1mky1y+1RB/e0F2 +-----END CERTIFICATE----- diff --git a/certs/test-serial0/generate_certs.sh b/certs/test-serial0/generate_certs.sh new file mode 100755 index 0000000000..047b21703c --- /dev/null +++ b/certs/test-serial0/generate_certs.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# +# Generate test certificates for serial number 0 testing (issue #8615) +# +# Tests verify that root CAs (self-signed + CA:TRUE) with serial 0 are +# accepted as trust anchors, while all other cert types with serial 0 +# are rejected per RFC 5280 section 4.1.2.2. +# +# Output files (certs only -- EE keys use temp files): +# root_serial0.pem / root_serial0_key.pem - Root CA with serial 0 +# ee_serial0.pem - EE cert with serial 0 (rejected) +# ee_normal.pem - Normal EE cert (serial 100) +# selfsigned_nonca_serial0.pem - Self-signed non-CA, serial 0 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "===================================================" +echo "Generating serial 0 test certificates in: $SCRIPT_DIR" +echo "===================================================" + +# 1. Create Root CA with serial number 0 +echo "" +echo "[1/4] Creating Root CA with serial number 0..." +openssl req -x509 -newkey rsa:2048 -keyout root_serial0_key.pem -out root_serial0.pem \ + -days 7300 -nodes -subj "/CN=Test Root CA Serial 0/O=wolfSSL Test/C=US" \ + -set_serial 0 \ + -addext "basicConstraints=critical,CA:TRUE" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" + +echo " Root CA serial number:" +openssl x509 -in root_serial0.pem -noout -serial + +# 2. Create end-entity cert with serial 0 signed by root_serial0 +echo "" +echo "[2/4] Creating end-entity certificate with serial number 0..." +openssl req -newkey rsa:2048 -keyout ee_serial0_key.tmp -out ee_serial0.csr.tmp -nodes \ + -subj "/CN=End Entity Serial 0/O=wolfSSL Test/C=US" + +openssl x509 -req -in ee_serial0.csr.tmp -CA root_serial0.pem -CAkey root_serial0_key.pem \ + -out ee_serial0.pem -days 3650 -set_serial 0 \ + -extfile <(echo "basicConstraints=CA:FALSE +keyUsage=digitalSignature,keyEncipherment +extendedKeyUsage=serverAuth,clientAuth") + +rm -f ee_serial0_key.tmp ee_serial0.csr.tmp + +echo " End-entity cert serial number:" +openssl x509 -in ee_serial0.pem -noout -serial + +# 3. Create normal end-entity cert signed by root CA with serial 0 +echo "" +echo "[3/4] Creating normal end-entity certificate (signed by serial 0 root)..." +openssl req -newkey rsa:2048 -keyout ee_normal_key.tmp -out ee_normal.csr.tmp -nodes \ + -subj "/CN=End Entity Normal/O=wolfSSL Test/C=US" + +openssl x509 -req -in ee_normal.csr.tmp -CA root_serial0.pem -CAkey root_serial0_key.pem \ + -out ee_normal.pem -days 3650 -set_serial 100 \ + -extfile <(echo "basicConstraints=CA:FALSE +keyUsage=digitalSignature,keyEncipherment +extendedKeyUsage=serverAuth,clientAuth") + +rm -f ee_normal_key.tmp ee_normal.csr.tmp + +echo " Normal end-entity cert serial number:" +openssl x509 -in ee_normal.pem -noout -serial + +# 4. Create self-signed non-CA certificate with serial 0 +echo "" +echo "[4/4] Creating self-signed non-CA certificate with serial number 0..." +openssl req -x509 -newkey rsa:2048 -keyout selfsigned_nonca_serial0_key.tmp \ + -out selfsigned_nonca_serial0.pem -days 3650 -nodes \ + -subj "/CN=Self-Signed Non-CA Serial 0/O=wolfSSL Test/C=US" \ + -set_serial 0 \ + -addext "basicConstraints=CA:FALSE" \ + -addext "keyUsage=digitalSignature,keyEncipherment" + +rm -f selfsigned_nonca_serial0_key.tmp + +echo " Self-signed non-CA cert serial number:" +openssl x509 -in selfsigned_nonca_serial0.pem -noout -serial + +echo "" +echo "===================================================" +echo "Certificate generation complete!" +echo "===================================================" diff --git a/certs/test-serial0/include.am b/certs/test-serial0/include.am new file mode 100644 index 0000000000..cf20e1f8da --- /dev/null +++ b/certs/test-serial0/include.am @@ -0,0 +1,11 @@ +# vim:ft=automake +# included from Top Level Makefile.am +# All paths should be given relative to the root + +EXTRA_DIST += certs/test-serial0/generate_certs.sh \ + certs/test-serial0/root_serial0.pem \ + certs/test-serial0/root_serial0_key.pem \ + certs/test-serial0/ee_serial0.pem \ + certs/test-serial0/ee_normal.pem \ + certs/test-serial0/selfsigned_nonca_serial0.pem + diff --git a/certs/test-serial0/root_serial0.pem b/certs/test-serial0/root_serial0.pem new file mode 100644 index 0000000000..764ba2dcff --- /dev/null +++ b/certs/test-serial0/root_serial0.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBADANBgkqhkiG9w0BAQsFADBEMR4wHAYDVQQDDBVUZXN0 +IFJvb3QgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDELMAkGA1UE +BhMCVVMwHhcNMjYwMzE5MjA0NjM2WhcNNDYwMzE0MjA0NjM2WjBEMR4wHAYDVQQD +DBVUZXN0IFJvb3QgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDEL +MAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChQKbH +G0su7BpynIN8GtTCQk8jNXKR0TnOnCHLyekO8p22QxW+A6r6sfRo/TGxPOqG8VGh +Fec8bs6wv7KX+SbYCxvhZUab4ygnlYCjW/ab6eiRTr4BloBSYWBUqz01k53CDjRD +1CK6orZ4JTAqi+4qtJkqTupMY1sKyxxw9F69PPqzNDHTsUwfZUYcvHa5VQIdX0k3 +cFs4MyereNkUvLW5BZrGcDecsZRuvZ9CB5+z6ser2/ROxR3ty6ZgwHA9fLmQoxXo +r89Gb0mrXQLjNTTSDvP3eC4GPqFSLZYFf6aaLx/qFNKICUPEA6Fmk+blliE/c/Z7 +DquD86OB+VqON0e1AgMBAAGjYzBhMB0GA1UdDgQWBBRh4Nck2fNzW7ljbZ7aY0JN +A1WHwDAfBgNVHSMEGDAWgBRh4Nck2fNzW7ljbZ7aY0JNA1WHwDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAE25IZNDe +ChqyTQ5X00g0ktzN3Lxh8C7EOnWPfhJOcCovgVKY7dj8bLQQ6kZiS1lS+MoLDzgC +XfhrCPMXIk/BGaUUEcfWZbGP579dL3uh2G4aB52lQrbTD81fgfVeqPaTIs3VsVg2 +wcgh+UvEun+G/XYSp+ZnexLjhhmBlNtTagppAhvFjDEnuLlgTkXV/4CJSy290R/7 +JeATYvKGR0DVhrEa3xNEI4uoYBieFJX7pmwVcuwDyLskOurNvUTVQ1W/IesuP7JT +dFKtsxUkKU3yyBatQ1vwl117d+2dXFA9GzjtQM3kvPZoJ6Q9tOL9Y5+G+K/PoHVm +ujFE1ZTiQhm+4A== +-----END CERTIFICATE----- diff --git a/certs/test-serial0/root_serial0_key.pem b/certs/test-serial0/root_serial0_key.pem new file mode 100644 index 0000000000..7d5bdebfe1 --- /dev/null +++ b/certs/test-serial0/root_serial0_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQChQKbHG0su7Bpy +nIN8GtTCQk8jNXKR0TnOnCHLyekO8p22QxW+A6r6sfRo/TGxPOqG8VGhFec8bs6w +v7KX+SbYCxvhZUab4ygnlYCjW/ab6eiRTr4BloBSYWBUqz01k53CDjRD1CK6orZ4 +JTAqi+4qtJkqTupMY1sKyxxw9F69PPqzNDHTsUwfZUYcvHa5VQIdX0k3cFs4Myer +eNkUvLW5BZrGcDecsZRuvZ9CB5+z6ser2/ROxR3ty6ZgwHA9fLmQoxXor89Gb0mr +XQLjNTTSDvP3eC4GPqFSLZYFf6aaLx/qFNKICUPEA6Fmk+blliE/c/Z7DquD86OB ++VqON0e1AgMBAAECgf4QdS4GA0WpWXNZ4lHVqJjGqCLu6ap3HqZDz590p2jsrp21 +opfXXlR77+54p3L5OqavTarh0Ugr1NKOeF5CLkXVD9zgoZPXyzZhakRTOkxlCfo4 +wL6qGE5HPRvwNK/9xV1PJSeOd7+DYEu5rt+wuXYlPxscDPTV+yMrvy8lyOmIjZey +uS64bMaiWVvuF5KEc3p0+VqX5Q1FzSOBxBMdSuN+5xbckeVMBWWQpBUAitxdpGUs +koieaE8vjrwAXlgha2iUF9/3WJO+IYRz4e2x/2cU3wAXB/5Ga4VSltHFgHcpahc5 +CN7QxBaMAisKvoGmHL9cmi47KP9+ScUWwNGHgQKBgQDS4oW6oO12W/5+5uGNP6zR +KtJ4utKwrhSeWpV6qu5cXjYC1pJEM0XN2K+K4KGPUxoNtLpspCJ2SR911y+jhz4a +oQ2U5l+gHEYjQP7bOHPwN8aCIZj6HHyD8aTcsWl3vb9dizhRvTw8D+vCiNCWM2sQ +fSdtMLk1Ju7ud5i0onJ+lQKBgQDDv+3RW+7apjuIc5CDlOIG+0Ir2/iyvtkhDRMn +thEUZYL+a8sTKhOk4SfgDZbxdz9oVJVwXFSV01F5ac/gu0/C0VLYb6vbGr2TPf3K +RPRQjoJh3XvM6ydx47hO/iUm8E5bEWjYqBXuhinh0lj11zjCtsf+zkhxCy+0i5ot +WW/8oQKBgD8iGa75pp2chOAw9q12tqIYE9KY+6JxOzL9I2sJ6To16i2HV1qbjvZF +PKhy/2sNEeuwg28q5DZNReHdfiGSx4DpXkuJfG9Oh6DeQG4YxHzR9dfXfxjBlnVZ +zmVTp6N1Zuj2WPH/mRzSF16x3uBYnGDfVwJVZ90Fvtoda9YIHAbRAoGBAKvhuacd +/GvNj3TPVNPVRWsv8PimHIiHgAy/eFRkUDcCs7VHXXekeL9MXUElbab1OJ4Zt2aE +DFnKxj3AJaKFlxHPz9jwpYysvE2wH0sepRCfMelRG8Xhri8Y79uc2W6Jj6Pzc4ba +gPeCowABPdAQfWysJoydAYsRcYAtHOI5KFZBAoGAeiLvbtj/odW1tuOcijKVX4kC +er/cqIYgvgQg1TTMsb02Fv1GBQn8A1C1Jb6TV/cJvxW3TIrqk07cSrM8qIHy1qAk +omQbnSV6p6c4FaJXBFPk9ileSPwBegxuBSiby2X29cJ88D1Wdq8Osjmc5wK/umuy +Vowu3kDcLFcpu3v6368= +-----END PRIVATE KEY----- diff --git a/certs/test-serial0/selfsigned_nonca_serial0.pem b/certs/test-serial0/selfsigned_nonca_serial0.pem new file mode 100644 index 0000000000..76947d029a --- /dev/null +++ b/certs/test-serial0/selfsigned_nonca_serial0.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaTCCAlGgAwIBAgIBADANBgkqhkiG9w0BAQsFADBKMSQwIgYDVQQDDBtTZWxm +LVNpZ25lZCBOb24tQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDEL +MAkGA1UEBhMCVVMwHhcNMjYwMzE5MjA0NjM2WhcNMzYwMzE2MjA0NjM2WjBKMSQw +IgYDVQQDDBtTZWxmLVNpZ25lZCBOb24tQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdv +bGZTU0wgVGVzdDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvEEbR+b6q0W5Kz3i37Iyfphd4qdEV2/dAzVSQC7Wgyd0soR2Fh6qB +t2C2yD/Ixj+JtdsR48mUUs0MPsbtreJOTKe9It2PJrp7mR1oOGHwfhebAegpyIh3 +ytO6c7YNAE5Yd4bSAGjw0UwjpN3Yl4DVx9QLkf9+YnXaz71fNQmmC07WN7vJcn2Y +mvD8AHf3ujsRkIDZEsUsamIFnPvICqxK9d1nMVSZSN4Uf7wdsJxct0L13rlJ/TKC +UETpgyydIk3VVwPWeOYU4C/sDhI2Wl8rytqhOVnBssw+pyLGkgsQ1VDo/j+FOEup +M+sMb4s6oSLePw17L0YXkF/kdbi0J7P5AgMBAAGjWjBYMB0GA1UdDgQWBBQQg6+e +TkcmWxgBTgHYn8NawThfozAfBgNVHSMEGDAWgBQQg6+eTkcmWxgBTgHYn8NawThf +ozAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDANBgkqhkiG9w0BAQsFAAOCAQEARLo5 +6LXXU5Pp/+uF2AA+C+Eg4lECJD+j8Ice4f/WnHpdrEgyfluOw0d/XI8OnBDckvDx +cjuWOEhjwQCXSkB/604TVw6DncWVotfkA/RKJGuvmUkkSXGjRka7rDAnHqzs5xvn +4iU/n3NdWc9R0NjE9yFiURGMWbh/xRiEThm9ge0L5wzetdtIdne7tReznVy+uOgQ +FB6n5AyKK/y6ONfhNUoDnukGBuH1RB7lTs/ZuvpCEM7vrP6Llt0A9wqffOOeZ8B2 +9z6YPT95t39Z6899mhnBc2rq+b7EP526Ou+KYU7e2u5gj4rZHuvlkuKA3JVn7V35 +dgos00E67av5sbeXDw== +-----END CERTIFICATE----- diff --git a/tests/api.c b/tests/api.c index 05a7688d7f..18378f29e3 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22128,24 +22128,24 @@ static int test_MakeCertWith0Ser(void) { EXPECT_DECLS; #if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \ - defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \ - defined(WOLFSSL_ASN_TEMPLATE) + defined(WOLFSSL_CERT_GEN) && !defined(NO_RSA) && \ + defined(WOLFSSL_KEY_GEN) && defined(WOLFSSL_ASN_TEMPLATE) Cert cert; DecodedCert decodedCert; byte der[FOURK_BUF]; int derSize = 0; WC_RNG rng; - ecc_key key; + RsaKey key; int ret; XMEMSET(&rng, 0, sizeof(WC_RNG)); - XMEMSET(&key, 0, sizeof(ecc_key)); + XMEMSET(&key, 0, sizeof(RsaKey)); XMEMSET(&cert, 0, sizeof(Cert)); XMEMSET(&decodedCert, 0, sizeof(DecodedCert)); ExpectIntEQ(wc_InitRng(&rng), 0); - ExpectIntEQ(wc_ecc_init(&key), 0); - ExpectIntEQ(wc_ecc_make_key(&rng, 32, &key), 0); + ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0); + ExpectIntEQ(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng), 0); ExpectIntEQ(wc_InitCert(&cert), 0); (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); @@ -22159,20 +22159,16 @@ static int test_MakeCertWith0Ser(void) CTC_NAME_SIZE); cert.selfSigned = 1; - cert.isCA = 1; - cert.sigType = CTC_SHA256wECDSA; - -#ifdef WOLFSSL_CERT_EXT - cert.keyUsage |= KEYUSE_KEY_CERT_SIGN; -#endif + cert.isCA = 0; + cert.sigType = CTC_SHA256wRSA; /* set serial number to 0 */ cert.serialSz = 1; cert.serial[0] = 0; - ExpectIntGE(wc_MakeCert(&cert, der, FOURK_BUF, NULL, &key, &rng), 0); + ExpectIntGE(wc_MakeCert(&cert, der, FOURK_BUF, &key, NULL, &rng), 0); ExpectIntGE(derSize = wc_SignCert(cert.bodySz, cert.sigType, der, - FOURK_BUF, NULL, &key, &rng), 0); + FOURK_BUF, &key, NULL, &rng), 0); wc_InitDecodedCert(&decodedCert, der, (word32)derSize, NULL); @@ -22185,7 +22181,7 @@ static int test_MakeCertWith0Ser(void) #endif wc_FreeDecodedCert(&decodedCert); - ret = wc_ecc_free(&key); + ret = wc_FreeRsaKey(&key); ExpectIntEQ(ret, 0); ret = wc_FreeRng(&rng); ExpectIntEQ(ret, 0); diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 64be65084f..bcca20c513 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1029,6 +1029,72 @@ int test_DecodeAltNames_length_underflow(void) return EXPECT_RESULT(); } +int test_SerialNumber0_RootCA(void) +{ + EXPECT_DECLS; + +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ + !defined(WOLFSSL_NO_PEM) && defined(WOLFSSL_PEM_TO_DER) + /* Test that root CA certificates with serial number 0 are accepted, + * while non-root certificates with serial 0 are rejected (issue #8615) */ + +#if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ + !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) && \ + !defined(WOLFSSL_TEST_APPLE_NATIVE_CERT_VALIDATION) + WOLFSSL_CERT_MANAGER* cm = NULL; + const char* rootSerial0File = "./certs/test-serial0/root_serial0.pem"; + const char* selfSignedNonCASerial0File = + "./certs/test-serial0/selfsigned_nonca_serial0.pem"; + + /* Test 1: Root CA with serial 0 should load successfully */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm, rootSerial0File, NULL), + WOLFSSL_SUCCESS); + +#if (!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH)) || \ + defined(OPENSSL_EXTRA) + { + const char* eeSerial0File = "./certs/test-serial0/ee_serial0.pem"; + const char* eeNormalFile = "./certs/test-serial0/ee_normal.pem"; + + /* Test 2: End-entity cert with serial 0 should be rejected during + * verify */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, eeSerial0File, + WOLFSSL_FILETYPE_PEM), WC_NO_ERR_TRACE(ASN_PARSE_E)); + + /* Test 3: Normal end-entity cert signed by root CA with serial 0 + * should verify successfully */ + ExpectIntEQ(wolfSSL_CertManagerVerify(cm, eeNormalFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + } +#endif + + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + /* Balance the wolfSSL_Init refcount incremented by internal + * wolfSSL_CTX_new_ex calls in CertManagerLoadCA/Verify. */ + wolfSSL_Cleanup(); + + /* Test 4: Self-signed non-CA certificate with serial 0 should be rejected */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntNE(wolfSSL_CertManagerLoadCA(cm, selfSignedNonCASerial0File, NULL), + WOLFSSL_SUCCESS); + + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + wolfSSL_Cleanup(); +#endif /* !WOLFSSL_NO_ASN_STRICT && !WOLFSSL_PYTHON && + !WOLFSSL_ASN_ALLOW_0_SERIAL && + !WOLFSSL_TEST_APPLE_NATIVE_CERT_VALIDATION */ +#endif /* !NO_CERTS && !NO_FILESYSTEM && !NO_RSA && !WOLFSSL_NO_PEM */ + + return EXPECT_RESULT(); +} + int test_wc_DecodeObjectId(void) { EXPECT_DECLS; diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index 97a2ee2b7f..86542e61f7 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -29,6 +29,7 @@ int test_GetSetShortInt(void); int test_wc_IndexSequenceOf(void); int test_wolfssl_local_MatchBaseName(void); int test_wc_DecodeRsaPssParams(void); +int test_SerialNumber0_RootCA(void); int test_DecodeAltNames_length_underflow(void); int test_wc_DecodeObjectId(void); @@ -38,7 +39,8 @@ int test_wc_DecodeObjectId(void); TEST_DECL_GROUP("asn", test_wc_IndexSequenceOf), \ TEST_DECL_GROUP("asn", test_wolfssl_local_MatchBaseName), \ TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams), \ - TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \ + TEST_DECL_GROUP("asn", test_SerialNumber0_RootCA), \ + TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \ TEST_DECL_GROUP("asn", test_wc_DecodeObjectId) #endif /* WOLFCRYPT_TEST_ASN_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 9a3be56616..015b223523 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -20762,21 +20762,10 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, cert->version = version; cert->serialSz = (int)serialSz; - #if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ - !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) - /* RFC 5280 section 4.1.2.2 states that non-conforming CAs may issue - * a negative or zero serial number and should be handled gracefully. - * Since it is a non-conforming CA that issues a serial of 0 then we - * treat it as an error here. */ - if (cert->serialSz == 1 && cert->serial[0] == 0) { - WOLFSSL_MSG("Error serial number of 0, use WOLFSSL_NO_ASN_STRICT " - "if wanted"); - ret = ASN_PARSE_E; - } - #endif + /* RFC 5280 requires serial number to be present and at least 1 byte */ if (cert->serialSz == 0) { - WOLFSSL_MSG("Error serial size is zero. Should be at least one " - "even with no serial number."); + WOLFSSL_MSG("Error: certificate serial number is empty " + "(zero-length serial is invalid per RFC 5280)"); ret = ASN_PARSE_E; } @@ -20994,6 +20983,25 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } } +#if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ + !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) + /* Check for serial number of 0. RFC 5280 section 4.1.2.2 requires + * positive serial numbers. However, allow zero for self-signed CA + * certificates (root CAs) being loaded as trust anchors since they + * are explicitly trusted and some legacy root CAs in real-world + * trust stores have serial number 0. */ + if ((ret == 0) && (cert->serialSz == 1) && (cert->serial[0] == 0)) { + if (!(cert->isCA && cert->selfSigned) +#ifdef WOLFSSL_CERT_REQ + && !cert->isCSR +#endif + ) { + WOLFSSL_MSG("Error serial number of 0 for non-root certificate"); + ret = ASN_PARSE_E; + } + } +#endif + if ((ret == 0) && (!done) && (badDate != 0)) { /* Parsed whole certificate fine but return any date errors. */ ret = badDate; @@ -22399,6 +22407,26 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, } #endif +#if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ + !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) + /* Check for serial number of 0. RFC 5280 section 4.1.2.2 requires + * positive serial numbers. However, allow zero for self-signed CA + * certificates (root CAs) being loaded as trust anchors since they + * are explicitly trusted and some legacy root CAs in real-world + * trust stores have serial number 0. */ + if ((ret == 0) && (cert->serialSz == 1) && (cert->serial[0] == 0)) { + if (!((type == CA_TYPE || type == TRUSTED_PEER_TYPE) && + cert->isCA && cert->selfSigned) +#ifdef WOLFSSL_CERT_REQ + && !cert->isCSR +#endif + ) { + WOLFSSL_MSG("Error serial number of 0 for non-root certificate"); + return ASN_PARSE_E; + } + } +#endif + #ifndef ALLOW_INVALID_CERTSIGN /* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 * If the cA boolean is not asserted, then the keyCertSign bit in the From 3ef89158422e62065603cd88788674fd90712af7 Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Mon, 13 Apr 2026 10:52:22 -0600 Subject: [PATCH 2/6] asn: drop redundant serial-0 check in DecodeCertInternal to avoid fail-open on forged isC --- wolfcrypt/src/asn.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 015b223523..f369d269f3 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -20983,25 +20983,6 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, } } -#if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ - !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) - /* Check for serial number of 0. RFC 5280 section 4.1.2.2 requires - * positive serial numbers. However, allow zero for self-signed CA - * certificates (root CAs) being loaded as trust anchors since they - * are explicitly trusted and some legacy root CAs in real-world - * trust stores have serial number 0. */ - if ((ret == 0) && (cert->serialSz == 1) && (cert->serial[0] == 0)) { - if (!(cert->isCA && cert->selfSigned) -#ifdef WOLFSSL_CERT_REQ - && !cert->isCSR -#endif - ) { - WOLFSSL_MSG("Error serial number of 0 for non-root certificate"); - ret = ASN_PARSE_E; - } - } -#endif - if ((ret == 0) && (!done) && (badDate != 0)) { /* Parsed whole certificate fine but return any date errors. */ ret = badDate; From 49f01c187f8ddc61722a47d2964093fffba17892 Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Fri, 24 Apr 2026 11:27:52 -0600 Subject: [PATCH 3/6] Skoll review --- certs/test-serial0/generate_certs.sh | 29 ++++- certs/test-serial0/include.am | 3 +- certs/test-serial0/intermediate_serial0.pem | 21 ++++ tests/api.c | 112 ++++++++++---------- tests/api/test_asn.c | 19 +++- wolfcrypt/src/asn.c | 39 +++++-- 6 files changed, 148 insertions(+), 75 deletions(-) create mode 100644 certs/test-serial0/intermediate_serial0.pem diff --git a/certs/test-serial0/generate_certs.sh b/certs/test-serial0/generate_certs.sh index 047b21703c..76df6f3f7f 100755 --- a/certs/test-serial0/generate_certs.sh +++ b/certs/test-serial0/generate_certs.sh @@ -11,6 +11,8 @@ # ee_serial0.pem - EE cert with serial 0 (rejected) # ee_normal.pem - Normal EE cert (serial 100) # selfsigned_nonca_serial0.pem - Self-signed non-CA, serial 0 +# intermediate_serial0.pem - Intermediate CA, serial 0 +# (CA:TRUE but issuer != subject) set -e @@ -23,7 +25,7 @@ echo "===================================================" # 1. Create Root CA with serial number 0 echo "" -echo "[1/4] Creating Root CA with serial number 0..." +echo "[1/5] Creating Root CA with serial number 0..." openssl req -x509 -newkey rsa:2048 -keyout root_serial0_key.pem -out root_serial0.pem \ -days 7300 -nodes -subj "/CN=Test Root CA Serial 0/O=wolfSSL Test/C=US" \ -set_serial 0 \ @@ -35,7 +37,7 @@ openssl x509 -in root_serial0.pem -noout -serial # 2. Create end-entity cert with serial 0 signed by root_serial0 echo "" -echo "[2/4] Creating end-entity certificate with serial number 0..." +echo "[2/5] Creating end-entity certificate with serial number 0..." openssl req -newkey rsa:2048 -keyout ee_serial0_key.tmp -out ee_serial0.csr.tmp -nodes \ -subj "/CN=End Entity Serial 0/O=wolfSSL Test/C=US" @@ -52,7 +54,7 @@ openssl x509 -in ee_serial0.pem -noout -serial # 3. Create normal end-entity cert signed by root CA with serial 0 echo "" -echo "[3/4] Creating normal end-entity certificate (signed by serial 0 root)..." +echo "[3/5] Creating normal end-entity certificate (signed by serial 0 root)..." openssl req -newkey rsa:2048 -keyout ee_normal_key.tmp -out ee_normal.csr.tmp -nodes \ -subj "/CN=End Entity Normal/O=wolfSSL Test/C=US" @@ -69,7 +71,7 @@ openssl x509 -in ee_normal.pem -noout -serial # 4. Create self-signed non-CA certificate with serial 0 echo "" -echo "[4/4] Creating self-signed non-CA certificate with serial number 0..." +echo "[4/5] Creating self-signed non-CA certificate with serial number 0..." openssl req -x509 -newkey rsa:2048 -keyout selfsigned_nonca_serial0_key.tmp \ -out selfsigned_nonca_serial0.pem -days 3650 -nodes \ -subj "/CN=Self-Signed Non-CA Serial 0/O=wolfSSL Test/C=US" \ @@ -82,6 +84,25 @@ rm -f selfsigned_nonca_serial0_key.tmp echo " Self-signed non-CA cert serial number:" openssl x509 -in selfsigned_nonca_serial0.pem -noout -serial +# 5. Create intermediate CA cert with serial 0, signed by root_serial0 +# (CA:TRUE but issuer != subject, so cert->selfSigned will be 0). +echo "" +echo "[5/5] Creating intermediate CA certificate with serial number 0..." +openssl req -newkey rsa:2048 -keyout intermediate_serial0_key.tmp \ + -out intermediate_serial0.csr.tmp -nodes \ + -subj "/CN=Intermediate CA Serial 0/O=wolfSSL Test/C=US" + +openssl x509 -req -in intermediate_serial0.csr.tmp \ + -CA root_serial0.pem -CAkey root_serial0_key.pem \ + -out intermediate_serial0.pem -days 3650 -set_serial 0 \ + -extfile <(echo "basicConstraints=critical,CA:TRUE +keyUsage=critical,keyCertSign,cRLSign") + +rm -f intermediate_serial0_key.tmp intermediate_serial0.csr.tmp + +echo " Intermediate CA cert serial number:" +openssl x509 -in intermediate_serial0.pem -noout -serial + echo "" echo "===================================================" echo "Certificate generation complete!" diff --git a/certs/test-serial0/include.am b/certs/test-serial0/include.am index cf20e1f8da..557e1da26c 100644 --- a/certs/test-serial0/include.am +++ b/certs/test-serial0/include.am @@ -7,5 +7,6 @@ EXTRA_DIST += certs/test-serial0/generate_certs.sh \ certs/test-serial0/root_serial0_key.pem \ certs/test-serial0/ee_serial0.pem \ certs/test-serial0/ee_normal.pem \ - certs/test-serial0/selfsigned_nonca_serial0.pem + certs/test-serial0/selfsigned_nonca_serial0.pem \ + certs/test-serial0/intermediate_serial0.pem diff --git a/certs/test-serial0/intermediate_serial0.pem b/certs/test-serial0/intermediate_serial0.pem new file mode 100644 index 0000000000..57bc4fa35c --- /dev/null +++ b/certs/test-serial0/intermediate_serial0.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDaTCCAlGgAwIBAgIBADANBgkqhkiG9w0BAQsFADBEMR4wHAYDVQQDDBVUZXN0 +IFJvb3QgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVzdDELMAkGA1UE +BhMCVVMwHhcNMjYwNDIzMjI0ODU3WhcNMzYwNDIwMjI0ODU3WjBHMSEwHwYDVQQD +DBhJbnRlcm1lZGlhdGUgQ0EgU2VyaWFsIDAxFTATBgNVBAoMDHdvbGZTU0wgVGVz +dDELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4 +vuEhBsjGh1kUXub+mnS+siPwFTUds5/wtCeJWeBZ1r7ks7jq0RMWHlPCnsB1RxFO +zKtRI1Vq4JlJmPN47VsWhRMm3RvQINf6L4dKhjVvYsJHWpJ0pEaopqogl4YlJPvM +yTuWm3O4FItS1iye9XdTBCdfsPOJgqHU8JxpFIBCijjReWYIqEP2wxQkO6/HcIm9 +eL4x3gaTzc8ye3ZBQcxRhjzg96AZ3N91pBFkyJAxzeBXME/L+j321svlsDNPQ1cn +pd39gEnGuhriy6R5UgOg5CptG7EGFrIpWI/+jexE3GM8zr6x+dXzUOccbJq40F1o +voEihRt+dXsScsp5oFMRAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBSJPt0qcSUhNus1U4l3OxjvkBdOszAfBgNVHSME +GDAWgBRh4Nck2fNzW7ljbZ7aY0JNA1WHwDANBgkqhkiG9w0BAQsFAAOCAQEAcO0A +3EOvpp9dMlQmt71HQ5dGtm2cerPe7yv9hiTWiL+s76eroaNoVptdoJvNnNcLGNdi +/5fGTnaCklbM/YW8xatxtvdiVMXQTVqVc+iZy3bc+j2eOJjl9qxsxv5mGjWefSL4 +DRrtZLQ1RqvZn2x1uVk3KIbCEfK1JpERN/rfHAsvR+adMxozUhNO5w6SMn5CnzNU +X6agw1zNXp/zaBuY8sXanRVRL71lHSEHYJSZtkBTcXH7/setEjVEVnC5Eq0OAB+P +ltJtU8Jrev2ABsy3di0rzC9jyZQMx+Bvw5LtIBRkONfe2bAsJVrgMznuANtLwDCV +nqmmSDLk6ODB/3Zo6g== +-----END CERTIFICATE----- diff --git a/tests/api.c b/tests/api.c index 18378f29e3..a582bee4b6 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22124,67 +22124,69 @@ static int test_PathLenNoKeyUsage(void) return EXPECT_RESULT(); } -static int test_MakeCertWith0Ser(void) +/* Exhaustive matrix coverage of the serial-0 predicate in + * ParseCertRelative (asn.c). Inputs are openssl-generated PEM fixtures + * under certs/test-serial0/ — no cert-under-test data is generated by + * wolfSSL, so the test cannot pass for the wrong reason if wc_MakeCert + * encoding ever drifts. + * + * Predicate exempts only (CA_TYPE|TRUSTED_PEER_TYPE) && isCA && selfSigned. + * + * Fixture isCA selfSigned CERT_TYPE CA_TYPE + * root_serial0.pem 1 1 reject accept + * intermediate_serial0.pem 1 0 reject reject + * selfsigned_nonca_serial0.pem 0 1 reject reject + * ee_serial0.pem 0 0 reject reject + */ +static int test_ParseSerial0FixtureMatrix(void) { EXPECT_DECLS; -#if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \ - defined(WOLFSSL_CERT_GEN) && !defined(NO_RSA) && \ - defined(WOLFSSL_KEY_GEN) && defined(WOLFSSL_ASN_TEMPLATE) - Cert cert; - DecodedCert decodedCert; - byte der[FOURK_BUF]; - int derSize = 0; - WC_RNG rng; - RsaKey key; - int ret; - - XMEMSET(&rng, 0, sizeof(WC_RNG)); - XMEMSET(&key, 0, sizeof(RsaKey)); - XMEMSET(&cert, 0, sizeof(Cert)); - XMEMSET(&decodedCert, 0, sizeof(DecodedCert)); - - ExpectIntEQ(wc_InitRng(&rng), 0); - ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0); - ExpectIntEQ(wc_MakeRsaKey(&key, 2048, WC_RSA_EXPONENT, &rng), 0); - ExpectIntEQ(wc_InitCert(&cert), 0); - - (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.state, "state", CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.org, "yourOrgNameHere", CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.unit, "yourUnitNameHere", CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.commonName, "www.yourDomain.com", - CTC_NAME_SIZE); - (void)XSTRNCPY(cert.subject.email, "yourEmail@yourDomain.com", - CTC_NAME_SIZE); +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + defined(WOLFSSL_PEM_TO_DER) && !defined(WOLFSSL_NO_PEM) && \ + !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ + !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) + struct { + const char* path; + int expectedCertType; /* expected wc_ParseCert(..., CERT_TYPE) */ + int expectedCaType; /* expected wc_ParseCert(..., CA_TYPE) */ + } cases[] = { + { "./certs/test-serial0/root_serial0.pem", + WC_NO_ERR_TRACE(ASN_PARSE_E), 0 }, + { "./certs/test-serial0/intermediate_serial0.pem", + WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, + { "./certs/test-serial0/selfsigned_nonca_serial0.pem", + WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, + { "./certs/test-serial0/ee_serial0.pem", + WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, + }; + size_t i; - cert.selfSigned = 1; - cert.isCA = 0; - cert.sigType = CTC_SHA256wRSA; + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + byte* pemBuf = NULL; + size_t pemSz = 0; + byte* derBuf = NULL; + int derSz = 0; + DecodedCert dc; - /* set serial number to 0 */ - cert.serialSz = 1; - cert.serial[0] = 0; + ExpectIntEQ(load_file(cases[i].path, &pemBuf, &pemSz), 0); + ExpectNotNull(derBuf = (byte*)XMALLOC(pemSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntGE(derSz = wc_CertPemToDer(pemBuf, (int)pemSz, derBuf, + (int)pemSz, CERT_TYPE), 0); - ExpectIntGE(wc_MakeCert(&cert, der, FOURK_BUF, &key, NULL, &rng), 0); - ExpectIntGE(derSize = wc_SignCert(cert.bodySz, cert.sigType, der, - FOURK_BUF, &key, NULL, &rng), 0); + wc_InitDecodedCert(&dc, derBuf, (word32)derSz, NULL); + ExpectIntEQ(wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL), + cases[i].expectedCertType); + wc_FreeDecodedCert(&dc); - wc_InitDecodedCert(&decodedCert, der, (word32)derSize, NULL); + wc_InitDecodedCert(&dc, derBuf, (word32)derSz, NULL); + ExpectIntEQ(wc_ParseCert(&dc, CA_TYPE, NO_VERIFY, NULL), + cases[i].expectedCaType); + wc_FreeDecodedCert(&dc); -#if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ - !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) - ExpectIntEQ(wc_ParseCert(&decodedCert, CERT_TYPE, NO_VERIFY, NULL), - WC_NO_ERR_TRACE(ASN_PARSE_E)); -#else - ExpectIntEQ(wc_ParseCert(&decodedCert, CERT_TYPE, NO_VERIFY, NULL), 0); -#endif - - wc_FreeDecodedCert(&decodedCert); - ret = wc_FreeRsaKey(&key); - ExpectIntEQ(ret, 0); - ret = wc_FreeRng(&rng); - ExpectIntEQ(ret, 0); + XFREE(derBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pemBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } #endif return EXPECT_RESULT(); } @@ -37008,7 +37010,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_PathLenSelfIssued), TEST_DECL(test_PathLenSelfIssuedAllowed), TEST_DECL(test_PathLenNoKeyUsage), - TEST_DECL(test_MakeCertWith0Ser), + TEST_DECL(test_ParseSerial0FixtureMatrix), TEST_DECL(test_MakeCertWithCaFalse), #ifdef WOLFSSL_CERT_SIGN_CB TEST_DECL(test_wc_SignCert_cb), diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index bcca20c513..4d48491489 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1073,9 +1073,6 @@ int test_SerialNumber0_RootCA(void) wolfSSL_CertManagerFree(cm); cm = NULL; } - /* Balance the wolfSSL_Init refcount incremented by internal - * wolfSSL_CTX_new_ex calls in CertManagerLoadCA/Verify. */ - wolfSSL_Cleanup(); /* Test 4: Self-signed non-CA certificate with serial 0 should be rejected */ ExpectNotNull(cm = wolfSSL_CertManagerNew()); @@ -1086,7 +1083,21 @@ int test_SerialNumber0_RootCA(void) wolfSSL_CertManagerFree(cm); cm = NULL; } - wolfSSL_Cleanup(); + + /* Test 5: Intermediate CA (CA:TRUE but issuer != subject) with serial 0 + * must be rejected when loaded as CA_TYPE. Exercises the selfSigned + * half of the ParseCertRelative exemption predicate. */ + { + const char* intermediateSerial0File = + "./certs/test-serial0/intermediate_serial0.pem"; + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntNE(wolfSSL_CertManagerLoadCA(cm, intermediateSerial0File, + NULL), WOLFSSL_SUCCESS); + if (cm != NULL) { + wolfSSL_CertManagerFree(cm); + cm = NULL; + } + } #endif /* !WOLFSSL_NO_ASN_STRICT && !WOLFSSL_PYTHON && !WOLFSSL_ASN_ALLOW_0_SERIAL && !WOLFSSL_TEST_APPLE_NATIVE_CERT_VALIDATION */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index f369d269f3..4d1d7a01cd 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -20988,6 +20988,13 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt, ret = badDate; } + /* Note: serial-0 rejection is performed in ParseCertRelative (after + * basicConstraints has been parsed and isCA is authoritative), not + * here. Checking isCA at this point would fail-open on a forged + * isCA flag. Callers that invoke DecodeCert/DecodeToKey/wc_GetPubX509 + * directly are pubkey-extraction paths and do not make trust + * decisions; trust-bearing flows go through ParseCertRelative. */ + return ret; } @@ -22390,18 +22397,28 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, #if !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) - /* Check for serial number of 0. RFC 5280 section 4.1.2.2 requires - * positive serial numbers. However, allow zero for self-signed CA - * certificates (root CAs) being loaded as trust anchors since they - * are explicitly trusted and some legacy root CAs in real-world - * trust stores have serial number 0. */ + /* RFC 5280 section 4.1.2.2 requires conforming CAs to issue + * positive serial numbers; the same section notes that verifiers + * SHOULD gracefully handle non-conforming certs with zero or + * negative serials. wolfSSL's policy is to reject as a security + * guard, with an exemption for self-signed CA certs loaded as + * explicitly-trusted anchors (some legacy real-world roots have + * serial 0). + * + * Note: cert->selfSigned is a subject/issuer name-hash compare + * (see DecodeCertInternal where it's set), not a validated + * self-signature. That is acceptable here because the trust + * decision is user-driven via CertManagerLoadCA; this check is + * only a structural sanity guard. */ if ((ret == 0) && (cert->serialSz == 1) && (cert->serial[0] == 0)) { - if (!((type == CA_TYPE || type == TRUSTED_PEER_TYPE) && - cert->isCA && cert->selfSigned) -#ifdef WOLFSSL_CERT_REQ - && !cert->isCSR -#endif - ) { + int isTrustAnchorLoad = + (type == CA_TYPE || type == TRUSTED_PEER_TYPE) + && cert->isCA && cert->selfSigned; + int isCsr = 0; + #ifdef WOLFSSL_CERT_REQ + isCsr = cert->isCSR; + #endif + if (!isTrustAnchorLoad && !isCsr) { WOLFSSL_MSG("Error serial number of 0 for non-root certificate"); return ASN_PARSE_E; } From 4822c6953e6612dd8c7b71994ffbd8a206ca245e Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Tue, 28 Apr 2026 16:44:13 -0600 Subject: [PATCH 4/6] tests/api.c: assert serial-0 policy outcome, not specific rejection error code --- tests/api.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/tests/api.c b/tests/api.c index a582bee4b6..e7928fc704 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22145,19 +22145,24 @@ static int test_ParseSerial0FixtureMatrix(void) defined(WOLFSSL_PEM_TO_DER) && !defined(WOLFSSL_NO_PEM) && \ !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) + /* Each case asserts a policy outcome (accept vs reject), not a specific + * error code. wc_ParseCert can fail via several distinct codes + * (ASN_PARSE_E, ASN_UNKNOWN_OID_E, etc.) depending on which + * OID-recognition features are compiled into the build; matching any + * specific code is brittle across configs. */ struct { const char* path; - int expectedCertType; /* expected wc_ParseCert(..., CERT_TYPE) */ - int expectedCaType; /* expected wc_ParseCert(..., CA_TYPE) */ + int certTypeShouldPass; /* 1: expect ret == 0; 0: expect ret != 0 */ + int caTypeShouldPass; } cases[] = { - { "./certs/test-serial0/root_serial0.pem", - WC_NO_ERR_TRACE(ASN_PARSE_E), 0 }, - { "./certs/test-serial0/intermediate_serial0.pem", - WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, - { "./certs/test-serial0/selfsigned_nonca_serial0.pem", - WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, - { "./certs/test-serial0/ee_serial0.pem", - WC_NO_ERR_TRACE(ASN_PARSE_E), WC_NO_ERR_TRACE(ASN_PARSE_E) }, + /* Root CA serial 0 is rejected as CERT_TYPE, accepted as trust + * anchor (CA_TYPE) per the exemption in ParseCertRelative. */ + { "./certs/test-serial0/root_serial0.pem", 0, 1 }, + /* Intermediate CA: CA:TRUE but issuer != subject, so the trust + * anchor exemption (cert->selfSigned) does not apply. */ + { "./certs/test-serial0/intermediate_serial0.pem", 0, 0 }, + { "./certs/test-serial0/selfsigned_nonca_serial0.pem", 0, 0 }, + { "./certs/test-serial0/ee_serial0.pem", 0, 0 }, }; size_t i; @@ -22167,6 +22172,7 @@ static int test_ParseSerial0FixtureMatrix(void) byte* derBuf = NULL; int derSz = 0; DecodedCert dc; + int ret; ExpectIntEQ(load_file(cases[i].path, &pemBuf, &pemSz), 0); ExpectNotNull(derBuf = (byte*)XMALLOC(pemSz, NULL, @@ -22175,13 +22181,19 @@ static int test_ParseSerial0FixtureMatrix(void) (int)pemSz, CERT_TYPE), 0); wc_InitDecodedCert(&dc, derBuf, (word32)derSz, NULL); - ExpectIntEQ(wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL), - cases[i].expectedCertType); + ret = wc_ParseCert(&dc, CERT_TYPE, NO_VERIFY, NULL); + if (cases[i].certTypeShouldPass) + ExpectIntEQ(ret, 0); + else + ExpectIntNE(ret, 0); wc_FreeDecodedCert(&dc); wc_InitDecodedCert(&dc, derBuf, (word32)derSz, NULL); - ExpectIntEQ(wc_ParseCert(&dc, CA_TYPE, NO_VERIFY, NULL), - cases[i].expectedCaType); + ret = wc_ParseCert(&dc, CA_TYPE, NO_VERIFY, NULL); + if (cases[i].caTypeShouldPass) + ExpectIntEQ(ret, 0); + else + ExpectIntNE(ret, 0); wc_FreeDecodedCert(&dc); XFREE(derBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); From ce4b248c64332c43733aebe68cc8273aeef1dbc8 Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Wed, 29 Apr 2026 15:02:03 -0600 Subject: [PATCH 5/6] tests/api.c: gate test_ParseSerial0FixtureMatrix on (fixtures are RSA) --- tests/api.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/api.c b/tests/api.c index e7928fc704..bc8c41b5aa 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22141,10 +22141,12 @@ static int test_PathLenNoKeyUsage(void) static int test_ParseSerial0FixtureMatrix(void) { EXPECT_DECLS; -#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ +#if !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ defined(WOLFSSL_PEM_TO_DER) && !defined(WOLFSSL_NO_PEM) && \ !defined(WOLFSSL_NO_ASN_STRICT) && !defined(WOLFSSL_PYTHON) && \ !defined(WOLFSSL_ASN_ALLOW_0_SERIAL) + /* Fixture certs are RSA-2048; NO_RSA builds can't parse them. Mirrors + * the gate on the sibling test_SerialNumber0_RootCA. */ /* Each case asserts a policy outcome (accept vs reject), not a specific * error code. wc_ParseCert can fail via several distinct codes * (ASN_PARSE_E, ASN_UNKNOWN_OID_E, etc.) depending on which From 3658357a28dc64d699df90168cde146df5577cc5 Mon Sep 17 00:00:00 2001 From: jackctj117 Date: Thu, 30 Apr 2026 15:08:17 -0600 Subject: [PATCH 6/6] tests/api.c, certs/test-serial0/include.am: drop em dash and trailing blank line --- certs/test-serial0/include.am | 1 - tests/api.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/certs/test-serial0/include.am b/certs/test-serial0/include.am index 557e1da26c..18d02e523a 100644 --- a/certs/test-serial0/include.am +++ b/certs/test-serial0/include.am @@ -9,4 +9,3 @@ EXTRA_DIST += certs/test-serial0/generate_certs.sh \ certs/test-serial0/ee_normal.pem \ certs/test-serial0/selfsigned_nonca_serial0.pem \ certs/test-serial0/intermediate_serial0.pem - diff --git a/tests/api.c b/tests/api.c index bc8c41b5aa..133943b5bd 100644 --- a/tests/api.c +++ b/tests/api.c @@ -22126,7 +22126,7 @@ static int test_PathLenNoKeyUsage(void) /* Exhaustive matrix coverage of the serial-0 predicate in * ParseCertRelative (asn.c). Inputs are openssl-generated PEM fixtures - * under certs/test-serial0/ — no cert-under-test data is generated by + * under certs/test-serial0/ -- no cert-under-test data is generated by * wolfSSL, so the test cannot pass for the wrong reason if wc_MakeCert * encoding ever drifts. *