Allow PKCS7_sign to work for signing kernel modules.

Linux module signing uses PKCS#7 / CMS because everything is awful and
broken. In order to make the lives of kernel developers easier, support
the calling pattern that the kernel uses to sign modules.

The kernel utility was written at a time when PKCS#7 was hard coded to
use SHA-1 for signing in OpenSSL and it reflects this: you can only
specify “sha1” on the command line, for example. As of OpenSSL 1.1.1, at
least, OpenSSL uses SHA-256 and thus so does this change.

Change-Id: I32b036123a0d8b272ec9e1c0130c45bf3ed0d2c7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49545
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/pkcs7/internal.h b/crypto/pkcs7/internal.h
index 9541bea..5ee8e8a 100644
--- a/crypto/pkcs7/internal.h
+++ b/crypto/pkcs7/internal.h
@@ -32,14 +32,23 @@
 // NULL.
 int pkcs7_parse_header(uint8_t **der_bytes, CBS *out, CBS *cbs);
 
-// pkcs7_bundle writes a PKCS#7, SignedData structure to |out| and then calls
-// |cb| with a CBB to which certificate or CRL data can be written, and the
-// opaque context pointer, |arg|. The callback can return zero to indicate an
-// error.
+// pkcs7_add_signed_data writes a PKCS#7, SignedData structure to |out|. While
+// doing so it makes callbacks to let the caller fill in parts of the structure.
+// All callbacks are ignored if NULL and return one on success or zero on error.
 //
-// pkcs7_bundle returns one on success or zero on error.
-int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
-                 const void *arg);
+//   digest_algos_cb: may write AlgorithmIdentifiers into the given CBB, which
+//       is a SET of digest algorithms.
+//   cert_crl_cb: may write the |certificates| or |crls| fields.
+//       (See https://datatracker.ietf.org/doc/html/rfc2315#section-9.1)
+//   signer_infos_cb: may write the contents of the |signerInfos| field.
+//       (See https://datatracker.ietf.org/doc/html/rfc2315#section-9.1)
+//
+// pkcs7_add_signed_data returns one on success or zero on error.
+int pkcs7_add_signed_data(CBB *out,
+                          int (*digest_algos_cb)(CBB *out, const void *arg),
+                          int (*cert_crl_cb)(CBB *out, const void *arg),
+                          int (*signer_infos_cb)(CBB *out, const void *arg),
+                          const void *arg);
 
 
 #if defined(__cplusplus)
diff --git a/crypto/pkcs7/pkcs7.c b/crypto/pkcs7/pkcs7.c
index 1d0b139..6be5a07 100644
--- a/crypto/pkcs7/pkcs7.c
+++ b/crypto/pkcs7/pkcs7.c
@@ -131,8 +131,11 @@
   return ret;
 }
 
-int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
-                 const void *arg) {
+int pkcs7_add_signed_data(CBB *out,
+                          int (*digest_algos_cb)(CBB *out, const void *arg),
+                          int (*cert_crl_cb)(CBB *out, const void *arg),
+                          int (*signer_infos_cb)(CBB *out, const void *arg),
+                          const void *arg) {
   CBB outer_seq, oid, wrapped_seq, seq, version_bytes, digest_algos_set,
       content_info, signer_infos;
 
@@ -147,11 +150,13 @@
       !CBB_add_asn1(&seq, &version_bytes, CBS_ASN1_INTEGER) ||
       !CBB_add_u8(&version_bytes, 1) ||
       !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) ||
+      (digest_algos_cb != NULL && !digest_algos_cb(&digest_algos_set, arg)) ||
       !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1(&content_info, &oid, CBS_ASN1_OBJECT) ||
       !CBB_add_bytes(&oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
-      !cb(&seq, arg) ||
-      !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET)) {
+      (cert_crl_cb != NULL && !cert_crl_cb(&seq, arg)) ||
+      !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) ||
+      (signer_infos_cb != NULL && !signer_infos_cb(&signer_infos, arg))) {
     return 0;
   }
 
