diff mbox series

Add optional CMS single signer verification

Message ID 20210517151728.8679-1-andrew.mulbrook@garmin.com
State Accepted
Headers show
Series Add optional CMS single signer verification | expand

Commit Message

Mulbrook, Andrew May 17, 2021, 3:17 p.m. UTC
This change introduces a Kconfig parameter allowing CMS verification
when additional unrecognized signatures are included in the CMS stream.
Content verification is required against all signatures, but swupdate
only requires a single signature in the set to be verified against the
public key specified to swupdate. This operation requires manual
checking of signatures outside of the CMS_verify operation as OpenSSL
requires all signatures within the CMS envelop to verify.

Signed-off-by: Andrew Mulbrook <andrew.mulbrook@garmin.com>
---
 Kconfig                       |  4 +++
 corelib/swupdate_cms_verify.c | 58 ++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 1 deletion(-)

Comments

Mulbrook, Andrew May 17, 2021, 5:10 p.m. UTC | #1
[Snip Earlier Path Portions]

> +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
> +static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE*
> +store) {
> +	int i, ret = 1;
> +
> +	X509_STORE_CTX *ctx = X509_STORE_CTX_new();
> +	STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms);
> +	STACK_OF(X509)* cms_certs = CMS_get1_certs(cms);
> +
> +	if (!ctx) {
> +		ERROR("Failed to allocate verification context");
> +		return ret;
> +	}
> +
> +	for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) {
> +		CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i);
> +		X509 *signer = NULL;
> +
> +		CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL);
> +		if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) {
> +			ERROR("Failed to initialize signer verification
> operation");
> +			break;
> +		}
> +
> +		X509_STORE_CTX_set_default(ctx, "smime_sign");
> +		if (X509_verify_cert(ctx) > 0) {
> +			TRACE("Verified signature %d in signer sequence", i);
> +			ret = 0;
> +		} else {

I think your earlier observation about overlap between this and the signature name
verification is spot on. There's a small issue I could see here where we might
verify the certificate of a different signer than the required signer with the name.
This is not our use case - but it seems like the approach in that case would be
to perform the signer name check here instead of earlier. Alternatively, this
code could be folded into the signer name check - but that struck me as a
bit ugly when formatting the patch. If this remains a compile time parameter, my
aesthetics would lean toward adding the name check here instead of
performing it earlier.

The approach here was to make this a predefined compilation option to avoid
any questions as to other effect. It looks like the signer name option did not
take that approach.

Apologies for this taking so long to repost as email - I was waiting on a mail
server change to avoid corrupting the patch.

Best Regards,
Andrew
Stefano Babic May 18, 2021, 1:22 p.m. UTC | #2
Hi Andrew,

On 17.05.21 19:10, 'Mulbrook, Andrew' via swupdate wrote:
> 
>   [Snip Earlier Path Portions]
> 
>> +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
>> +static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE*
>> +store) {
>> +	int i, ret = 1;
>> +
>> +	X509_STORE_CTX *ctx = X509_STORE_CTX_new();
>> +	STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms);
>> +	STACK_OF(X509)* cms_certs = CMS_get1_certs(cms);
>> +
>> +	if (!ctx) {
>> +		ERROR("Failed to allocate verification context");
>> +		return ret;
>> +	}
>> +
>> +	for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) {
>> +		CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i);
>> +		X509 *signer = NULL;
>> +
>> +		CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL);
>> +		if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) {
>> +			ERROR("Failed to initialize signer verification
>> operation");
>> +			break;
>> +		}
>> +
>> +		X509_STORE_CTX_set_default(ctx, "smime_sign");
>> +		if (X509_verify_cert(ctx) > 0) {
>> +			TRACE("Verified signature %d in signer sequence", i);
>> +			ret = 0;
>> +		} else {
> 
> I think your earlier observation about overlap between this and the signature name
> verification is spot on. There's a small issue I could see here where we might
> verify the certificate of a different signer than the required signer with the name.
> This is not our use case - but it seems like the approach in that case would be
> to perform the signer name check here instead of earlier. Alternatively, this
> code could be folded into the signer name check - but that struck me as a
> bit ugly when formatting the patch. If this remains a compile time parameter, my
> aesthetics would lean toward adding the name check here instead of
> performing it earlier.

Ok - I see. If we let as compile time parameter, the patch has no 
influence on the current behavior and can be merged.

> 
> The approach here was to make this a predefined compilation option to avoid
> any questions as to other effect.

Ok

> It looks like the signer name option did not
> take that approach.

Yes, it is a run time parameter.

> 
> Apologies for this taking so long to repost as email - I was waiting on a mail
> server change to avoid corrupting the patch.

I appreciate this :-)

It saves me time.

What I miss in the patch is some documentation that explains this use 
case and add reference to the compile time parameter, so that users are 
informed. Could be in  doc/source/signed_images.rst ?

Best regards,
Stefano
Stefano Babic May 27, 2021, 6:58 a.m. UTC | #3
Hi Andrew,

On 17.05.21 17:17, 'Andrew Mulbrook' via swupdate wrote:
> This change introduces a Kconfig parameter allowing CMS verification
> when additional unrecognized signatures are included in the CMS stream.
> Content verification is required against all signatures, but swupdate
> only requires a single signature in the set to be verified against the
> public key specified to swupdate. This operation requires manual
> checking of signatures outside of the CMS_verify operation as OpenSSL
> requires all signatures within the CMS envelop to verify.
> 
> Signed-off-by: Andrew Mulbrook <andrew.mulbrook@garmin.com>
> ---
>   Kconfig                       |  4 +++
>   corelib/swupdate_cms_verify.c | 58 ++++++++++++++++++++++++++++++++++-
>   2 files changed, 61 insertions(+), 1 deletion(-)
> 
> diff --git a/Kconfig b/Kconfig
> index 75f9eaa..2a8133c 100644
> --- a/Kconfig
> +++ b/Kconfig
> @@ -457,6 +457,10 @@ config CMS_IGNORE_EXPIRED_CERTIFICATE
>   config CMS_IGNORE_CERTIFICATE_PURPOSE
>   	bool "Ignore X.509 certificate purpose"
>   	depends on SIGALG_CMS
> +
> +config CMS_SKIP_UNKNOWN_SIGNERS
> +	bool "Ignore unverifiable signatures if known signer verifies"
> +	depends on SIGALG_CMS
>   endmenu
>   
>   
> diff --git a/corelib/swupdate_cms_verify.c b/corelib/swupdate_cms_verify.c
> index 5ec3878..2c0ba39 100644
> --- a/corelib/swupdate_cms_verify.c
> +++ b/corelib/swupdate_cms_verify.c
> @@ -16,6 +16,12 @@
>   #include "util.h"
>   #include "swupdate_verify_private.h"
>   
> +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
> +#define VERIFY_UNKNOWN_SIGNER_FLAGS (CMS_NO_SIGNER_CERT_VERIFY)
> +#else
> +#define VERIFY_UNKNOWN_SIGNER_FLAGS (0)
> +#endif
> +
>   int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca)
>   {
>   	X509 *x = (X509 *)crt;
> @@ -182,6 +188,47 @@ static int check_signer_name(CMS_ContentInfo *cms, const char *name)
>   	return ret;
>   }
>   
> +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
> +static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE* store)
> +{
> +	int i, ret = 1;
> +
> +	X509_STORE_CTX *ctx = X509_STORE_CTX_new();
> +	STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms);
> +	STACK_OF(X509)* cms_certs = CMS_get1_certs(cms);
> +
> +	if (!ctx) {
> +		ERROR("Failed to allocate verification context");
> +		return ret;
> +	}
> +
> +	for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) {
> +		CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i);
> +		X509 *signer = NULL;
> +
> +		CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL);
> +		if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) {
> +			ERROR("Failed to initialize signer verification operation");
> +			break;
> +		}
> +
> +		X509_STORE_CTX_set_default(ctx, "smime_sign");
> +		if (X509_verify_cert(ctx) > 0) {
> +			TRACE("Verified signature %d in signer sequence", i);
> +			ret = 0;
> +		} else {
> +			TRACE("Failed to verify certificate %d in signer sequence", i);
> +		}
> +
> +		X509_STORE_CTX_cleanup(ctx);
> +	}
> +
> +	X509_STORE_CTX_free(ctx);
> +
> +	return ret;
> +}
> +#endif
> +
>   int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
>   		const char *file, const char *signer_name)
>   {
> @@ -221,13 +268,22 @@ int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
>   
>   	/* Then try to verify signature */
>   	if (!CMS_verify(cms, NULL, dgst->certs, content_bio,
> -			NULL, CMS_BINARY)) {
> +			NULL, CMS_BINARY | VERIFY_UNKNOWN_SIGNER_FLAGS)) {
>   		ERR_print_errors_fp(stderr);
>   		ERROR("Signature verification failed");
>   		status = -EBADMSG;
>   		goto out;
>   	}
>   
> +#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
> +	/* Verify at least one signer authenticates */
> +	if (check_verified_signer(cms, dgst->certs)) {
> +		ERROR("Authentication of all signatures failed");
> +		status = -EBADMSG;
> +		goto out;
> +	}
> +#endif
> +
>   	TRACE("Verified OK");
>   
>   	/* Signature is valid */
> 

