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..fd8406a07 --- /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 + +}