diff --git a/crypto/pkcs7/pkcs7_test.cc b/crypto/pkcs7/pkcs7_test.cc
index 8e14603..9cca175 100644
--- a/crypto/pkcs7/pkcs7_test.cc
+++ b/crypto/pkcs7/pkcs7_test.cc
@@ -775,3 +775,201 @@
   check_order(crl1.get(), crl2.get());
   check_order(crl2.get(), crl1.get());
 }
+
+TEST(PKCS7Test, KernelModuleSigning) {
+  // Sign a message with the same call that the Linux kernel's sign-file.c
+  // makes.
+  static const char kCert[] = R"(
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIURVkPzF/4dwy7419Qk75uhIuyf0EwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA5MjExOTIyMTJaFw0yMjA5
+MjExOTIyMTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC1+MOn+BopcEVR4QMvjXdAxGkWFllXyQFDToL+qOiP
+RU1yN7C8KCtkbOAFttJIO4O/i0iZ7KqYbnmB6YUA/ONAcakocnrdoESgRJcVMeAx
+Dk/11OtMF5yIfeOOO/TUeVNmAUaT63gFbKy/adpqhzJtOv9BBl5VcYNGGSE+0wtb
+mjpmNsxunEQR1KLDc97fGYHeRfKoSyrCIEE8IaAEpKGR2Sku3v9Jwh7RpjupgiUA
+kH6pJk7VMZm5vl2wFjYvfysgjeN5ZtsxFDMaPYZStpxMxpNd5C9DsO2Ljp5NMpGf
+NGmG4ZqiaQg8z2cIM6ESmN1zDJdUh5IXed1fOxBZD/poUFH0wDRFWnvzlaPmjJEF
+rYLMK8svnE5nEQp9vu93ISFBx7cofs+niMaUXPEqaRSqruifN2M1it3kOf/8YZl1
+vurs+VtHD6nOJo6bd11+37aBidIB/BaWnzLrDmSTcPFa1tkTHwoLqc9+jThTq9jZ
+6w3lAMPpsoenyD19UmQB589+4kNp2SIO/TtzVQCGgQPXE2jDCl6G9aIPMkfvpPZK
+4THVil3WQRCFYnYdDO4HQXo2ZuC4RiqgY5ygfeoL+fa9k383lgxxAHQLS7xsbaVB
+40RmfdbdevgPYIwZNNO78ddRmMdSv6IknSW9gydGzY//btY+t1SWcBZWzn1Ewq8g
+2QIDAQABo1MwUTAdBgNVHQ4EFgQUotZD9ajEvnQYVezIWzcW4pzvMcUwHwYDVR0j
+BBgwFoAUotZD9ajEvnQYVezIWzcW4pzvMcUwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAqCe42PIWoyLDx9bR+5cSp99N5xo5lLiSLtWx2emDbZB2
+AunqKYeEgIV+TWNF2w1SZ/ckFgV7SlL2Yl73N/veSNRfNAnpjLksGDFpdJb7YXrx
+cUvxdy1mr8oau6J7PC9JGjBTBrnhqwCQX1FtcAxODKll2Lsfuj6+bdC3rCK7KBEo
+ENamMJZIeo8lRP9qFF2xwCEzZjRv2zvB6O5o9045aTUcdCrwUfKE2sqY6EXRzFTC
+waK0HRCd1FLv9omhz/Ug5PMHP4d6MZfnAbFm+AzAhnpkrk/9TJYSOoNTNLWsuqhp
+dN0rKqiFWv1zIwfknXvTh1P1Ap+G5jffAca0zWUH1oKjE7ZZioSsaZ6gySnD8+WQ
+TPbOYtG+n0mhCH1TrU8Dqi3rd8g5IbC8loYLRH94QtodOnevD4Qo9Orfrsr8hGOW
+ABespanZArhoQ03DAtpNhtHm2NWJQF2uHNqcTrkq0omqZBTbMD1GKMBujoNooAUu
+w51U9r+RycPJTFqEGHb0nd7EjoyXEXtuX1Ld5fTZjQ9SszmQKQ8w3lHqRGNlkSiO
+e3IOOq2ruXmq1jykxpmi82IcTRUE8TZBfL/yz0nxpHKAYC1VwMezrkgZDGz4npxf
+1z2+qd58xU6/jsf7/+3xdPFubeEJujdbCkWQsQC5Rzm48zDWGq/pyzFji44K3TA=
+-----END CERTIFICATE-----
+)";
+
+  static const char kKey[] = R"(
+-----BEGIN PRIVATE KEY-----
+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
+hJTbHtjEDJ7BHLC/CNUhXbpyyu1y
+-----END PRIVATE KEY-----
+)";
+
+  bssl::UniquePtr<BIO> cert_bio(
+      BIO_new_mem_buf(const_cast<char *>(kCert), sizeof(kCert) - 1));
+  bssl::UniquePtr<X509> cert(
+      PEM_read_bio_X509(cert_bio.get(), nullptr, nullptr, nullptr));
+
+  bssl::UniquePtr<BIO> key_bio(
+      BIO_new_mem_buf(const_cast<char *>(kKey), sizeof(kKey) - 1));
+  bssl::UniquePtr<EVP_PKEY> key(
+      PEM_read_bio_PrivateKey(key_bio.get(), nullptr, nullptr, nullptr));
+
+  static const char kSignedData[] = "signed data";
+  bssl::UniquePtr<BIO> data_bio(BIO_new_mem_buf(const_cast<char *>(kSignedData),
+                                                sizeof(kSignedData) - 1));
+
+  bssl::UniquePtr<PKCS7> pkcs7(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS | PKCS7_DETACHED));
+  ASSERT_TRUE(pkcs7);
+
+  uint8_t *pkcs7_bytes = nullptr;
+  const int pkcs7_len = i2d_PKCS7(pkcs7.get(), &pkcs7_bytes);
+  ASSERT_GE(pkcs7_len, 0);
+  bssl::UniquePtr<uint8_t> pkcs7_storage(pkcs7_bytes);
+
+  // RSA signatures are deterministic so the output should not change.
+  static const uint8_t kExpectedOutput[] = {
+      0x30, 0x82, 0x02, 0xbc, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+      0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0xad, 0x30, 0x82, 0x02, 0xa9, 0x02,
+      0x01, 0x01, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+      0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48,
+      0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0x31, 0x82, 0x02, 0x86, 0x30, 0x82,
+      0x02, 0x82, 0x02, 0x01, 0x01, 0x30, 0x5d, 0x30, 0x45, 0x31, 0x0b, 0x30,
+      0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13,
+      0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d,
+      0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
+      0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+      0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50,
+      0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x02, 0x14, 0x45, 0x59, 0x0f, 0xcc,
+      0x5f, 0xf8, 0x77, 0x0c, 0xbb, 0xe3, 0x5f, 0x50, 0x93, 0xbe, 0x6e, 0x84,
+      0x8b, 0xb2, 0x7f, 0x41, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+      0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+      0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x02, 0x00,
+      0x54, 0xd4, 0x7c, 0xdc, 0x19, 0x86, 0xa1, 0xb2, 0xbe, 0xe3, 0xa4, 0x5e,
+      0xad, 0x16, 0x6f, 0x7c, 0xf9, 0xa6, 0x40, 0x90, 0xb8, 0x78, 0x85, 0xf1,
+      0x02, 0x59, 0xe5, 0x9f, 0x83, 0xfb, 0x20, 0xcf, 0x29, 0x52, 0xb6, 0x35,
+      0x5c, 0xf9, 0xef, 0x4e, 0xc5, 0xd3, 0xa6, 0x45, 0x6e, 0xfa, 0x0a, 0xa7,
+      0x53, 0xc8, 0xf4, 0xf9, 0xd6, 0xc5, 0xd8, 0xd8, 0x04, 0x3d, 0xb4, 0x15,
+      0xa7, 0x7a, 0x53, 0xdd, 0x27, 0xfa, 0x58, 0x2e, 0x5e, 0xc4, 0xcd, 0x45,
+      0xaa, 0xc2, 0x7b, 0xf9, 0x3d, 0xd7, 0x22, 0x20, 0x90, 0xbb, 0xa5, 0x62,
+      0xd5, 0xaa, 0x39, 0x8f, 0xc1, 0x00, 0xef, 0x4b, 0x03, 0x2c, 0x32, 0xc0,
+      0xad, 0x27, 0xb6, 0xfe, 0x86, 0xe5, 0x9d, 0xf0, 0xbe, 0xb1, 0x0d, 0xa4,
+      0xa3, 0x40, 0xe0, 0xaa, 0x0a, 0x13, 0x6e, 0x61, 0x9a, 0x3b, 0xae, 0x78,
+      0xd4, 0x6f, 0x2d, 0x1d, 0x40, 0x4b, 0xe3, 0x5f, 0xf8, 0xe8, 0x21, 0x89,
+      0x35, 0x73, 0x6d, 0x7e, 0x41, 0xc6, 0x0f, 0x0c, 0x01, 0x64, 0x61, 0xa8,
+      0x37, 0xef, 0x2b, 0x95, 0xb7, 0x34, 0xac, 0xc7, 0xdb, 0x66, 0x87, 0x45,
+      0xb4, 0x0f, 0x60, 0x01, 0x07, 0x29, 0x74, 0x32, 0x0b, 0xae, 0xbc, 0x08,
+      0x88, 0x15, 0xc3, 0x79, 0x4a, 0x1c, 0x5a, 0x9c, 0xc2, 0xfb, 0x4f, 0xd3,
+      0x17, 0xc2, 0x40, 0x71, 0x37, 0xea, 0xa6, 0x1e, 0xf0, 0x5b, 0xa5, 0xd7,
+      0x9b, 0x9e, 0x57, 0x44, 0x74, 0xc5, 0xd5, 0x5f, 0xba, 0xbc, 0xd7, 0xe1,
+      0xae, 0xd0, 0xd3, 0xb5, 0x10, 0xc6, 0x8b, 0xb1, 0x83, 0x7c, 0xaa, 0x3a,
+      0xbb, 0xe8, 0x7f, 0x56, 0xc4, 0x3b, 0x9d, 0x45, 0x09, 0x9b, 0x34, 0xc9,
+      0xfb, 0x5a, 0xa1, 0xab, 0xd0, 0x07, 0x79, 0x43, 0x58, 0x44, 0xd7, 0x40,
+      0xc4, 0xa7, 0xd3, 0xe9, 0x18, 0xb9, 0x78, 0x1d, 0x93, 0x0b, 0xc1, 0xdb,
+      0xc3, 0xae, 0xc9, 0xe8, 0x2c, 0xa7, 0x8c, 0x7e, 0x31, 0x1e, 0xec, 0x1c,
+      0xab, 0x83, 0xa0, 0x5d, 0x0e, 0xc3, 0x6a, 0x7c, 0x97, 0x09, 0xcf, 0x00,
+      0xa9, 0x66, 0xda, 0x21, 0x85, 0xaa, 0x47, 0xd8, 0xea, 0x8f, 0x72, 0x54,
+      0x03, 0x6c, 0xbc, 0x4b, 0xf9, 0x92, 0xae, 0x82, 0x75, 0x33, 0x10, 0x4d,
+      0x65, 0x4d, 0x0e, 0x73, 0x5d, 0x6f, 0x09, 0xee, 0x56, 0x78, 0x87, 0x0b,
+      0xa3, 0xaa, 0xc2, 0x5f, 0x49, 0x73, 0x0d, 0x78, 0xfa, 0x40, 0xc1, 0x25,
+      0x2f, 0x5d, 0x8a, 0xe1, 0xbf, 0x38, 0x2c, 0xd0, 0x26, 0xbd, 0xf5, 0x6e,
+      0x02, 0x01, 0x2e, 0x9e, 0x27, 0x64, 0x4b, 0x61, 0x8c, 0x68, 0x6e, 0x09,
+      0xfe, 0x0b, 0xf8, 0x36, 0x4e, 0x84, 0xb7, 0x76, 0xcb, 0x41, 0xf0, 0x40,
+      0x72, 0xc9, 0x74, 0x64, 0x5f, 0xbe, 0x9e, 0xfe, 0x9e, 0xce, 0x89, 0x84,
+      0x68, 0x81, 0x57, 0x2a, 0xdb, 0xd6, 0x01, 0xa8, 0x1b, 0x6e, 0x5d, 0xc4,
+      0x65, 0xbd, 0x0d, 0x98, 0x54, 0xa3, 0x18, 0x23, 0x09, 0x4a, 0x8d, 0x6c,
+      0xc6, 0x2e, 0xfe, 0x7a, 0xa9, 0x11, 0x92, 0x8b, 0xd0, 0xc1, 0xe7, 0x76,
+      0x71, 0xec, 0x34, 0xfc, 0xc8, 0x2a, 0x5e, 0x38, 0x52, 0xe6, 0xc8, 0xa5,
+      0x1d, 0x0b, 0xce, 0xf5, 0xc0, 0xe5, 0x0b, 0x88, 0xa9, 0x55, 0x88, 0x6c,
+      0xfa, 0xea, 0xaa, 0x39, 0x66, 0xdd, 0x80, 0x52, 0xe0, 0x7e, 0x45, 0x8e,
+      0x51, 0x2c, 0x36, 0x07, 0xd7, 0x2b, 0xf1, 0x46, 0x00, 0x66, 0xb2, 0x5a,
+      0x39, 0xbe, 0xf7, 0x26, 0x15, 0xbc, 0x55, 0xdb, 0xe9, 0x01, 0xdd, 0x54,
+      0x27, 0x2b, 0xfe, 0x86, 0x52, 0xef, 0xc6, 0x27, 0xa3, 0xf7, 0x55, 0x55,
+      0xb8, 0xe2, 0x1f, 0xcb, 0x32, 0xd8, 0xba, 0xd6, 0x69, 0xde, 0x8d, 0xa7,
+      0xfa, 0xad, 0xf6, 0x2a, 0xc0, 0x6f, 0x86, 0x50, 0x27, 0x5a, 0xe2, 0xe3,
+      0xf6, 0xb9, 0x01, 0xec, 0x01, 0x37, 0x84, 0x01,
+  };
+  EXPECT_EQ(Bytes(pkcs7_bytes, pkcs7_len),
+            Bytes(kExpectedOutput, sizeof(kExpectedOutput)));
+
+  // Other option combinations should fail.
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_BINARY | PKCS7_NOCERTS | PKCS7_DETACHED));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_TEXT | PKCS7_NOCERTS | PKCS7_DETACHED));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_DETACHED));
+
+  ERR_clear_error();
+}
diff --git a/crypto/pkcs7/pkcs7_x509.c b/crypto/pkcs7/pkcs7_x509.c
index 3f1526c..773c592 100644
--- a/crypto/pkcs7/pkcs7_x509.c
+++ b/crypto/pkcs7/pkcs7_x509.c
@@ -20,6 +20,7 @@
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
+#include <openssl/obj.h>
 #include <openssl/pem.h>
 #include <openssl/pool.h>
 #include <openssl/stack.h>
