PKCS#7: Better handling of unsupported crypto
Provide better handling of unsupported crypto when verifying a PKCS#7 message.
If we can't bridge the gap between a pair of X.509 certs or between a signed
info block and an X.509 cert because it involves some crypto we don't support,
that's not necessarily the end of the world as there may be other ways points
at which we can intersect with a ring of trusted keys.
Instead, only produce ENOPKG immediately if all the signed info blocks in a
PKCS#7 message require unsupported crypto to bridge to the first X.509 cert.
Otherwise, we defer the generation of ENOPKG until we get ENOKEY during trust
validation.
Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Vivek Goyal <vgoyal@redhat.com>
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index 91949f9..efc7dc9 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -23,6 +23,7 @@
struct x509_certificate *signer; /* Signing certificate (in msg->certs) */
unsigned index;
bool trusted;
+ bool unsupported_crypto; /* T if not usable due to missing crypto */
/* Message digest - the digest of the Content Data (or NULL) */
const void *msgdigest;
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 09197e5..8bd474e 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -35,6 +35,11 @@
kenter(",%u,", sinfo->index);
+ if (sinfo->unsupported_crypto) {
+ kleave(" = -ENOPKG [cached]");
+ return -ENOPKG;
+ }
+
for (x509 = sinfo->signer; x509; x509 = x509->signer) {
if (x509->seen) {
if (x509->verified) {
@@ -139,24 +144,28 @@
{
struct pkcs7_signed_info *sinfo;
struct x509_certificate *p;
- int cached_ret = 0, ret;
+ int cached_ret = -ENOKEY;
+ int ret;
for (p = pkcs7->certs; p; p = p->next)
p->seen = false;
for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) {
ret = pkcs7_validate_trust_one(pkcs7, sinfo, trust_keyring);
- if (ret < 0) {
- if (ret == -ENOPKG) {
+ switch (ret) {
+ case -ENOKEY:
+ continue;
+ case -ENOPKG:
+ if (cached_ret == -ENOKEY)
cached_ret = -ENOPKG;
- } else if (ret == -ENOKEY) {
- if (cached_ret == 0)
- cached_ret = -ENOKEY;
- } else {
- return ret;
- }
+ continue;
+ case 0:
+ *_trusted |= sinfo->trusted;
+ cached_ret = 0;
+ continue;
+ default:
+ return ret;
}
- *_trusted |= sinfo->trusted;
}
return cached_ret;
diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index 57e90fa..bd26405 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -181,7 +181,7 @@
x509->seen = true;
ret = x509_get_sig_params(x509);
if (ret < 0)
- return ret;
+ goto maybe_missing_crypto_in_x509;
pr_debug("- issuer %s\n", x509->issuer);
if (x509->authority)
@@ -203,7 +203,7 @@
ret = x509_check_signature(x509->pub, x509);
if (ret < 0)
- return ret;
+ goto maybe_missing_crypto_in_x509;
x509->signer = x509;
pr_debug("- self-signed\n");
return 0;
@@ -245,6 +245,17 @@
x509 = p;
might_sleep();
}
+
+maybe_missing_crypto_in_x509:
+ /* Just prune the certificate chain at this point if we lack some
+ * crypto module to go further. Note, however, we don't want to set
+ * sinfo->missing_crypto as the signed info block may still be
+ * validatable against an X.509 cert lower in the chain that we have a
+ * trusted copy of.
+ */
+ if (ret == -ENOPKG)
+ return 0;
+ return ret;
}
/*
@@ -286,11 +297,33 @@
/**
* pkcs7_verify - Verify a PKCS#7 message
* @pkcs7: The PKCS#7 message to be verified
+ *
+ * Verify a PKCS#7 message is internally consistent - that is, the data digest
+ * matches the digest in the AuthAttrs and any signature in the message or one
+ * of the X.509 certificates it carries that matches another X.509 cert in the
+ * message can be verified.
+ *
+ * This does not look to match the contents of the PKCS#7 message against any
+ * external public keys.
+ *
+ * Returns, in order of descending priority:
+ *
+ * (*) -EKEYREJECTED if a signature failed to match for which we found an
+ * appropriate X.509 certificate, or:
+ *
+ * (*) -EBADMSG if some part of the message was invalid, or:
+ *
+ * (*) -ENOPKG if none of the signature chains are verifiable because suitable
+ * crypto modules couldn't be found, or:
+ *
+ * (*) 0 if all the signature chains that don't incur -ENOPKG can be verified
+ * (note that a signature chain may be of zero length), or:
*/
int pkcs7_verify(struct pkcs7_message *pkcs7)
{
struct pkcs7_signed_info *sinfo;
struct x509_certificate *x509;
+ int enopkg = -ENOPKG;
int ret, n;
kenter("");
@@ -306,12 +339,17 @@
for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) {
ret = pkcs7_verify_one(pkcs7, sinfo);
if (ret < 0) {
+ if (ret == -ENOPKG) {
+ sinfo->unsupported_crypto = true;
+ continue;
+ }
kleave(" = %d", ret);
return ret;
}
+ enopkg = 0;
}
- kleave(" = 0");
- return 0;
+ kleave(" = %d", enopkg);
+ return enopkg;
}
EXPORT_SYMBOL_GPL(pkcs7_verify);
diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h
index 0e8d59b..4e1a384 100644
--- a/crypto/asymmetric_keys/x509_parser.h
+++ b/crypto/asymmetric_keys/x509_parser.h
@@ -38,6 +38,7 @@
bool seen; /* Infinite recursion prevention */
bool verified;
bool trusted;
+ bool unsupported_crypto; /* T if can't be verified due to missing crypto */
};
/*
diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index c60905c..1d9a4c5 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -115,6 +115,8 @@
pr_devel("==>%s()\n", __func__);
+ if (cert->unsupported_crypto)
+ return -ENOPKG;
if (cert->sig.rsa.s)
return 0;
@@ -127,8 +129,13 @@
* big the hash operational data will be.
*/
tfm = crypto_alloc_shash(hash_algo_name[cert->sig.pkey_hash_algo], 0, 0);
- if (IS_ERR(tfm))
- return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);
+ if (IS_ERR(tfm)) {
+ if (PTR_ERR(tfm) == -ENOENT) {
+ cert->unsupported_crypto = true;
+ return -ENOPKG;
+ }
+ return PTR_ERR(tfm);
+ }
desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
digest_size = crypto_shash_digestsize(tfm);
@@ -175,6 +182,8 @@
return ret;
ret = public_key_verify_signature(pub, &cert->sig);
+ if (ret == -ENOPKG)
+ cert->unsupported_crypto = true;
pr_debug("Cert Verification: %d\n", ret);
return ret;
}