Applied to -master, thanks !

Best regards,
Stefano Babic
diff mbox series

Patch

diff --git a/Kconfig b/Kconfig
index 75f9eaa..2a8133c 100644
--- a/Kconfig
+++ b/Kconfig
@@ -457,6 +457,10 @@  config CMS_IGNORE_EXPIRED_CERTIFICATE
 config CMS_IGNORE_CERTIFICATE_PURPOSE
 	bool "Ignore X.509 certificate purpose"
 	depends on SIGALG_CMS
+
+config CMS_SKIP_UNKNOWN_SIGNERS
+	bool "Ignore unverifiable signatures if known signer verifies"
+	depends on SIGALG_CMS
 endmenu
 
 
diff --git a/corelib/swupdate_cms_verify.c b/corelib/swupdate_cms_verify.c
index 5ec3878..2c0ba39 100644
--- a/corelib/swupdate_cms_verify.c
+++ b/corelib/swupdate_cms_verify.c
@@ -16,6 +16,12 @@ 
 #include "util.h"
 #include "swupdate_verify_private.h"
 
+#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
+#define VERIFY_UNKNOWN_SIGNER_FLAGS (CMS_NO_SIGNER_CERT_VERIFY)
+#else
+#define VERIFY_UNKNOWN_SIGNER_FLAGS (0)
+#endif
+
 int check_code_sign(const X509_PURPOSE *xp, const X509 *crt, int ca)
 {
 	X509 *x = (X509 *)crt;
@@ -182,6 +188,47 @@  static int check_signer_name(CMS_ContentInfo *cms, const char *name)
 	return ret;
 }
 
