diff --git a/certs/test/cert-ext-nc-combined.der b/certs/test/cert-ext-nc-combined.der new file mode 100644 index 000000000..b1182a74d Binary files /dev/null and b/certs/test/cert-ext-nc-combined.der differ diff --git a/certs/test/cert-ext-nc-combined.pem b/certs/test/cert-ext-nc-combined.pem new file mode 100644 index 000000000..4cff48ed7 --- /dev/null +++ b/certs/test/cert-ext-nc-combined.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCA0KgAwIBAgIUVxNILYrtvic5fahe1thKz5+9MBkwDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0Jv +emVtYW4xFDASBgNVBAoMC3dvbGZTU0wgSW5jMRgwFgYDVQQLDA9EZXYgYW5kIFRl +c3RpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTAeFw0yNjAxMjIyMTE4MjJa +Fw0yODEwMTgyMTE4MjJaMHsxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5h +MRAwDgYDVQQHDAdCb3plbWFuMRQwEgYDVQQKDAt3b2xmU1NMIEluYzEYMBYGA1UE +CwwPRGV2IGFuZCBUZXN0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW230kVBJwFlxkWu +8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98q2SoF/zKXXu6 +4CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSzrgCgY8X2fwtZ +aHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7LyG1/WZRDL1U +s+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAyloAyI5Whd7oT +0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW39nQT63XAgMB +AAGjgdUwgdIwHQYDVR0OBBYEFLMRMsmSmITiyfjQO24DQsofDo48MB8GA1UdIwQY +MBaAFLMRMsmSmITiyfjQO24DQsofDo48MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD +VR0PAQH/BAQDAgGGMC4GA1UdHgEB/wQkMCKgIDAOhgwud29sZnNzbC5jb20wDoIM +LndvbGZzc2wuY29tMDwGCWCGSAGG+EIBDQQvFi1UZXN0aW5nIGNvbWJpbmVkIFVS +SSBhbmQgRE5TIG5hbWUgY29uc3RyYWludHMwDQYJKoZIhvcNAQELBQADggEBAKA5 +4xPLP6RVWnOSkHYi+Cr6KegUOQNxmPVoaAwph+QMR8Z2sdLKIWt9U1xL4lkH6L51 +S54kLMH/jnv2WD9bYvDe+CjWZEM97Nm+YURHDv5QAoqxY9gw9Y8TMGi8xOC5cubR +JXpjN4U60N/mdHbxMQbcuHJLowjXSlCp3q6S+iz2Bh7TaP8w7EoTR6pQEK6nMo6L +C/CRztvpaFgOZ4ia8O8C3EHBaBSECWWtPMyh6WappneKkT2p9wh8LdMB58AjKqoJ +/Zg6lp0Qj+NOhpVYXiT2+RlxVkttZJmLv3DIYH9LMsS8jhnTriIXpx2DaS56dEVn +aFzrG/ecf3YLPUrKgHw= +-----END CERTIFICATE----- diff --git a/certs/test/cert-ext-ncdns.pem b/certs/test/cert-ext-ncdns.pem new file mode 100644 index 000000000..2ebacfc16 --- /dev/null +++ b/certs/test/cert-ext-ncdns.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIUBd10yS05H9xt7w0qR43nO7q47hUwDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCQVUxEzARBgNVBAgMClF1ZWVuc2xhbmQxETAPBgNVBAcM +CEJyaXNiYW5lMRQwEgYDVQQKDAt3b2xmU1NMIEluYzEUMBIGA1UECwwLRW5naW5l +ZXJpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTAeFw0yNjAxMjIyMTE4MjJa +Fw0yODEwMTgyMTE4MjJaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApRdWVlbnNs +YW5kMREwDwYDVQQHDAhCcmlzYmFuZTEUMBIGA1UECgwLd29sZlNTTCBJbmMxFDAS +BgNVBAsMC0VuZ2luZWVyaW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW230kVBJwFlxkWu +8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98q2SoF/zKXXu6 +4CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSzrgCgY8X2fwtZ +aHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7LyG1/WZRDL1U +s+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAyloAyI5Whd7oT +0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW39nQT63XAgMB +AAGjgb4wgbswHQYDVR0OBBYEFLMRMsmSmITiyfjQO24DQsofDo48MB8GA1UdIwQY +MBaAFLMRMsmSmITiyfjQO24DQsofDo48MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD +VR0PAQH/BAQDAgGGMCwGA1UdHgEB/wQiMCCgHjANggt3b2xmc3NsLmNvbTANggtl +eGFtcGxlLmNvbTAnBglghkgBhvhCAQ0EGhYYVGVzdGluZyBuYW1lIGNvbnN0cmFp +bnRzMA0GCSqGSIb3DQEBCwUAA4IBAQCkCFJl/uWp3JinCS01T3vxZF8UT71w165B +Fqz49w4UScy3wStJ/fcP/+M1mxbClvGmfBhNW7l8BNixPU4L9OYs+5/rWsMh6No+ +ZbPjWfkkHRWlmGKVNmk+C9OD7vVOAGVuPhdQGZfs9rYD3AqPk+CYC7AE/o3T97C9 +tGzfpt4ccEjyFV5liDnxr2SvMuG2KBIJovX2+QYXsb4u4tinKyOyvA9PF8nGLYvA +mQk0ZQy+vnYjWv3luU5ZEBBPrRlC9Ph5sOzNKBaKdZ+GAy6UCqMYlFHSzq+0GsnO +I1zCNn1XgpvX6V/31AVYPgiAQj6qMHuYxJR0pQG5kTeN3v+FdXR3 +-----END CERTIFICATE----- diff --git a/certs/test/cert-ext-ncip.der b/certs/test/cert-ext-ncip.der new file mode 100644 index 000000000..69f0c9ac5 Binary files /dev/null and b/certs/test/cert-ext-ncip.der differ diff --git a/certs/test/cert-ext-ncip.pem b/certs/test/cert-ext-ncip.pem new file mode 100644 index 000000000..47942bd0d --- /dev/null +++ b/certs/test/cert-ext-ncip.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIENDCCAxygAwIBAgIUNQdk2FntK/mSUrXLLySPJwId8FowDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCQVUxEzARBgNVBAgMClF1ZWVuc2xhbmQxETAPBgNVBAcM +CEJyaXNiYW5lMRQwEgYDVQQKDAt3b2xmU1NMIEluYzEUMBIGA1UECwwLRW5naW5l +ZXJpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTAeFw0yNjAxMjAyMjQ2MTFa +Fw0yODEwMTYyMjQ2MTFaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApRdWVlbnNs +YW5kMREwDwYDVQQHDAhCcmlzYmFuZTEUMBIGA1UECgwLd29sZlNTTCBJbmMxFDAS +BgNVBAsMC0VuZ2luZWVyaW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW230kVBJwFlxkWu +8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98q2SoF/zKXXu6 +4CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSzrgCgY8X2fwtZ +aHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7LyG1/WZRDL1U +s+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAyloAyI5Whd7oT +0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW39nQT63XAgMB +AAGjga8wgawwHQYDVR0OBBYEFLMRMsmSmITiyfjQO24DQsofDo48MB8GA1UdIwQY +MBaAFLMRMsmSmITiyfjQO24DQsofDo48MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD +VR0PAQH/BAQDAgGGMBoGA1UdHgEB/wQQMA6gDDAKhwjAqAEA////ADAqBglghkgB +hvhCAQ0EHRYbVGVzdGluZyBJUCBuYW1lIGNvbnN0cmFpbnRzMA0GCSqGSIb3DQEB +CwUAA4IBAQCOpK6M3RK5jcp2E3CaH9bTQfbcbppXJwFHdUG85sjf/K5i6c3/hr3X +eKihdD+h62KgiUZFPrGzEDCLD26EWwiJJCkxakhjtY45r9luLXj3kpUMXQ3aeqXC +M5rtW80w+9Hz0WEkK4UkaKEultWX8mnrF7dH/MHctyyLDcy28qbH5SwAhVqE1XAZ +0j/1Mw0MsQd8ycpbmONhQEgXTVlHspvn/vBcKvGS6oimeTlgO+Ghlnt9eeQfFRT0 +y7MacpE2kULmzy8qzXxqVvQI2V66wz7xC/8BYzj/KBYGwi7e2LeGKU5eEV4622sR +QtT99fpv0XMKNPMTI5Iz9l/ZPWvZgXJE +-----END CERTIFICATE----- diff --git a/certs/test/cert-ext-ncmulti.der b/certs/test/cert-ext-ncmulti.der new file mode 100644 index 000000000..e2d744744 Binary files /dev/null and b/certs/test/cert-ext-ncmulti.der differ diff --git a/certs/test/cert-ext-ncmulti.pem b/certs/test/cert-ext-ncmulti.pem new file mode 100644 index 000000000..f913bd288 --- /dev/null +++ b/certs/test/cert-ext-ncmulti.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEljCCA36gAwIBAgIUL0V4sh34dBCPx7JGnW1VkkjOB4wwDQYJKoZIhvcNAQEL +BQAwezELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0Jv +emVtYW4xFDASBgNVBAoMC3dvbGZTU0wgSW5jMRgwFgYDVQQLDA9EZXYgYW5kIFRl +c3RpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTAeFw0yNjAxMjIyMTE4MjJa +Fw0yODEwMTgyMTE4MjJaMHsxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdNb250YW5h +MRAwDgYDVQQHDAdCb3plbWFuMRQwEgYDVQQKDAt3b2xmU1NMIEluYzEYMBYGA1UE +CwwPRGV2IGFuZCBUZXN0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW230kVBJwFlxkWu +8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98q2SoF/zKXXu6 +4CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSzrgCgY8X2fwtZ +aHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7LyG1/WZRDL1U +s+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAyloAyI5Whd7oT +0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW39nQT63XAgMB +AAGjggEQMIIBDDAdBgNVHQ4EFgQUsxEyyZKYhOLJ+NA7bgNCyh8OjjwwHwYDVR0j +BBgwFoAUsxEyyZKYhOLJ+NA7bgNCyh8OjjwwEgYDVR0TAQH/BAgwBgEB/wIBADAO +BgNVHQ8BAf8EBAMCAYYwYAYDVR0eAQH/BFYwVKAgMA6CDC5leGFtcGxlLmNvbTAO +gQwuZXhhbXBsZS5jb22hMDAWghQuYmxvY2tlZC5leGFtcGxlLmNvbTAWgRQuYmxv +Y2tlZC5leGFtcGxlLmNvbTBEBglghkgBhvhCAQ0ENxY1VGVzdGluZyBtaXhlZCBw +ZXJtaXR0ZWQgYW5kIGV4Y2x1ZGVkIG5hbWUgY29uc3RyYWludHMwDQYJKoZIhvcN +AQELBQADggEBAEULvBMSjm5ENjZ7WNDnSPXwKm3ka1eK7AUCTmZdMl3Op1ge/yqq +rdkG2xvX4cfAe8iPOUDMyvh/Jf9B8T2njOGnpUTueslRzDvOs7qBo/0VYRalkye9 +Qw0ysgKcvvnevMHMnErGCkLEvL0VmTTmSR9HA8YxRih962fBrv38GZytqmFw/TEm +s0KMQRumxQWPHHAQ/AbWbzCIXZo0kOsZlIZV3geCf9M0klDhG/XLgFJqihwGDeT4 +Yvy1mtqJu87LduC03UKKqbMR0ltTOkoCm5xTjKQuTbHxPBw2q8UVZ7Ud2iE47UXi +c4Zd4IxO9TTO5SCQaZLPq0dhp3SxjgtZ3tw= +-----END CERTIFICATE----- diff --git a/certs/test/gen-ext-certs.sh b/certs/test/gen-ext-certs.sh index 65e8caca5..41e8f00e9 100755 --- a/certs/test/gen-ext-certs.sh +++ b/certs/test/gen-ext-certs.sh @@ -81,7 +81,7 @@ rm -f ./certs/test/cert-ext-mnc.pem OUT=certs/test/cert-ext-ncdns -KEYFILE=certs/test/cert-ext-nc-key.der +KEYFILE=certs/test/cert-ext-ncdns-key.der CONFIG=certs/test/cert-ext-ncdns.cfg tee >$CONFIG <$CONFIG <$CONFIG <$CONFIG <altNames, x509->heap); x509->altNames = NULL; } + #ifndef IGNORE_NAME_CONSTRAINTS + if (x509->permittedNames) { + FreeNameSubtrees(x509->permittedNames, x509->heap); + x509->permittedNames = NULL; + } + if (x509->excludedNames) { + FreeNameSubtrees(x509->excludedNames, x509->heap); + x509->excludedNames = NULL; + } + #endif #ifdef WOLFSSL_DUAL_ALG_CERTS XFREE(x509->sapkiDer, x509->heap, DYNAMIC_TYPE_X509_EXT); @@ -13325,6 +13335,62 @@ static void AddSessionCertToChain(WOLFSSL_X509_CHAIN* chain, * OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL || * WOLFSSL_ACERT */ +#if (defined(KEEP_PEER_CERT) || defined(SESSION_CERTS) || \ + defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)) && \ + !defined(IGNORE_NAME_CONSTRAINTS) +/* Duplicate a Base_entry */ +static Base_entry* BaseEntryDup(Base_entry* from, void* heap) +{ + Base_entry* entry; + + if (from == NULL) { + return NULL; + } + + entry = (Base_entry*)XMALLOC(sizeof(Base_entry), heap, + DYNAMIC_TYPE_ALTNAME); + if (entry == NULL) { + return NULL; + } + XMEMSET(entry, 0, sizeof(Base_entry)); + + entry->name = (char*)XMALLOC((word32)from->nameSz + 1, heap, + DYNAMIC_TYPE_ALTNAME); + if (entry->name == NULL) { + XFREE(entry, heap, DYNAMIC_TYPE_ALTNAME); + return NULL; + } + XMEMCPY(entry->name, from->name, (word32)from->nameSz); + entry->name[from->nameSz] = '\0'; + entry->nameSz = from->nameSz; + entry->type = from->type; + + return entry; +} + +/* Copy a Base_entry list */ +static int CopyBaseEntry(Base_entry** to, Base_entry* from, void* heap) +{ + Base_entry** next = to; + + if (to == NULL) { + return BAD_FUNC_ARG; + } + + for (; from != NULL; from = from->next) { + Base_entry* entry = BaseEntryDup(from, heap); + if (entry == NULL) { + WOLFSSL_MSG("BaseEntryDup failed"); + return MEMORY_E; + } + *next = entry; + next = &entry->next; + } + + return 0; +} +#endif /* (KEEP_PEER_CERT || SESSION_CERTS || OPENSSL_EXTRA || + * OPENSSL_EXTRA_X509_SMALL) && !IGNORE_NAME_CONSTRAINTS */ #if defined(KEEP_PEER_CERT) || defined(SESSION_CERTS) || \ defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) @@ -13661,6 +13727,23 @@ int CopyDecodedToX509(WOLFSSL_X509* x509, DecodedCert* dCert) #endif /* OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL */ x509->altNamesNext = x509->altNames; /* index hint */ +#ifndef IGNORE_NAME_CONSTRAINTS + /* copy name constraints from dCert to X509 */ + if (dCert->permittedNames != NULL) { + if (CopyBaseEntry(&x509->permittedNames, dCert->permittedNames, + x509->heap) != 0) { + return MEMORY_E; + } + } + if (dCert->excludedNames != NULL) { + if (CopyBaseEntry(&x509->excludedNames, dCert->excludedNames, + x509->heap) != 0) { + return MEMORY_E; + } + } + x509->nameConstraintCrit = dCert->extNameConstraintCrit; +#endif /* !IGNORE_NAME_CONSTRAINTS */ + x509->isCa = dCert->isCA; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) x509->basicConstCrit = dCert->extBasicConstCrit; diff --git a/src/ssl_sk.c b/src/ssl_sk.c index 4b01b443f..2d1d374c2 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -168,6 +168,7 @@ static void* wolfssl_sk_node_get_data(WOLFSSL_STACK* node, int no_static) case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_GENERAL_SUBTREE: default: ret = node->data.generic; break; @@ -212,6 +213,7 @@ static void wolfssl_sk_node_set_data(WOLFSSL_STACK* node, WOLF_STACK_TYPE type, case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_GENERAL_SUBTREE: default: node->data.generic = (void*)data; #ifdef OPENSSL_ALL @@ -430,6 +432,7 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) case STACK_TYPE_BY_DIR_hash: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_GENERAL_SUBTREE: default: WOLFSSL_MSG("Unsupported stack type"); err = 1; @@ -622,6 +625,7 @@ void* wolfSSL_sk_value(const WOLFSSL_STACK* sk, int i) case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_GENERAL_SUBTREE: default: val = sk->data.generic; break; @@ -806,6 +810,12 @@ static wolfSSL_sk_freefunc wolfssl_sk_get_free_func(WOLF_STACK_TYPE type) case STACK_TYPE_GEN_NAME: func = (wolfSSL_sk_freefunc)wolfSSL_GENERAL_NAME_free; break; + case STACK_TYPE_GENERAL_SUBTREE: + #if defined(OPENSSL_EXTRA) && !defined(IGNORE_NAME_CONSTRAINTS) && \ + !defined(WOLFSSL_LINUXKM) + func = (wolfSSL_sk_freefunc)wolfSSL_GENERAL_SUBTREE_free; + #endif + break; case STACK_TYPE_STRING: #if defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) || \ defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) diff --git a/src/x509.c b/src/x509.c index 7594b0dae..cec26ca24 100644 --- a/src/x509.c +++ b/src/x509.c @@ -2218,6 +2218,160 @@ out: #endif /* OPENSSL_ALL || OPENSSL_EXTRA */ +#if defined(OPENSSL_EXTRA) && !defined(IGNORE_NAME_CONSTRAINTS) && \ + !defined(WOLFSSL_LINUXKM) +/* + * Convert a Base_entry linked list to a STACK of GENERAL_SUBTREE. + * + * Base_entry stores name constraint data from DecodedCert. This function + * converts it to GENERAL_SUBTREE format. + * + * Supported types: ASN_DNS_TYPE, ASN_RFC822_TYPE, ASN_DIR_TYPE, ASN_IP_TYPE, + * ASN_URI_TYPE + * + * Returns 0 on success, negative on error. + */ +static int ConvertBaseEntryToSubtreeStack(Base_entry* list, WOLFSSL_STACK* sk, + void* heap) +{ + Base_entry* entry = list; + WOLFSSL_GENERAL_SUBTREE* subtree = NULL; + WOLFSSL_GENERAL_NAME* gn = NULL; + (void)heap; + + while (entry != NULL) { + + if (entry->type != ASN_DNS_TYPE && entry->type != ASN_RFC822_TYPE && + entry->type != ASN_DIR_TYPE && entry->type != ASN_IP_TYPE && + entry->type != ASN_URI_TYPE) { + entry = entry->next; + continue; + } + + /* Allocate subtree and general name */ + subtree = (WOLFSSL_GENERAL_SUBTREE*)XMALLOC( + sizeof(WOLFSSL_GENERAL_SUBTREE), heap, DYNAMIC_TYPE_OPENSSL); + if (subtree == NULL) { + WOLFSSL_MSG("Failed to allocate GENERAL_SUBTREE"); + return MEMORY_E; + } + XMEMSET(subtree, 0, sizeof(WOLFSSL_GENERAL_SUBTREE)); + + gn = wolfSSL_GENERAL_NAME_new(); + if (gn == NULL) { + WOLFSSL_MSG("Failed to allocate GENERAL_NAME"); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + + /* Free default ia5 string allocated by GENERAL_NAME_new */ + wolfSSL_ASN1_STRING_free(gn->d.ia5); + gn->d.ia5 = NULL; + + switch (entry->type) { + case ASN_DNS_TYPE: + case ASN_RFC822_TYPE: + case ASN_URI_TYPE: + { + if (entry->type == ASN_DNS_TYPE) { + gn->type = WOLFSSL_GEN_DNS; + } + else if (entry->type == ASN_RFC822_TYPE) { + gn->type = WOLFSSL_GEN_EMAIL; + } + else { + gn->type = WOLFSSL_GEN_URI; + } + gn->d.ia5 = wolfSSL_ASN1_STRING_new(); + if (gn->d.ia5 == NULL) { + WOLFSSL_MSG("Failed to allocate ASN1_STRING"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + if (wolfSSL_ASN1_STRING_set(gn->d.ia5, entry->name, + entry->nameSz) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("Failed to set ASN1_STRING"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + gn->d.ia5->type = WOLFSSL_V_ASN1_IA5STRING; + break; + } + + case ASN_DIR_TYPE: + { + byte* seqBuf = NULL; + unsigned char* p = NULL; + int seqLen = 0; + + /* Wrap in SEQUENCE and parse as X509_NAME */ + gn->type = WOLFSSL_GEN_DIRNAME; + seqBuf = (byte*)XMALLOC((word32)entry->nameSz + MAX_SEQ_SZ, + heap, DYNAMIC_TYPE_TMP_BUFFER); + if (seqBuf == NULL) { + WOLFSSL_MSG("Failed to allocate sequence buffer"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + + seqLen = SetSequence(entry->nameSz, seqBuf); + XMEMCPY(seqBuf + seqLen, entry->name, entry->nameSz); + + p = seqBuf; + gn->d.directoryName = wolfSSL_d2i_X509_NAME(NULL, &p, + (long)entry->nameSz + seqLen); + XFREE(seqBuf, heap, DYNAMIC_TYPE_TMP_BUFFER); + + if (gn->d.directoryName == NULL) { + WOLFSSL_MSG("Failed to parse directoryName"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return ASN_PARSE_E; + } + break; + } + + case ASN_IP_TYPE: + { + /* For IP address, store raw bytes as OCTET_STRING. */ + gn->type = WOLFSSL_GEN_IPADD; + gn->d.iPAddress = wolfSSL_ASN1_STRING_new(); + if (gn->d.iPAddress == NULL) { + WOLFSSL_MSG("Failed to allocate ASN1_STRING for IP"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + if (wolfSSL_ASN1_STRING_set(gn->d.iPAddress, entry->name, + entry->nameSz) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("Failed to set IP ASN1_STRING"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + gn->d.iPAddress->type = WOLFSSL_V_ASN1_OCTET_STRING; + break; + } + } + + subtree->base = gn; + + if (wolfSSL_sk_push(sk, subtree) <= 0) { + WOLFSSL_MSG("Failed to push subtree onto stack"); + wolfSSL_GENERAL_NAME_free(gn); + XFREE(subtree, heap, DYNAMIC_TYPE_OPENSSL); + return MEMORY_E; + } + entry = entry->next; + } + + return 0; +} +#endif /* OPENSSL_EXTRA && !IGNORE_NAME_CONSTRAINTS && !WOLFSSL_LINUXKM */ + #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) /* Looks for the extension matching the passed in nid * @@ -2720,9 +2874,70 @@ void* wolfSSL_X509_get_ext_d2i(const WOLFSSL_X509* x509, int nid, int* c, } break; + #if defined(OPENSSL_EXTRA) && !defined(IGNORE_NAME_CONSTRAINTS) && \ + !defined(WOLFSSL_LINUXKM) case NAME_CONS_OID: - WOLFSSL_MSG("Name Constraint OID extension not supported"); - break; + { + WOLFSSL_NAME_CONSTRAINTS* nc = NULL; + + /* Check if name constraints exist in stored X509 */ + if (x509->permittedNames == NULL && x509->excludedNames == NULL) { + WOLFSSL_MSG("No Name Constraints set"); + break; + } + + if (c != NULL) { + *c = x509->nameConstraintCrit; + } + + nc = (WOLFSSL_NAME_CONSTRAINTS*)XMALLOC( + sizeof(WOLFSSL_NAME_CONSTRAINTS), x509->heap, + DYNAMIC_TYPE_OPENSSL); + if (nc == NULL) { + WOLFSSL_MSG("Failed to allocate NAME_CONSTRAINTS"); + break; + } + XMEMSET(nc, 0, sizeof(WOLFSSL_NAME_CONSTRAINTS)); + + /* Convert permitted names */ + if (x509->permittedNames != NULL) { + nc->permittedSubtrees = wolfSSL_sk_new_null(); + if (nc->permittedSubtrees == NULL) { + WOLFSSL_MSG("Failed to allocate permitted stack"); + wolfSSL_NAME_CONSTRAINTS_free(nc); + break; + } + nc->permittedSubtrees->type = STACK_TYPE_GENERAL_SUBTREE; + + if (ConvertBaseEntryToSubtreeStack(x509->permittedNames, + nc->permittedSubtrees, x509->heap) != 0) { + WOLFSSL_MSG("Failed to convert permitted names"); + wolfSSL_NAME_CONSTRAINTS_free(nc); + break; + } + } + + /* Convert excluded names */ + if (x509->excludedNames != NULL) { + nc->excludedSubtrees = wolfSSL_sk_new_null(); + if (nc->excludedSubtrees == NULL) { + WOLFSSL_MSG("Failed to allocate excluded stack"); + wolfSSL_NAME_CONSTRAINTS_free(nc); + break; + } + nc->excludedSubtrees->type = STACK_TYPE_GENERAL_SUBTREE; + + if (ConvertBaseEntryToSubtreeStack(x509->excludedNames, + nc->excludedSubtrees, x509->heap) != 0) { + WOLFSSL_MSG("Failed to convert excluded names"); + wolfSSL_NAME_CONSTRAINTS_free(nc); + break; + } + } + + return nc; + } + #endif /* OPENSSL_EXTRA && !IGNORE_NAME_CONSTRAINTS && !WOLFSSL_LINUXKM */ case PRIV_KEY_USAGE_PERIOD_OID: WOLFSSL_MSG("Private Key Usage Period extension not supported"); @@ -5112,6 +5327,373 @@ void wolfSSL_EXTENDED_KEY_USAGE_free(WOLFSSL_STACK * sk) wolfSSL_sk_X509_pop_free(sk, NULL); } +#if !defined(IGNORE_NAME_CONSTRAINTS) && !defined(WOLFSSL_LINUXKM) +/* + * Allocate and initialize an empty GENERAL_SUBTREE structure. + * Returns NULL on allocation failure. + */ +WOLFSSL_GENERAL_SUBTREE* wolfSSL_GENERAL_SUBTREE_new(void) +{ + WOLFSSL_GENERAL_SUBTREE* subtree; + + WOLFSSL_ENTER("wolfSSL_GENERAL_SUBTREE_new"); + + subtree = (WOLFSSL_GENERAL_SUBTREE*)XMALLOC(sizeof(WOLFSSL_GENERAL_SUBTREE), + NULL, DYNAMIC_TYPE_OPENSSL); + if (subtree == NULL) { + WOLFSSL_MSG("Failed to allocate GENERAL_SUBTREE"); + return NULL; + } + XMEMSET(subtree, 0, sizeof(WOLFSSL_GENERAL_SUBTREE)); + return subtree; +} + +/* + * Create an empty NAME_CONSTRAINTS structure. + * Returns NULL on allocation failure. + */ +WOLFSSL_NAME_CONSTRAINTS* wolfSSL_NAME_CONSTRAINTS_new(void) +{ + WOLFSSL_NAME_CONSTRAINTS* nc; + + WOLFSSL_ENTER("wolfSSL_NAME_CONSTRAINTS_new"); + + nc = (WOLFSSL_NAME_CONSTRAINTS*)XMALLOC(sizeof(WOLFSSL_NAME_CONSTRAINTS), + NULL, DYNAMIC_TYPE_OPENSSL); + if (nc == NULL) { + WOLFSSL_MSG("Failed to allocate NAME_CONSTRAINTS"); + return NULL; + } + XMEMSET(nc, 0, sizeof(WOLFSSL_NAME_CONSTRAINTS)); + return nc; +} + +/* Free a GENERAL_SUBTREE and its contents. */ +void wolfSSL_GENERAL_SUBTREE_free(WOLFSSL_GENERAL_SUBTREE* subtree) +{ + if (subtree == NULL) { + return; + } + wolfSSL_GENERAL_NAME_free(subtree->base); + XFREE(subtree, NULL, DYNAMIC_TYPE_OPENSSL); +} + +/* Free a NAME_CONSTRAINTS structure and all its contents. */ +void wolfSSL_NAME_CONSTRAINTS_free(WOLFSSL_NAME_CONSTRAINTS* nc) +{ + WOLFSSL_ENTER("wolfSSL_NAME_CONSTRAINTS_free"); + + if (nc == NULL) { + return; + } + + if (nc->permittedSubtrees != NULL) { + wolfSSL_sk_pop_free(nc->permittedSubtrees, + (wolfSSL_sk_freefunc)wolfSSL_GENERAL_SUBTREE_free); + } + + if (nc->excludedSubtrees != NULL) { + wolfSSL_sk_pop_free(nc->excludedSubtrees, + (wolfSSL_sk_freefunc)wolfSSL_GENERAL_SUBTREE_free); + } + + XFREE(nc, NULL, DYNAMIC_TYPE_OPENSSL); +} + +/* Get number of items in GENERAL_SUBTREE stack. */ +int wolfSSL_sk_GENERAL_SUBTREE_num(const WOLFSSL_STACK* sk) +{ + WOLFSSL_ENTER("wolfSSL_sk_GENERAL_SUBTREE_num"); + + return wolfSSL_sk_num(sk); +} + +/* Get GENERAL_SUBTREE at index from stack. */ +WOLFSSL_GENERAL_SUBTREE* wolfSSL_sk_GENERAL_SUBTREE_value( + const WOLFSSL_STACK* sk, int idx) +{ + WOLFSSL_ENTER("wolfSSL_sk_GENERAL_SUBTREE_value"); + + return (WOLFSSL_GENERAL_SUBTREE*)wolfSSL_sk_value(sk, idx); +} + +/* Check IP address string matches constraint. + * + * name: IP address string (ex "192.168.1.50") + * nameSz: length of name string + * gn: GENERAL_NAME containing IP constraint (IP + mask bytes) + * + * Return 1 on match, otherwise 0 + */ +static int MatchIpName(const char* name, int nameSz, WOLFSSL_GENERAL_NAME* gn) +{ + int ipLen = 0; + int constraintLen; + char ipStr[WOLFSSL_MAX_IPSTR]; + unsigned char ipBytes[16]; /* Max 16 bytes for IPv6 */ + const unsigned char* constraintData; + + if (name == NULL || nameSz <= 0 || gn == NULL || gn->d.iPAddress == NULL) { + return 0; + } + + constraintData = wolfSSL_ASN1_STRING_get0_data(gn->d.iPAddress); + constraintLen = wolfSSL_ASN1_STRING_length(gn->d.iPAddress); + if (constraintData == NULL || constraintLen <= 0) { + return 0; + } + + /* Null-terminate IP string */ + if (nameSz >= (int)sizeof(ipStr)) { + return 0; + } + XMEMCPY(ipStr, name, nameSz); + ipStr[nameSz] = '\0'; + + /* IPv4 constraint 8 bytes (IP + mask), + * IPv6 constraint 32 bytes (IP + mask) */ + if (constraintLen == 8) { + if (XINET_PTON(WOLFSSL_IP4, ipStr, ipBytes) == 1) { + ipLen = 4; + } + } + else if (constraintLen == 32) { + if (XINET_PTON(WOLFSSL_IP6, ipStr, ipBytes) == 1) { + ipLen = 16; + } + } + + if (ipLen == 0) { + return 0; + } + + return wolfssl_local_MatchIpSubnet(ipBytes, ipLen, + constraintData, constraintLen); +} + +/* Extract host from URI for name constraint matching. + * URI format: scheme://[userinfo@]host[:port][/path][?query][#fragment] + * IPv6 literals are enclosed in brackets: scheme://[ipv6addr]:port/path + * Returns pointer to host start and sets hostLen, or NULL on failure. */ +static const char* ExtractHostFromUri(const char* uri, int uriLen, int* hostLen) +{ + const char* hostStart; + const char* hostEnd; + const char* p; + const char* uriEnd = uri + uriLen; + + if (uri == NULL || uriLen <= 0 || hostLen == NULL) { + return NULL; + } + + /* Find "://" to skip scheme */ + hostStart = NULL; + for (p = uri; p < uriEnd - 2; p++) { + if (p[0] == ':' && p[1] == '/' && p[2] == '/') { + hostStart = p + 3; + break; + } + } + if (hostStart == NULL || hostStart >= uriEnd) { + return NULL; + } + + /* Skip userinfo if present (look for @ before any /, ?, #) + * userinfo can contain ':' (ex: user:pass@host), don't stop at ':' + * For IPv6, also don't stop at '[' in userinfo */ + for (p = hostStart; p < uriEnd; p++) { + if (*p == '@') { + hostStart = p + 1; + break; + } + if (*p == '/' || *p == '?' || *p == '#') { + /* No userinfo found */ + break; + } + /* If '[' before '@', found IPv6 literal, not userinfo */ + if (*p == '[') { + break; + } + } + if (hostStart >= uriEnd) { + return NULL; + } + + /* Check for IPv6 literal */ + if (*hostStart == '[') { + /* Find closing bracket, skip opening one */ + hostStart++; + hostEnd = hostStart; + while (hostEnd < uriEnd && *hostEnd != ']') { + hostEnd++; + } + if (hostEnd >= uriEnd) { + /* No closing bracket found, malformed */ + return NULL; + } + /* hostEnd points to closing bracket, extract content between */ + *hostLen = (int)(hostEnd - hostStart); + if (*hostLen <= 0) { + return NULL; + } + return hostStart; + } + + /* Regular hostname, find end */ + hostEnd = hostStart; + while (hostEnd < uriEnd && *hostEnd != ':' && *hostEnd != '/' && + *hostEnd != '?' && *hostEnd != '#') { + hostEnd++; + } + + *hostLen = (int)(hostEnd - hostStart); + if (*hostLen <= 0) { + return NULL; + } + + return hostStart; +} + +/* Helper to check if name string matches a single GENERAL_NAME constraint. + * Returns 1 if matches, 0 if not. */ +static int MatchNameConstraint(int type, const char* name, int nameSz, + WOLFSSL_GENERAL_NAME* gn) +{ + const char* baseStr; + int baseLen; + + if (gn == NULL || gn->type != type) { + return 0; + } + + switch (type) { + case WOLFSSL_GEN_IPADD: + return MatchIpName(name, nameSz, gn); + + case WOLFSSL_GEN_DNS: + case WOLFSSL_GEN_EMAIL: + case WOLFSSL_GEN_URI: + if (gn->d.ia5 == NULL) { + return 0; + } + baseStr = (const char*)wolfSSL_ASN1_STRING_get0_data(gn->d.ia5); + baseLen = wolfSSL_ASN1_STRING_length(gn->d.ia5); + if (baseStr == NULL || baseLen <= 0) { + return 0; + } + + if (type == WOLFSSL_GEN_EMAIL) { + return wolfssl_local_MatchBaseName(ASN_RFC822_TYPE, name, + nameSz, baseStr, baseLen); + } + else if (type == WOLFSSL_GEN_URI) { + const char* host; + int hostLen; + + /* For URI, extract host and match against DNS-style */ + host = ExtractHostFromUri(name, nameSz, &hostLen); + if (host == NULL) { + return 0; + } + return wolfssl_local_MatchBaseName(ASN_DNS_TYPE, host, hostLen, + baseStr, baseLen); + } + else { + /* WOLFSSL_GEN_DNS uses DNS-style matching */ + return wolfssl_local_MatchBaseName(ASN_DNS_TYPE, name, nameSz, + baseStr, baseLen); + } + + default: + /* Unsupported type */ + return 0; + } +} + +/* + * Check if a name string satisfies given name constraints. + * + * nc: NAME_CONSTRAINTS struct containing permitted/excluded subtrees + * type: GeneralName type (WOLFSSL_GEN_DNS, WOLFSSL_GEN_EMAIL, etc.) + * name: The name string to check + * nameSz: Length of name string + * + * Returns 1 if name satisfies constraints (permitted and not excluded), + * otherwise 0 if name does not satisfy constraints or on error + * + * A name satisfies constraints if permitted subtrees exist for the type, + * name matches at least one, and name does not match any excluded subtree. + */ +int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc, + int type, const char* name, int nameSz) +{ + int i, num; + int hasPermittedType = 0; + int matchedPermitted = 0; + WOLFSSL_GENERAL_SUBTREE* subtree; + WOLFSSL_GENERAL_NAME* gn; + + WOLFSSL_ENTER("wolfSSL_NAME_CONSTRAINTS_check_name"); + + if (nc == NULL || name == NULL || nameSz <= 0) { + WOLFSSL_MSG("Bad argument to NAME_CONSTRAINTS_check_name"); + return 0; + } + + /* Check permitted subtrees */ + if (nc->permittedSubtrees != NULL) { + num = wolfSSL_sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); + for (i = 0; i < num; i++) { + subtree = wolfSSL_sk_GENERAL_SUBTREE_value( + nc->permittedSubtrees, i); + if (subtree == NULL || subtree->base == NULL) { + continue; + } + + gn = subtree->base; + if (gn->type != type) { + continue; + } + hasPermittedType = 1; + + if (MatchNameConstraint(type, name, nameSz, gn)) { + matchedPermitted = 1; + break; + } + } + } + + /* If permitted constraints exist for this type but none matched, fail */ + if (hasPermittedType && !matchedPermitted) { + WOLFSSL_MSG("Name not in permitted subtrees"); + return 0; + } + + /* Check excluded subtrees */ + if (nc->excludedSubtrees != NULL) { + num = wolfSSL_sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); + for (i = 0; i < num; i++) { + subtree = wolfSSL_sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i); + if (subtree == NULL || subtree->base == NULL) { + continue; + } + + gn = subtree->base; + if (gn->type != type) { + continue; + } + + if (MatchNameConstraint(type, name, nameSz, gn)) { + WOLFSSL_MSG("Name in excluded subtrees"); + return 0; + } + } + } + + return 1; +} +#endif /* !IGNORE_NAME_CONSTRAINTS && !WOLFSSL_LINUXKM */ + #if defined(OPENSSL_ALL) && !defined(NO_BIO) /* Outputs name string of the given WOLFSSL_GENERAL_NAME_OBJECT to WOLFSSL_BIO. * Can handle following GENERAL_NAME_OBJECT types: diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index 0a1218650..84b26b55b 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1549,3 +1549,727 @@ int test_wolfSSL_X509V3_EXT_print(void) return EXPECT_RESULT(); } +/* + * Test retrieving Name Constraints extension via X509_get_ext_d2i. + * Tests basic retrieval of permitted and excluded subtrees, stack operations + * (num, value), GENERAL_NAME type and data extraction, free functions. + */ +int test_wolfSSL_X509_get_ext_d2i_name_constraints(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + GENERAL_SUBTREE* subtree = NULL; + GENERAL_NAME* gn = NULL; + int numPermitted = 0; + int numExcluded = 0; + int critical = -1; + + /* Test NULL input handling */ + ExpectNull(X509_get_ext_d2i(NULL, NID_name_constraints, NULL, NULL)); + + /* Test certificate without name constraints + * server-cert.pem does not have name constraints extension */ + ExpectTrue((f = XFOPEN("./certs/server-cert.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + /* Should return NULL for certificate without name constraints */ + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + &critical, NULL); + ExpectNull(nc); + X509_free(x509); + x509 = NULL; + + /* Test certificate with permitted email name constraint. + * cert-ext-nc.pem has nameConstraints with permitted email */ + ExpectTrue((f = XFOPEN("./certs/test/cert-ext-nc.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + critical = -1; + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + &critical, NULL); + ExpectNotNull(nc); + + /* Verify critical flag is set (cert marks it critical) */ + ExpectIntEQ(critical, 1); + + /* Check permitted subtrees */ + if (nc != NULL) { + ExpectNotNull(nc->permittedSubtrees); + if (nc->permittedSubtrees != NULL) { + numPermitted = sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); + ExpectIntGT(numPermitted, 0); + + /* Get first permitted subtree */ + subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 0); + ExpectNotNull(subtree); + if (subtree != NULL) { + ExpectNotNull(subtree->base); + if (subtree->base != NULL) { + /* Check GENERAL_NAME type is GEN_EMAIL */ + gn = subtree->base; + ExpectIntEQ(gn->type, GEN_EMAIL); + + /* Verify email constraint value */ + ExpectNotNull(gn->d.ia5); + if (gn->d.ia5 != NULL) { + ExpectNotNull(gn->d.ia5->data); + ExpectIntGT(gn->d.ia5->length, 0); + } + } + } + } + + /* Check excluded subtrees, should be NULL or empty */ + if (nc->excludedSubtrees != NULL) { + numExcluded = sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); + ExpectIntEQ(numExcluded, 0); + } + + /* Test out of bounds access */ + if (nc->permittedSubtrees != NULL) { + ExpectNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 100)); + } + } + + /* Test NULL stack handling, wolfSSL returns 0 */ + ExpectIntEQ(sk_GENERAL_SUBTREE_num(NULL), 0); + ExpectNull(sk_GENERAL_SUBTREE_value(NULL, 0)); + + NAME_CONSTRAINTS_free(nc); + nc = NULL; + X509_free(x509); + x509 = NULL; + + /* Test free functions with NULL */ + NAME_CONSTRAINTS_free(NULL); + wolfSSL_GENERAL_SUBTREE_free(NULL); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test sk_GENERAL_SUBTREE_num and sk_GENERAL_SUBTREE_value functions. + */ +int test_wolfSSL_sk_GENERAL_SUBTREE(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + GENERAL_SUBTREE* subtree = NULL; + int num = 0; + int i; + + /* Load certificate with name constraints (cert-ext-nc.pem has 1 email) */ + ExpectTrue((f = XFOPEN("./certs/test/cert-ext-nc.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + + if (nc != NULL) { + ExpectNotNull(nc->permittedSubtrees); + if (nc->permittedSubtrees != NULL) { + /* Test sk_GENERAL_SUBTREE_num */ + num = sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); + ExpectIntGT(num, 0); + + /* Test sk_GENERAL_SUBTREE_value with valid indices */ + for (i = 0; i < num && i < 10; i++) { + subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i); + ExpectNotNull(subtree); + if (subtree != NULL) { + ExpectNotNull(subtree->base); + } + } + + /* Test sk_GENERAL_SUBTREE_value at boundaries */ + ExpectNotNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 0)); + if (num > 0) { + ExpectNotNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, + num - 1)); + } + + /* Test invalid indices (out of bounds) */ + ExpectNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, num)); + ExpectNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, + num + 1)); + ExpectNull(sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 10000)); + } + } + + /* Test NULL stack - wolfSSL returns 0 */ + ExpectIntEQ(sk_GENERAL_SUBTREE_num(NULL), 0); + ExpectNull(sk_GENERAL_SUBTREE_value(NULL, 0)); + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test GENERAL_NAME types in Name Constraints. + * Verify that different GENERAL_NAME types (DNS, EMAIL, DIRNAME) are properly + * extracted from name constraints. + */ +int test_wolfSSL_NAME_CONSTRAINTS_types(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + GENERAL_SUBTREE* subtree = NULL; + GENERAL_NAME* gn = NULL; + + /* Test EMAIL type constraint from cert-ext-nc.pem */ + ExpectTrue((f = XFOPEN("./certs/test/cert-ext-nc.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + if (EXPECT_SUCCESS()) { + ExpectNotNull(nc->permittedSubtrees); + } + if (EXPECT_SUCCESS()) { + subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 0); + ExpectNotNull(subtree); + } + if (EXPECT_SUCCESS()) { + ExpectNotNull(subtree->base); + } + if (EXPECT_SUCCESS()) { + gn = subtree->base; + ExpectIntEQ(gn->type, GEN_EMAIL); + ExpectNotNull(gn->d.ia5); + } + if (EXPECT_SUCCESS()) { + ExpectNotNull(gn->d.ia5->data); + ExpectIntGT(gn->d.ia5->length, 0); + } + if (EXPECT_SUCCESS()) { + /* Constraint should contain "wolfssl.com" */ + ExpectNotNull(XSTRSTR((const char*)gn->d.ia5->data, "wolfssl.com")); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test URI type in Name Constraints. Verifies that GEN_URI type name + * constraints are properly extracted and stored as IA5STRING. + */ +int test_wolfSSL_NAME_CONSTRAINTS_uri(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + GENERAL_SUBTREE* subtree = NULL; + GENERAL_NAME* gn = NULL; + int i; + int numSubtrees; + int foundUri = 0; + + /* Test URI type constraint from cert-ext-nc-combined.pem + * This cert has both URI and DNS constraints */ + ExpectTrue((f = XFOPEN("./certs/test/cert-ext-nc-combined.pem", "rb")) + != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + if (EXPECT_SUCCESS()) { + ExpectNotNull(nc->permittedSubtrees); + } + /* Find the URI constraint by iterating through subtrees + * (wolfSSL may store them in a different order than in the cert) */ + if (EXPECT_SUCCESS()) { + numSubtrees = sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); + for (i = 0; i < numSubtrees; i++) { + subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i); + if (subtree != NULL && subtree->base != NULL && + subtree->base->type == GEN_URI) { + gn = subtree->base; + foundUri = 1; + break; + } + } + ExpectIntEQ(foundUri, 1); + } + if (EXPECT_SUCCESS() && foundUri) { + ExpectNotNull(gn->d.ia5); + } + if (EXPECT_SUCCESS() && foundUri) { + ExpectNotNull(gn->d.ia5->data); + ExpectIntGT(gn->d.ia5->length, 0); + } + if (EXPECT_SUCCESS() && foundUri) { + /* Constraint should contain "wolfssl.com" */ + ExpectNotNull(XSTRSTR((const char*)gn->d.ia5->data, "wolfssl.com")); + } + + /* Test URI constraint matching with NAME_CONSTRAINTS_check_name + * Constraint is ".wolfssl.com" (leading dot), matches subdomains only */ + if (EXPECT_SUCCESS()) { + /* Full URIs with subdomain hosts - should match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://www.wolfssl.com/path", 28), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "http://sub.wolfssl.com", 22), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://a.b.c.wolfssl.com:8080/path?q=1", 39), 1); + + /* Exact domain, should not match .wolfssl.com per RFC 5280 */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://wolfssl.com/", 20), 0); + + /* Different domains, should not match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://www.example.com/", 24), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://fakewolfssl.com/", 24), 0); + + /* URI with userinfo, should extract host correctly */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://user@www.wolfssl.com/", 29), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://user:pass@www.wolfssl.com/path", 38), 1); + + /* IPv6 literal URIs, host extracted without brackets. + * These don't match .wolfssl.com constraint (different host type) */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://[::1]:8080/path", 23), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://[2001:db8::1]/", 22), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://[fe80::1%25eth0]:443/", 29), 0); + + /* IPv6 with userinfo */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://user@[::1]:8080/", 24), 0); + + /* Malformed IPv6 (missing closing bracket), should fail */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "https://[::1/path", 17), 0); + + /* Invalid URIs, should fail */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "not-a-uri", 9), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, + "://no-scheme", 12), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test IP address type in Name Constraints. + * Verifies that GEN_IPADD type name constraints are properly extracted + * and contain the raw IP bytes in OCTET_STRING format. + * Format: [IP bytes][subnet mask bytes] (8 bytes for IPv4, 32 for IPv6) + */ +int test_wolfSSL_NAME_CONSTRAINTS_ipaddr(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + GENERAL_SUBTREE* subtree = NULL; + GENERAL_NAME* gn = NULL; + int numPermitted = 0; + int critical = -1; + + /* Test IP address type constraint from cert-ext-ncip.pem + * This cert has permitted IP: 192.168.1.0/255.255.255.0 */ + if ((f = XFOPEN("./certs/test/cert-ext-ncip.pem", "rb")) == XBADFILE) { + return TEST_SKIPPED; + } + x509 = PEM_read_X509(f, NULL, NULL, NULL); + XFCLOSE(f); + f = XBADFILE; + + if (x509 == NULL) { + /* Certificate may fail to load due to constraints, skip */ + return TEST_SKIPPED; + } + + critical = -1; + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + &critical, NULL); + ExpectNotNull(nc); + + /* Verify critical flag is set */ + ExpectIntEQ(critical, 1); + + if (EXPECT_SUCCESS()) { + ExpectNotNull(nc->permittedSubtrees); + } + if (EXPECT_SUCCESS()) { + numPermitted = sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); + ExpectIntEQ(numPermitted, 1); + subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, 0); + ExpectNotNull(subtree); + } + if (EXPECT_SUCCESS()) { + ExpectNotNull(subtree->base); + } + if (EXPECT_SUCCESS()) { + gn = subtree->base; + /* Verify GENERAL_NAME type is GEN_IPADD */ + ExpectIntEQ(gn->type, GEN_IPADD); + /* Verify IP data is stored in d.ip as OCTET_STRING */ + ExpectNotNull(gn->d.ip); + } + if (EXPECT_SUCCESS()) { + ExpectNotNull(gn->d.ip->data); + /* IPv4 constraint: 4 bytes IP + 4 bytes mask = 8 */ + ExpectIntEQ(gn->d.ip->length, 8); + } + if (EXPECT_SUCCESS()) { + /* Verify the IP address bytes (192.168.1.0) */ + ExpectIntEQ((unsigned char)gn->d.ip->data[0], 192); + ExpectIntEQ((unsigned char)gn->d.ip->data[1], 168); + ExpectIntEQ((unsigned char)gn->d.ip->data[2], 1); + ExpectIntEQ((unsigned char)gn->d.ip->data[3], 0); + /* Verify the subnet mask bytes (255.255.255.0) */ + ExpectIntEQ((unsigned char)gn->d.ip->data[4], 255); + ExpectIntEQ((unsigned char)gn->d.ip->data[5], 255); + ExpectIntEQ((unsigned char)gn->d.ip->data[6], 255); + ExpectIntEQ((unsigned char)gn->d.ip->data[7], 0); + } + if (EXPECT_SUCCESS() && nc->excludedSubtrees != NULL) { + /* Excluded subtrees should be empty */ + ExpectIntEQ(sk_GENERAL_SUBTREE_num(nc->excludedSubtrees), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test wolfSSL_NAME_CONSTRAINTS_check_name() function, checking individual + * names against name constraints. + */ +int test_wolfSSL_NAME_CONSTRAINTS_check_name(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + + /* Test email constraint checking with cert-ext-nc.pem + * This cert has permitted email for .wolfssl.com (subdomains only) */ + ExpectTrue((f = XFOPEN("./certs/test/cert-ext-nc.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + + if (EXPECT_SUCCESS()) { + /* Constraint is ".wolfssl.com" (leading dot). Per RFC 5280, this + * matches emails where domain ends with ".wolfssl.com" (subdomains + * only), not the exact domain. */ + + /* Subdomain emails, should match .wolfssl.com */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "test@sub.wolfssl.com", 20), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@mail.wolfssl.com", 21), 1); + /* Deeper subdomain, should also match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "admin@a.b.c.wolfssl.com", 23), 1); + + /* Exact domain, should not match .wolfssl.com per RFC */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@wolfssl.com", 16), 0); + + /* Different domains, should not match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@other.com", 14), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@notwolfssl.com", 19), 0); + /* Suffix that doesn't have dot boundary */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@fakewolfssl.com", 20), 0); + + /* Test DNS names, no DNS constraint, so all should pass */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.example.com", 15), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "any.domain.org", 14), 1); + + /* Test NULL/invalid arguments */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(NULL, GEN_EMAIL, + "user@wolfssl.com", 16), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + NULL, 16), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@wolfssl.com", 0), 0); + /* Invalid email format (no @) */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "invalid-email", 13), 0); + /* @ at start */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "@wolfssl.com", 12), 0); + /* @ at end */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@", 5), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + x509 = NULL; + nc = NULL; + + /* Test IP address constraint checking with cert-ext-ncip.pem + * This cert has permitted IP 192.168.1.0/255.255.255.0 */ + if ((f = XFOPEN("./certs/test/cert-ext-ncip.pem", "rb")) == XBADFILE) { + return TEST_SKIPPED; + } + x509 = PEM_read_X509(f, NULL, NULL, NULL); + XFCLOSE(f); + f = XBADFILE; + + if (x509 == NULL) { + return TEST_SKIPPED; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + + if (EXPECT_SUCCESS()) { + /* Test permitted IPs, within 192.168.1.0/24 subnet */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "192.168.1.1", 11), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "192.168.1.50", 12), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "192.168.1.254", 13), 1); + + /* Test non-permitted IPs, outside 192.168.1.0/24 subnet */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "192.168.2.1", 11), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "10.0.0.1", 8), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "8.8.8.8", 7), 0); + + /* Test invalid IP format */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "invalid", 7), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_IPADD, + "256.1.1.1", 9), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test DNS type name constraint checking with leading dot (subdomain matching). + * Uses cert-ext-nc-combined.pem which has permitted;DNS:.wolfssl.com + */ +int test_wolfSSL_NAME_CONSTRAINTS_dns(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + + /* Test DNS constraint checking with cert-ext-nc-combined.pem + * This cert has permitted DNS for .wolfssl.com (subdomains only) */ + f = XFOPEN("./certs/test/cert-ext-nc-combined.pem", "rb"); + if (f == XBADFILE) { + return TEST_SKIPPED; + } + x509 = PEM_read_X509(f, NULL, NULL, NULL); + XFCLOSE(f); + f = XBADFILE; + + if (x509 == NULL) { + return TEST_SKIPPED; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + + if (EXPECT_SUCCESS()) { + /* Constraint is ".wolfssl.com" (leading dot). Per RFC 5280, this + * matches DNS names that end with ".wolfssl.com" (subdomains only). */ + + /* Subdomain DNS names, should match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.wolfssl.com", 15), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "mail.wolfssl.com", 16), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "a.b.c.wolfssl.com", 17), 1); + + /* Exact domain, should not match .wolfssl.com per RFC */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "wolfssl.com", 11), 0); + + /* Different domains, should not match */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.example.com", 15), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "fakewolfssl.com", 15), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + +/* + * Test excluded name constraints. + * Uses cert-ext-ncmulti.pem which has: + * permitted;DNS:.example.com, permitted;email:.example.com + * excluded;DNS:.blocked.example.com, excluded;email:.blocked.example.com + */ +int test_wolfSSL_NAME_CONSTRAINTS_excluded(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(IGNORE_NAME_CONSTRAINTS) + XFILE f = XBADFILE; + X509* x509 = NULL; + NAME_CONSTRAINTS* nc = NULL; + + /* Test excluded constraint checking with cert-ext-ncmulti.pem + * This cert permits .example.com but excludes .blocked.example.com */ + if ((f = XFOPEN("./certs/test/cert-ext-ncmulti.pem", "rb")) == XBADFILE) { + return TEST_SKIPPED; + } + x509 = PEM_read_X509(f, NULL, NULL, NULL); + XFCLOSE(f); + f = XBADFILE; + + if (x509 == NULL) { + return TEST_SKIPPED; + } + + nc = (NAME_CONSTRAINTS*)X509_get_ext_d2i(x509, NID_name_constraints, + NULL, NULL); + ExpectNotNull(nc); + + if (EXPECT_SUCCESS()) { + /* Verify both permitted and excluded subtrees are populated */ + ExpectNotNull(nc->permittedSubtrees); + ExpectNotNull(nc->excludedSubtrees); + ExpectIntGT(sk_GENERAL_SUBTREE_num(nc->excludedSubtrees), 0); + } + + if (EXPECT_SUCCESS()) { + /* Permitted .example.com subdomains should be allowed */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.example.com", 15), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "mail.example.com", 16), 1); + + /* Excluded .blocked.example.com, subdomains should be blocked */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.blocked.example.com", 23), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "sub.blocked.example.com", 23), 0); + + /* blocked.example.com is permitted because + * .blocked.example.com (with leading dot) only matches subdomains + * per RFC 5280, and it still matches the permitted .example.com + * constraint */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "blocked.example.com", 19), 1); + + /* Domains outside permitted .example.com should not be allowed */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "www.wolfssl.com", 15), 0); + + /* Permitted email .example.com subdomains should be allowed */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@www.example.com", 20), 1); + + /* Excluded email .blocked.example.com, should be blocked */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_EMAIL, + "user@www.blocked.example.com", 28), 0); + } + + NAME_CONSTRAINTS_free(nc); + X509_free(x509); + +#endif /* OPENSSL_EXTRA && !NO_FILESYSTEM && !NO_CERTS && !NO_RSA && + * !IGNORE_NAME_CONSTRAINTS */ + return EXPECT_RESULT(); +} + diff --git a/tests/api/test_ossl_x509_ext.h b/tests/api/test_ossl_x509_ext.h index 3a0ea0d42..06e04f1f8 100644 --- a/tests/api/test_ossl_x509_ext.h +++ b/tests/api/test_ossl_x509_ext.h @@ -46,6 +46,14 @@ int test_wolfSSL_X509V3_EXT_san(void); int test_wolfSSL_X509V3_EXT_aia(void); int test_wolfSSL_X509V3_EXT(void); int test_wolfSSL_X509V3_EXT_print(void); +int test_wolfSSL_X509_get_ext_d2i_name_constraints(void); +int test_wolfSSL_sk_GENERAL_SUBTREE(void); +int test_wolfSSL_NAME_CONSTRAINTS_types(void); +int test_wolfSSL_NAME_CONSTRAINTS_uri(void); +int test_wolfSSL_NAME_CONSTRAINTS_ipaddr(void); +int test_wolfSSL_NAME_CONSTRAINTS_check_name(void); +int test_wolfSSL_NAME_CONSTRAINTS_dns(void); +int test_wolfSSL_NAME_CONSTRAINTS_excluded(void); #define TEST_OSSL_X509_EXT_DECLS \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_get_extension_flags), \ @@ -71,6 +79,15 @@ int test_wolfSSL_X509V3_EXT_print(void); TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_san), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_aia), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT), \ - TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_print) + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_print), \ + TEST_DECL_GROUP("ossl_x509_ext", \ + test_wolfSSL_X509_get_ext_d2i_name_constraints), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_sk_GENERAL_SUBTREE), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_types), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_uri), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_ipaddr), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_check_name),\ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_dns), \ + TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_NAME_CONSTRAINTS_excluded) #endif /* WOLFCRYPT_TEST_OSSL_X509_EXT_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 31466ca84..6f01deb4e 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -19403,9 +19403,9 @@ int wolfssl_local_MatchBaseName(int type, const char* name, int nameSz, return 0; while (nameSz > 0) { - if (XTOLOWER((unsigned char)*name) != - XTOLOWER((unsigned char)*base)) + if (XTOLOWER((unsigned char)*name) != XTOLOWER((unsigned char)*base)) { return 0; + } name++; base++; nameSz--; @@ -19414,6 +19414,42 @@ int wolfssl_local_MatchBaseName(int type, const char* name, int nameSz, return 1; } +/* Check if IP address matches a name constraint. + * IP name constraints contain IP address and subnet mask. + * IPv4: ip is 4 bytes, constraint is 8 bytes (4 IP + 4 mask) + * IPv6: ip is 16 bytes, constraint is 32 bytes (16 IP + 16 mask) + * + * ip: IP address bytes + * ipSz: length of ip + * constraint: constraint bytes (IP + mask) + * constraintSz: length of constraint + * + * return 1 if IP matches constraint, otherwise 0 + */ +int wolfssl_local_MatchIpSubnet(const byte* ip, int ipSz, + const byte* constraint, int constraintSz) +{ + int i; + int match = 1; + + if (ip == NULL || constraint == NULL || ipSz <= 0 || constraintSz <= 0) { + return 0; + } + + /* Constraint should be 2x address length (IP + mask) */ + if (constraintSz != ipSz * 2) { + return 0; + } + + for (i = 0; i < ipSz; i++) { + if ((ip[i] & constraint[ipSz + i]) != + (constraint[i] & constraint[ipSz + i])) { + match = 0; + } + } + + return match; +} /* Search through the list to find if the name is permitted. * name The DNS name to search for @@ -19432,7 +19468,16 @@ static int PermittedListOk(DNS_entry* name, Base_entry* dnsList, byte nameType) while (current != NULL) { if (current->type == nameType) { need = 1; /* restriction on permitted names is set for this type */ - if (name->len >= current->nameSz && + if (nameType == ASN_IP_TYPE) { + /* IP address uses subnet matching (IP + mask) */ + if (wolfssl_local_MatchIpSubnet((const byte*)name->name, + name->len, (const byte*)current->name, + current->nameSz)) { + match = 1; + break; + } + } + else if (name->len >= current->nameSz && wolfssl_local_MatchBaseName(nameType, name->name, name->len, current->name, current->nameSz)) { match = 1; /* found the current name in the permitted list*/ @@ -19463,7 +19508,16 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) while (current != NULL) { if (current->type == nameType) { - if (name->len >= current->nameSz && + if (nameType == ASN_IP_TYPE) { + /* IP address uses subnet matching (IP + mask) */ + if (wolfssl_local_MatchIpSubnet((const byte*)name->name, + name->len, (const byte*)current->name, + current->nameSz)) { + ret = 1; + break; + } + } + else if (name->len >= current->nameSz && wolfssl_local_MatchBaseName(nameType, name->name, name->len, current->name, current->nameSz)) { ret = 1; @@ -19479,7 +19533,8 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) { - const byte nameTypes[] = {ASN_RFC822_TYPE, ASN_DNS_TYPE, ASN_DIR_TYPE}; + const byte nameTypes[] = {ASN_RFC822_TYPE, ASN_DNS_TYPE, ASN_DIR_TYPE, + ASN_IP_TYPE}; int i; if (signer == NULL || cert == NULL) @@ -19500,6 +19555,10 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) * subjectDnsName too */ name = cert->altNames; break; + case ASN_IP_TYPE: + /* IP addresses are stored in altNames with type ASN_IP_TYPE */ + name = cert->altNames; + break; case ASN_RFC822_TYPE: /* Shouldn't it validate E= in subject as well? */ name = cert->altEmailNames; @@ -19544,15 +19603,20 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) } while (name != NULL) { - if (IsInExcludedList(name, signer->excludedNames, nameType) == 1) { - WOLFSSL_MSG("Excluded name was found!"); - return 0; - } + /* Only check entries that match the current nameType. */ + if (name->type == nameType) { + if (IsInExcludedList(name, signer->excludedNames, + nameType) == 1) { + WOLFSSL_MSG("Excluded name was found!"); + return 0; + } - /* Check against the permitted list */ - if (PermittedListOk(name, signer->permittedNames, nameType) != 1) { - WOLFSSL_MSG("Permitted name was not found!"); - return 0; + /* Check against the permitted list */ + if (PermittedListOk(name, signer->permittedNames, + nameType) != 1) { + WOLFSSL_MSG("Permitted name was not found!"); + return 0; + } } name = name->next; @@ -22021,7 +22085,8 @@ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, bType = (byte)(b & ASN_TYPE_MASK); if (bType == ASN_DNS_TYPE || bType == ASN_RFC822_TYPE || - bType == ASN_DIR_TYPE) { + bType == ASN_DIR_TYPE || bType == ASN_IP_TYPE || + bType == ASN_URI_TYPE) { Base_entry* entry; /* if constructed has leading sequence */ @@ -22099,7 +22164,9 @@ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head, /* Check GeneralName tag is one of the types we can handle. */ if (t == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE) || t == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE) || - t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_DIR_TYPE)) { + t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_DIR_TYPE) || + t == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE) || + t == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE)) { /* Parse the general name and store a new entry. */ ret = DecodeSubtreeGeneralName(input + GetASNItem_DataIdx(dataASN[SUBTREEASN_IDX_BASE], input), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 037811e32..6f1543378 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5253,6 +5253,7 @@ typedef enum { STACK_TYPE_X509_CRL = 16, STACK_TYPE_X509_NAME_ENTRY = 17, STACK_TYPE_X509_REQ_ATTR = 18, + STACK_TYPE_GENERAL_SUBTREE = 19, } WOLF_STACK_TYPE; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) @@ -5281,6 +5282,7 @@ struct WOLFSSL_STACK { void* generic; char* string; WOLFSSL_GENERAL_NAME* gn; + WOLFSSL_GENERAL_SUBTREE* subtree; WOLFSSL_BY_DIR_entry* dir_entry; WOLFSSL_BY_DIR_HASH* dir_hash; WOLFSSL_X509_OBJECT* x509_obj; @@ -5321,6 +5323,9 @@ struct WOLFSSL_X509_NAME { #ifdef NO_ASN typedef struct DNS_entry DNS_entry; + #ifndef IGNORE_NAME_CONSTRAINTS + typedef struct Base_entry Base_entry; + #endif #endif struct WOLFSSL_X509 { @@ -5349,6 +5354,11 @@ struct WOLFSSL_X509 { buffer sig; int sigOID; DNS_entry* altNames; /* alt names list */ +#ifndef IGNORE_NAME_CONSTRAINTS + Base_entry* permittedNames; /* name constraints */ + Base_entry* excludedNames; + byte nameConstraintCrit:1; +#endif buffer pubKey; int pubKeyOID; DNS_entry* altNamesNext; /* hint for retrieval */ diff --git a/wolfssl/openssl/x509v3.h b/wolfssl/openssl/x509v3.h index 123a9e3ce..c6a05172f 100644 --- a/wolfssl/openssl/x509v3.h +++ b/wolfssl/openssl/x509v3.h @@ -199,8 +199,16 @@ typedef struct WOLFSSL_v3_ext_method X509V3_EXT_METHOD; typedef struct WOLFSSL_AUTHORITY_KEYID AUTHORITY_KEYID; typedef struct WOLFSSL_BASIC_CONSTRAINTS BASIC_CONSTRAINTS; typedef struct WOLFSSL_ACCESS_DESCRIPTION ACCESS_DESCRIPTION; +typedef struct WOLFSSL_GENERAL_SUBTREE GENERAL_SUBTREE; +typedef struct WOLFSSL_NAME_CONSTRAINTS NAME_CONSTRAINTS; #define BASIC_CONSTRAINTS_free wolfSSL_BASIC_CONSTRAINTS_free +#define NAME_CONSTRAINTS_new wolfSSL_NAME_CONSTRAINTS_new +#define NAME_CONSTRAINTS_free wolfSSL_NAME_CONSTRAINTS_free +#define GENERAL_SUBTREE_new wolfSSL_GENERAL_SUBTREE_new +#define GENERAL_SUBTREE_free wolfSSL_GENERAL_SUBTREE_free +#define sk_GENERAL_SUBTREE_num wolfSSL_sk_GENERAL_SUBTREE_num +#define sk_GENERAL_SUBTREE_value wolfSSL_sk_GENERAL_SUBTREE_value #define AUTHORITY_KEYID_free wolfSSL_AUTHORITY_KEYID_free #define SSL_CTX_get_cert_store(x) wolfSSL_CTX_get_cert_store ((x)) #define ASN1_INTEGER WOLFSSL_ASN1_INTEGER diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ba8f56ca0..b590524f5 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -343,6 +343,8 @@ typedef struct WOLFSSL_BASIC_CONSTRAINTS WOLFSSL_BASIC_CONSTRAINTS; typedef struct WOLFSSL_ACCESS_DESCRIPTION WOLFSSL_ACCESS_DESCRIPTION; typedef struct WOLFSSL_DIST_POINT_NAME WOLFSSL_DIST_POINT_NAME; typedef struct WOLFSSL_DIST_POINT WOLFSSL_DIST_POINT; +typedef struct WOLFSSL_GENERAL_SUBTREE WOLFSSL_GENERAL_SUBTREE; +typedef struct WOLFSSL_NAME_CONSTRAINTS WOLFSSL_NAME_CONSTRAINTS; typedef struct WOLFSSL_CONF_CTX WOLFSSL_CONF_CTX; @@ -483,6 +485,16 @@ struct WOLFSSL_GENERAL_NAME { } d; /* dereference */ }; +/* GeneralSubtree for Name Constraints extension (RFC 5280) */ +struct WOLFSSL_GENERAL_SUBTREE { + WOLFSSL_GENERAL_NAME* base; +}; + +struct WOLFSSL_NAME_CONSTRAINTS { + WOLF_STACK_OF(WOLFSSL_GENERAL_SUBTREE)* permittedSubtrees; + WOLF_STACK_OF(WOLFSSL_GENERAL_SUBTREE)* excludedSubtrees; +}; + struct WOLFSSL_DIST_POINT_NAME { int type; @@ -1972,6 +1984,18 @@ WOLFSSL_API int wolfSSL_GENERAL_NAME_print(WOLFSSL_BIO* out, WOLFSSL_GENERAL_NAME* name); WOLFSSL_API void wolfSSL_EXTENDED_KEY_USAGE_free(WOLFSSL_STACK * sk); +#if !defined(IGNORE_NAME_CONSTRAINTS) && !defined(WOLFSSL_LINUXKM) +WOLFSSL_API WOLFSSL_NAME_CONSTRAINTS* wolfSSL_NAME_CONSTRAINTS_new(void); +WOLFSSL_API void wolfSSL_NAME_CONSTRAINTS_free(WOLFSSL_NAME_CONSTRAINTS* nc); +WOLFSSL_API int wolfSSL_NAME_CONSTRAINTS_check_name( + WOLFSSL_NAME_CONSTRAINTS* nc, int type, const char* name, int nameSz); +WOLFSSL_API WOLFSSL_GENERAL_SUBTREE* wolfSSL_GENERAL_SUBTREE_new(void); +WOLFSSL_API void wolfSSL_GENERAL_SUBTREE_free(WOLFSSL_GENERAL_SUBTREE* subtree); +WOLFSSL_API int wolfSSL_sk_GENERAL_SUBTREE_num(const WOLFSSL_STACK* sk); +WOLFSSL_API WOLFSSL_GENERAL_SUBTREE* wolfSSL_sk_GENERAL_SUBTREE_value( + const WOLFSSL_STACK* sk, int idx); +#endif /* !IGNORE_NAME_CONSTRAINTS && !WOLFSSL_LINUXKM */ + WOLFSSL_API WOLFSSL_DIST_POINT* wolfSSL_DIST_POINT_new(void); WOLFSSL_API void wolfSSL_DIST_POINT_free(WOLFSSL_DIST_POINT* dp); WOLFSSL_API int wolfSSL_sk_DIST_POINT_push(WOLFSSL_DIST_POINTS* sk, diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 3b3779cb3..992c715e7 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2925,6 +2925,9 @@ WOLFSSL_LOCAL int VerifyX509Acert(const byte* cert, word32 certSz, WOLFSSL_TEST_VIS int wolfssl_local_MatchBaseName(int type, const char* name, int nameSz, const char* base, int baseSz); +WOLFSSL_TEST_VIS int wolfssl_local_MatchIpSubnet(const byte* ip, int ipSz, + const byte* constraint, + int constraintSz); #endif #if ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)) \