@@ -197,7 +198,9 @@
 }
 
 int PKCS7_bundle_certificates(CBB *out, const STACK_OF(X509) *certs) {
-  return pkcs7_bundle(out, pkcs7_bundle_certificates_cb, certs);
+  return pkcs7_add_signed_data(out, /*digest_algos_cb=*/NULL,
+                               pkcs7_bundle_certificates_cb,
+                               /*signer_infos_cb=*/NULL, certs);
 }
 
 static int pkcs7_bundle_crls_cb(CBB *out, const void *arg) {
@@ -228,7 +231,9 @@
 }
 
 int PKCS7_bundle_CRLs(CBB *out, const STACK_OF(X509_CRL) *crls) {
-  return pkcs7_bundle(out, pkcs7_bundle_crls_cb, crls);
+  return pkcs7_add_signed_data(out, /*digest_algos_cb=*/NULL,
+                               pkcs7_bundle_crls_cb,
+                               /*signer_infos_cb=*/NULL, crls);
 }
 
 static PKCS7 *pkcs7_new(CBS *cbs) {
@@ -362,26 +367,160 @@
 int PKCS7_type_is_signed(const PKCS7 *p7) { return 1; }
 int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7) { return 0; }
 
+// write_sha256_ai writes an AlgorithmIdentifier for SHA-256 to
+// |digest_algos_set|.
+static int write_sha256_ai(CBB *digest_algos_set, const void *arg) {
+  CBB seq;
+  return CBB_add_asn1(digest_algos_set, &seq, CBS_ASN1_SEQUENCE) &&
+         OBJ_nid2cbb(&seq, NID_sha256) &&  //
+         // https://datatracker.ietf.org/doc/html/rfc5754#section-2
+         // "Implementations MUST generate SHA2 AlgorithmIdentifiers with absent
+         //  parameters."
+         CBB_flush(digest_algos_set);
+}
+
+// sign_sha256 writes at most |max_out_sig| bytes of the signature of |data| by
+// |pkey| to |out_sig| and sets |*out_sig_len| to the number of bytes written.
+// It returns one on success or zero on error.
+static int sign_sha256(uint8_t *out_sig, size_t *out_sig_len,
+                       size_t max_out_sig, EVP_PKEY *pkey, BIO *data) {
+  static const size_t kBufSize = 4096;
+  uint8_t *buffer = OPENSSL_malloc(kBufSize);
+  if (!buffer) {
+    return 0;
+  }
+
+  EVP_MD_CTX ctx;
+  EVP_MD_CTX_init(&ctx);
+
+  int ret = 0;
+  if (!EVP_DigestSignInit(&ctx, NULL, EVP_sha256(), NULL, pkey)) {
+    goto out;
+  }
+
+  for (;;) {
+    const int n = BIO_read(data, buffer, kBufSize);
+    if (n == 0) {
+      break;
+    } else if (n < 0 || !EVP_DigestSignUpdate(&ctx, buffer, n)) {
+      goto out;
+    }
+  }
+
+  *out_sig_len = max_out_sig;
+  if (!EVP_DigestSignFinal(&ctx, out_sig, out_sig_len)) {
+    goto out;
+  }
+
+  ret = 1;
+
+out:
+  EVP_MD_CTX_cleanup(&ctx);
+  OPENSSL_free(buffer);
+  return ret;
+}
+
+struct signer_info_data {
+  const X509 *sign_cert;
+  uint8_t *signature;
+  size_t signature_len;
+};
+
+// write_signer_info writes the SignerInfo structure from
+// https://datatracker.ietf.org/doc/html/rfc2315#section-9.2 to |out|. It
+// returns one on success or zero on error.
+static int write_signer_info(CBB *out, const void *arg) {
+  const struct signer_info_data *const si_data = arg;
+
+  int ret = 0;
+  uint8_t *subject_bytes = NULL;
+  uint8_t *serial_bytes = NULL;
+
+  const int subject_len =
+      i2d_X509_NAME(X509_get_subject_name(si_data->sign_cert), &subject_bytes);
+  const int serial_len = i2d_ASN1_INTEGER(
+      (ASN1_INTEGER *)X509_get0_serialNumber(si_data->sign_cert),
+      &serial_bytes);
+
+  CBB seq, issuer_and_serial, signing_algo, null, signature;
+  if (subject_len < 0 ||
+      serial_len < 0 ||
+      !CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
+      // version
+      !CBB_add_asn1_uint64(&seq, 1) ||
+      !CBB_add_asn1(&seq, &issuer_and_serial, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_bytes(&issuer_and_serial, subject_bytes, subject_len) ||
+      !CBB_add_bytes(&issuer_and_serial, serial_bytes, serial_len) ||
+      !write_sha256_ai(&seq, NULL) ||
+      !CBB_add_asn1(&seq, &signing_algo, CBS_ASN1_SEQUENCE) ||
+      !OBJ_nid2cbb(&signing_algo, NID_rsaEncryption) ||
+      !CBB_add_asn1(&signing_algo, &null, CBS_ASN1_NULL) ||
+      !CBB_add_asn1(&seq, &signature, CBS_ASN1_OCTETSTRING) ||
+      !CBB_add_bytes(&signature, si_data->signature, si_data->signature_len) ||
+      !CBB_flush(out)) {
+    goto out;
+  }
+
+  ret = 1;
+
+out:
+  OPENSSL_free(subject_bytes);
+  OPENSSL_free(serial_bytes);
+  return ret;
+}
+
 PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey, STACK_OF(X509) *certs,
                   BIO *data, int flags) {
-  if (sign_cert != NULL || pkey != NULL || flags != PKCS7_DETACHED) {
-    OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+  CBB cbb;
+  if (!CBB_init(&cbb, 2048)) {
     return NULL;
   }
 
-  uint8_t *der;
+  uint8_t *der = NULL;
   size_t len;
-  CBB cbb;
-  if (!CBB_init(&cbb, 2048) ||
-      !PKCS7_bundle_certificates(&cbb, certs) ||
-      !CBB_finish(&cbb, &der, &len)) {
-    CBB_cleanup(&cbb);
-    return NULL;
+  PKCS7 *ret = NULL;
+
+  if (sign_cert == NULL && pkey == NULL && flags == PKCS7_DETACHED) {
+    // Caller just wants to bundle certificates.
+    if (!PKCS7_bundle_certificates(&cbb, certs)) {
+      goto out;
+    }
+  } else if (sign_cert != NULL && pkey != NULL && certs == NULL &&
+             data != NULL &&
+             flags == (PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS |
+                       PKCS7_DETACHED) &&
+             EVP_PKEY_id(pkey) == NID_rsaEncryption) {
+    // sign-file.c from the Linux kernel.
+    const size_t signature_max_len = EVP_PKEY_size(pkey);
+    struct signer_info_data si_data = {
+      .sign_cert = sign_cert,
+      .signature = OPENSSL_malloc(signature_max_len),
+    };
+
+    if (!si_data.signature ||
+        !sign_sha256(si_data.signature, &si_data.signature_len,
+                     signature_max_len, pkey, data) ||
+        !pkcs7_add_signed_data(&cbb, write_sha256_ai, /*cert_crl_cb=*/NULL,
+                               write_signer_info, &si_data)) {
+      OPENSSL_free(si_data.signature);
+      goto out;
+    }
+    OPENSSL_free(si_data.signature);
+  } else {
+    OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    goto out;
+  }
+
+  if (!CBB_finish(&cbb, &der, &len)) {
+    goto out;
   }
 
   CBS cbs;
   CBS_init(&cbs, der, len);
-  PKCS7 *ret = pkcs7_new(&cbs);
+  ret = pkcs7_new(&cbs);
+
+out:
+  CBB_cleanup(&cbb);
   OPENSSL_free(der);
   return ret;
 }
diff --git a/include/openssl/pkcs7.h b/include/openssl/pkcs7.h
index 8f2a885..77e13d7 100644
--- a/include/openssl/pkcs7.h
+++ b/include/openssl/pkcs7.h
@@ -200,15 +200,22 @@
 #define PKCS7_STREAM 0x1000
 #define PKCS7_PARTIAL 0x4000
 
-// PKCS7_sign assembles |certs| into a PKCS#7 signed data ContentInfo with
+// PKCS7_sign can operate in two modes to provide some backwards compatibility:
+//
+// The first mode assembles |certs| into a PKCS#7 signed data ContentInfo with
 // external data and no signatures. It returns a newly-allocated |PKCS7| on
 // success or NULL on error. |sign_cert| and |pkey| must be NULL. |data| is
-// ignored. |flags| must be equal to |PKCS7_DETACHED|.
-//
-// Note this function only implements a subset of the corresponding OpenSSL
-// function. It is provided for backwards compatibility only. Additionally,
+// ignored. |flags| must be equal to |PKCS7_DETACHED|. Additionally,
 // certificates in SignedData structures are unordered. The order of |certs|
 // will not be preserved.
+//
+// The second mode generates a detached RSA SHA-256 signature of |data| using
+// |pkey| and produces a PKCS#7 SignedData structure containing it. |certs|
+// must be NULL and |flags| must be exactly |PKCS7_NOATTR | PKCS7_BINARY |
+// PKCS7_NOCERTS | PKCS7_DETACHED|.
+//
+// Note this function only implements a subset of the corresponding OpenSSL
+// function. It is provided for backwards compatibility only.
 OPENSSL_EXPORT PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey,
                                  STACK_OF(X509) *certs, BIO *data, int flags);