From patchwork Wed Jan 30 15:40:25 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefano Babic X-Patchwork-Id: 1033621 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::440; helo=mail-wr1-x440.google.com; envelope-from=swupdate+bncbcxploxj6ikrb4uky7rakgqeyo4bjkq@googlegroups.com; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=denx.de Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.b="W100yL7z"; dkim-atps=neutral Received: from mail-wr1-x440.google.com (mail-wr1-x440.google.com [IPv6:2a00:1450:4864:20::440]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 43qSHn3VDnz9sBb for ; Thu, 31 Jan 2019 02:40:36 +1100 (AEDT) Received: by mail-wr1-x440.google.com with SMTP id q18sf9560842wrx.0 for ; Wed, 30 Jan 2019 07:40:36 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1548862834; cv=pass; d=google.com; s=arc-20160816; b=ysHRvmDT4tjOKEUZME8vPZPbHg8AMc22uYPfw7c01q4MKmtx+2DNflpB5Mce242UrX 93TESzlrK4YB4fKGEvtdtuxstwIGgdmO+t0R3Zg862qOxh4exSOZGgrfd8ZChM9XvZoC kB3ckbq7R+JmSgIoVrJBjfY/5KZuURXb2G4uquIxQlN1xr6mCI6tWA/v0aqG77ilaE1B NvT71RS+oxB/lzY9s3/Ti3n8CnYIAd7w4HBWxML8OZvtSV2Ghe02bKBBDaYJ+VVzLz0P Boct0ieoYfcOFDcu18IZ7SIIeotgDBF9mNqP8fZzz1a3xqtJZDgeERdKsyeZJM/0gXBN TWlA== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:message-id:date:subject:cc:to:from :mime-version:sender:dkim-signature; bh=q8l6aSDCHXNkXpiAi8KeWpCHO44OrZuX06ufLXKPLR0=; b=SYoM9g29Iq4Tsu/g5w7gxfPnFNDFUoYYgPOs5hJDyQVdo4azI3WRIl3OG7uqEEaL/b OXFbxvyNY0S1vE4UzFnu1zKjakW0dyylfHNYR1BsmBlPYG+3E3163aMtF065qQ6Sk70m nFM6KN5e9Lk367b4OGnjtxlRfwzjrahL0WsKxXMJdIemV2YiqXRtieFsTbeA9qE5eKeq fQPVmSpiI2lucWodsMV+hksx/RpoIWfskeWddOfaqiasdDkYwGv5uK8HTwmXbd6A0Pjo 4wliXeyzbmCicd/64T8pIkBgVlaluKm5NmYVTTgeL7+FwPOe1F5+wAMEEBYWDuNAFLZZ O3Pg== ARC-Authentication-Results: i=2; gmr-mx.google.com; spf=neutral (google.com: 212.18.0.9 is neither permitted nor denied by best guess record for domain of sbabic@denx.de) smtp.mailfrom=sbabic@denx.de DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20161025; h=sender:mime-version:from:to:cc:subject:date:message-id :x-original-sender:x-original-authentication-results:precedence :mailing-list:list-id:list-post:list-help:list-archive :list-subscribe:list-unsubscribe; bh=q8l6aSDCHXNkXpiAi8KeWpCHO44OrZuX06ufLXKPLR0=; b=W100yL7zrOhq1P+rEF/mWc/Ms1XXwTg1P3+vqAK8+gP0sJI6Av5GSE94mdMXnUsO8S 9SMkV+NZXcTeyGin4a0FrpWEjVDDx0rR0J77vHb/Gkl8T7a3CJr4q25nwzYm+HWxYzVa SyvUGpaeGUtgNeiw9K2AIL2IfI9pYZ0c5AmjqZIrbpHvbPA2+QuCvEi/4ez4PqzSb6Wx 6DLqZQvx5+nDqHhKqmqy0kHN9VNMXJmMqCrgV7mtd4fnqvYRxOvGijbob8UX469WPueb r/ZAXXeu1eu8lmP8x4jxcVt4FnItPQMWZs5IWyg+KPEK/EHvPOT9smuiBk8Om25tDlZl jG9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=sender:x-gm-message-state:mime-version:from:to:cc:subject:date :message-id:x-original-sender:x-original-authentication-results :precedence:mailing-list:list-id:x-spam-checked-in-group:list-post :list-help:list-archive:list-subscribe:list-unsubscribe; bh=q8l6aSDCHXNkXpiAi8KeWpCHO44OrZuX06ufLXKPLR0=; b=irsJINr3hfY5sceLZ1lFdqOMFLi3KryFiEcKVWnJ/WydqkjHCT5RLdmHu/UPMqUSNA 1i2guJtFFFHH3NTVRPTGyvH+jJ4UBxIbIuJzwUHKvWXqO8kl+wphv9tn4fhbFc7/ugh8 P0zJViuhss+GrAdfL2M5NamrLFpy32sTvq1OMdXP9/AzJw6ndhBkGlvB94aq84n0Mzsd 3s2iS5n9HAfBBVTIBNiL7zaJYAqhE8z0xKUFPeW3/BswxTlm49bFrP+wyA73cOuvrEDi KOEC0XBoKrG92Z7nwi3dm4fdwJITe1ggA4xjVNwq8tZFdtB9ktX5sbk8uORjxjW4/Prc 42Hg== Sender: swupdate@googlegroups.com X-Gm-Message-State: AHQUAuasBT/HUD/HW/ufZeAXb5nuKWZIje8Ha8v5pXtiA/7trzJAB1gw yN9rwbMtMb8UWs1tgYD+OyI= X-Google-Smtp-Source: AHgI3IZ8/eAkS6bbFrJoeCb7li9U8fqwYLtzfpZcHysw+mVetphB4/gO3eo79q5l/3/wPa+mIESB5g== X-Received: by 2002:adf:e58e:: with SMTP id l14mr110590wrm.2.1548862834359; Wed, 30 Jan 2019 07:40:34 -0800 (PST) MIME-Version: 1.0 X-BeenThere: swupdate@googlegroups.com Received: by 2002:a05:6000:149:: with SMTP id r9ls593961wrx.7.gmail; Wed, 30 Jan 2019 07:40:33 -0800 (PST) X-Received: by 2002:a5d:6543:: with SMTP id z3mr687721wrv.7.1548862833817; Wed, 30 Jan 2019 07:40:33 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548862833; cv=none; d=google.com; s=arc-20160816; b=W2Y0BPgtIq9J2hzP6K9Ds7lg1wDWhyvDASKK7RYf4PS8DOqAxaI9dc9bn4plhDzwkh xef3AZubwWizCO+h5lbD0UCQ7csk2HQ3Sqlcv4/z+Bu4mFxMoi2ppbUpmt7AkDRUNw7m zZoBXTVjAD92HGAzW/kLDDmsLrCb2U6ChiBL5CIwpxWBqODgwchFDu/cQHDk1b3wPk0N AppcD4mYr/DMM860yn7nsPvleSMC4eOvFCgyWCUbDhSEscKHidhRsBHAFGydJMHsg6L6 ha211LeaMCEcDvJBHk64mMtIWG06Al5Vyekw5hlR8R/9Kfx8Ve/jppr30tXeGW+5LPDj AOyQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=message-id:date:subject:cc:to:from; bh=zQZ9MDg27W6cBm9P/imOmRSv8Hb1KZyMdOb0FsNdVlA=; b=I/Xie7c5dCi+ujW6OBXFsaN87uPflWyQXS+SvNy7hNY/lc8AUNpAHp2ahw1xArTbYr hFZmNDRLrF7CVdnJ0MtX3wrE/gb2Es3V6CCKtnguuowynhEB1IH//k9axTi2r3pTQ6Xu PrKxRybmxiEBbQFqbNKucdoKAKqiaG09Dgdp+fB99X66JAJpwITjjmQxSrnZdPc9RfnF 9QM3+l99YJL/ysbk03YP43OO3u3kyD0XCgns9u+7vZEPqDGOYJWH4yV8VMciUx7NHwjf PxXa3jBE1zsq5KKbjg9M3Nr1AmVWVEQzUsyKkeTxTMvMX0bQDkbPZDaDQdSnx3A6LvMG 4b0w== ARC-Authentication-Results: i=1; gmr-mx.google.com; spf=neutral (google.com: 212.18.0.9 is neither permitted nor denied by best guess record for domain of sbabic@denx.de) smtp.mailfrom=sbabic@denx.de Received: from mail-out.m-online.net (mail-out.m-online.net. [212.18.0.9]) by gmr-mx.google.com with ESMTPS id i11si86416wmb.2.2019.01.30.07.40.33 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 30 Jan 2019 07:40:33 -0800 (PST) Received-SPF: neutral (google.com: 212.18.0.9 is neither permitted nor denied by best guess record for domain of sbabic@denx.de) client-ip=212.18.0.9; Received: from frontend01.mail.m-online.net (unknown [192.168.8.182]) by mail-out.m-online.net (Postfix) with ESMTP id 43qSHj4RdWz1qym3; Wed, 30 Jan 2019 16:40:33 +0100 (CET) Received: from localhost (dynscan1.mnet-online.de [192.168.6.70]) by mail.m-online.net (Postfix) with ESMTP id 43qSHj4Dfqz1rVwp; Wed, 30 Jan 2019 16:40:33 +0100 (CET) X-Virus-Scanned: amavisd-new at mnet-online.de Received: from mail.mnet-online.de ([192.168.8.182]) by localhost (dynscan1.mail.m-online.net [192.168.6.70]) (amavisd-new, port 10024) with ESMTP id RiJ8XVndwtgq; Wed, 30 Jan 2019 16:40:31 +0100 (CET) Received: from babic.homelinux.org (host-88-217-136-221.customer.m-online.net [88.217.136.221]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.mnet-online.de (Postfix) with ESMTPS; Wed, 30 Jan 2019 16:40:31 +0100 (CET) Received: from localhost (mail.babic.homelinux.org [127.0.0.1]) by babic.homelinux.org (Postfix) with ESMTP id BC1C7454035E; Wed, 30 Jan 2019 16:40:30 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at babic.homelinux.org Received: from babic.homelinux.org ([127.0.0.1]) by localhost (mail.babic.homelinux.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id L-3BZcflp_Rw; Wed, 30 Jan 2019 16:40:27 +0100 (CET) Received: from papero.fritz.box (papero.fritz.box [192.168.178.132]) by babic.homelinux.org (Postfix) with ESMTP id 871B9454034D; Wed, 30 Jan 2019 16:40:27 +0100 (CET) From: Stefano Babic To: swupdate@googlegroups.com Cc: Stefano Babic Subject: [swupdate] [PATCH] Allow to pass a CA and split code for verification Date: Wed, 30 Jan 2019 16:40:25 +0100 Message-Id: <20190130154025.977-1-sbabic@denx.de> X-Mailer: git-send-email 2.17.1 X-Original-Sender: sbabic@denx.de X-Original-Authentication-Results: gmr-mx.google.com; spf=neutral (google.com: 212.18.0.9 is neither permitted nor denied by best guess record for domain of sbabic@denx.de) smtp.mailfrom=sbabic@denx.de Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , A CA public certificate can be used to verify the SWU via CMS. Additionally, split code related verification in separate files. SWUpdate does not support both RSA and CMS at the same time and it is more readable to have the related code in separate files. Signed-off-by: Stefano Babic --- core/swupdate.c | 5 +- corelib/Makefile | 3 + corelib/swupdate_cms_verify.c | 247 ++++++++++++++++++ corelib/swupdate_rsa_verify.c | 181 ++++++++++++++ corelib/swupdate_verify_private.h | 26 ++ corelib/verify_signature.c | 399 +----------------------------- 6 files changed, 463 insertions(+), 398 deletions(-) create mode 100644 corelib/swupdate_cms_verify.c create mode 100644 corelib/swupdate_rsa_verify.c create mode 100644 corelib/swupdate_verify_private.h diff --git a/core/swupdate.c b/core/swupdate.c index 61e6dc0..4f3d9d6 100644 --- a/core/swupdate.c +++ b/core/swupdate.c @@ -81,6 +81,7 @@ static struct option long_options[] = { {"no-downgrading", required_argument, NULL, 'N'}, #ifdef CONFIG_SIGNED_IMAGES {"key", required_argument, NULL, 'k'}, + {"ca-path", required_argument, NULL, 'k'}, {"cert-purpose", required_argument, NULL, '1'}, {"forced-signer-name", required_argument, NULL, '2'}, #endif @@ -131,6 +132,7 @@ static void usage(char *programname) " --cert-purpose : set expected certificate purpose\n" " [emailProtection|codeSigning] (default: emailProtection)\n" " --forced-signer-name : set expected common name of signer certificate\n" + " --ca-path : path to the Certificate Authority (PEM)\n" #endif #ifdef CONFIG_ENCRYPTED_IMAGES " -K, --key-aes : the file contains the symmetric key to be used\n" @@ -327,7 +329,6 @@ static int install_from_file(char *fname, int check) exit(EXIT_FAILURE); } - if (check_hw_compatibility(&swcfg)) { ERROR("SW not compatible with hardware"); exit(EXIT_FAILURE); @@ -490,6 +491,8 @@ static int read_globals_settings(void *elem, void *data) GET_FIELD_STRING(LIBCFG_PARSER, elem, "public-key-file", sw->globals.publickeyfname); + GET_FIELD_STRING(LIBCFG_PARSER, elem, + "ca-path", sw->globals.publickeyfname); GET_FIELD_STRING(LIBCFG_PARSER, elem, "aes-key-file", sw->globals.aeskeyfname); GET_FIELD_STRING(LIBCFG_PARSER, elem, diff --git a/corelib/Makefile b/corelib/Makefile index 4b30f9c..d6edf65 100644 --- a/corelib/Makefile +++ b/corelib/Makefile @@ -18,3 +18,6 @@ lib-$(CONFIG_LIBCONFIG) += swupdate_settings.o \ parsing_library_libconfig.o lib-$(CONFIG_JSON) += parsing_library_libjson.o lib-$(CONFIG_CHANNEL_CURL) += channel_curl.o +lib-$(CONFIG_SIGALG_RAWRSA) += swupdate_rsa_verify.o +lib-$(CONFIG_SIGALG_CMS) += swupdate_cms_verify.o + diff --git a/corelib/swupdate_cms_verify.c b/corelib/swupdate_cms_verify.c new file mode 100644 index 0000000..774a904 --- /dev/null +++ b/corelib/swupdate_cms_verify.c @@ -0,0 +1,247 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, DENX Software Engineering, sbabic@denx.de. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "sslapi.h" +#include "util.h" +#include "swupdate_verify_private.h" + +int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca) +{ + X509 *x = (X509 *)crt; + uint32_t ex_flags = SSL_X509_get_extension_flags(x); + uint32_t ex_xkusage = SSL_X509_get_extended_key_usage(x); + + (void)xp; + + if (ca) { + int idx; + const X509_PURPOSE *pt; + + if ((ex_flags & EXFLAG_XKUSAGE) && !(ex_xkusage & XKU_CODE_SIGN)) + return 0; + + idx = X509_PURPOSE_get_by_id(X509_PURPOSE_OCSP_HELPER); + if (idx == -1) + return 0; + + pt = X509_PURPOSE_get0(idx); + return pt->check_purpose(pt, x, ca); + } + + return (ex_flags & EXFLAG_XKUSAGE) && (ex_xkusage & XKU_CODE_SIGN); +} + +static int cms_verify_callback(int ok, X509_STORE_CTX *ctx) { + int cert_error = X509_STORE_CTX_get_error(ctx); + + if (!ok) { + switch (cert_error) { +#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CERT_NOT_YET_VALID: + ok = 1; + break; +#endif +#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) + case X509_V_ERR_INVALID_PURPOSE: + ok = 1; + break; +#endif + default: + break; + } + } + + return ok; +} + +X509_STORE *load_cert_chain(const char *file) +{ + X509_STORE *castore = X509_STORE_new(); + if (!castore) { + return NULL; + } + + /* + * Set error callback function for verification of CRTs and CRLs in order + * to ignore some errors depending on configuration + */ + X509_STORE_set_verify_cb(castore, cms_verify_callback); + + BIO *castore_bio = BIO_new_file(file, "r"); + if (!castore_bio) { + TRACE("failed: BIO_new_file(%s)", file); + return NULL; + } + + int crt_count = 0; + X509 *crt = NULL; + do { + crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); + if (crt) { + crt_count++; + char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); + char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); + TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); + free(subj); + free(issuer); + if (X509_STORE_add_cert(castore, crt) == 0) { + TRACE("Adding certificate to X509_STORE failed"); + BIO_free(castore_bio); + X509_STORE_free(castore); + return NULL; + } + } + } while (crt); + BIO_free(castore_bio); + + if (crt_count == 0) { + X509_STORE_free(castore); + return NULL; + } + + return castore; +} + +static inline int next_common_name(X509_NAME *subject, int i) +{ + return X509_NAME_get_index_by_NID(subject, NID_commonName, i); +} + +static int check_common_name(X509_NAME *subject, const char *name) +{ + int i = -1, ret = 1; + + while ((i = next_common_name(subject, i)) > -1) { + X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, i); + ASN1_STRING *d = X509_NAME_ENTRY_get_data(e); + unsigned char *cn; + size_t len = strlen(name); + bool matches = (ASN1_STRING_to_UTF8(&cn, d) == (int)len) + && (strncmp(name, (const char *)cn, len) == 0); + + OPENSSL_free(cn); + if (!matches) { + char *subj = X509_NAME_oneline(subject, NULL, 0); + + ERROR("common name of '%s' does not match expected '%s'", + subj, name); + OPENSSL_free(subj); + return 2; + } else { + ret = 0; + } + } + + if (ret == 0) { + char *subj = X509_NAME_oneline(subject, NULL, 0); + + TRACE("verified signer cert: %s", subj); + OPENSSL_free(subj); + } + + return ret; +} + +static int check_signer_name(CMS_ContentInfo *cms, const char *name) +{ + STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); + STACK_OF(X509) *crts; + int i, ret = 1; + + if ((name == NULL) || (name[0] == '\0')) + return 0; + + crts = CMS_get1_certs(cms); + for (i = 0; i < sk_CMS_SignerInfo_num(infos); ++i) { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); + int j; + + for (j = 0; j < sk_X509_num(crts); ++j) { + X509 *crt = sk_X509_value(crts, j); + + if (CMS_SignerInfo_cert_cmp(si, crt) == 0) { + ret = check_common_name( + X509_get_subject_name(crt), name); + } + } + } + sk_X509_pop_free(crts, X509_free); + + return ret; +} + +int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, + const char *file, const char *signer_name) +{ + int status = -EFAULT; + CMS_ContentInfo *cms = NULL; + BIO *content_bio = NULL; + + /* Open CMS blob that needs to be checked */ + BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); + if (!sigfile_bio) { + ERROR("%s cannot be opened", sigfile); + status = -EBADF; + goto out; + } + + /* Parse the DER-encoded CMS message */ + cms = d2i_CMS_bio(sigfile_bio, NULL); + if (!cms) { + ERROR("%s cannot be parsed as DER-encoded CMS signature blob", sigfile); + status = -EFAULT; + goto out; + } + + if (check_signer_name(cms, signer_name)) { + ERROR("failed to verify signer name"); + status = -EFAULT; + goto out; + } + + /* Open the content file (data which was signed) */ + content_bio = BIO_new_file(file, "rb"); + if (!content_bio) { + ERROR("%s cannot be opened", file); + status = -EBADF; + goto out; + } + + /* Then try to verify signature */ + if (!CMS_verify(cms, NULL, dgst->certs, content_bio, + NULL, CMS_BINARY)) { + ERR_print_errors_fp(stderr); + ERROR("Signature verification failed"); + status = -EBADMSG; + goto out; + } + + TRACE("Verified OK"); + + /* Signature is valid */ + status = 0; +out: + + if (cms) { + CMS_ContentInfo_free(cms); + } + if (content_bio) { + BIO_free(content_bio); + } + if (sigfile_bio) { + BIO_free(sigfile_bio); + } + return status; +} diff --git a/corelib/swupdate_rsa_verify.c b/corelib/swupdate_rsa_verify.c new file mode 100644 index 0000000..6231a70 --- /dev/null +++ b/corelib/swupdate_rsa_verify.c @@ -0,0 +1,181 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, DENX Software Engineering, sbabic@denx.de. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Code mostly taken from openssl examples + */ + +#include +#include +#include +#include +#include "swupdate.h" +#include "sslapi.h" +#include "util.h" +#include "swupdate_verify_private.h" + +#define BUFSIZE (1024 * 8) + +EVP_PKEY *load_pubkey(const char *file) +{ + BIO *key=NULL; + EVP_PKEY *pkey=NULL; + + if (file == NULL) + { + ERROR("no keyfile specified"); + goto end; + } + + key=BIO_new(BIO_s_file()); + if (key == NULL) + { + goto end; + } + + if (BIO_read_filename(key, file) <= 0) + { + printf("Error opening %s \n", file); + goto end; + } + + pkey=PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); +end: + if (key != NULL) BIO_free(key); + if (pkey == NULL) + ERROR("unable to load key filename %s", file); + return(pkey); +} + +static int dgst_verify_init(struct swupdate_digest *dgst) +{ + int rc; + + rc = EVP_DigestVerifyInit(dgst->ctx, NULL, EVP_sha256(), NULL, dgst->pkey); + if (rc != 1) { + ERROR("EVP_DigestVerifyInit failed, error 0x%lx", ERR_get_error()); + return -EFAULT; /* failed */ + } + + return 0; +} + +static int verify_update(struct swupdate_digest *dgst, char *msg, unsigned int mlen) +{ + int rc; + + rc = EVP_DigestVerifyUpdate(dgst->ctx, msg, mlen); + if(rc != 1) { + ERROR("EVP_DigestVerifyUpdate failed, error 0x%lx", ERR_get_error()); + return -EFAULT; + } + + return 0; +} + +static int verify_final(struct swupdate_digest *dgst, unsigned char *sig, unsigned int slen) +{ + unsigned int rc; + + /* Clear any errors for the call below */ + ERR_clear_error(); + rc = EVP_DigestVerifyFinal(dgst->ctx, sig, slen); + if(rc != 1) { + ERROR("EVP_DigestVerifyFinal failed, error 0x%lx %d", ERR_get_error(), rc); + return -1; + } + + return rc; +} + +int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, + const char *file, const char *signer_name) +{ + FILE *fp = NULL; + BIO *sigbio; + int siglen = 0; + int i; + unsigned char *sigbuf = NULL; + char *msg = NULL; + int size; + size_t rbytes; + int status = 0; + + (void)signer_name; + if (!dgst) { + ERROR("Wrong crypto initialization: did you pass the key ?"); + status = -ENOKEY; + goto out; + } + + msg = malloc(BUFSIZE); + if (!msg) { + status = -ENOMEM; + goto out; + } + + sigbio = BIO_new_file(sigfile, "rb"); + siglen = EVP_PKEY_size(dgst->pkey); + sigbuf = OPENSSL_malloc(siglen); + + siglen = BIO_read(sigbio, sigbuf, siglen); + BIO_free(sigbio); + + if(siglen <= 0) { + ERROR("Error reading signature file %s", sigfile); + status = -ENOKEY; + goto out; + } + + if ((dgst_init(dgst, EVP_sha256()) < 0) || (dgst_verify_init(dgst) < 0)) { + status = -ENOKEY; + goto out; + } + + fp = fopen(file, "r"); + if (!fp) { + ERROR("%s cannot be opened", file); + status = -EBADF; + goto out; + } + + size = 0; + for (;;) { + rbytes = fread(msg, 1, BUFSIZE, fp); + if (rbytes > 0) { + size += rbytes; + if (verify_update(dgst, msg, rbytes) < 0) + break; + } + if (feof(fp)) + break; + } + + TRACE("Verify signed image: Read %d bytes", size); + i = verify_final(dgst, sigbuf, (unsigned int)siglen); + if(i > 0) { + TRACE("Verified OK"); + status = 0; + } else if(i == 0) { + TRACE("Verification Failure"); + status = -EBADMSG; + } else { + TRACE("Error Verifying Data"); + status = -EFAULT; + } + +out: + if (fp) + fclose(fp); + if (msg) + free(msg); + if (sigbuf) + OPENSSL_free(sigbuf); + + return status; +} + + + diff --git a/corelib/swupdate_verify_private.h b/corelib/swupdate_verify_private.h new file mode 100644 index 0000000..e16f2ce --- /dev/null +++ b/corelib/swupdate_verify_private.h @@ -0,0 +1,26 @@ +/* + * (C) Copyright 2019 + * Stefano Babic, DENX Software Engineering, sbabic@denx.de. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef _SWUPDATE_VERIFY_H +#define _SWUPDATE_VERIFY_H + +#include + +struct swupdate_digest; +int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md); + +#if defined(CONFIG_SIGALG_RAWRSA) +EVP_PKEY *load_pubkey(const char *file); +#endif + +#ifdef CONFIG_SIGALG_CMS +#include +int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca); +X509_STORE *load_cert_chain(const char *file); +#endif + +#endif diff --git a/corelib/verify_signature.c b/corelib/verify_signature.c index dc3fe6d..d08d657 100644 --- a/corelib/verify_signature.c +++ b/corelib/verify_signature.c @@ -14,10 +14,9 @@ #include "swupdate.h" #include "sslapi.h" #include "util.h" +#include "swupdate_verify_private.h" -#define BUFSIZE (1024 * 8) - -static int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md) +int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md) { int rc; @@ -31,400 +30,6 @@ static int dgst_init(struct swupdate_digest *dgst, const EVP_MD *md) return 0; } -/* - * depending on the algorithm, load a RSA public key - * or a certificate - */ -#if defined(CONFIG_SIGALG_RAWRSA) -static EVP_PKEY *load_pubkey(const char *file) -{ - BIO *key=NULL; - EVP_PKEY *pkey=NULL; - - if (file == NULL) - { - ERROR("no keyfile specified"); - goto end; - } - - key=BIO_new(BIO_s_file()); - if (key == NULL) - { - goto end; - } - - if (BIO_read_filename(key, file) <= 0) - { - printf("Error opening %s \n", file); - goto end; - } - - pkey=PEM_read_bio_PUBKEY(key, NULL, NULL, NULL); -end: - if (key != NULL) BIO_free(key); - if (pkey == NULL) - ERROR("unable to load key filename %s", file); - return(pkey); -} - -static int dgst_verify_init(struct swupdate_digest *dgst) -{ - int rc; - - rc = EVP_DigestVerifyInit(dgst->ctx, NULL, EVP_sha256(), NULL, dgst->pkey); - if (rc != 1) { - ERROR("EVP_DigestVerifyInit failed, error 0x%lx", ERR_get_error()); - return -EFAULT; /* failed */ - } - - return 0; -} - -static int verify_update(struct swupdate_digest *dgst, char *msg, unsigned int mlen) -{ - int rc; - - rc = EVP_DigestVerifyUpdate(dgst->ctx, msg, mlen); - if(rc != 1) { - ERROR("EVP_DigestVerifyUpdate failed, error 0x%lx", ERR_get_error()); - return -EFAULT; - } - - return 0; -} - -static int verify_final(struct swupdate_digest *dgst, unsigned char *sig, unsigned int slen) -{ - unsigned int rc; - - /* Clear any errors for the call below */ - ERR_clear_error(); - rc = EVP_DigestVerifyFinal(dgst->ctx, sig, slen); - if(rc != 1) { - ERROR("EVP_DigestVerifyFinal failed, error 0x%lx %d", ERR_get_error(), rc); - return -1; - } - - return rc; -} - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - FILE *fp = NULL; - BIO *sigbio; - int siglen = 0; - int i; - unsigned char *sigbuf = NULL; - char *msg = NULL; - int size; - size_t rbytes; - int status = 0; - - (void)signer_name; - if (!dgst) { - ERROR("Wrong crypto initialization: did you pass the key ?"); - status = -ENOKEY; - goto out; - } - - msg = malloc(BUFSIZE); - if (!msg) { - status = -ENOMEM; - goto out; - } - - sigbio = BIO_new_file(sigfile, "rb"); - siglen = EVP_PKEY_size(dgst->pkey); - sigbuf = OPENSSL_malloc(siglen); - - siglen = BIO_read(sigbio, sigbuf, siglen); - BIO_free(sigbio); - - if(siglen <= 0) { - ERROR("Error reading signature file %s", sigfile); - status = -ENOKEY; - goto out; - } - - if ((dgst_init(dgst, EVP_sha256()) < 0) || (dgst_verify_init(dgst) < 0)) { - status = -ENOKEY; - goto out; - } - - fp = fopen(file, "r"); - if (!fp) { - ERROR("%s cannot be opened", file); - status = -EBADF; - goto out; - } - - size = 0; - for (;;) { - rbytes = fread(msg, 1, BUFSIZE, fp); - if (rbytes > 0) { - size += rbytes; - if (verify_update(dgst, msg, rbytes) < 0) - break; - } - if (feof(fp)) - break; - } - - TRACE("Verify signed image: Read %d bytes", size); - i = verify_final(dgst, sigbuf, (unsigned int)siglen); - if(i > 0) { - TRACE("Verified OK"); - status = 0; - } else if(i == 0) { - TRACE("Verification Failure"); - status = -EBADMSG; - } else { - TRACE("Error Verifying Data"); - status = -EFAULT; - } - -out: - if (fp) - fclose(fp); - if (msg) - free(msg); - if (sigbuf) - OPENSSL_free(sigbuf); - - return status; -} - -#elif defined(CONFIG_SIGALG_CMS) -static int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca) -{ - X509 *x = (X509 *)crt; - uint32_t ex_flags = SSL_X509_get_extension_flags(x); - uint32_t ex_xkusage = SSL_X509_get_extended_key_usage(x); - - (void)xp; - - if (ca) { - int idx; - const X509_PURPOSE *pt; - - if ((ex_flags & EXFLAG_XKUSAGE) && !(ex_xkusage & XKU_CODE_SIGN)) - return 0; - - idx = X509_PURPOSE_get_by_id(X509_PURPOSE_OCSP_HELPER); - if (idx == -1) - return 0; - - pt = X509_PURPOSE_get0(idx); - return pt->check_purpose(pt, x, ca); - } - - return (ex_flags & EXFLAG_XKUSAGE) && (ex_xkusage & XKU_CODE_SIGN); -} - -static int cms_verify_callback(int ok, X509_STORE_CTX *ctx) { - int cert_error = X509_STORE_CTX_get_error(ctx); - - if (!ok) { - switch (cert_error) { -#if defined(CONFIG_CMS_IGNORE_EXPIRED_CERTIFICATE) - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CERT_NOT_YET_VALID: - ok = 1; - break; -#endif -#if defined(CONFIG_CMS_IGNORE_CERTIFICATE_PURPOSE) - case X509_V_ERR_INVALID_PURPOSE: - ok = 1; - break; -#endif - default: - break; - } - } - - return ok; -} -static X509_STORE *load_cert_chain(const char *file) -{ - X509_STORE *castore = X509_STORE_new(); - if (!castore) { - return NULL; - } - - /* - * Set error callback function for verification of CRTs and CRLs in order - * to ignore some errors depending on configuration - */ - X509_STORE_set_verify_cb(castore, cms_verify_callback); - - BIO *castore_bio = BIO_new_file(file, "r"); - if (!castore_bio) { - TRACE("failed: BIO_new_file(%s)", file); - return NULL; - } - - int crt_count = 0; - X509 *crt = NULL; - do { - crt = PEM_read_bio_X509(castore_bio, NULL, 0, NULL); - if (crt) { - crt_count++; - char *subj = X509_NAME_oneline(X509_get_subject_name(crt), NULL, 0); - char *issuer = X509_NAME_oneline(X509_get_issuer_name(crt), NULL, 0); - TRACE("Read PEM #%d: %s %s", crt_count, issuer, subj); - free(subj); - free(issuer); - if (X509_STORE_add_cert(castore, crt) == 0) { - TRACE("Adding certificate to X509_STORE failed"); - BIO_free(castore_bio); - X509_STORE_free(castore); - return NULL; - } - } - } while (crt); - BIO_free(castore_bio); - - if (crt_count == 0) { - X509_STORE_free(castore); - return NULL; - } - - return castore; -} - -static inline int next_common_name(X509_NAME *subject, int i) -{ - return X509_NAME_get_index_by_NID(subject, NID_commonName, i); -} - -static int check_common_name(X509_NAME *subject, const char *name) -{ - int i = -1, ret = 1; - - while ((i = next_common_name(subject, i)) > -1) { - X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, i); - ASN1_STRING *d = X509_NAME_ENTRY_get_data(e); - unsigned char *cn; - size_t len = strlen(name); - bool matches = (ASN1_STRING_to_UTF8(&cn, d) == (int)len) - && (strncmp(name, (const char *)cn, len) == 0); - - OPENSSL_free(cn); - if (!matches) { - char *subj = X509_NAME_oneline(subject, NULL, 0); - - ERROR("common name of '%s' does not match expected '%s'", - subj, name); - OPENSSL_free(subj); - return 2; - } else { - ret = 0; - } - } - - if (ret == 0) { - char *subj = X509_NAME_oneline(subject, NULL, 0); - - TRACE("verified signer cert: %s", subj); - OPENSSL_free(subj); - } - - return ret; -} - -static int check_signer_name(CMS_ContentInfo *cms, const char *name) -{ - STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms); - STACK_OF(X509) *crts; - int i, ret = 1; - - if ((name == NULL) || (name[0] == '\0')) - return 0; - - crts = CMS_get1_certs(cms); - for (i = 0; i < sk_CMS_SignerInfo_num(infos); ++i) { - CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i); - int j; - - for (j = 0; j < sk_X509_num(crts); ++j) { - X509 *crt = sk_X509_value(crts, j); - - if (CMS_SignerInfo_cert_cmp(si, crt) == 0) { - ret = check_common_name( - X509_get_subject_name(crt), name); - } - } - } - sk_X509_pop_free(crts, X509_free); - - return ret; -} - -int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile, - const char *file, const char *signer_name) -{ - int status = -EFAULT; - CMS_ContentInfo *cms = NULL; - BIO *content_bio = NULL; - - /* Open CMS blob that needs to be checked */ - BIO *sigfile_bio = BIO_new_file(sigfile, "rb"); - if (!sigfile_bio) { - ERROR("%s cannot be opened", sigfile); - status = -EBADF; - goto out; - } - - /* Parse the DER-encoded CMS message */ - cms = d2i_CMS_bio(sigfile_bio, NULL); - if (!cms) { - ERROR("%s cannot be parsed as DER-encoded CMS signature blob", sigfile); - status = -EFAULT; - goto out; - } - - if (check_signer_name(cms, signer_name)) { - ERROR("failed to verify signer name"); - status = -EFAULT; - goto out; - } - - /* Open the content file (data which was signed) */ - content_bio = BIO_new_file(file, "rb"); - if (!content_bio) { - ERROR("%s cannot be opened", file); - status = -EBADF; - goto out; - } - - /* Then try to verify signature */ - if (!CMS_verify(cms, NULL, dgst->certs, content_bio, - NULL, CMS_BINARY)) { - ERR_print_errors_fp(stderr); - ERROR("Signature verification failed"); - status = -EBADMSG; - goto out; - } - - TRACE("Verified OK"); - - /* Signature is valid */ - status = 0; -out: - - if (cms) { - CMS_ContentInfo_free(cms); - } - if (content_bio) { - BIO_free(content_bio); - } - if (sigfile_bio) { - BIO_free(sigfile_bio); - } - return status; -} -#endif struct swupdate_digest *swupdate_HASH_init(const char *SHAlength) { struct swupdate_digest *dgst;