From 8fdb10efd2098faa450b9f22f97c36b980f67cc7 Mon Sep 17 00:00:00 2001 From: Otavio Pontes Date: Wed, 12 Feb 2020 12:46:25 -0800 Subject: [PATCH] 3rd-party: Import certificate from 3rd-party repository on repo add When trying to add the repository for the first time, download the public certificate published by the 3rd party repository and with a user confirmation, use it to install the 3rd-party os-core. After that the official certificate will be installed for future commands. Signed-off-by: Otavio Pontes --- src/3rd_party_add.c | 152 ++++++++++++++++++ .../3rd-party-repo-add-certificate.bats | 100 ++++++++++++ 2 files changed, 252 insertions(+) create mode 100755 test/functional/3rd-party/3rd-party-repo-add-certificate.bats diff --git a/src/3rd_party_add.c b/src/3rd_party_add.c index aea90f5f..76bf3435 100644 --- a/src/3rd_party_add.c +++ b/src/3rd_party_add.c @@ -19,9 +19,12 @@ #define _GNU_SOURCE +#include #include +#include #include "3rd_party_repos.h" +#include "signature.h" #include "swupd.h" #ifdef THIRDPARTY @@ -89,6 +92,138 @@ static int remove_repo(const char *repo_name) return 0; } +static bool confirm_certificate(const char *cert) +{ + int c; + + info("Importing 3rd-party repository public certificate:\n\n"); + signature_print_info(cert); + + info("\n"); + info("Do you want to accept this certificate? (y/N): "); + + c = tolower(getchar()); + info("\n"); + + return c == 'y'; +} + +static int import_temp_certificate(int version, char *hash) +{ + char *cert_tar, *cert, *url; + int ret = 0; + + cert_tar = sys_path_join(globals.state_dir, "temp_cert.tar"); + cert = sys_path_join(globals.state_dir, hash); + + url = str_or_die("%s/%d/files/%s.tar", globals.content_url, version, hash); + + ret = swupd_curl_get_file(url, cert_tar); + if (ret != 0) { + goto out; + } + + ret = archives_check_single_file_tarball(cert_tar, hash); + if (ret != 0) { + goto out; + } + + ret = archives_extract_to(cert_tar, globals.state_dir); + if (ret != 0) { + goto out; + } + + if (confirm_certificate(cert)) { + signature_deinit(); + if (!signature_init(cert, NULL)) { + signature_deinit(); + ret = -EPERM; + goto out; + } + } else { + ret = -EACCES; + goto out; + } + +out: + unlink(cert_tar); + unlink(cert); + free(cert); + free(cert_tar); + free(url); + return ret; +} + +static int import_certificate_from_version(int version) +{ + int ret = 0; + char *os_core, *url, *os_core_tar; + struct manifest *manifest = NULL; + struct list *i; + struct file *cert_file = NULL; + + os_core_tar = sys_path_join(globals.state_dir, "temp_manifest.tar"); + os_core = sys_path_join(globals.state_dir, "Manifest.os-core"); + url = str_or_die("%s/%d/%s", globals.content_url, version, "Manifest.os-core.tar"); + + unlink(os_core_tar); + ret = swupd_curl_get_file(url, os_core_tar); + if (ret) { + goto out; + } + + ret = archives_extract_to(os_core_tar, globals.state_dir); + if (ret != 0) { + goto out; + } + + manifest = manifest_parse("os-core", os_core, false); + if (!manifest) { + ret = -EPROTO; + goto out; + } + + for (i = manifest->files; i; i = i->next) { + struct file *f = i->data; + if (strncmp(f->filename, CERT_PATH, sizeof(CERT_PATH)) == 0) { + cert_file = f; + break; + } + } + + if (!cert_file) { + ret = -ENOENT; + goto out; + } + + ret = import_temp_certificate(cert_file->last_change, cert_file->hash); + +out: + manifest_free(manifest); + unlink(os_core_tar); + unlink(os_core); + free(os_core); + free(os_core_tar); + free(url); + return ret; +} + +static int import_third_party_certificate(void) +{ + char *url; + int tmp_version; + + url = str_or_die("%s/version/format%s/latest", globals.content_url, globals.format_string); + tmp_version = get_int_from_url(url); + free(url); + + if (tmp_version <= 0) { + return -1; + } + + return import_certificate_from_version(tmp_version); +} + enum swupd_code third_party_add_main(int argc, char **argv) { enum swupd_code ret_code = SWUPD_OK; @@ -158,6 +293,23 @@ enum swupd_code third_party_add_main(int argc, char **argv) goto finish; } + if (globals.sigcheck && !globals.user_defined_cert_path) { + ret = import_third_party_certificate(); + if (ret < 0) { + if (ret == -ENOENT) { + error("Public certificate not found on 3rd-party repository\n"); + info("To ignore certificate check use --nosigcheck\n"); + } else if (ret != -EACCES) { + error("Impossible to import 3rd party repository certificate\n"); + info("To ignore certificate check use --nosigcheck\n"); + } + revert = true; + + ret_code = SWUPD_BAD_CERT; + goto finish; + } + } + /* get repo's latest version */ repo_version = get_latest_version(repo->url); if (repo_version < 0) { diff --git a/test/functional/3rd-party/3rd-party-repo-add-certificate.bats b/test/functional/3rd-party/3rd-party-repo-add-certificate.bats new file mode 100755 index 00000000..e8f819ae --- /dev/null +++ b/test/functional/3rd-party/3rd-party-repo-add-certificate.bats @@ -0,0 +1,100 @@ +#!/usr/bin/env bats + +# Author: Otavio Pontes +# Email: otavio.pontes@intel.com + +load "../testlib" + +export repo1 +export repo2 + +global_setup() { + # Skip this test for local development because we write the certificate on / + # This is necessary because the default certificate location is hardcoded on build time and we + # need to run swupd without -C parameter to test this feature. + if [ -z "${RUNNING_IN_CI}" ]; then + return + fi + + if [ ! -f /usr/share/clear/update-ca/Swupd_Root.pem ]; then + sudo mkdir -p /usr/share/clear/update-ca + sudo cp "$TEST_ROOT_DIR"/Swupd_Root.pem /usr/share/clear/update-ca + export CERT_WAS_INSTALLED=1 + fi +} + +global_teardown() { + + if [ -z "${RUNNING_IN_CI}" ]; then + return + fi + + if [ ! -z "${CERT_WAS_INSTALLED}" ]; then + sudo rm usr/share/clear/update-ca/Swupd_Root.pem + fi +} + +test_setup() { + + create_test_environment "$TEST_NAME" + create_third_party_repo "$TEST_NAME" 10 staging test-repo1 + repo1="$TPWEBDIR" + create_third_party_repo "$TEST_NAME" 10 staging test-repo2 + repo2="$TPWEBDIR" +} + +@test "TPR066: Add a single repo importing the certificate" { + + run sudo sh -c "echo 'y' | $SWUPD 3rd-party add test-repo1 file://$repo1 $SWUPD_OPTS_NO_CERT" + assert_status_is "$SWUPD_OK" + expected_output=$(cat <<-EOM + Adding 3rd-party repository test-repo1... + Importing 3rd-party repository public certificate: + Issuer: /C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost + Subject: /C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost + .* + Installing the required bundle 'os-core' from the repository... + Note that all bundles added from a 3rd-party repository are forced to run with the --no-scripts flag for security reasons + Loading required manifests... + Downloading packs for: + - os-core + Finishing packs extraction... + Validate downloaded files + No extra files need to be downloaded + Installing files... + Warning: post-update helper scripts skipped due to --no-scripts argument + Successfully installed 1 bundle + Repository added successfully + EOM + ) + assert_regex_is_output "$expected_output" + + run sudo sh -c "cat $STATEDIR/3rd-party/repo.ini" + expected_output=$(cat <<-EOM + [test-repo1] + url=file://$repo1 + EOM + ) + assert_is_output "$expected_output" + + # make sure the os-core bundle of the repo is installed in the appropriate place + assert_file_exists "$TARGETDIR"/"$THIRD_PARTY_BUNDLES_DIR"/test-repo1/usr/share/clear/bundles/os-core + assert_file_exists "$TARGETDIR"/"$THIRD_PARTY_BUNDLES_DIR"/test-repo1/usr/lib/os-release + +} + +@test "TPR067: Reject a certificate when adding a repository" { + + run sudo sh -c "echo 'n' | $SWUPD 3rd-party add test-repo1 file://$repo1 $SWUPD_OPTS_NO_CERT" + assert_status_is "$SWUPD_BAD_CERT" + expected_output=$(cat <<-EOM + Adding 3rd-party repository test-repo1... + Importing 3rd-party repository public certificate: + Issuer: /C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost + Subject: /C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost + .* + Failed to add repository + EOM + ) + assert_regex_is_output "$expected_output" +}