diff --git a/.gitignore b/.gitignore index b68ee09..c0c0701 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ /sscg-3.0.7.tar.gz /sscg-3.0.8.tar.gz /sscg-4.0.0.tar.gz +/sscg-4.0.1.tar.gz diff --git a/0001-Revert-Don-t-default-to-generating-DH-params-file.patch b/0001-Revert-Don-t-default-to-generating-DH-params-file.patch index 95396e9..ccc1879 100644 --- a/0001-Revert-Don-t-default-to-generating-DH-params-file.patch +++ b/0001-Revert-Don-t-default-to-generating-DH-params-file.patch @@ -1,4 +1,4 @@ -From 70fddc4518f88624b141a056a07e5baf9ed2b31b Mon Sep 17 00:00:00 2001 +From 48660f15f990c645c10c261fadd53b128206802a Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Mon, 27 Oct 2025 14:58:11 -0400 Subject: [PATCH] Revert "Don't default to generating DH params file" @@ -11,10 +11,10 @@ This reverts commit 0e5e011acc2dc19f3c2fcb5699cf8fa662a2b135. 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/arguments.c b/src/arguments.c -index c9345342a353cc0ef68bbb668f8cc2baab168120..29373a33c232d54056ff14eec0819019a9ca4f10 100644 +index 53d5f44cec63b7a02a1c949844a28c6dcb49c7e3..0f27efd8d62a0566a5c8ab16a0c5f5e69e2fb51f 100644 --- a/src/arguments.c +++ b/src/arguments.c -@@ -681,7 +681,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, +@@ -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. " @@ -23,7 +23,7 @@ index c9345342a353cc0ef68bbb668f8cc2baab168120..29373a33c232d54056ff14eec0819019 NULL }, -@@ -691,7 +691,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, +@@ -692,7 +692,7 @@ sscg_handle_arguments (TALLOC_CTX *mem_ctx, POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN, &options->skip_dhparams, 0, @@ -33,10 +33,10 @@ index c9345342a353cc0ef68bbb668f8cc2baab168120..29373a33c232d54056ff14eec0819019 }, diff --git a/src/sscg.c b/src/sscg.c -index e4f9bfef624bfdb5df79a198a000c6cc5610c94d..cf0f80d47c37ebe26deaedd1c5bf4218b33e2dee 100644 +index 0beec82585228d4de49db3aecbedb13eb1e7be51..760e9ed258f06c09a09b3e1cdf1d237f792f7e5f 100644 --- a/src/sscg.c +++ b/src/sscg.c -@@ -187,9 +187,16 @@ main (int argc, const char **argv) +@@ -165,9 +165,16 @@ main (int argc, const char **argv) options->crl_mode); CHECK_OK (ret); @@ -55,16 +55,16 @@ index e4f9bfef624bfdb5df79a198a000c6cc5610c94d..cf0f80d47c37ebe26deaedd1c5bf4218 CHECK_MEM (dhparams_file); ret = sscg_io_utils_add_output_file (options->streams, -@@ -198,7 +205,6 @@ main (int argc, const char **argv) +@@ -177,7 +184,6 @@ main (int argc, const char **argv) options->dhparams_mode); CHECK_OK (ret); } - /* Validate and open the file paths */ - ret = sscg_io_utils_open_output_files (options->streams, options->overwrite); + 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 c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f752a3370 100755 +index aeeabc66458d1ad90fd5e954532f68f5619cb0a0..ca6fc77a268d2ef5d3d49309190ddd05a087f89e 100755 --- a/test/test_dhparams_creation.sh +++ b/test/test_dhparams_creation.sh @@ -42,10 +42,6 @@ @@ -99,7 +99,7 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f # Run sscg with the specified arguments local cmd_args=( "${MESON_BUILD_ROOT}/sscg" -@@ -114,22 +110,22 @@ function run_test { +@@ -115,23 +111,23 @@ function run_test { --cert-file "${output_dir}/service.pem" --cert-key-file "${output_dir}/service-key.pem" ) @@ -108,6 +108,7 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f if [ -n "$dhparams_output_file" ]; then cmd_args+=("--dhparams-file=$dhparams_output_file") fi + echo "${cmd_args[@]}" - + local exit_code=0 @@ -127,7 +128,7 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f # Check file creation if [ "$should_create_file" = "true" ]; then if [ ! -f "$expected_file" ]; then -@@ -149,17 +145,17 @@ function run_test { +@@ -151,17 +147,17 @@ function run_test { test_passed=false fi fi @@ -148,7 +149,7 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f popd >/dev/null echo } -@@ -176,7 +172,7 @@ run_test \ +@@ -178,7 +174,7 @@ run_test \ "" \ 0 \ "$WRITABLE_DIR/dhparams.pem" \ @@ -157,7 +158,7 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f "$WRITABLE_DIR" # Test 2: No --dhparams-file, readonly directory, no existing file -@@ -252,7 +248,7 @@ run_test \ +@@ -254,7 +250,7 @@ run_test \ "false" \ "$WRITABLE_DIR" @@ -167,5 +168,5 @@ index c7c289cce00e303621562fb8b160faa9a122cd00..5c957e3e226f915bfe77458f55d81b0f run_test \ 8 \ -- -2.51.0 +2.51.1 diff --git a/0002-Handle-opening-the-same-file.patch b/0002-Handle-opening-the-same-file.patch new file mode 100644 index 0000000..0785319 --- /dev/null +++ b/0002-Handle-opening-the-same-file.patch @@ -0,0 +1,361 @@ +From ad7ec4c30d8b0911574131157c7c7aeee9b4c570 Mon Sep 17 00:00:00 2001 +From: Stephen Gallagher +Date: Mon, 1 Dec 2025 11:31:43 -0500 +Subject: [PATCH 2/2] Handle opening the same file + +We introduced a regression in 4.0.1 when closing the TOCTOU issue. We +incorrectly refuse to open the same file twice to save both a CA and +cert into the same location. + +This changes the opening logic to always open for both reading and +writing to check if it is zero-length. + +Also switches the EC curve tests to output the CA and service cert to a +common path to ensure we are testing this case properly. + +Updates some incorrect expected behaviors in the test_dhparams_creation +test as well. + +Signed-off-by: Stephen Gallagher +--- + src/io_utils.c | 85 +++++++++++++++++++++++++++++--- + src/sscg.c | 44 ++++++++++++----- + test/test_dhparams_creation.sh | 37 +++++++------- + test/test_ecdsa_cert_validity.sh | 12 ++--- + 4 files changed, 135 insertions(+), 43 deletions(-) + +diff --git a/src/io_utils.c b/src/io_utils.c +index 1cc855bb8f394fe6a11faf28eb127a54ab8df3f5..10c5f6818115a0c8219bca586e710ac9f3266bd5 100644 +--- a/src/io_utils.c ++++ b/src/io_utils.c +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + + #include "config.h" + #ifdef HAVE_GETTEXT +@@ -102,17 +103,85 @@ static int + sscg_io_utils_open_file (const char *path, bool overwrite, FILE **fp) + { + FILE *_fp = NULL; +- if (overwrite) +- _fp = fopen (path, "w"); +- else +- _fp = fopen (path, "wx"); ++ struct stat st; ++ int ret; ++ int fd; ++ ++ /* Try to open with r+ mode (file must exist) */ ++ _fp = fopen (path, "r+"); + if (!_fp) + { +- SSCG_ERROR ("Could not open file %s: %s\n", path, strerror (errno)); +- return errno; ++ /* If file doesn't exist, create it with w+ mode */ ++ if (errno == ENOENT) ++ { ++ _fp = fopen (path, "w+"); ++ if (!_fp) ++ { ++ SSCG_ERROR ( ++ "Could not create file %s: %s\n", path, strerror (errno)); ++ return errno; ++ } ++ /* New file has size 0, so we can proceed */ ++ ret = EOK; ++ goto done; ++ } ++ else ++ { ++ SSCG_ERROR ("Could not open file %s: %s\n", path, strerror (errno)); ++ ret = errno; ++ goto done; ++ } + } +- *fp = _fp; +- return EOK; ++ ++ /* File exists and was opened successfully, check its size */ ++ fd = fileno (_fp); ++ ret = fstat (fd, &st); ++ if (ret != 0) ++ { ++ SSCG_ERROR ("Could not stat file %s: %s\n", path, strerror (errno)); ++ ret = errno; ++ goto done; ++ } ++ ++ /* Check if file size is greater than zero */ ++ if (st.st_size > 0) ++ { ++ if (overwrite) ++ { ++ /* Truncate the file */ ++ ret = ftruncate (fd, 0); ++ if (ret != 0) ++ { ++ SSCG_ERROR ( ++ "Could not truncate file %s: %s\n", path, strerror (errno)); ++ ret = errno; ++ goto done; ++ } ++ /* Rewind to beginning after truncation */ ++ rewind (_fp); ++ } ++ else ++ { ++ /* File exists and overwrite is false - return error */ ++ SSCG_ERROR ("File %s already exists\n", path); ++ ret = EEXIST; ++ goto done; ++ } ++ } ++ ++ /* File size is zero or has been truncated */ ++ ret = EOK; ++ ++done: ++ if (ret == EOK) ++ { ++ *fp = _fp; ++ _fp = NULL; ++ } ++ ++ if (_fp) ++ fclose (_fp); ++ return ret; + } + + +diff --git a/src/sscg.c b/src/sscg.c +index 760e9ed258f06c09a09b3e1cdf1d237f792f7e5f..7ce0a331e56d32c5ebf94e580f9bd9836a909028 100644 +--- a/src/sscg.c ++++ b/src/sscg.c +@@ -75,10 +75,11 @@ main (int argc, const char **argv) + + struct sscg_stream *stream = NULL; + +- /* Always use umask 0577 for generating certificates and keys +- This means that it's opened as write-only by the effective +- user. */ +- umask (0577); ++ /* Always use umask 0177 for generating certificates and keys ++ This means that it's opened as read/write only by the effective ++ user. ++ */ ++ umask (0177); + + #ifdef HAVE_GETTEXT + /* Initialize internationalization */ +@@ -170,19 +171,38 @@ main (int argc, const char **argv) + 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); + } + else + { + dhparams_file = talloc_strdup (main_ctx, "./dhparams.pem"); +- } +- CHECK_MEM (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); ++ if (ret == EACCES || ret == EEXIST) ++ { ++ SSCG_LOG (SSCG_VERBOSE, ++ "Could not open dhparams file %s: %s\n", ++ dhparams_file, ++ strerror (ret)); ++ ret = EOK; ++ } ++ else ++ { ++ CHECK_OK (ret); ++ } ++ } + } + /* Validate and open the file paths */ + ret = sscg_io_utils_open_BIOs (options->streams); +diff --git a/test/test_dhparams_creation.sh b/test/test_dhparams_creation.sh +index ca6fc77a268d2ef5d3d49309190ddd05a087f89e..49f2b08d23246c90663eb7d2e5078817eb42139b 100755 +--- a/test/test_dhparams_creation.sh ++++ b/test/test_dhparams_creation.sh +@@ -50,6 +50,7 @@ set -e + DHPARAMS_TMPDIR=$(mktemp --directory --tmpdir=$GITHUB_WORKSPACE sscg_dhparams_test_XXXXXX) + WRITABLE_DIR="$DHPARAMS_TMPDIR/writable" + READONLY_DIR="$DHPARAMS_TMPDIR/readonly" ++READONLY_WITH_DHPARAMS_DIR="$DHPARAMS_TMPDIR/readonly_with_dhparams" + DHPARAMS_DIR="$DHPARAMS_TMPDIR/dhparams" + + function cleanup { +@@ -65,14 +66,16 @@ trap cleanup EXIT + # Set up test directories + mkdir -p "$WRITABLE_DIR" + mkdir -p "$READONLY_DIR" ++mkdir -p "$READONLY_WITH_DHPARAMS_DIR" + mkdir -p "$DHPARAMS_DIR" + + # Create a pre-existing dhparams.pem file for some tests +-touch "$DHPARAMS_DIR/dhparams.pem" ++echo "preexisting dhparams.pem" > "$DHPARAMS_DIR/dhparams.pem" + + # Copy pre-existing dhparams.pem to readonly directory before making it readonly +-cp "$DHPARAMS_DIR/dhparams.pem" "$READONLY_DIR/dhparams.pem" +-chmod 555 "$READONLY_DIR" ++cp "$DHPARAMS_DIR/dhparams.pem" "$READONLY_WITH_DHPARAMS_DIR/dhparams.pem" ++chmod 0555 "$READONLY_DIR" ++chmod 0555 "$READONLY_WITH_DHPARAMS_DIR" + + failed_tests=0 + total_tests=8 +@@ -86,9 +89,9 @@ function run_test { + local expected_file="$6" + local should_create_file="$7" + local output_dir="$8" +- ++ + echo "Test $test_num: $description" +- ++ + pushd "$work_dir" >/dev/null + + # Check if the expected file exists before running sscg +@@ -102,7 +105,7 @@ function run_test { + if [ -z "$output_dir" ]; then + output_dir="." + fi +- ++ + # Run sscg with the specified arguments + local cmd_args=( + "${MESON_BUILD_ROOT}/sscg" +@@ -111,23 +114,23 @@ function run_test { + --cert-file "${output_dir}/service.pem" + --cert-key-file "${output_dir}/service-key.pem" + ) +- ++ + if [ -n "$dhparams_output_file" ]; then + cmd_args+=("--dhparams-file=$dhparams_output_file") + fi + echo "${cmd_args[@]}" +- ++ + local exit_code=0 + "${cmd_args[@]}" >/dev/null 2>&1 || exit_code=$? +- ++ + local test_passed=true +- ++ + # Check exit code + if [ "$exit_code" -ne "$expected_exit_code" ]; then + echo " FAIL: Expected exit code $expected_exit_code, got $exit_code" + test_passed=false + fi +- ++ + # Check file creation + if [ "$should_create_file" = "true" ]; then + if [ ! -f "$expected_file" ]; then +@@ -147,17 +150,17 @@ function run_test { + test_passed=false + fi + fi +- ++ + if [ "$test_passed" = "true" ]; then + echo " PASS" + else + ((failed_tests++)) + fi +- ++ + # Clean up any created files for next test + rm -f "${output_dir}/ca.crt" "${output_dir}/service.pem" "${output_dir}/service-key.pem" + rm -f "$expected_file" || true # Ignore errors +- ++ + popd >/dev/null + echo + } +@@ -250,15 +253,15 @@ run_test \ + "false" \ + "$WRITABLE_DIR" + +-# Test 8: --dhparams-file to non-writable path, existing file ++# Test 8: --dhparams-file to non-writable path, existing file + # Arguments: test_num description work_dir dhparams_output_file expected_exit_code expected_file should_create_file output_dir + run_test \ + 8 \ + "--dhparams-file to non-writable path, existing file" \ + "$WRITABLE_DIR" \ +- "$READONLY_DIR/dhparams.pem" \ ++ "$READONLY_WITH_DHPARAMS_DIR/dhparams.pem" \ + 17 \ +- "$READONLY_DIR/dhparams.pem" \ ++ "$READONLY_WITH_DHPARAMS_DIR/dhparams.pem" \ + "false" \ + "$WRITABLE_DIR" + +diff --git a/test/test_ecdsa_cert_validity.sh b/test/test_ecdsa_cert_validity.sh +index e7a58c543b692099fc6aeb965e19a6cbe7841975..996be7d1628204433a342f322c72d0b3359fd109 100755 +--- a/test/test_ecdsa_cert_validity.sh ++++ b/test/test_ecdsa_cert_validity.sh +@@ -185,18 +185,18 @@ pushd "$TMPDIR" + --key-type ecdsa \ + --ec-curve "$_arg_ec_curve" \ + --cert-key-password mypassword \ +- --dhparams-file "dhparams.pem" ++ --dhparams-file "dhparams.pem" \ ++ --ca-file "combined.crt" \ ++ --cert-file "combined.crt" + + # Verify that the expected files were created + test -e dhparams.pem +-test -e ca.crt +-test -e service.pem ++test -e combined.crt + test -e service-key.pem + + # Verify that they have the correct file format + openssl dhparam -noout -in dhparams.pem +-openssl x509 -noout -in ca.crt +-openssl x509 -noout -in service.pem ++openssl x509 -noout -in combined.crt + + grep "ENCRYPTED PRIVATE KEY" service-key.pem + openssl pkey -noout -in service-key.pem -passin pass:mypassword +@@ -210,7 +210,7 @@ curve_info=$(openssl pkey -text -noout -in service-key.pem -passin pass:mypasswo + echo "$curve_info" | grep "$_arg_ec_curve" + + # Validate the certificates +-openssl verify -x509_strict -CAfile ca.crt service.pem ++openssl verify -x509_strict -CAfile combined.crt combined.crt + + popd # $TMPDIR + +-- +2.52.0 + diff --git a/sources b/sources index 6dc9c06..2f64a75 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (sscg-4.0.0.tar.gz) = 163fd89520714a8e9643c0f83c84b3d602b77351e4094bb214a2545cacaef10a0b195fd7f176ac3c8bca975c3b96568eb802e562f13d5e94438f728cea6e8965 +SHA512 (sscg-4.0.1.tar.gz) = 935ce6f22cb5fb8fd5cf325a6df296de5d8527c22e86f948b6b15b5e568c767fe26267aaaaf6d741dad02bfabf821eb6c8a5b729dcab3ed91af4f553478f7fd9 diff --git a/sscg.spec b/sscg.spec index 229a56a..166aff8 100644 --- a/sscg.spec +++ b/sscg.spec @@ -9,7 +9,7 @@ %{!?meson_test: %global meson_test %{__meson} test -C %{_vpath_builddir} --num-processes %{_smp_build_ncpus} --print-errorlogs} Name: sscg -Version: 4.0.0 +Version: 4.0.1 Release: %autorelease Summary: Simple Signed Certificate Generator @@ -21,7 +21,6 @@ BuildRequires: libtalloc-devel BuildRequires: openssl BuildRequires: openssl-devel BuildRequires: popt-devel -BuildRequires: libpath_utils-devel BuildRequires: meson BuildRequires: ninja-build BuildRequires: help2man @@ -31,6 +30,9 @@ BuildRequires: help2man # dhparam file generation by default. Patch: 0001-Revert-Don-t-default-to-generating-DH-params-file.patch +# Fix a regression in SSCG 4.0.1 that prevents use of the same file for +# the cert and CA. +Patch: 0002-Handle-opening-the-same-file.patch %description A utility to aid in the creation of more secure "self-signed"