+#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
+static int check_verified_signer(CMS_ContentInfo* cms, X509_STORE* store)
+{
+	int i, ret = 1;
+
+	X509_STORE_CTX *ctx = X509_STORE_CTX_new();
+	STACK_OF(CMS_SignerInfo) *infos = CMS_get0_SignerInfos(cms);
+	STACK_OF(X509)* cms_certs = CMS_get1_certs(cms);
+
+	if (!ctx) {
+		ERROR("Failed to allocate verification context");
+		return ret;
+	}
+
+	for (i = 0; i < sk_CMS_SignerInfo_num(infos) && ret != 0; ++i) {
+		CMS_SignerInfo *si = sk_CMS_SignerInfo_value(infos, i);
+		X509 *signer = NULL;
+
+		CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL);
+		if (!X509_STORE_CTX_init(ctx, store, signer, cms_certs)) {
+			ERROR("Failed to initialize signer verification operation");
+			break;
+		}
+
+		X509_STORE_CTX_set_default(ctx, "smime_sign");
+		if (X509_verify_cert(ctx) > 0) {
+			TRACE("Verified signature %d in signer sequence", i);
+			ret = 0;
+		} else {
+			TRACE("Failed to verify certificate %d in signer sequence", i);
+		}
+
+		X509_STORE_CTX_cleanup(ctx);
+	}
+
+	X509_STORE_CTX_free(ctx);
+
+	return ret;
+}
+#endif
+
 int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
 		const char *file, const char *signer_name)
 {
@@ -221,13 +268,22 @@  int swupdate_verify_file(struct swupdate_digest *dgst, const char *sigfile,
 
 	/* Then try to verify signature */
 	if (!CMS_verify(cms, NULL, dgst->certs, content_bio,
-			NULL, CMS_BINARY)) {
+			NULL, CMS_BINARY | VERIFY_UNKNOWN_SIGNER_FLAGS)) {
 		ERR_print_errors_fp(stderr);
 		ERROR("Signature verification failed");
 		status = -EBADMSG;
 		goto out;
 	}
 
+#if defined(CONFIG_CMS_SKIP_UNKNOWN_SIGNERS)
+	/* Verify at least one signer authenticates */
+	if (check_verified_signer(cms, dgst->certs)) {
+		ERROR("Authentication of all signatures failed");
+		status = -EBADMSG;
+		goto out;
+	}
+#endif
+
 	TRACE("Verified OK");
 
 	/* Signature is valid */