mirror of
https://github.com/clearlinux/swupd-client.git
synced 2026-10-03 15:29:29 +00:00
Implement signature verification
This patch adds functionality for the swupd client to do signature verification on the MoM, which ensures a root of trust for the rest of the update by guaranteeing the authenticity of the MoM and content it includes. As we focus now on just verifying the signed MoM, a number of functions now become static to src/signature.c. By default MoM signature verification is disabled. Configure --enable-signature-verification to enable it. When enabled the default cert for verification is /usr/share/clear/update-ca/ClearLinuxRoot.pem, as specified by concatenation of SWUPDCERT onto UPDATE_CA_CERTS_PATH. The SWUPDCERT can be overridden via configure --with-swupdcert=some.pem. When signature verification is enabled, and the MoM's signature does _NOT_ verify, currently only a warning is presented but the swupd operation continues. In the future signature verification will become mandatory. We first need to sort out a few details with mixer to insure the right thing happens there. Signed-off-by: Tim Pepper <timothy.c.pepper@linux.intel.com> Signed-off-by: Tudor Marcu <tudor.marcu@intel.com>
This commit is contained in:
+1
-1
@@ -90,7 +90,7 @@ swupd_sig_verifytest_LDADD = $(SWUPD_CORE_LIBS)
|
||||
|
||||
noinst_HEADERS = $(top_srcdir)/include/*
|
||||
|
||||
swupdcertsdir = @swupdcertsdir@
|
||||
swupdcertsdir = @update_ca_certs_path@
|
||||
SWUPD_CERTS = certs/157753a5.0 \
|
||||
certs/425b0f6b.0 \
|
||||
certs/425b0f6b.key \
|
||||
|
||||
+19
-9
@@ -11,11 +11,7 @@ AM_SILENT_RULES([yes])
|
||||
AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
AC_LANG(C)
|
||||
AC_ARG_WITH([swupdcertsdir],
|
||||
[AS_HELP_STRING([--with-swupdcertsdir=DIR], [swupdcertsdir files])],
|
||||
[swupdcertsdir=$withval],
|
||||
[swupdcertsdir="/usr/share/clear/update-ca"])
|
||||
AC_SUBST([swupdcertsdir], [$swupdcertsdir])
|
||||
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
PKG_CHECK_MODULES([bsdiff], [bsdiff])
|
||||
PKG_CHECK_MODULES([lzma], [liblzma])
|
||||
@@ -29,6 +25,19 @@ AS_IF([test "x$enable_bzip2" != "xno" ],
|
||||
AC_CHECK_LIB([bz2], [BZ2_bzBuffToBuffCompress], [], [AC_MSG_ERROR([the libbz2 library is missing])])],
|
||||
[AC_DEFINE(SWUPD_WITHOUT_BZIP2,1,[Do not use bzip2 compression])]
|
||||
)
|
||||
AC_ARG_ENABLE(
|
||||
[signature-verification],
|
||||
[AS_HELP_STRING([--enable-signature-verification], [Enable signature check (disabled by default)])],
|
||||
[AC_DEFINE([SIGNATURES], [], [Enable signature check as default])]
|
||||
|
||||
)
|
||||
|
||||
if test "$enable_signature_verification" = "yes" ; then
|
||||
AC_ARG_WITH([swupdcert],
|
||||
[AS_HELP_STRING([--with-swupdcert=FILE], [swupd verification cert])],
|
||||
[AC_DEFINE_UNQUOTED([SWUPDCERT], ["$withval"], [swupd verification cert])],
|
||||
[AC_DEFINE([SWUPDCERT], ["ClearLinuxRoot.pem"], [swupd verification cert])])
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(
|
||||
[tests],
|
||||
@@ -83,12 +92,13 @@ AS_IF([test "$enable_tests" != "no"], [
|
||||
AM_CONDITIONAL([ENABLE_TESTS], [test "$enable_tests" != "no"])
|
||||
|
||||
PKG_CHECK_MODULES([curl], [libcurl])
|
||||
PKG_CHECK_MODULES([openssl], [libcrypto >= 0.9.8])
|
||||
PKG_CHECK_MODULES([openssl], [libcrypto >= 1.0.2])
|
||||
AC_CHECK_LIB([pthread], [pthread_create])
|
||||
AC_CHECK_PROGS(TAR, tar)
|
||||
|
||||
# default to Linux rootfs build
|
||||
enable_linux_rootfs_build="yes"
|
||||
certs_path="/usr/share/clear/update-ca"
|
||||
|
||||
# document all options for build variants
|
||||
## (1) build variants
|
||||
@@ -103,7 +113,6 @@ AH_TEMPLATE([LOG_DIR],[Directory for swupd log files])
|
||||
AH_TEMPLATE([LOCK_DIR],[Directory for lock file])
|
||||
AH_TEMPLATE([BUNDLES_DIR],[Directory to use for bundles])
|
||||
AH_TEMPLATE([UPDATE_CA_CERTS_PATH],[Location of CA certificates])
|
||||
AH_TEMPLATE([SIGNATURE_CA_CERT],[CA certificate to use])
|
||||
AH_TEMPLATE([MOTD_FILE],[motd file path])
|
||||
|
||||
if test "$enable_linux_rootfs_build" = "yes"; then
|
||||
@@ -113,13 +122,14 @@ if test "$enable_linux_rootfs_build" = "yes"; then
|
||||
AC_DEFINE([LOG_DIR],["/var/log/swupd"])
|
||||
AC_DEFINE([LOCK_DIR],["/run/lock"])
|
||||
AC_DEFINE([BUNDLES_DIR],["/usr/share/clear/bundles"])
|
||||
AC_DEFINE([UPDATE_CA_CERTS_PATH],["/usr/share/clear/update-ca"])
|
||||
AC_DEFINE([SIGNATURE_CA_CERT],["test-do-not-ship-R0-0.pem"])
|
||||
AC_DEFINE_UNQUOTED([UPDATE_CA_CERTS_PATH],["$certs_path"])
|
||||
AC_DEFINE([MOTD_FILE],["/usr/lib/motd.d/001-new-release"])
|
||||
else
|
||||
AC_MSG_ERROR([Unknown build variant])
|
||||
fi
|
||||
|
||||
AC_SUBST([update_ca_certs_path], ["$certs_path"])
|
||||
|
||||
AC_CONFIG_FILES([Makefile data/check-update.service data/check-update.timer])
|
||||
AC_REQUIRE_AUX_FILE([tap-driver.sh])
|
||||
AC_OUTPUT
|
||||
|
||||
+1
-27
@@ -3,26 +3,6 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* Initialize this module.
|
||||
* @param ca_cert_filename - the file containing the CA certificate
|
||||
* @return true <=> no error
|
||||
*/
|
||||
bool signature_initialize(const char *ca_cert_filename);
|
||||
|
||||
/*
|
||||
* Terminate usage of this module, free resources.
|
||||
*/
|
||||
void signature_terminate(void);
|
||||
|
||||
/*
|
||||
* Verify data file contents against a purported signature.
|
||||
* @param data_filename - the file containing the data
|
||||
* @param sig_filename - the file containing the signature
|
||||
* @return true <=> no error
|
||||
*/
|
||||
bool signature_verify(const char *data_filename, const char *sig_filename);
|
||||
|
||||
/*
|
||||
* The given data file has already been downloaded from the given URL.
|
||||
* Download the corresponding signature file, and verify the data against the signature.
|
||||
@@ -30,12 +10,6 @@ bool signature_verify(const char *data_filename, const char *sig_filename);
|
||||
* @param data_url - the URL from which the data came
|
||||
* @param data_filename - the file containing the data
|
||||
*/
|
||||
bool signature_download_and_verify(const char *data_url, const char *data_filename);
|
||||
|
||||
/*
|
||||
* Delete the signature file corresponding to given data file.
|
||||
* @param data_filename - the file containing the data
|
||||
*/
|
||||
void signature_delete(const char *data_filename);
|
||||
bool download_and_verify_signature(const char *data_url, const char *data_filename);
|
||||
|
||||
#endif /* SIGNATURE_H_ */
|
||||
|
||||
+21
-19
@@ -428,7 +428,7 @@ static int try_delta_manifest_download(int current, int new, char *component, st
|
||||
struct stat buf;
|
||||
|
||||
if (strcmp(component, "MoM") == 0) {
|
||||
#warning "need to crypto validate MoM and allow delta"
|
||||
// We don't do MoM deltas.
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -459,16 +459,9 @@ static int try_delta_manifest_download(int current, int new, char *component, st
|
||||
unlink(deltafile);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!signature_download_and_verify(url, deltafile)) {
|
||||
ret = -1;
|
||||
unlink(deltafile);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now apply the manifest delta */
|
||||
|
||||
string_or_die(&newfile, "%s/%i/Manifest.%s", state_dir, new, component);
|
||||
|
||||
ret = apply_bsdiff_delta(original, newfile, deltafile);
|
||||
@@ -480,7 +473,6 @@ static int try_delta_manifest_download(int current, int new, char *component, st
|
||||
}
|
||||
|
||||
unlink(deltafile);
|
||||
signature_delete(deltafile);
|
||||
|
||||
out:
|
||||
free(original);
|
||||
@@ -494,7 +486,7 @@ out:
|
||||
/* TODO: This should deal with nested manifests better */
|
||||
static int retrieve_manifests(int current, int version, char *component, struct file *file)
|
||||
{
|
||||
char *url;
|
||||
char *url = NULL;
|
||||
char *filename;
|
||||
char *dir;
|
||||
int ret = 0;
|
||||
@@ -503,17 +495,19 @@ static int retrieve_manifests(int current, int version, char *component, struct
|
||||
|
||||
string_or_die(&filename, "%s/%i/Manifest.%s.tar", state_dir, version, component);
|
||||
if (stat(filename, &sb) == 0) {
|
||||
return 0;
|
||||
ret = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!check_network()) {
|
||||
return -ENOSWUPDSERVER;
|
||||
ret = -ENOSWUPDSERVER;
|
||||
goto out;
|
||||
}
|
||||
|
||||
string_or_die(&dir, "%s/%i", state_dir, version);
|
||||
ret = mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
if ((ret != 0) && (errno != EEXIST)) {
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
free(dir);
|
||||
|
||||
@@ -532,11 +526,6 @@ static int retrieve_manifests(int current, int version, char *component, struct
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!signature_download_and_verify(url, filename)) {
|
||||
unlink(filename);
|
||||
goto out;
|
||||
}
|
||||
|
||||
string_or_die(&tar, TAR_COMMAND " -C %s/%i -xf %s/%i/Manifest.%s.tar 2> /dev/null",
|
||||
state_dir, version, state_dir, version, component);
|
||||
|
||||
@@ -588,6 +577,8 @@ struct manifest *load_mom(int version)
|
||||
{
|
||||
struct manifest *manifest = NULL;
|
||||
int ret = 0;
|
||||
char *filename;
|
||||
char *url;
|
||||
|
||||
ret = retrieve_manifests(version, version, "MoM", NULL);
|
||||
if (ret != 0) {
|
||||
@@ -595,14 +586,25 @@ struct manifest *load_mom(int version)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
string_or_die(&filename, "%s/%i/Manifest.MoM", state_dir, version);
|
||||
string_or_die(&url, "%s/%i/Manifest.MoM", content_url, version);
|
||||
if (!download_and_verify_signature(url, filename)) {
|
||||
printf("WARNING!!! FAILED TO VERIFY SIGNATURE OF Manifest.MoM\n");
|
||||
}
|
||||
free(filename);
|
||||
free(url);
|
||||
|
||||
manifest = manifest_from_file(version, "MoM");
|
||||
|
||||
if (manifest == NULL) {
|
||||
printf("Failed to load %d MoM manifest\n", version);
|
||||
return NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
|
||||
out:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Loads the MANIFEST for bundle associated with FILE at VERSION, referenced by
|
||||
|
||||
@@ -66,13 +66,6 @@ static int download_pack(int oldversion, int newversion, char *module)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!signature_download_and_verify(url, filename)) {
|
||||
free(url);
|
||||
unlink(filename);
|
||||
free(filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(url);
|
||||
|
||||
printf("Extracting pack.\n");
|
||||
|
||||
+362
-141
@@ -17,6 +17,8 @@
|
||||
*
|
||||
* Authors:
|
||||
* Tom Keel <thomas.keel@intel.com>
|
||||
* Tudor Marcu <tudor.marcu@intel.com>
|
||||
* Tim Pepper <timothy.c.pepper@linux.intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -24,198 +26,417 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "signature.h"
|
||||
#include "swupd.h"
|
||||
|
||||
/*
|
||||
* Implementation flavors:
|
||||
* FAKE ..... do nothing, always return success
|
||||
* FORGIVE .. do everything, always return success
|
||||
* REAL ..... do everything, return the real status
|
||||
*/
|
||||
#define IMPL_FAKE 0
|
||||
#define IMPL_FORGIVE 1
|
||||
#define IMPL_REAL 2
|
||||
#ifdef SIGNATURES
|
||||
|
||||
#warning "TODO pick signing scheme"
|
||||
#if defined(SWUPD_LINUX_ROOTFS)
|
||||
#define IMPL IMPL_FAKE
|
||||
#endif
|
||||
#define CERTNAME UPDATE_CA_CERTS_PATH"/"SWUPDCERT
|
||||
|
||||
#if IMPL != IMPL_FAKE
|
||||
static bool validate_certificate(void);
|
||||
static int verify_callback(int, X509_STORE_CTX *);
|
||||
static bool get_pubkey();
|
||||
|
||||
static X509_STORE *create_store(const char *, const char *, const char *);
|
||||
FILE *fp_pubkey = NULL;
|
||||
static EVP_PKEY *pkey = NULL;
|
||||
static X509 *cert = NULL;
|
||||
static X509_STORE *store = NULL;
|
||||
static STACK_OF(X509) *x509_stack = NULL;
|
||||
//TODO: static char *chain = NULL;
|
||||
static char *crl = NULL;
|
||||
|
||||
static char *VERIF_FAIL = "Signature verification failed";
|
||||
static char *XSTORE_FAIL = "XSTORE creation failed";
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
static X509_STORE *x509_store = NULL;
|
||||
|
||||
bool signature_initialize(const char *ca_cert_filename)
|
||||
/* This function must be called before trying to sign any file.
|
||||
* It loads string for errors, and ciphers are auto-loaded by OpenSSL now.
|
||||
* If this function fails it may be because the certificate cannot
|
||||
* be validated.
|
||||
*
|
||||
* returns: true if can initialize and validate certificates, otherwise false */
|
||||
static bool initialize_signature(void)
|
||||
{
|
||||
if (initialized) {
|
||||
ERR_load_crypto_strings();
|
||||
ERR_load_PKCS7_strings();
|
||||
if (!get_pubkey()) {
|
||||
goto fail;
|
||||
}
|
||||
if (!validate_certificate()) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Push our trust cert(s) to the stack, which is a set of certificates
|
||||
* in which to search for the signer's cert. */
|
||||
x509_stack = sk_X509_new_null();
|
||||
if (!x509_stack) {
|
||||
goto fail;
|
||||
}
|
||||
sk_X509_push(x509_stack, cert);
|
||||
|
||||
return true;
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Delete the memory used for string errors as well as memory allocated for
|
||||
* certificates and private keys. */
|
||||
static void terminate_signature(void)
|
||||
{
|
||||
//TODO: once implemented, must free chain
|
||||
//TODO: once implemented, must free crl
|
||||
if (store) {
|
||||
X509_STORE_free(store);
|
||||
store = NULL;
|
||||
}
|
||||
if (x509_stack) {
|
||||
sk_X509_pop_free(x509_stack, X509_free);
|
||||
x509_stack = NULL;
|
||||
}
|
||||
ERR_free_strings();
|
||||
if (pkey) {
|
||||
EVP_PKEY_free(pkey);
|
||||
pkey = NULL;
|
||||
}
|
||||
EVP_cleanup();
|
||||
if (fp_pubkey) {
|
||||
fclose(fp_pubkey);
|
||||
}
|
||||
if (cert) {
|
||||
cert = NULL;
|
||||
}
|
||||
ERR_remove_thread_state(NULL);
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
}
|
||||
|
||||
/* Verifies that the file and the signature exists, and does a signature
|
||||
* check afterwards.
|
||||
*
|
||||
* returns: true if able to validate the signature, false otherwise */
|
||||
static bool verify_signature(const char *data_filename, const char *sig_filename)
|
||||
{
|
||||
int ret;
|
||||
struct stat st;
|
||||
|
||||
int data_fd;
|
||||
size_t data_len;
|
||||
unsigned char *data = NULL;
|
||||
BIO *data_BIO;
|
||||
|
||||
int sig_fd;
|
||||
size_t sig_len;
|
||||
unsigned char *sig = NULL;
|
||||
BIO *sig_BIO;
|
||||
|
||||
PKCS7 *p7;
|
||||
BIO *verify_BIO;
|
||||
|
||||
if (!initialize_signature()) {
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
|
||||
//this function has issues: EVP_add_digest(EVP_sha256());
|
||||
OpenSSL_add_all_digests();
|
||||
|
||||
/* get the signature */
|
||||
sig_fd = open(sig_filename, O_RDONLY);
|
||||
if (sig_fd == -1) {
|
||||
fprintf(stderr, "Failed open %s\n", sig_filename);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
if (fstat(sig_fd, &st) != 0) {
|
||||
fprintf(stderr, "Failed to stat %s file\n", sig_filename);
|
||||
close(sig_fd);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
sig_len = st.st_size;
|
||||
sig = mmap(NULL, sig_len, PROT_READ, MAP_PRIVATE, sig_fd, 0);
|
||||
if (sig == MAP_FAILED) {
|
||||
fprintf(stderr, "Failed to mmap %s signature\n", sig_filename);
|
||||
close(sig_fd);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
sig_BIO = BIO_new_mem_buf(sig, sig_len);
|
||||
if (!sig_BIO) {
|
||||
fprintf(stderr, "Failed to read %s signature into BIO\n", sig_filename);
|
||||
ERR_print_errors_fp(stderr);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* the signature is in DER format, so d2i it into verification pkcs7 form */
|
||||
p7 = d2i_PKCS7_bio(sig_BIO, NULL);
|
||||
if (p7 == NULL) {
|
||||
fprintf(stderr, "NULL PKCS7 File\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
BIO_free(sig_BIO);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get the data to be verified */
|
||||
data_fd = open(data_filename, O_RDONLY);
|
||||
if (data_fd == -1) {
|
||||
fprintf(stderr, "Failed open %s\n", data_filename);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
BIO_free(sig_BIO);
|
||||
PKCS7_free(p7);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
if (fstat(data_fd, &st) != 0) {
|
||||
fprintf(stderr, "Failed to stat %s\n", data_filename);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
close(data_fd);
|
||||
BIO_free(sig_BIO);
|
||||
PKCS7_free(p7);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
data_len = st.st_size;
|
||||
data = mmap(NULL, data_len, PROT_READ, MAP_PRIVATE, data_fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
fprintf(stderr, "Failed to mmap %s\n", data_filename);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
close(data_fd);
|
||||
BIO_free(sig_BIO);
|
||||
PKCS7_free(p7);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
data_BIO = BIO_new_mem_buf(data, data_len);
|
||||
if (!data_BIO) {
|
||||
fprintf(stderr, "Failed to read %s into BIO\n", data_filename);
|
||||
ERR_print_errors_fp(stderr);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
munmap(data, data_len);
|
||||
close(data_fd);
|
||||
BIO_free(sig_BIO);
|
||||
PKCS7_free(p7);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* munge the signature and data into a verifiable format */
|
||||
verify_BIO = PKCS7_dataInit(p7, data_BIO);
|
||||
if (!verify_BIO) {
|
||||
fprintf(stderr, "Failed PKCS7_dataInit()\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
munmap(data, data_len);
|
||||
close(data_fd);
|
||||
BIO_free(sig_BIO);
|
||||
BIO_free(data_BIO);
|
||||
PKCS7_free(p7);
|
||||
terminate_signature();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Verify the signature, outdata can be NULL because we don't use it */
|
||||
ret = PKCS7_verify(p7, x509_stack, store, verify_BIO, NULL, 0);
|
||||
|
||||
munmap(sig, sig_len);
|
||||
close(sig_fd);
|
||||
munmap(data, data_len);
|
||||
close(data_fd);
|
||||
BIO_free(sig_BIO);
|
||||
BIO_free(data_BIO);
|
||||
BIO_free(verify_BIO);
|
||||
PKCS7_free(p7);
|
||||
|
||||
ERR_print_errors_fp(stderr);
|
||||
terminate_signature();
|
||||
|
||||
if (ret == 1) {
|
||||
printf("Signature check succeeded.\n");
|
||||
return true;
|
||||
}
|
||||
OpenSSL_add_all_algorithms();
|
||||
ERR_load_crypto_strings();
|
||||
x509_store = create_store(ca_cert_filename, NULL, NULL);
|
||||
if (x509_store == NULL) {
|
||||
ERR_free_strings(); // undoes ERR_load_crypto_strings
|
||||
EVP_cleanup(); // undoes OpenSSL_add_all_algorithms
|
||||
return false || (IMPL == IMPL_FORGIVE);
|
||||
|
||||
fprintf(stderr, "Signature check failed!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Make sure the certificate exists and extract the public key from it.
|
||||
*
|
||||
* returns: true if it can get the public key, false otherwise */
|
||||
static bool get_pubkey(void)
|
||||
{
|
||||
fp_pubkey = fopen(CERTNAME, "r");
|
||||
if (!fp_pubkey) {
|
||||
fprintf(stderr, "Failed fopen %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cert = PEM_read_X509(fp_pubkey, NULL, NULL, NULL);
|
||||
if (!cert) {
|
||||
fprintf(stderr, "Failed PEM_read_X509() for %s\n", CERTNAME);
|
||||
fclose(fp_pubkey);
|
||||
goto error;
|
||||
}
|
||||
|
||||
pkey = X509_get_pubkey(cert);
|
||||
if (!pkey) {
|
||||
fprintf(stderr, "Failed X509_get_pubkey() for %s\n", CERTNAME);
|
||||
fclose(fp_pubkey);
|
||||
X509_free(cert);
|
||||
goto error;
|
||||
}
|
||||
initialized = true;
|
||||
return true;
|
||||
error:
|
||||
ERR_print_errors_fp(stderr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void signature_terminate(void)
|
||||
/* This function makes sure the certificate is still valid by not having any
|
||||
* compromised certificates in the chain.
|
||||
* If there is no Certificate Revocation List (CRL) it may be that the private
|
||||
* keys have not been compromised or the CRL has not been generated by the
|
||||
* Certificate Authority (CA)
|
||||
*
|
||||
* returns: true if certificate is valid, false otherwise */
|
||||
static bool validate_certificate(void)
|
||||
{
|
||||
if (initialized) {
|
||||
X509_STORE_free(x509_store); // undocumented...
|
||||
ERR_free_strings(); // undoes ERR_load_crypto_strings
|
||||
EVP_cleanup(); // undoes OpenSSL_add_all_algorithms
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
X509_LOOKUP *lookup = NULL;
|
||||
X509_STORE_CTX *verify_ctx = NULL;
|
||||
|
||||
bool signature_verify(const char *data_filename, const char *sig_filename)
|
||||
{
|
||||
BIO *bio_data = NULL;
|
||||
BIO *bio_sig = NULL;
|
||||
PKCS7 *pkcs7 = NULL;
|
||||
int ret;
|
||||
bool result = false;
|
||||
/* TODO: CRL and Chains are not required for the current setup, but we may
|
||||
* implement them in the future
|
||||
if (!crl) {
|
||||
printf("No certificate revocation list provided\n");
|
||||
}
|
||||
if (!chain) {
|
||||
printf("No certificate chain provided\n");
|
||||
}
|
||||
*/
|
||||
|
||||
if (!initialized) {
|
||||
return false || (IMPL == IMPL_FORGIVE);
|
||||
/* create the cert store and set the verify callback */
|
||||
if (!(store = X509_STORE_new())) {
|
||||
fprintf(stderr, "Failed X509_STORE_new() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
bio_data = BIO_new_file(data_filename, "r"); // i.e. fopen
|
||||
if (bio_data == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
bio_sig = BIO_new_file(sig_filename, "r"); // i.e. fopen
|
||||
if (bio_sig == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
pkcs7 = PEM_read_bio_PKCS7(bio_sig, NULL, NULL, NULL);
|
||||
if (pkcs7 == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
ret = PKCS7_verify(pkcs7, NULL, x509_store, bio_data, NULL, 0);
|
||||
if (ret != 1) {
|
||||
goto exit;
|
||||
}
|
||||
result = true;
|
||||
exit:
|
||||
/*
|
||||
* The free functions below tolerate NULL arguments.
|
||||
* The documentation doesn't really say so, but both testing and
|
||||
* examination of openssl source code confirm that such is the case.
|
||||
*/
|
||||
PKCS7_free(pkcs7); // undocumented...
|
||||
BIO_free(bio_sig); // i.e. fclose
|
||||
BIO_free(bio_data); // i.e. fclose
|
||||
return result || (IMPL == IMPL_FORGIVE);
|
||||
}
|
||||
|
||||
static X509_STORE *create_store(const char *ca_filename, const char *ca_dirname,
|
||||
const char *crl_filename)
|
||||
{
|
||||
X509_STORE *store = X509_STORE_new();
|
||||
X509_STORE_set_verify_cb_func(store, verify_callback);
|
||||
|
||||
if (!store) {
|
||||
return NULL;
|
||||
/* Add the certificates to be verified to the store */
|
||||
if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()))) {
|
||||
fprintf(stderr, "Failed X509_STORE_add_lookup() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
if (X509_STORE_load_locations(store, ca_filename, ca_dirname) != 1) {
|
||||
goto err;
|
||||
|
||||
/* Load the our Root cert, which can be in either DER or PEM format */
|
||||
if (!X509_load_cert_file(lookup, CERTNAME, X509_FILETYPE_PEM)) {
|
||||
fprintf(stderr, "Failed X509_load_cert_file() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
if (X509_STORE_set_default_paths(store) != 1) {
|
||||
goto err;
|
||||
}
|
||||
if (crl_filename) {
|
||||
X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
|
||||
if (!lookup) {
|
||||
goto err;
|
||||
}
|
||||
if (X509_load_crl_file(lookup, crl_filename, X509_FILETYPE_PEM) != 1) {
|
||||
goto err;
|
||||
|
||||
if (crl) {
|
||||
if (!(lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) ||
|
||||
(X509_load_crl_file(lookup, crl, X509_FILETYPE_PEM) != 1)) {
|
||||
fprintf(stderr, "Failed X509 crl init for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
/* set the flags of the store so that CLRs are consulted */
|
||||
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
|
||||
}
|
||||
return store;
|
||||
err:
|
||||
X509_STORE_free(x509_store);
|
||||
return NULL;
|
||||
|
||||
/* create a verification context and initialize it */
|
||||
if (!(verify_ctx = X509_STORE_CTX_new())) {
|
||||
fprintf(stderr, "Failed X509_STORE_CTX_new() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (X509_STORE_CTX_init(verify_ctx, store, cert, NULL) != 1) {
|
||||
fprintf(stderr, "Failed X509_STORE_CTX_init() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Specify which cert to validate in the verify context.
|
||||
* This is required because we may add multiple certs to the X509 store,
|
||||
* but we want to validate a specific one out of the group/chain. */
|
||||
X509_STORE_CTX_set_cert(verify_ctx, cert);
|
||||
|
||||
/* verify the certificate */
|
||||
if (X509_verify_cert(verify_ctx) != 1) {
|
||||
fprintf(stderr, "Failed X509_verify_cert() for %s\n", CERTNAME);
|
||||
goto error;
|
||||
}
|
||||
|
||||
X509_STORE_CTX_free(verify_ctx);
|
||||
|
||||
/* Certificate verified correctly */
|
||||
return true;
|
||||
|
||||
error:
|
||||
ERR_print_errors_fp(stderr);
|
||||
|
||||
if (store) {
|
||||
X509_STORE_free(store);
|
||||
}
|
||||
if (lookup) {
|
||||
X509_LOOKUP_free(lookup);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool signature_download_and_verify(const char *data_url, const char *data_filename)
|
||||
int verify_callback(int ok, X509_STORE_CTX *stor)
|
||||
{
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Error: %s\n",
|
||||
X509_verify_cert_error_string(stor->error));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Downloads the corresponding signature filename from the
|
||||
* swupd server.
|
||||
*
|
||||
* returns: true if signature was downloaded, false otherwise
|
||||
*/
|
||||
bool download_and_verify_signature(const char *data_url, const char *data_filename)
|
||||
{
|
||||
char *sig_url;
|
||||
char *sig_filename;
|
||||
int ret;
|
||||
bool result;
|
||||
|
||||
string_or_die(&sig_url, "%s.signed", data_url);
|
||||
string_or_die(&sig_url, "%s.sig", data_url);
|
||||
|
||||
string_or_die(&sig_filename, "%s.signed", data_filename);
|
||||
string_or_die(&sig_filename, "%s.sig", data_filename);
|
||||
|
||||
ret = swupd_curl_get_file(sig_url, sig_filename, NULL, NULL, false);
|
||||
if (ret) {
|
||||
result = false;
|
||||
} else {
|
||||
result = signature_verify(data_filename, sig_filename);
|
||||
}
|
||||
if (!result) {
|
||||
unlink(sig_filename);
|
||||
result = verify_signature(data_filename, sig_filename);
|
||||
}
|
||||
free(sig_filename);
|
||||
free(sig_url);
|
||||
return result || (IMPL == IMPL_FORGIVE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void signature_delete(const char *data_filename)
|
||||
{
|
||||
char *sig_filename;
|
||||
|
||||
string_or_die(&sig_filename, "%s.signed", data_filename);
|
||||
|
||||
unlink(sig_filename);
|
||||
|
||||
free(sig_filename);
|
||||
}
|
||||
|
||||
#else // IMPL == IMPL_FAKE
|
||||
|
||||
bool signature_initialize(const char UNUSED_PARAM *ca_cert_filename)
|
||||
#else
|
||||
bool download_and_verify_signature(const char UNUSED_PARAM *data_url, const char UNUSED_PARAM *data_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void signature_terminate(void)
|
||||
{
|
||||
}
|
||||
|
||||
bool signature_verify(const char UNUSED_PARAM *data_filename, const char UNUSED_PARAM *sig_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool signature_download_and_verify(const char UNUSED_PARAM *data_url, const char UNUSED_PARAM *data_filename)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void signature_delete(const char UNUSED_PARAM *data_filename)
|
||||
{
|
||||
}
|
||||
|
||||
#endif // IMPL == IMPL_FAKE
|
||||
#endif
|
||||
|
||||
@@ -242,10 +242,6 @@ int main_update()
|
||||
printf("Update started.\n");
|
||||
read_subscriptions_alt();
|
||||
|
||||
if (!signature_initialize(UPDATE_CA_CERTS_PATH "/" SIGNATURE_CA_CERT)) {
|
||||
goto clean_curl;
|
||||
}
|
||||
|
||||
/* Step 1: get versions */
|
||||
|
||||
ret = check_versions(¤t_version, &server_version, path_prefix);
|
||||
@@ -402,7 +398,6 @@ clean_exit:
|
||||
free_manifest(server_manifest);
|
||||
|
||||
clean_curl:
|
||||
signature_terminate();
|
||||
swupd_curl_cleanup();
|
||||
free_subscriptions();
|
||||
free_globals();
|
||||
|
||||
+1
-5
@@ -641,10 +641,6 @@ int verify_main(int argc, char **argv)
|
||||
* FIXME: We need a command line option to override this in case the
|
||||
* certificate is hosed and the admin knows it and wants to recover.
|
||||
*/
|
||||
if (!signature_initialize(UPDATE_CA_CERTS_PATH "/" SIGNATURE_CA_CERT)) {
|
||||
printf("Can't initialize the SSL certificates\n");
|
||||
goto brick_the_system_and_clean_curl;
|
||||
}
|
||||
|
||||
ret = rm_staging_dir_contents("download");
|
||||
if (ret != 0) {
|
||||
@@ -658,7 +654,7 @@ int verify_main(int argc, char **argv)
|
||||
* is not available, or if there is a server error and a manifest is
|
||||
* not provided.
|
||||
*/
|
||||
printf("Unable to download %d Manifest.MoM\n", version);
|
||||
printf("Unable to download/verify %d Manifest.MoM\n", version);
|
||||
ret = EXIT_FAILURE;
|
||||
|
||||
/* No repair is possible without a manifest, nor is accurate reporting
|
||||
|
||||
@@ -46,19 +46,12 @@ int main(int argc, char **argv)
|
||||
usage(argv[0]);
|
||||
}
|
||||
|
||||
if (!signature_initialize(argv[3])) {
|
||||
fprintf(stderr, "Can't initialize!\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (signature_verify(argv[1], argv[2])) {
|
||||
if (download_and_verify_signature(argv[1], argv[2])) {
|
||||
fprintf(stderr, "Verification successful!\n");
|
||||
} else {
|
||||
fprintf(stderr, "Verification failed!\n");
|
||||
}
|
||||
|
||||
signature_terminate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user