diff --git a/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch b/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch index 5ad7b9d..1d61a2e 100644 --- a/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch +++ b/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch @@ -1,7 +1,7 @@ -From d2277e711bb16e3b98f43565e71b7865b5fed423 Mon Sep 17 00:00:00 2001 +From 67ef8f036f7324fe37bc7a7e31a38e7088d21df2 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Sat, 7 Aug 2021 11:48:04 -0400 -Subject: [PATCH 1/2] Drop usage of ERR_GET_FUNC() +Subject: [PATCH 1/6] Drop usage of ERR_GET_FUNC() This macro was dropped in OpenSSL 3.0 and has actually not been providing a valid return code for some time. @@ -17,9 +17,7 @@ diff --git a/include/sscg.h b/include/sscg.h index faf86ba4f68e186bd35c7bc3ec77b98b8e37d253..851dc93175607e5223a70ef40a5feb24b7b69215 100644 --- a/include/sscg.h +++ b/include/sscg.h -@@ -94,11 +94,10 @@ - if (_sslret != 1) \ - { \ +@@ -96,7 +96,6 @@ /* Get information about error from OpenSSL */ \ unsigned long _ssl_error = ERR_get_error (); \ if ((ERR_GET_LIB (_ssl_error) == ERR_LIB_UI) && \ @@ -27,8 +25,6 @@ index faf86ba4f68e186bd35c7bc3ec77b98b8e37d253..851dc93175607e5223a70ef40a5feb24 ((ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_LARGE) || \ (ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_SMALL))) \ { \ - fprintf ( \ - stderr, \ -- -2.33.0 +2.49.0 diff --git a/SOURCES/0002-Correct-certificate-lifetime-calculation.patch b/SOURCES/0002-Correct-certificate-lifetime-calculation.patch index 5a0b87b..37a73ea 100644 --- a/SOURCES/0002-Correct-certificate-lifetime-calculation.patch +++ b/SOURCES/0002-Correct-certificate-lifetime-calculation.patch @@ -1,7 +1,7 @@ -From 87604820a935f87a8f533e3f294419d27c0514eb Mon Sep 17 00:00:00 2001 +From 5852d74f338bb6de3f303275aa73024f082b47bf Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Tue, 26 Oct 2021 12:32:13 +0200 -Subject: [PATCH 2/2] Correct certificate lifetime calculation +Subject: [PATCH 2/6] Correct certificate lifetime calculation sscg allows passing the certificate lifetime, as a number of days, as a commandline argument. It converts this value to seconds using the @@ -28,9 +28,7 @@ diff --git a/src/x509.c b/src/x509.c index dc1594a4bdcb9d81607f0fe5ad2d4562e5edb533..7c7e4dfe56d5756862f3e0f851941e846ce96f31 100644 --- a/src/x509.c +++ b/src/x509.c -@@ -416,11 +416,11 @@ sscg_sign_x509_csr (TALLOC_CTX *mem_ctx, - X509_set_issuer_name (cert, X509_REQ_get_subject_name (csr)); - } +@@ -418,7 +418,7 @@ sscg_sign_x509_csr (TALLOC_CTX *mem_ctx, /* set time */ X509_gmtime_adj (X509_get_notBefore (cert), 0); @@ -39,8 +37,6 @@ index dc1594a4bdcb9d81607f0fe5ad2d4562e5edb533..7c7e4dfe56d5756862f3e0f851941e84 /* set subject */ subject = X509_NAME_dup (X509_REQ_get_subject_name (csr)); - sslret = X509_set_subject_name (cert, subject); - CHECK_SSL (sslret, X509_set_subject_name); -- -2.33.0 +2.49.0 diff --git a/SOURCES/0003-Truncate-IP-address-in-SAN.patch b/SOURCES/0003-Truncate-IP-address-in-SAN.patch index c492f38..ce2f302 100644 --- a/SOURCES/0003-Truncate-IP-address-in-SAN.patch +++ b/SOURCES/0003-Truncate-IP-address-in-SAN.patch @@ -1,7 +1,7 @@ -From 0875cd6169e876c4296a307631d49b801fc686dc Mon Sep 17 00:00:00 2001 +From c633de3d77987cef5b652c861aa646774c6f1167 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Tue, 8 Mar 2022 16:33:35 -0500 -Subject: [PATCH] Truncate IP address in SAN +Subject: [PATCH 3/6] Truncate IP address in SAN In OpenSSL 1.1, this was done automatically when addind a SAN extension, but in OpenSSL 3.0 it is rejected as an invalid input. @@ -15,9 +15,7 @@ diff --git a/src/x509.c b/src/x509.c index 7c7e4dfe56d5756862f3e0f851941e846ce96f31..e828ec725b23d7ea79393151e7bb436e2f61bdb8 100644 --- a/src/x509.c +++ b/src/x509.c -@@ -131,10 +131,11 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, - size_t i; - X509_NAME *subject; +@@ -133,6 +133,7 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, char *alt_name = NULL; char *tmp = NULL; char *san = NULL; @@ -25,11 +23,7 @@ index 7c7e4dfe56d5756862f3e0f851941e846ce96f31..e828ec725b23d7ea79393151e7bb436e TALLOC_CTX *tmp_ctx; X509_EXTENSION *ex = NULL; struct sscg_x509_req *csr; - - /* Make sure we have a key available */ -@@ -265,10 +266,16 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, - tmp_ctx, "DNS:%s", certinfo->subject_alt_names[i]); - } +@@ -267,6 +268,12 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, else { san = talloc_strdup (tmp_ctx, certinfo->subject_alt_names[i]); @@ -42,11 +36,7 @@ index 7c7e4dfe56d5756862f3e0f851941e846ce96f31..e828ec725b23d7ea79393151e7bb436e } CHECK_MEM (san); - if (strnlen (san, MAXHOSTNAMELEN + 5) > MAXHOSTNAMELEN + 4) - { -@@ -287,11 +294,17 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, - alt_name = tmp; - } +@@ -289,7 +296,13 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, } ex = X509V3_EXT_conf_nid (NULL, NULL, NID_subject_alt_name, alt_name); @@ -61,8 +51,6 @@ index 7c7e4dfe56d5756862f3e0f851941e846ce96f31..e828ec725b23d7ea79393151e7bb436e sk_X509_EXTENSION_push (certinfo->extensions, ex); /* Set the public key for the certificate */ - sslret = X509_REQ_set_pubkey (csr->x509_req, spkey->evp_pkey); - CHECK_SSL (sslret, X509_REQ_set_pubkey (OU)); -- -2.35.1 +2.49.0 diff --git a/SOURCES/0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch b/SOURCES/0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch index 27deba5..46f658b 100644 --- a/SOURCES/0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch +++ b/SOURCES/0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch @@ -1,7 +1,7 @@ -From 282f819bc39c9557ee34f73c6f6623182f680792 Mon Sep 17 00:00:00 2001 +From 259c4c83307273551fd267585ec8854896a168bd Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Wed, 16 Nov 2022 15:27:58 -0500 -Subject: [PATCH] dhparams: don't fail if default file can't be created +Subject: [PATCH 4/6] dhparams: don't fail if default file can't be created Resolves: rhbz#2143206 @@ -135,5 +135,5 @@ index 1bf8019c2dda136abe56acd101dfe8ad0b3d725d..dcff4cd2b8dfd2e11c8612d36ecc94b1 sscg_io_utils_finalize_output_files (options->streams); -- -2.38.1 +2.49.0 diff --git a/SOURCES/0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch b/SOURCES/0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch index d35a8a2..3e86c2c 100644 --- a/SOURCES/0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch +++ b/SOURCES/0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch @@ -1,7 +1,7 @@ -From e65a507c487a37dd5a8c90b7dbd1ff3274146239 Mon Sep 17 00:00:00 2001 +From 7abb9f7f929eb85fa3ab66a150978bbc5e198e5c Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Mon, 13 Dec 2021 15:20:55 +0100 -Subject: [PATCH 5/5] dhparams: Fix the FIPS_mode() call for OpenSSL 3.0 +Subject: [PATCH 5/6] dhparams: Fix the FIPS_mode() call for OpenSSL 3.0 This function has been removed from OpenSSL 3.0, replaced by EVP_default_properties_is_fips_enabled(). @@ -28,5 +28,5 @@ index 5c50128970d48790df910b9f9531e61e1d4c5758..61fd57aeedca47fba49f75d356cd5f42 i = 0; while (dh_nonfips_groups[i]) -- -2.31.1 +2.49.0 diff --git a/SOURCES/0006-x509-Use-proper-version-for-CSR.patch b/SOURCES/0006-x509-Use-proper-version-for-CSR.patch new file mode 100644 index 0000000..73066f8 --- /dev/null +++ b/SOURCES/0006-x509-Use-proper-version-for-CSR.patch @@ -0,0 +1,31 @@ +From 6b48b480d57f75fc93ea646fbe6a457c4afd319f Mon Sep 17 00:00:00 2001 +From: Sebastian Andrzej Siewior +Date: Sat, 19 Oct 2024 15:43:20 +0200 +Subject: [PATCH 6/6] x509: Use proper version for CSR. + +RFC 2986 only defines a single version for CSRs: X509_VERSION_1 (0). +OpenSSL starting with 3.4 rejects everything else. + +Use X509_VERSION_1 as version for X509_REQ_set_version. + +Signed-off-by: Sebastian Andrzej Siewior +--- + src/x509.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/x509.c b/src/x509.c +index e828ec725b23d7ea79393151e7bb436e2f61bdb8..22f8163ec5a6b20bcb16177edf8088cf148a8661 100644 +--- a/src/x509.c ++++ b/src/x509.c +@@ -156,7 +156,7 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, + talloc_set_destructor ((TALLOC_CTX *)csr, _sscg_csr_destructor); + + /* We will generate only x509v3 certificates */ +- sslret = X509_REQ_set_version (csr->x509_req, 2); ++ sslret = X509_REQ_set_version (csr->x509_req, X509_VERSION_1); + CHECK_SSL (sslret, X509_REQ_set_version); + + subject = X509_REQ_get_subject_name (csr->x509_req); +-- +2.49.0 + diff --git a/SOURCES/0007-Ensure-critical-basicConstraint-for-CA-cert.patch b/SOURCES/0007-Ensure-critical-basicConstraint-for-CA-cert.patch new file mode 100644 index 0000000..30d0b6f --- /dev/null +++ b/SOURCES/0007-Ensure-critical-basicConstraint-for-CA-cert.patch @@ -0,0 +1,29 @@ +From 499ce83c85d14dd8cbc52f6431e775f1d00578d6 Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Tue, 22 Apr 2025 13:09:32 -0400 +Subject: [PATCH 7/7] Ensure 'critical' basicConstraint for CA cert + +Fixes: https://github.com/sgallagher/sscg/issues/74 + +Signed-off-by: Stephen Gallagher +--- + src/authority.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/authority.c b/src/authority.c +index af60e1a93023c32e3fdf6da920fba4464256ed81..044c62f5192e75a9f7d3f49616f852a97da7505a 100644 +--- a/src/authority.c ++++ b/src/authority.c +@@ -89,7 +89,8 @@ create_private_CA (TALLOC_CTX *mem_ctx, + sk_X509_EXTENSION_push (ca_certinfo->extensions, ex); + + /* Mark it as a CA */ +- ex = X509V3_EXT_conf_nid (NULL, NULL, NID_basic_constraints, "CA:TRUE"); ++ ex = X509V3_EXT_conf_nid ( ++ NULL, NULL, NID_basic_constraints, "critical,CA:TRUE"); + CHECK_MEM (ex); + sk_X509_EXTENSION_push (ca_certinfo->extensions, ex); + +-- +2.49.0 + diff --git a/SOURCES/0008-Fix-IP-address-handling-in-CA-certificate-SAN-constr.patch b/SOURCES/0008-Fix-IP-address-handling-in-CA-certificate-SAN-constr.patch new file mode 100644 index 0000000..770641a --- /dev/null +++ b/SOURCES/0008-Fix-IP-address-handling-in-CA-certificate-SAN-constr.patch @@ -0,0 +1,1115 @@ +From aac0351de2fade86572eac4f24e22bd667177f7e Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Mon, 21 Jul 2025 15:13:31 -0400 +Subject: [PATCH 8/8] Fix IP address handling in CA certificate SAN constraints + +- Add automatic single-IP subnet mask to IP addresses in CA name constraints +- Update help text to show simplified IP format without subnet mask +- Add comprehensive test for basicConstraints + +Signed-off-by: Stephen Gallagher +--- + src/arguments.c | 2 +- + src/authority.c | 84 ++++ + test/create_ca_test.c | 960 +++++++++++++++++++++++++++++++++++++++++- + 3 files changed, 1044 insertions(+), 2 deletions(-) + +diff --git a/src/arguments.c b/src/arguments.c +index 770d834aacc05d6d92cc0c855852eadb88f8c9bc..96e3bbb1bcb2efc4e155b646104ffc8f2500079e 100644 +--- a/src/arguments.c ++++ b/src/arguments.c +@@ -309,7 +309,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, + _ ("Optional additional valid hostnames for the certificate. " + "In addition to hostnames, this option also accepts explicit values " + "supported by RFC 5280 such as " +- "IP:xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy " ++ "IP:xxx.xxx.xxx.xxx " + "May be specified multiple times."), + _ ("alt.example.com") + }, +diff --git a/src/authority.c b/src/authority.c +index 044c62f5192e75a9f7d3f49616f852a97da7505a..8590e844bee3cc2888476135dc44711d7ba6fc28 100644 +--- a/src/authority.c ++++ b/src/authority.c +@@ -109,6 +109,90 @@ create_private_CA (TALLOC_CTX *mem_ctx, + san = talloc_asprintf ( + tmp_ctx, "DNS:%s", options->subject_alt_names[i]); + } ++ else if (strncmp (options->subject_alt_names[i], "IP:", 3) == 0) ++ { ++ char *ip_addr = options->subject_alt_names[i] + 3; ++ char *slash = strchr (ip_addr, '/'); ++ char *clean_ip = ip_addr; ++ const char *netmask_str = NULL; ++ ++ if (slash) ++ { ++ /* Extract IP and netmask parts */ ++ clean_ip = ++ talloc_strndup (tmp_ctx, ip_addr, slash - ip_addr); ++ char *cidr_str = slash + 1; ++ int cidr_bits = atoi (cidr_str); ++ ++ /* Convert CIDR to appropriate netmask format */ ++ if (strchr (clean_ip, ':')) ++ { ++ /* IPv6 - convert CIDR to hex netmask */ ++ if (cidr_bits == 128) ++ { ++ netmask_str = ++ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"; ++ } ++ else if (cidr_bits == 64) ++ { ++ netmask_str = "FFFF:FFFF:FFFF:FFFF:0:0:0:0"; ++ } ++ else ++ { ++ /* For other values, default to /128 */ ++ netmask_str = ++ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"; ++ } ++ } ++ else ++ { ++ /* IPv4 - convert CIDR to dotted decimal */ ++ if (cidr_bits == 32) ++ { ++ netmask_str = "255.255.255.255"; ++ } ++ else if (cidr_bits == 24) ++ { ++ netmask_str = "255.255.255.0"; ++ } ++ else if (cidr_bits == 16) ++ { ++ netmask_str = "255.255.0.0"; ++ } ++ else if (cidr_bits == 8) ++ { ++ netmask_str = "255.0.0.0"; ++ } ++ else ++ { ++ /* For other values, default to /32 */ ++ netmask_str = "255.255.255.255"; ++ } ++ } ++ } ++ else ++ { ++ /* No netmask provided - add single host netmask */ ++ if (strchr (clean_ip, ':')) ++ { ++ /* IPv6 - use /128 netmask */ ++ netmask_str = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF"; ++ } ++ else ++ { ++ /* IPv4 - use /32 netmask */ ++ netmask_str = "255.255.255.255"; ++ } ++ } ++ ++ san = ++ talloc_asprintf (tmp_ctx, "IP:%s/%s", clean_ip, netmask_str); ++ ++ if (slash && clean_ip != ip_addr) ++ { ++ talloc_free (clean_ip); ++ } ++ } + else + { + san = talloc_strdup (tmp_ctx, options->subject_alt_names[i]); +diff --git a/test/create_ca_test.c b/test/create_ca_test.c +index 270cfa3dc189540bb807a3d3140ca45335e727b0..eaf2e77052569fd63c09200636004bcd5afcd97c 100644 +--- a/test/create_ca_test.c ++++ b/test/create_ca_test.c +@@ -21,9 +21,861 @@ + #include + #include + #include ++#include ++#include + + #include "include/sscg.h" + #include "include/x509.h" ++#include "include/authority.h" ++ ++static int ++verify_subject_alt_names (struct sscg_x509_cert *cert) ++{ ++ X509 *x509 = cert->certificate; ++ STACK_OF (GENERAL_NAME) *san_names = NULL; ++ GENERAL_NAME *name = NULL; ++ ASN1_STRING *san_str = NULL; ++ int san_count = 0; ++ int found_primary_cn = 0; ++ int found_alt1 = 0; ++ int found_alt2 = 0; ++ int found_ip4_1 = 0; ++ int found_ip4_2 = 0; ++ int found_ip6 = 0; ++ int found_ip4_netmask = 0; ++ int found_ip6_netmask = 0; ++ int found_email = 0; ++ int found_uri = 0; ++ int found_wildcard = 0; ++ int found_subdomain = 0; ++ int found_international = 0; ++ char *name_str = NULL; ++ ++ /* Get the Subject Alternative Name extension */ ++ san_names = X509_get_ext_d2i (x509, NID_subject_alt_name, NULL, NULL); ++ if (!san_names) ++ { ++ printf ("Certificate missing Subject Alternative Name extension.\n"); ++ return EINVAL; ++ } ++ ++ san_count = sk_GENERAL_NAME_num (san_names); ++ printf ("\n Processing %d Subject Alternative Names:\n", san_count); ++ ++ /* Check each SAN entry */ ++ for (int i = 0; i < san_count; i++) ++ { ++ name = sk_GENERAL_NAME_value (san_names, i); ++ ++ switch (name->type) ++ { ++ case GEN_DNS: ++ san_str = name->d.dNSName; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ printf (" DNS: %s\n", name_str); ++ ++ if (strcmp (name_str, "server.example.com") == 0) ++ found_primary_cn = 1; ++ else if (strcmp (name_str, "alt1.example.com") == 0) ++ found_alt1 = 1; ++ else if (strcmp (name_str, "alt2.example.com") == 0) ++ found_alt2 = 1; ++ else if (strcmp (name_str, "*.wildcard.example.com") == 0) ++ found_wildcard = 1; ++ else if (strcmp (name_str, "subdomain.alt1.example.com") == 0) ++ found_subdomain = 1; ++ else if (strcmp (name_str, "xn--nxasmq6b.example.com") == 0) ++ found_international = 1; ++ break; ++ ++ case GEN_IPADD: ++ san_str = name->d.iPAddress; ++ /* IP addresses are stored as binary data */ ++ if (ASN1_STRING_length (san_str) == 4) /* IPv4 */ ++ { ++ const unsigned char *ip_data = ASN1_STRING_get0_data (san_str); ++ printf (" IP (IPv4): %d.%d.%d.%d\n", ++ ip_data[0], ++ ip_data[1], ++ ip_data[2], ++ ip_data[3]); ++ ++ if (ip_data[0] == 192 && ip_data[1] == 168 && ip_data[2] == 1 && ++ ip_data[3] == 100) ++ found_ip4_1 = 1; ++ else if (ip_data[0] == 10 && ip_data[1] == 0 && ++ ip_data[2] == 0 && ip_data[3] == 1) ++ found_ip4_2 = 1; ++ else if (ip_data[0] == 203 && ip_data[1] == 0 && ++ ip_data[2] == 113 && ip_data[3] == 0) ++ found_ip4_netmask = 1; ++ } ++ else if (ASN1_STRING_length (san_str) == 16) /* IPv6 */ ++ { ++ const unsigned char *ip_data = ASN1_STRING_get0_data (san_str); ++ printf (" IP (IPv6): "); ++ for (int j = 0; j < 16; j += 2) ++ { ++ printf ("%02x%02x", ip_data[j], ip_data[j + 1]); ++ if (j < 14) ++ printf (":"); ++ } ++ printf ("\n"); ++ ++ /* Check for 2001:db8::1 */ ++ if (ip_data[0] == 0x20 && ip_data[1] == 0x01 && ++ ip_data[2] == 0x0d && ip_data[3] == 0xb8 && ++ ip_data[4] == 0x00 && ip_data[5] == 0x00 && ++ ip_data[6] == 0x00 && ip_data[7] == 0x00 && ++ ip_data[8] == 0x00 && ip_data[9] == 0x00 && ++ ip_data[10] == 0x00 && ip_data[11] == 0x00 && ++ ip_data[12] == 0x00 && ip_data[13] == 0x00 && ++ ip_data[14] == 0x00 && ip_data[15] == 0x01) ++ found_ip6 = 1; ++ /* Check for 2001:db8:85a3:: (netmask stripped) */ ++ else if (ip_data[0] == 0x20 && ip_data[1] == 0x01 && ++ ip_data[2] == 0x0d && ip_data[3] == 0xb8 && ++ ip_data[4] == 0x85 && ip_data[5] == 0xa3 && ++ ip_data[6] == 0x00 && ip_data[7] == 0x00 && ++ ip_data[8] == 0x00 && ip_data[9] == 0x00 && ++ ip_data[10] == 0x00 && ip_data[11] == 0x00 && ++ ip_data[12] == 0x00 && ip_data[13] == 0x00 && ++ ip_data[14] == 0x00 && ip_data[15] == 0x00) ++ found_ip6_netmask = 1; ++ } ++ break; ++ ++ case GEN_EMAIL: ++ san_str = name->d.rfc822Name; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ printf (" Email: %s\n", name_str); ++ ++ if (strcmp (name_str, "admin@example.com") == 0) ++ found_email = 1; ++ break; ++ ++ case GEN_URI: ++ san_str = name->d.uniformResourceIdentifier; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ printf (" URI: %s\n", name_str); ++ ++ if (strcmp (name_str, "https://www.example.com/service") == 0) ++ found_uri = 1; ++ break; ++ ++ default: printf (" Other type: %d\n", name->type); break; ++ } ++ } ++ ++ GENERAL_NAMES_free (san_names); ++ ++ /* Verify all expected SANs were found */ ++ int missing_count = 0; ++ ++ if (!found_primary_cn) ++ { ++ printf ( ++ " MISSING: Primary CN not found in Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_alt1) ++ { ++ printf ( ++ " MISSING: alt1.example.com not found in Subject Alternative " ++ "Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_alt2) ++ { ++ printf ( ++ " MISSING: alt2.example.com not found in Subject Alternative " ++ "Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_ip4_1) ++ { ++ printf ( ++ " MISSING: IPv4 192.168.1.100 not found in Subject Alternative " ++ "Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_ip4_2) ++ { ++ printf ( ++ " MISSING: IPv4 10.0.0.1 not found in Subject Alternative " ++ "Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_ip6) ++ { ++ printf ( ++ " MISSING: IPv6 2001:db8::1 not found in Subject Alternative " ++ "Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_ip4_netmask) ++ { ++ printf ( ++ " MISSING: IPv4 203.0.113.0 (from 203.0.113.0/24, netmask " ++ "stripped) not found in Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_ip6_netmask) ++ { ++ printf ( ++ " MISSING: IPv6 2001:db8:85a3:: (from 2001:db8:85a3::/64, netmask " ++ "stripped) not found in Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_email) ++ { ++ printf ( ++ " MISSING: Email admin@example.com not found in Subject " ++ "Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_uri) ++ { ++ printf ( ++ " MISSING: URI https://www.example.com/service not found in " ++ "Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_wildcard) ++ { ++ printf ( ++ " MISSING: Wildcard *.wildcard.example.com not found in Subject " ++ "Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_subdomain) ++ { ++ printf ( ++ " MISSING: Subdomain subdomain.alt1.example.com not found in " ++ "Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (!found_international) ++ { ++ printf ( ++ " MISSING: International domain xn--nxasmq6b.example.com not found " ++ "in Subject Alternative Names.\n"); ++ missing_count++; ++ } ++ ++ if (missing_count > 0) ++ { ++ printf (" %d expected SAN entries were missing.\n", missing_count); ++ return EINVAL; ++ } ++ ++ printf (" All expected SAN entries found successfully.\n"); ++ return EOK; ++} ++ ++static int ++test_san_edge_cases (struct sscg_x509_cert *cert) ++{ ++ X509 *x509 = cert->certificate; ++ STACK_OF (GENERAL_NAME) *san_names = NULL; ++ GENERAL_NAME *name = NULL; ++ ASN1_STRING *san_str = NULL; ++ int san_count = 0; ++ int dns_count = 0; ++ int ip_count = 0; ++ int email_count = 0; ++ int uri_count = 0; ++ char *name_str = NULL; ++ ++ /* Get the Subject Alternative Name extension */ ++ san_names = X509_get_ext_d2i (x509, NID_subject_alt_name, NULL, NULL); ++ if (!san_names) ++ { ++ printf ("Certificate missing Subject Alternative Name extension.\n"); ++ return EINVAL; ++ } ++ ++ san_count = sk_GENERAL_NAME_num (san_names); ++ ++ printf ("\n Performing comprehensive SAN validation:\n"); ++ ++ /* Count and validate all SAN types */ ++ for (int i = 0; i < san_count; i++) ++ { ++ name = sk_GENERAL_NAME_value (san_names, i); ++ ++ switch (name->type) ++ { ++ case GEN_DNS: ++ dns_count++; ++ san_str = name->d.dNSName; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ ++ /* Validate DNS name format */ ++ if (strlen (name_str) == 0) ++ { ++ printf (" ERROR: Empty DNS name found in SANs.\n"); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ /* Allow wildcards and validate domain format */ ++ if (name_str[0] != '*' && !strchr (name_str, '.')) ++ { ++ printf (" ERROR: DNS name '%s' missing domain part.\n", ++ name_str); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ /* Validate wildcard format */ ++ if (name_str[0] == '*' && name_str[1] != '.') ++ { ++ printf (" ERROR: Invalid wildcard DNS name '%s'.\n", ++ name_str); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ break; ++ ++ case GEN_IPADD: ++ ip_count++; ++ san_str = name->d.iPAddress; ++ ++ /* Validate IP address length */ ++ int ip_len = ASN1_STRING_length (san_str); ++ if (ip_len != 4 && ip_len != 16) /* IPv4 or IPv6 */ ++ { ++ printf (" ERROR: Invalid IP address length: %d bytes.\n", ++ ip_len); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ break; ++ ++ case GEN_EMAIL: ++ email_count++; ++ san_str = name->d.rfc822Name; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ ++ /* Validate email format */ ++ if (!strchr (name_str, '@')) ++ { ++ printf (" ERROR: Invalid email address '%s' - missing @.\n", ++ name_str); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ break; ++ ++ case GEN_URI: ++ uri_count++; ++ san_str = name->d.uniformResourceIdentifier; ++ name_str = (char *)ASN1_STRING_get0_data (san_str); ++ ++ /* Validate URI format - must have scheme */ ++ if (!strstr (name_str, "://")) ++ { ++ printf (" ERROR: Invalid URI '%s' - missing scheme.\n", ++ name_str); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ break; ++ ++ default: ++ /* Other SAN types are acceptable but not validated here */ ++ break; ++ } ++ } ++ ++ printf (" Found %d total SANs: %d DNS, %d IP, %d Email, %d URI.\n", ++ san_count, ++ dns_count, ++ ip_count, ++ email_count, ++ uri_count); ++ ++ /* Validate expected counts for comprehensive test */ ++ int expected_dns = ++ 6; /* CN + alt1 + alt2 + wildcard + subdomain + international */ ++ int expected_ip = 5; /* IPv4 x2 + IPv6 x1 + IPv4 netmask + IPv6 netmask */ ++ int expected_email = 1; ++ int expected_uri = 1; ++ ++ if (dns_count < expected_dns) ++ { ++ printf (" ERROR: Expected at least %d DNS names, found %d.\n", ++ expected_dns, ++ dns_count); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ if (ip_count < expected_ip) ++ { ++ printf (" ERROR: Expected at least %d IP addresses, found %d.\n", ++ expected_ip, ++ ip_count); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ if (email_count < expected_email) ++ { ++ printf (" ERROR: Expected at least %d email addresses, found %d.\n", ++ expected_email, ++ email_count); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ if (uri_count < expected_uri) ++ { ++ printf (" ERROR: Expected at least %d URIs, found %d.\n", ++ expected_uri, ++ uri_count); ++ GENERAL_NAMES_free (san_names); ++ return EINVAL; ++ } ++ ++ printf (" All SAN format validations passed successfully.\n"); ++ ++ GENERAL_NAMES_free (san_names); ++ return EOK; ++} ++ ++static int ++test_ip_netmask_handling (struct sscg_x509_cert *cert) ++{ ++ X509 *x509 = cert->certificate; ++ STACK_OF (GENERAL_NAME) *san_names = NULL; ++ GENERAL_NAME *name = NULL; ++ ASN1_STRING *san_str = NULL; ++ int san_count = 0; ++ int found_netmask_ipv4 = 0; ++ int found_netmask_ipv6 = 0; ++ ++ /* Get the Subject Alternative Name extension */ ++ san_names = X509_get_ext_d2i (x509, NID_subject_alt_name, NULL, NULL); ++ if (!san_names) ++ { ++ printf ("Certificate missing Subject Alternative Name extension.\n"); ++ return EINVAL; ++ } ++ ++ san_count = sk_GENERAL_NAME_num (san_names); ++ ++ printf ("\n Testing IP address netmask stripping:\n"); ++ ++ /* Look specifically for IP addresses that had netmasks stripped */ ++ for (int i = 0; i < san_count; i++) ++ { ++ name = sk_GENERAL_NAME_value (san_names, i); ++ ++ if (name->type == GEN_IPADD) ++ { ++ san_str = name->d.iPAddress; ++ ++ if (ASN1_STRING_length (san_str) == 4) /* IPv4 */ ++ { ++ const unsigned char *ip_data = ASN1_STRING_get0_data (san_str); ++ ++ /* Check for 203.0.113.0 (from original 203.0.113.0/24) */ ++ if (ip_data[0] == 203 && ip_data[1] == 0 && ip_data[2] == 113 && ++ ip_data[3] == 0) ++ { ++ printf ( ++ " ✓ IPv4 netmask stripped: 203.0.113.0/24 → " ++ "203.0.113.0\n"); ++ found_netmask_ipv4 = 1; ++ } ++ } ++ else if (ASN1_STRING_length (san_str) == 16) /* IPv6 */ ++ { ++ const unsigned char *ip_data = ASN1_STRING_get0_data (san_str); ++ ++ /* Check for 2001:db8:85a3:: (from original 2001:db8:85a3::/64) */ ++ if (ip_data[0] == 0x20 && ip_data[1] == 0x01 && ++ ip_data[2] == 0x0d && ip_data[3] == 0xb8 && ++ ip_data[4] == 0x85 && ip_data[5] == 0xa3 && ++ ip_data[6] == 0x00 && ip_data[7] == 0x00 && ++ ip_data[8] == 0x00 && ip_data[9] == 0x00 && ++ ip_data[10] == 0x00 && ip_data[11] == 0x00 && ++ ip_data[12] == 0x00 && ip_data[13] == 0x00 && ++ ip_data[14] == 0x00 && ip_data[15] == 0x00) ++ { ++ printf ( ++ " ✓ IPv6 netmask stripped: 2001:db8:85a3::/64 → " ++ "2001:db8:85a3::\n"); ++ found_netmask_ipv6 = 1; ++ } ++ } ++ } ++ } ++ ++ GENERAL_NAMES_free (san_names); ++ ++ /* Verify that netmask stripping worked correctly */ ++ if (!found_netmask_ipv4) ++ { ++ printf (" ERROR: IPv4 netmask stripping test failed.\n"); ++ return EINVAL; ++ } ++ ++ if (!found_netmask_ipv6) ++ { ++ printf (" ERROR: IPv6 netmask stripping test failed.\n"); ++ return EINVAL; ++ } ++ ++ printf (" All IP address netmask tests passed successfully.\n"); ++ return EOK; ++} ++ ++static int ++verify_name_constraints (struct sscg_x509_cert *ca_cert, ++ char **expected_san_list) ++{ ++ X509 *x509 = ca_cert->certificate; ++ X509_EXTENSION *name_constraints_ext = NULL; ++ ASN1_OCTET_STRING *ext_data = NULL; ++ BIO *bio = NULL; ++ char *ext_str = NULL; ++ char *line = NULL; ++ char *saveptr = NULL; ++ size_t ext_str_len = 0; ++ int found_constraints[20] = { ++ 0 ++ }; /* Track which expected constraints we found */ ++ int missing_count = 0; ++ int j; ++ ++ printf ("\n Verifying name constraints in CA certificate:\n"); ++ ++ /* Find the name constraints extension */ ++ int ext_idx = X509_get_ext_by_NID (x509, NID_name_constraints, -1); ++ if (ext_idx < 0) ++ { ++ printf ( ++ " ERROR: CA certificate missing Name Constraints extension.\n"); ++ return EINVAL; ++ } ++ ++ name_constraints_ext = X509_get_ext (x509, ext_idx); ++ if (!name_constraints_ext) ++ { ++ printf (" ERROR: Failed to get Name Constraints extension.\n"); ++ return EINVAL; ++ } ++ ++ /* Get the extension data */ ++ ext_data = X509_EXTENSION_get_data (name_constraints_ext); ++ if (!ext_data) ++ { ++ printf (" ERROR: Failed to get Name Constraints extension data.\n"); ++ return EINVAL; ++ } ++ ++ /* Convert the extension to a readable string using BIO */ ++ bio = BIO_new (BIO_s_mem ()); ++ if (!bio) ++ { ++ printf (" ERROR: Failed to create BIO for extension parsing.\n"); ++ return EINVAL; ++ } ++ ++ /* Print the extension to the BIO */ ++ if (!X509V3_EXT_print (bio, name_constraints_ext, 0, 0)) ++ { ++ printf (" ERROR: Failed to print Name Constraints extension.\n"); ++ BIO_free (bio); ++ return EINVAL; ++ } ++ ++ /* Get the string representation */ ++ ext_str_len = BIO_get_mem_data (bio, &ext_str); ++ if (ext_str_len <= 0 || !ext_str) ++ { ++ printf (" ERROR: Failed to get extension string data.\n"); ++ BIO_free (bio); ++ return EINVAL; ++ } ++ ++ /* Null-terminate the string for parsing */ ++ char *ext_str_copy = malloc (ext_str_len + 1); ++ if (!ext_str_copy) ++ { ++ printf ( ++ " ERROR: Failed to allocate memory for extension parsing.\n"); ++ BIO_free (bio); ++ return ENOMEM; ++ } ++ memcpy (ext_str_copy, ext_str, ext_str_len); ++ ext_str_copy[ext_str_len] = '\0'; ++ ++ printf (" Name Constraints content:\n%s\n", ext_str_copy); ++ ++ /* Parse the extension string to find constraints */ ++ line = strtok_r (ext_str_copy, "\n", &saveptr); ++ while (line) ++ { ++ /* Look for "Permitted:" sections and DNS/IP entries */ ++ if (strstr (line, "DNS:")) ++ { ++ char *dns_start = strstr (line, "DNS:"); ++ if (dns_start) ++ { ++ dns_start += 4; /* Skip "DNS:" */ ++ /* Trim whitespace */ ++ while (*dns_start == ' ' || *dns_start == '\t') ++ dns_start++; ++ ++ printf (" Found DNS constraint: %s\n", dns_start); ++ ++ /* Check if this matches our expected CN (truncated) */ ++ if (strstr (dns_start, "server")) ++ { ++ found_constraints[0] = 1; ++ } ++ ++ /* Check against our expected SAN list */ ++ if (expected_san_list) ++ { ++ for (j = 0; expected_san_list[j]; j++) ++ { ++ char *expected_dns = NULL; ++ ++ if (!strchr (expected_san_list[j], ':')) ++ { ++ expected_dns = expected_san_list[j]; ++ } ++ else if (strncmp (expected_san_list[j], "DNS:", 4) == 0) ++ { ++ expected_dns = expected_san_list[j] + 4; ++ } ++ ++ if (expected_dns && strstr (dns_start, expected_dns)) ++ { ++ found_constraints[j + 1] = 1; ++ } ++ } ++ } ++ } ++ } ++ else if (strstr (line, "IP:")) ++ { ++ char *ip_start = strstr (line, "IP:"); ++ if (ip_start) ++ { ++ ip_start += 3; /* Skip "IP:" */ ++ while (*ip_start == ' ' || *ip_start == '\t') ++ ip_start++; ++ ++ printf (" Found IP constraint: %s\n", ip_start); ++ ++ /* Check against expected IP SANs */ ++ if (expected_san_list) ++ { ++ for (j = 0; expected_san_list[j]; j++) ++ { ++ if (strncmp (expected_san_list[j], "IP:", 3) == 0) ++ { ++ char *expected_ip = expected_san_list[j] + 3; ++ char *slash = strchr (expected_ip, '/'); ++ char expected_constraint[128]; ++ char clean_ip[64]; ++ ++ /* Extract IP and netmask parts */ ++ if (slash) ++ { ++ int ip_len = slash - expected_ip; ++ strncpy (clean_ip, expected_ip, ip_len); ++ clean_ip[ip_len] = '\0'; ++ ++ /* Parse the CIDR netmask */ ++ char *cidr_str = slash + 1; ++ int cidr_bits = atoi (cidr_str); ++ ++ /* Convert to constraint format with proper netmask */ ++ if (strchr (clean_ip, ':')) ++ { ++ /* IPv6 - convert CIDR to hex netmask */ ++ const char *netmask; ++ if (cidr_bits == 128) ++ netmask = ++ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:" ++ "FFFF"; ++ else if (cidr_bits == 64) ++ netmask = "FFFF:FFFF:FFFF:FFFF:0:0:0:0"; ++ else ++ netmask = ++ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:" ++ "FFFF"; /* default to /128 */ ++ ++ /* Handle compressed IPv6 forms */ ++ if (strstr (clean_ip, "2001:db8::1")) ++ { ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:2001:DB8:0:0:0:0:0:1/%s", ++ netmask); ++ } ++ else if (strstr (clean_ip, ++ "2001:db8:85a3::")) ++ { ++ snprintf ( ++ expected_constraint, ++ sizeof (expected_constraint), ++ "IP:2001:DB8:85A3:0:0:0:0:0/%s", ++ netmask); ++ } ++ else ++ { ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:%s/%s", ++ clean_ip, ++ netmask); ++ } ++ } ++ else ++ { ++ /* IPv4 - convert CIDR to dotted decimal */ ++ const char *netmask; ++ if (cidr_bits == 32) ++ netmask = "255.255.255.255"; ++ else if (cidr_bits == 24) ++ netmask = "255.255.255.0"; ++ else if (cidr_bits == 16) ++ netmask = "255.255.0.0"; ++ else if (cidr_bits == 8) ++ netmask = "255.0.0.0"; ++ else ++ netmask = ++ "255.255.255.255"; /* default to /32 */ ++ ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:%s/%s", ++ clean_ip, ++ netmask); ++ } ++ } ++ else ++ { ++ /* No netmask - add single host netmask */ ++ strcpy (clean_ip, expected_ip); ++ ++ if (strchr (clean_ip, ':')) ++ { ++ /* IPv6 with /128 netmask */ ++ if (strstr (clean_ip, "2001:db8::1")) ++ { ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:2001:DB8:0:0:0:0:0:1/" ++ "FFFF:FFFF:FFFF:FFFF:FFFF:" ++ "FFFF:FFFF:FFFF"); ++ } ++ else if (strstr (clean_ip, ++ "2001:db8:85a3::")) ++ { ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:2001:DB8:85A3:0:0:0:0:0/" ++ "FFFF:FFFF:FFFF:FFFF:FFFF:" ++ "FFFF:FFFF:FFFF"); ++ } ++ else ++ { ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:%s/" ++ "FFFF:FFFF:FFFF:FFFF:FFFF:" ++ "FFFF:FFFF:FFFF", ++ clean_ip); ++ } ++ } ++ else ++ { ++ /* IPv4 with /32 netmask */ ++ snprintf (expected_constraint, ++ sizeof (expected_constraint), ++ "IP:%s/255.255.255.255", ++ clean_ip); ++ } ++ } ++ ++ /* Check if this expected constraint matches what we found */ ++ /* Skip the "IP:" prefix for comparison since ip_start doesn't include it */ ++ char *constraint_without_prefix = ++ expected_constraint + 3; /* Skip "IP:" */ ++ if (strcmp (ip_start, constraint_without_prefix) == ++ 0) ++ { ++ found_constraints[j + 1] = 1; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ line = strtok_r (NULL, "\n", &saveptr); ++ } ++ ++ free (ext_str_copy); ++ BIO_free (bio); ++ ++ /* Verify that we found all expected constraints */ ++ if (!found_constraints[0]) ++ { ++ printf (" MISSING: CN constraint 'server' not found.\n"); ++ missing_count++; ++ } ++ ++ if (expected_san_list) ++ { ++ for (j = 0; expected_san_list[j]; j++) ++ { ++ if (!found_constraints[j + 1]) ++ { ++ /* Only report missing DNS and IP constraints, skip email/URI */ ++ if (!strchr (expected_san_list[j], ':') || ++ strncmp (expected_san_list[j], "DNS:", 4) == 0 || ++ strncmp (expected_san_list[j], "IP:", 3) == 0) ++ { ++ printf (" MISSING: Constraint for '%s' not found.\n", ++ expected_san_list[j]); ++ missing_count++; ++ } ++ } ++ } ++ } ++ ++ if (missing_count > 0) ++ { ++ printf (" %d expected name constraints were missing.\n", ++ missing_count); ++ return EINVAL; ++ } ++ ++ printf (" All expected name constraints found successfully.\n"); ++ return EOK; ++} + + int + main (int argc, char **argv) +@@ -35,6 +887,11 @@ main (int argc, char **argv) + struct sscg_evp_pkey *pkey = NULL; + struct sscg_x509_cert *cert = NULL; + ++ /* Variables for CA testing */ ++ struct sscg_x509_cert *ca_cert = NULL; ++ struct sscg_evp_pkey *ca_key = NULL; ++ struct sscg_options ca_options; ++ + TALLOC_CTX *tmp_ctx = talloc_new (NULL); + if (!tmp_ctx) + { +@@ -98,7 +955,108 @@ main (int argc, char **argv) + tmp_ctx, csr, serial, 3650, NULL, pkey, EVP_sha512 (), &cert); + CHECK_OK (ret); + +- ret = EOK; ++ /* ============= SERVICE CERTIFICATE TESTS ============= */ ++ ++ /* Verify that subject alternative names were properly included */ ++ printf ("Verifying subject alternative names in service certificate. "); ++ int verify_ret = verify_subject_alt_names (cert); ++ if (verify_ret != EOK) ++ { ++ printf ("FAILED.\n"); ++ ret = verify_ret; /* Store first failure but continue testing */ ++ } ++ else ++ { ++ printf ("SUCCESS.\n"); ++ } ++ ++ /* Test additional SAN verification scenarios */ ++ printf ("Testing SAN edge cases and validation. "); ++ int edge_ret = test_san_edge_cases (cert); ++ if (edge_ret != EOK) ++ { ++ printf ("FAILED.\n"); ++ if (ret == EOK) ++ ret = edge_ret; /* Store first failure */ ++ } ++ else ++ { ++ printf ("SUCCESS.\n"); ++ } ++ ++ /* Test IP address netmask handling */ ++ printf ("Testing IP address netmask stripping functionality. "); ++ int netmask_ret = test_ip_netmask_handling (cert); ++ if (netmask_ret != EOK) ++ { ++ printf ("FAILED.\n"); ++ if (ret == EOK) ++ ret = netmask_ret; /* Store first failure */ ++ } ++ else ++ { ++ printf ("SUCCESS.\n"); ++ } ++ ++ /* ============= CA CERTIFICATE TESTS ============= */ ++ ++ printf ("\n=== CA CERTIFICATE TESTS ===\n"); ++ ++ /* Set up options for CA creation */ ++ memset (&ca_options, 0, sizeof (ca_options)); ++ ca_options.country = "US"; ++ ca_options.state = ""; ++ ca_options.locality = ""; ++ ca_options.org = "Unspecified"; ++ ca_options.email = ""; ++ ca_options.hostname = "server.example.com"; ++ ca_options.hash_fn = EVP_sha256 (); ++ ca_options.lifetime = 3650; ++ ca_options.verbosity = SSCG_QUIET; ++ ++ /* Set up the same subject alternative names for the CA */ ++ ca_options.subject_alt_names = certinfo->subject_alt_names; ++ ++ /* Create the private CA */ ++ printf ("Creating private CA certificate. "); ++ ret = create_private_CA (tmp_ctx, &ca_options, &ca_cert, &ca_key); ++ if (ret != EOK) ++ { ++ printf ("FAILED.\n"); ++ goto done; ++ } ++ else ++ { ++ printf ("SUCCESS.\n"); ++ } ++ ++ /* Verify name constraints in the CA certificate */ ++ printf ("Verifying name constraints in CA certificate. "); ++ int ca_constraints_ret = ++ verify_name_constraints (ca_cert, certinfo->subject_alt_names); ++ if (ca_constraints_ret != EOK) ++ { ++ printf ("FAILED.\n"); ++ if (ret == EOK) ++ ret = ca_constraints_ret; ++ } ++ else ++ { ++ printf ("SUCCESS.\n"); ++ } ++ ++ /* Summary of all test results */ ++ printf ("\n=== TEST SUMMARY ===\n"); ++ printf ("Service cert SAN verification: %s\n", ++ verify_ret == EOK ? "PASS" : "FAIL"); ++ printf ("Service cert edge case validation: %s\n", ++ edge_ret == EOK ? "PASS" : "FAIL"); ++ printf ("Service cert netmask handling: %s\n", ++ netmask_ret == EOK ? "PASS" : "FAIL"); ++ printf ("CA certificate creation: %s\n", ca_cert ? "PASS" : "FAIL"); ++ printf ("CA name constraints verification: %s\n", ++ ca_constraints_ret == EOK ? "PASS" : "FAIL"); ++ + done: + if (ret != EOK) + { +-- +2.50.1 + diff --git a/SPECS/sscg.spec b/SPECS/sscg.spec index eb650f9..e0a5d84 100644 --- a/SPECS/sscg.spec +++ b/SPECS/sscg.spec @@ -9,7 +9,7 @@ Name: sscg Version: 3.0.0 -Release: 7%{?dist} +Release: 10%{?dist} Summary: Simple SSL certificate generator License: GPLv3+ with exceptions @@ -31,6 +31,9 @@ Patch: 0002-Correct-certificate-lifetime-calculation.patch Patch: 0003-Truncate-IP-address-in-SAN.patch Patch: 0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch Patch: 0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch +Patch: 0006-x509-Use-proper-version-for-CSR.patch +Patch: 0007-Ensure-critical-basicConstraint-for-CA-cert.patch +Patch: 0008-Fix-IP-address-handling-in-CA-certificate-SAN-constr.patch %description A utility to aid in the creation of more secure "self-signed" @@ -61,7 +64,19 @@ false signatures from the service certificate. %{_mandir}/man8/%{name}.8* %changelog -* Fri Dec 02 2022 Stephen Gallagher - 3.0.0-6 +* Mon Aug 11 2025 Stephen Gallagher - 3.0.0-10 +- Fix IP address handling in CA certificate SAN constraints +- Resolves: RHEL-107289 + +* Tue Apr 22 2025 Stephen Gallagher - 3.0.0-9 +- Ensure 'critical' basicConstraint for CA cert +- Resolves: RHEL-88119 + +* Wed Apr 02 2025 Stephen Gallagher - 3.0.0-8 +- x509: Use proper version for CSR +- Resolves: RHEL-85851 + +* Fri Dec 02 2022 Stephen Gallagher - 3.0.0-7 - Use EVP_default_properties_is_fips_enabled() on OpenSSL 3.0 - Related: rhbz#2083879