diff --git a/.gitignore b/.gitignore index 4798d97..c63ab47 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/sscg-3.0.0.tar.xz +SOURCES/sscg-4.0.3.tar.gz diff --git a/.sscg.metadata b/.sscg.metadata index ba54818..845eebf 100644 --- a/.sscg.metadata +++ b/.sscg.metadata @@ -1 +1 @@ -81e3b33e118edff96583314ceb4bfde9a1e6b45c SOURCES/sscg-3.0.0.tar.xz +829d6dd6d5ad493499317a2bf6f25167c9b3c623 SOURCES/sscg-4.0.3.tar.gz diff --git a/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch b/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch deleted file mode 100644 index 1d61a2e..0000000 --- a/SOURCES/0001-Drop-usage-of-ERR_GET_FUNC.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 67ef8f036f7324fe37bc7a7e31a38e7088d21df2 Mon Sep 17 00:00:00 2001 -From: Stephen Gallagher -Date: Sat, 7 Aug 2021 11:48:04 -0400 -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. - -Related: rhbz#1964837 - -Signed-off-by: Stephen Gallagher ---- - include/sscg.h | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/include/sscg.h b/include/sscg.h -index faf86ba4f68e186bd35c7bc3ec77b98b8e37d253..851dc93175607e5223a70ef40a5feb24b7b69215 100644 ---- a/include/sscg.h -+++ b/include/sscg.h -@@ -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) && \ -- (ERR_GET_FUNC (_ssl_error) == UI_F_UI_SET_RESULT_EX) && \ - ((ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_LARGE) || \ - (ERR_GET_REASON (_ssl_error) == UI_R_RESULT_TOO_SMALL))) \ - { \ --- -2.49.0 - diff --git a/SOURCES/0001-Restore-defaulting-to-dhparams.pem-creation.patch b/SOURCES/0001-Restore-defaulting-to-dhparams.pem-creation.patch new file mode 100644 index 0000000..f1c692c --- /dev/null +++ b/SOURCES/0001-Restore-defaulting-to-dhparams.pem-creation.patch @@ -0,0 +1,119 @@ +From 771a7663bccbd360f017c4c22358a46abcdfa93f Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Mon, 27 Oct 2025 14:58:11 -0400 +Subject: [PATCH] Restore defaulting to dhparams.pem creation + +This was disabled upstream, but for backwards-compatibility in the RHEL +9 and RHEL 10 lifecycle, we'll continue to do so there. + +This reverts commit 0e5e011acc2dc19f3c2fcb5699cf8fa662a2b135. + +Signed-off-by: Stephen Gallagher +--- + src/arguments.c | 4 ++-- + src/sscg.c | 39 +++++++++++++++++++++++++--------- + test/test_dhparams_creation.sh | 6 +----- + 3 files changed, 32 insertions(+), 17 deletions(-) + +diff --git a/src/arguments.c b/src/arguments.c +index 38c8740c1f159368d6fc92d51ba48d83700c3320..4ff75fdf86728592e7ca05db4cf4ac88bf79ca2e 100644 +--- a/src/arguments.c ++++ b/src/arguments.c +@@ -682,7 +682,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, + &options->dhparams_file, + 0, + _("A file to contain a set of Diffie-Hellman parameters. " +- "(Default: not created)"), ++ "(Default: \"./dhparams.pem\")"), + NULL + }, + +@@ -692,7 +692,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, + POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN, + &options->skip_dhparams, + 0, +- _ ("Deprecated: Retained for backwards compatibility. To be removed in SSCG 5.0."), ++ _ ("Do not create the dhparams file"), + NULL + }, + +diff --git a/src/sscg.c b/src/sscg.c +index b9b191f109300f6447262858f57a3a8321a14966..d2dce334cff1342d975e9867a2c82a222d76925e 100644 +--- a/src/sscg.c ++++ b/src/sscg.c +@@ -166,19 +166,38 @@ main (int argc, const char **argv) + options->crl_mode); + CHECK_OK (ret); + +- if (options->dhparams_file) ++ if (!options->skip_dhparams) + { +- dhparams_file = talloc_strdup (main_ctx, options->dhparams_file); +- CHECK_MEM (dhparams_file); ++ if (options->dhparams_file) ++ { ++ dhparams_file = talloc_strdup (main_ctx, options->dhparams_file); ++ CHECK_MEM (dhparams_file); + +- ret = sscg_io_utils_add_output_file (options->streams, +- SSCG_FILE_TYPE_DHPARAMS, +- dhparams_file, +- options->overwrite, +- options->dhparams_mode); +- CHECK_OK (ret); ++ ret = sscg_io_utils_add_output_file (options->streams, ++ SSCG_FILE_TYPE_DHPARAMS, ++ dhparams_file, ++ options->overwrite, ++ options->dhparams_mode); ++ CHECK_OK (ret); ++ } ++ else ++ { ++ dhparams_file = talloc_strdup (main_ctx, "./dhparams.pem"); ++ CHECK_MEM (dhparams_file); ++ ++ ret = sscg_io_utils_add_output_file (options->streams, ++ SSCG_FILE_TYPE_DHPARAMS, ++ dhparams_file, ++ options->overwrite, ++ options->dhparams_mode); ++ SSCG_LOG (SSCG_VERBOSE, ++ "Could not open dhparams file %s: %s\n", ++ dhparams_file, ++ strerror (ret)); ++ /* This is non-fatal if the file path was not explicitly passed */ ++ ret = EOK; ++ } + } +- + /* Validate and open the file paths */ + ret = sscg_io_utils_open_BIOs (options->streams); + CHECK_OK (ret); +diff --git a/test/test_dhparams_creation.sh b/test/test_dhparams_creation.sh +index d0b4cbb71f3cd1656f1422524c4da7b30fbf3e0a..49f2b08d23246c90663eb7d2e5078817eb42139b 100755 +--- a/test/test_dhparams_creation.sh ++++ b/test/test_dhparams_creation.sh +@@ -42,10 +42,6 @@ + # just warn and ignore it if it was not (returning 0). However, if it is + # explicitly requested on the command-line and cannot be written to that + # location, it should fail with an error code. +-# +-# Updated 2025-10-21: SSCG 4.0 no longer creates the dhparams file by default. +-# It should not attempt to create it unless explicitly requested using the +-# --dhparams-file option. + + set -e + +@@ -181,7 +177,7 @@ run_test \ + "" \ + 0 \ + "$WRITABLE_DIR/dhparams.pem" \ +- "false" \ ++ "true" \ + "$WRITABLE_DIR" + + # Test 2: No --dhparams-file, readonly directory, no existing file +-- +2.52.0 + diff --git a/SOURCES/0002-Avoid-segfault-on-receiving-bad-CLI-arguments.patch b/SOURCES/0002-Avoid-segfault-on-receiving-bad-CLI-arguments.patch new file mode 100644 index 0000000..6da94b3 --- /dev/null +++ b/SOURCES/0002-Avoid-segfault-on-receiving-bad-CLI-arguments.patch @@ -0,0 +1,38 @@ +From f40d0070641543a140428d70211d53d36fd2c34b Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Tue, 2 Dec 2025 12:12:26 -0500 +Subject: [PATCH 2/3] Avoid segfault on receiving bad CLI arguments + +Signed-off-by: Stephen Gallagher +--- + src/sscg.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/src/sscg.c b/src/sscg.c +index d2dce334cff1342d975e9867a2c82a222d76925e..070d567bb189d42a20fd0a80f8fe2f7caae4d9eb 100644 +--- a/src/sscg.c ++++ b/src/sscg.c +@@ -59,7 +59,7 @@ int + main (int argc, const char **argv) + { + int ret, sret; +- struct sscg_options *options; ++ struct sscg_options *options = NULL; + bool build_client_cert = false; + char *dhparams_file = NULL; + +@@ -361,7 +361,10 @@ main (int argc, const char **argv) + done: + if (ret != EOK) + { +- sscg_io_utils_delete_output_files (options->streams); ++ if (options) ++ { ++ sscg_io_utils_delete_output_files (options->streams); ++ } + } + talloc_zfree (main_ctx); + if (getenv ("SSCG_TALLOC_REPORT")) +-- +2.52.0 + diff --git a/SOURCES/0002-Correct-certificate-lifetime-calculation.patch b/SOURCES/0002-Correct-certificate-lifetime-calculation.patch deleted file mode 100644 index 37a73ea..0000000 --- a/SOURCES/0002-Correct-certificate-lifetime-calculation.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 5852d74f338bb6de3f303275aa73024f082b47bf Mon Sep 17 00:00:00 2001 -From: Allison Karlitskaya -Date: Tue, 26 Oct 2021 12:32:13 +0200 -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 -formula - - days * 24 * 3650 - -which is incorrect. The correct value is 3600. - -This effectively adds an extra 20 minutes to the lifetime of the -certificate for each day as given on the commandline, and was enough to -cause some new integration tests in cockpit to fail. - -Interestingly, 3650 is the old default value for the number of days of -certificate validity (~10 years) so this probably slipped in as a sort -of muscle-memory-assisted typo. - -Let's just write `24 * 60 * 60` to make things clear. ---- - src/x509.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/x509.c b/src/x509.c -index dc1594a4bdcb9d81607f0fe5ad2d4562e5edb533..7c7e4dfe56d5756862f3e0f851941e846ce96f31 100644 ---- a/src/x509.c -+++ b/src/x509.c -@@ -418,7 +418,7 @@ sscg_sign_x509_csr (TALLOC_CTX *mem_ctx, - - /* set time */ - X509_gmtime_adj (X509_get_notBefore (cert), 0); -- X509_gmtime_adj (X509_get_notAfter (cert), days * 24 * 3650); -+ X509_gmtime_adj (X509_get_notAfter (cert), days * 24 * 60 * 60); - - /* set subject */ - subject = X509_NAME_dup (X509_REQ_get_subject_name (csr)); --- -2.49.0 - diff --git a/SOURCES/0003-Restore-error-message.patch b/SOURCES/0003-Restore-error-message.patch new file mode 100644 index 0000000..dd66fd6 --- /dev/null +++ b/SOURCES/0003-Restore-error-message.patch @@ -0,0 +1,29 @@ +From 08dacb632cc331027f39dcfa0b782aeb6f2f893a Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Tue, 2 Dec 2025 12:19:04 -0500 +Subject: [PATCH 3/3] Restore error message + +This was dropped in 4.0, but should be retained in RHEL 9 and 10 for +compatibility, particularly with existing tests that look for specific +messages. + +Signed-off-by: Stephen Gallagher +--- + src/sscg.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/sscg.c b/src/sscg.c +index 070d567bb189d42a20fd0a80f8fe2f7caae4d9eb..9f46cd622a4d55bd634a370ccc81ff063422b5af 100644 +--- a/src/sscg.c ++++ b/src/sscg.c +@@ -361,6 +361,7 @@ main (int argc, const char **argv) + done: + if (ret != EOK) + { ++ SSCG_ERROR ("%s\n", strerror (ret)); + if (options) + { + sscg_io_utils_delete_output_files (options->streams); +-- +2.52.0 + diff --git a/SOURCES/0003-Truncate-IP-address-in-SAN.patch b/SOURCES/0003-Truncate-IP-address-in-SAN.patch deleted file mode 100644 index ce2f302..0000000 --- a/SOURCES/0003-Truncate-IP-address-in-SAN.patch +++ /dev/null @@ -1,56 +0,0 @@ -From c633de3d77987cef5b652c861aa646774c6f1167 Mon Sep 17 00:00:00 2001 -From: Stephen Gallagher -Date: Tue, 8 Mar 2022 16:33:35 -0500 -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. - -Signed-off-by: Stephen Gallagher ---- - src/x509.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/src/x509.c b/src/x509.c -index 7c7e4dfe56d5756862f3e0f851941e846ce96f31..e828ec725b23d7ea79393151e7bb436e2f61bdb8 100644 ---- a/src/x509.c -+++ b/src/x509.c -@@ -133,6 +133,7 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, - char *alt_name = NULL; - char *tmp = NULL; - char *san = NULL; -+ char *slash = NULL; - TALLOC_CTX *tmp_ctx; - X509_EXTENSION *ex = NULL; - struct sscg_x509_req *csr; -@@ -267,6 +268,12 @@ sscg_x509v3_csr_new (TALLOC_CTX *mem_ctx, - else - { - san = talloc_strdup (tmp_ctx, certinfo->subject_alt_names[i]); -+ /* SAN IP addresses cannot include the subnet mask */ -+ if ((slash = strchr (san, '/'))) -+ { -+ /* Truncate at the slash */ -+ *slash = '\0'; -+ } - } - CHECK_MEM (san); - -@@ -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); -- CHECK_MEM (ex); -+ if (!ex) -+ { -+ ret = EINVAL; -+ fprintf (stderr, "Invalid subjectAlternativeName: %s\n", alt_name); -+ goto done; -+ } -+ - sk_X509_EXTENSION_push (certinfo->extensions, ex); - - /* Set the public key for the certificate */ --- -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 deleted file mode 100644 index 46f658b..0000000 --- a/SOURCES/0004-dhparams-don-t-fail-if-default-file-can-t-be-created.patch +++ /dev/null @@ -1,139 +0,0 @@ -From 259c4c83307273551fd267585ec8854896a168bd Mon Sep 17 00:00:00 2001 -From: Stephen Gallagher -Date: Wed, 16 Nov 2022 15:27:58 -0500 -Subject: [PATCH 4/6] dhparams: don't fail if default file can't be created - -Resolves: rhbz#2143206 - -Signed-off-by: Stephen Gallagher ---- - src/arguments.c | 1 - - src/io_utils.c | 12 +++++++++++ - src/sscg.c | 55 +++++++++++++++++++++++++++++++++---------------- - 3 files changed, 49 insertions(+), 19 deletions(-) - -diff --git a/src/arguments.c b/src/arguments.c -index 7b9da14a732875b0f33a12e22a97d51a78216839..770d834aacc05d6d92cc0c855852eadb88f8c9bc 100644 ---- a/src/arguments.c -+++ b/src/arguments.c -@@ -69,7 +69,6 @@ set_default_options (struct sscg_options *opts) - - opts->lifetime = 398; - -- opts->dhparams_file = talloc_strdup (opts, "dhparams.pem"); - opts->dhparams_group = talloc_strdup (opts, "ffdhe4096"); - opts->dhparams_generator = 2; - -diff --git a/src/io_utils.c b/src/io_utils.c -index 1b8bc41c3849acbe4657ae14dfe55e3010957129..5d34327bdbe450add5326ac20c337c9399b471dc 100644 ---- a/src/io_utils.c -+++ b/src/io_utils.c -@@ -544,6 +544,18 @@ sscg_io_utils_open_output_files (struct sscg_stream **streams, bool overwrite) - { - SSCG_LOG (SSCG_DEBUG, "Opening %s\n", stream->path); - stream->bio = BIO_new_file (stream->path, create_mode); -+ if (!stream->bio) -+ { -+ fprintf (stderr, -+ "Could not write to %s. Check directory permissions.\n", -+ stream->path); -+ -+ /* The dhparams file is special, it will be handled later */ -+ if (i != SSCG_FILE_TYPE_DHPARAMS) -+ { -+ continue; -+ } -+ } - CHECK_BIO (stream->bio, stream->path); - } - -diff --git a/src/sscg.c b/src/sscg.c -index 1bf8019c2dda136abe56acd101dfe8ad0b3d725d..dcff4cd2b8dfd2e11c8612d36ecc94b175e9dc26 100644 ---- a/src/sscg.c -+++ b/src/sscg.c -@@ -93,6 +93,7 @@ main (int argc, const char **argv) - int ret, sret; - struct sscg_options *options; - bool build_client_cert = false; -+ char *dhparams_file = NULL; - - struct sscg_x509_cert *cacert; - struct sscg_evp_pkey *cakey; -@@ -182,9 +183,19 @@ main (int argc, const char **argv) - options->crl_mode); - CHECK_OK (ret); - -+ if (options->dhparams_file) -+ { -+ dhparams_file = talloc_strdup (main_ctx, options->dhparams_file); -+ } -+ else -+ { -+ dhparams_file = talloc_strdup (main_ctx, "./dhparams.pem"); -+ } -+ CHECK_MEM (dhparams_file); -+ - ret = sscg_io_utils_add_output_file (options->streams, - SSCG_FILE_TYPE_DHPARAMS, -- options->dhparams_file, -+ dhparams_file, - options->dhparams_mode); - CHECK_OK (ret); - -@@ -281,28 +292,36 @@ main (int argc, const char **argv) - - - /* Create DH parameters file */ -- bp = GET_BIO (SSCG_FILE_TYPE_DHPARAMS); -- if (options->dhparams_prime_len > 0) -+ if ((bp = GET_BIO (SSCG_FILE_TYPE_DHPARAMS))) - { -- ret = create_dhparams (options->verbosity, -- options->dhparams_prime_len, -- options->dhparams_generator, -- &dhparams); -- CHECK_OK (ret); -+ if (options->dhparams_prime_len > 0) -+ { -+ ret = create_dhparams (options->verbosity, -+ options->dhparams_prime_len, -+ options->dhparams_generator, -+ &dhparams); -+ CHECK_OK (ret); -+ } -+ else -+ { -+ ret = get_params_by_named_group (options->dhparams_group, &dhparams); -+ CHECK_OK (ret); -+ } -+ -+ /* Export the DH parameters to the file */ -+ sret = PEM_write_bio_Parameters (bp, dhparams); -+ CHECK_SSL (sret, PEM_write_bio_Parameters ()); -+ ANNOUNCE_WRITE (SSCG_FILE_TYPE_DHPARAMS); -+ EVP_PKEY_free (dhparams); - } -- else -+ else if (options->dhparams_file) - { -- ret = get_params_by_named_group (options->dhparams_group, &dhparams); -- CHECK_OK (ret); -+ /* A filename was explicitly passed, but it couldn't be created */ -+ ret = EPERM; -+ fprintf (stderr, "Could not write to %s: ", options->dhparams_file); -+ goto done; - } - -- /* Export the DH parameters to the file */ -- sret = PEM_write_bio_Parameters (bp, dhparams); -- CHECK_SSL (sret, PEM_write_bio_Parameters ()); -- ANNOUNCE_WRITE (SSCG_FILE_TYPE_DHPARAMS); -- EVP_PKEY_free (dhparams); -- -- - /* Set the final file permissions */ - sscg_io_utils_finalize_output_files (options->streams); - --- -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 deleted file mode 100644 index 3e86c2c..0000000 --- a/SOURCES/0005-dhparams-Fix-the-FIPS_mode-call-for-OpenSSL-3.0.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 7abb9f7f929eb85fa3ab66a150978bbc5e198e5c Mon Sep 17 00:00:00 2001 -From: Simon Chopin -Date: Mon, 13 Dec 2021 15:20:55 +0100 -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(). - -Closes #50 ---- - src/dhparams.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/src/dhparams.c b/src/dhparams.c -index 5c50128970d48790df910b9f9531e61e1d4c5758..61fd57aeedca47fba49f75d356cd5f42b9586696 100644 ---- a/src/dhparams.c -+++ b/src/dhparams.c -@@ -231,7 +231,11 @@ is_valid_named_group (const char *group_name) - } - - /* Check non-FIPS groups */ -+#if OPENSSL_VERSION_NUMBER < 0x30000000L - if (!FIPS_mode ()) -+#else -+ if (!EVP_default_properties_is_fips_enabled(NULL)) -+#endif - { - i = 0; - while (dh_nonfips_groups[i]) --- -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 deleted file mode 100644 index 73066f8..0000000 --- a/SOURCES/0006-x509-Use-proper-version-for-CSR.patch +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 30d0b6f..0000000 --- a/SOURCES/0007-Ensure-critical-basicConstraint-for-CA-cert.patch +++ /dev/null @@ -1,29 +0,0 @@ -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 deleted file mode 100644 index 770641a..0000000 --- a/SOURCES/0008-Fix-IP-address-handling-in-CA-certificate-SAN-constr.patch +++ /dev/null @@ -1,1115 +0,0 @@ -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 e0a5d84..2c9787c 100644 --- a/SPECS/sscg.spec +++ b/SPECS/sscg.spec @@ -1,3 +1,13 @@ +## START: Set by rpmautospec +## (rpmautospec version 0.6.5) +## RPMAUTOSPEC: autorelease, autochangelog +%define autorelease(e:s:pb:n) %{?-p:0.}%{lua: + release_number = 2; + base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); + print(release_number + base_release_number - 1); +}%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} +## END: Set by rpmautospec + %global provider github %global provider_tld com %global project sgallagher @@ -8,32 +18,32 @@ Name: sscg -Version: 3.0.0 -Release: 10%{?dist} -Summary: Simple SSL certificate generator +Version: 4.0.3 +Release: %autorelease +Summary: Simple Signed Certificate Generator -License: GPLv3+ with exceptions +License: GPL-3.0-or-later WITH cryptsetup-OpenSSL-exception URL: https://%{provider_prefix} -Source0: https://%{provider_prefix}/releases/download/%{repo}-%{version}/%{repo}-%{version}.tar.xz - +Source0: %{URL}/archive/refs/tags/sscg-%{version}.tar.gz BuildRequires: gcc BuildRequires: libtalloc-devel BuildRequires: openssl-devel BuildRequires: popt-devel -BuildRequires: libpath_utils-devel BuildRequires: meson BuildRequires: ninja-build BuildRequires: help2man -Patch: 0001-Drop-usage-of-ERR_GET_FUNC.patch -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 +# For backwards-compatibility in RHEL, revert the 4.0 patch that disables +# dhparam file generation by default. +Patch: 0001-Restore-defaulting-to-dhparams.pem-creation.patch + +# Upstream patch to avoid segfault when receiving bad CLI arguments +Patch: 0002-Avoid-segfault-on-receiving-bad-CLI-arguments.patch + +# Downstream patch to restore error message at the end of execution that is +# checked by certain tests +Patch: 0003-Restore-error-message.patch %description A utility to aid in the creation of more secure "self-signed" @@ -44,7 +54,7 @@ up a full PKI environment and without exposing the machine to a risk of false signatures from the service certificate. %prep -%autosetup -p1 +%autosetup -p1 -n sscg-sscg-%{version} %build @@ -64,6 +74,19 @@ false signatures from the service certificate. %{_mandir}/man8/%{name}.8* %changelog +## START: Generated by rpmautospec +* Tue Dec 02 2025 Stephen Gallagher - 4.0.3-2 +- Fix issues discovered by OSCI tests + +* Tue Dec 02 2025 Stephen Gallagher - 4.0.3-1 +- Update to SSCG 4.0.3 + +* Tue Dec 02 2025 Stephen Gallagher - 4.0.1-1 +- Update to SSCG 4.0.1 + +* Mon Oct 27 2025 Stephen Gallagher - 4.0.0-2 +- Restore creation of dhparams file by default + * Mon Aug 11 2025 Stephen Gallagher - 3.0.0-10 - Fix IP address handling in CA certificate SAN constraints - Resolves: RHEL-107289 @@ -328,3 +351,5 @@ false signatures from the service certificate. * Mon Mar 16 2015 Stephen Gallagher 0.1-1 - First packaging + +## END: Generated by rpmautospec