From fa77119f6f6943c29b83145f8a6b7a6c5d7350e6 Mon Sep 17 00:00:00 2001 From: William Douglas Date: Wed, 28 Aug 2024 12:17:25 -0700 Subject: [PATCH] Add support for an alternative swupd certificate Enable an alternative swupd certificate location (the location of the default or given cert with an ".alt" appended to it). The purpose of this change is to allow more reliable and flexible key rotations. If either the main cert or alt cert fails when doing content verification then the other will be tried (and be used for the next operation). In this way, as long as both certs don't fail for the same content verification, progress can be made with either cert. Signed-off-by: William Douglas --- src/swupd_lib/signature.c | 63 ++++++++++- .../signature/alt-key-rotation.bats | 103 ++++++++++++++++++ 2 files changed, 164 insertions(+), 2 deletions(-) create mode 100755 test/functional/signature/alt-key-rotation.bats diff --git a/src/swupd_lib/signature.c b/src/swupd_lib/signature.c index 321f4f81f..7285025b6 100644 --- a/src/swupd_lib/signature.c +++ b/src/swupd_lib/signature.c @@ -49,6 +49,8 @@ static X509 *get_cert_from_path(const char *certificate_path); static X509_STORE *store = NULL; static STACK_OF(X509) *x509_stack = NULL; +static char *orig_cert = NULL; +static char *alt_cert = NULL; static int verify_callback_ignore_expiration(int ok, X509_STORE_CTX *local_store) { @@ -174,8 +176,7 @@ void signature_deinit(void) CRYPTO_cleanup_all_ex_data(); } -bool signature_verify_data(const void *data, size_t data_len, const void *sig_data, size_t sig_data_len, enum signature_flags flags) - +static bool signature_verify_data_internal(const void *data, size_t data_len, const void *sig_data, size_t sig_data_len, enum signature_flags flags) { bool result = false; int ret; @@ -267,6 +268,64 @@ bool signature_verify_data(const void *data, size_t data_len, const void *sig_da return result; } +static bool swap_certs(void) +{ + bool ret = true; + + signature_deinit(); + if (str_cmp(globals.cert_path, orig_cert) == 0) { + set_cert_path(alt_cert); + } else { + set_cert_path(orig_cert); + } + if (!signature_init(globals.cert_path, NULL)) { + ret = false; + } + + return ret; +} + +static bool use_alt_cert(void) +{ + bool ret = true; + + if (!globals.cert_path) { + return false; + } + + /* This function can be called multiple times and + * is intended to be able to handle swapping back + * and forth between main and alt cert files. */ + if (!orig_cert && !alt_cert) { + orig_cert = strdup_or_die(globals.cert_path); + string_or_die(&alt_cert, "%s.alt", globals.cert_path); + if (sys_file_exists(alt_cert)) { + warn("Default cert failed, attempting to use alternative: %s\n", alt_cert); + ret = swap_certs(); + } else { + FREE(alt_cert); + alt_cert = NULL; + ret = false; + } + } else if (!alt_cert) { + ret = false; + } else { + ret = swap_certs(); + } + + return ret; +} + +bool signature_verify_data(const void *data, size_t data_len, const void *sig_data, size_t sig_data_len, enum signature_flags flags) +{ + bool result = signature_verify_data_internal(data, data_len, sig_data, sig_data_len, flags); + if (!result && use_alt_cert()) { + result = signature_verify_data_internal(data, data_len, sig_data, sig_data_len, flags); + } + + return result; +} + bool signature_verify(const char *file, const char *sig_file, enum signature_flags flags) { bool result = false; diff --git a/test/functional/signature/alt-key-rotation.bats b/test/functional/signature/alt-key-rotation.bats new file mode 100755 index 000000000..749a5324c --- /dev/null +++ b/test/functional/signature/alt-key-rotation.bats @@ -0,0 +1,103 @@ +#!/usr/bin/env bats + +# Author: William Douglas +# Email: william.douglas@intel.com + +load "../testlib" + +test_setup() { + + create_test_environment -r "$TEST_NAME" 10 1 + export CERT_PATH="/usr/share/clear/update-ca/Swupd_Root.pem" + export ALT_CERT_PATH="$CERT_PATH.alt" + export SWUPD_OPTS_EXTRA="$SWUPD_OPTS_NO_FMT_NO_CERT -C $TARGET_DIR$CERT_PATH" + + create_version -r "$TEST_NAME" 20 10 + generate_certificate "$TEST_NAME/new_root.key" "$TEST_NAME/new_root.pem" + update_bundle -p "$TEST_NAME" os-core --add "$CERT_PATH":"$ABS_TEST_DIR"/Swupd_Root.pem + update_bundle "$TEST_NAME" os-core --add "$ALT_CERT_PATH":"$TEST_NAME/new_root.pem" + bump_format "$TEST_NAME" + + create_version -r "$TEST_NAME" 50 40 2 + +} + +@test "SIG029: alt key usable" { + + # Test the alternate keyfile is usable + + # file only used in check-update so not needed here + # sudo openssl smime -sign -binary -in "$WEB_DIR"/version/latest_version -signer "$TEST_NAME/new_root.pem" -inkey "$TEST_NAME/new_root.key" -out "$WEB_DIR"/version/latest_version.sig -outform DER + + # use the new cert to force the alt cert into use for the first time + sudo openssl smime -sign -binary -in "$WEB_DIR"/version/format2/latest -signer "$TEST_NAME/new_root.pem" -inkey "$TEST_NAME/new_root.key" -out "$WEB_DIR"/version/format2/latest.sig -outform DER + + # Sign the MoM with self signed intermediate cert + # This is verified first, use old cert to test swapping from alt to original certs + # sudo openssl smime -sign -binary -in "$WEB_DIR"/40/Manifest.MoM -signer "$TEST_NAME/new_root.pem" -inkey "$TEST_NAME/new_root.key" -out "$WEB_DIR"/40/Manifest.MoM.sig -outform DER + + # verified second, use new cert to test two cert swaps are usable in the same update + # and different manifest versions can be verified with different certs + sudo openssl smime -sign -binary -in "$WEB_DIR"/50/Manifest.MoM -signer "$TEST_NAME/new_root.pem" -inkey "$TEST_NAME/new_root.key" -out "$WEB_DIR"/50/Manifest.MoM.sig -outform DER + + run sudo sh -c "$SWUPD update -V 20 $SWUPD_OPTS_NO_FMT" + assert_status_is "$SWUPD_OK" + + assert_file_exists "$TARGET_DIR""$CERT_PATH" + assert_file_exists "$TARGET_DIR""$ALT_CERT_PATH" + run sudo sh -c "$SWUPD update -V 30 $SWUPD_OPTS_EXTRA" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Update started + Preparing to update from 20 to 30 + Downloading packs for: + - os-core + Finishing packs extraction... + Statistics for going from version 20 to version 30: + changed bundles : 1 + new bundles : 0 + deleted bundles : 0 + changed files : 2 + new files : 0 + deleted files : 0 + Validate downloaded files + No extra files need to be downloaded + Installing files... + Update was applied + Calling post-update helper scripts + Update successful - System updated from version 20 to version 30 + EOM + ) + assert_is_output "$expected_output" + assert_file_exists "$TARGET_DIR"/core + + run sudo sh -c "$SWUPD update -V 50 $SWUPD_OPTS_EXTRA" + + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Update started + Warning: Default cert failed, attempting to use alternative: .*alt + Preparing to update from 40 to 50 + Downloading packs for: + - os-core + Finishing packs extraction... + Statistics for going from version 40 to version 50: + changed bundles : 1 + new bundles : 0 + deleted bundles : 0 + changed files : 2 + new files : 0 + deleted files : 0 + Validate downloaded files + No extra files need to be downloaded + Installing files... + Update was applied + Calling post-update helper scripts + Update successful - System updated from version 40 to version 50 + EOM + ) + assert_regex_is_output "$expected_output" + assert_file_exists "$TARGET_DIR"/core + +}