PKCS#7: Handle PKCS#7 messages that contain no X.509 certs

The X.509 certificate list in a PKCS#7 message is optional.  To save space, we
can omit the inclusion of any X.509 certificates if we are sure that we can
look the relevant public key up by the serial number and issuer given in a
signed info block.

This also supports use of a signed info block for which we can't find a
matching X.509 cert in the certificate list, though it be populated.

Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Vivek Goyal <vgoyal@redhat.com>
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 8bd474e..ae47be6 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -55,13 +55,16 @@
 		 * keys.
 		 */
 		key = x509_request_asymmetric_key(trust_keyring, x509->id);
-		if (!IS_ERR(key))
+		if (!IS_ERR(key)) {
 			/* One of the X.509 certificates in the PKCS#7 message
 			 * is apparently the same as one we already trust.
 			 * Verify that the trusted variant can also validate
 			 * the signature on the descendant.
 			 */
+			pr_devel("sinfo %u: Cert %u as key %x\n",
+				 sinfo->index, x509->index, key_serial(key));
 			goto matched;
+		}
 		if (key == ERR_PTR(-ENOMEM))
 			return -ENOMEM;
 
@@ -81,15 +84,34 @@
 	/* No match - see if the root certificate has a signer amongst the
 	 * trusted keys.
 	 */
-	if (!last || !last->issuer || !last->authority) {
-		kleave(" = -ENOKEY [no backref]");
-		return -ENOKEY;
+	if (last && last->authority) {
+		key = x509_request_asymmetric_key(trust_keyring, last->authority);
+		if (!IS_ERR(key)) {
+			x509 = last;
+			pr_devel("sinfo %u: Root cert %u signer is key %x\n",
+				 sinfo->index, x509->index, key_serial(key));
+			goto matched;
+		}
+		if (PTR_ERR(key) != -ENOKEY)
+			return PTR_ERR(key);
 	}
 
-	key = x509_request_asymmetric_key(trust_keyring, last->authority);
-	if (IS_ERR(key))
-		return PTR_ERR(key) == -ENOMEM ? -ENOMEM : -ENOKEY;
-	x509 = last;
+	/* As a last resort, see if we have a trusted public key that matches
+	 * the signed info directly.
+	 */
+	key = x509_request_asymmetric_key(trust_keyring,
+					  sinfo->signing_cert_id);
+	if (!IS_ERR(key)) {
+		pr_devel("sinfo %u: Direct signer is key %x\n",
+			 sinfo->index, key_serial(key));
+		x509 = NULL;
+		goto matched;
+	}
+	if (PTR_ERR(key) != -ENOKEY)
+		return PTR_ERR(key);
+
+	kleave(" = -ENOKEY [no backref]");
+	return -ENOKEY;
 
 matched:
 	ret = verify_signature(key, sig);
@@ -103,10 +125,12 @@
 	}
 
 verified:
-	x509->verified = true;
-	for (p = sinfo->signer; p != x509; p = p->signer) {
-		p->verified = true;
-		p->trusted = trusted;
+	if (x509) {
+		x509->verified = true;
+		for (p = sinfo->signer; p != x509; p = p->signer) {
+			p->verified = true;
+			p->trusted = trusted;
+		}
 	}
 	sinfo->trusted = trusted;
 	kleave(" = 0");