|  | // Copyright 2025 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #include <openssl/cms.h> | 
|  |  | 
|  | #include <openssl/bio.h> | 
|  | #include <openssl/err.h> | 
|  | #include <openssl/evp.h> | 
|  | #include <openssl/mem.h> | 
|  | #include <openssl/x509.h> | 
|  |  | 
|  | #include "../pkcs7/internal.h" | 
|  |  | 
|  |  | 
|  | // TODO(davidben): Should we move the core PKCS#7 / CMS implementation into | 
|  | // crypto/cms instead of crypto/pkcs7? CMS is getting new features while PKCS#7 | 
|  | // is not. | 
|  | OPENSSL_DECLARE_ERROR_REASON(CMS, CERTIFICATE_HAS_NO_KEYID) | 
|  |  | 
|  | struct CMS_SignerInfo_st { | 
|  | X509 *signcert = nullptr; | 
|  | EVP_PKEY *pkey = nullptr; | 
|  | const EVP_MD *md = nullptr; | 
|  | bool use_key_id = false; | 
|  | }; | 
|  |  | 
|  | struct CMS_ContentInfo_st { | 
|  | bool has_signer_info = false; | 
|  | CMS_SignerInfo signer_info; | 
|  | uint8_t *der = nullptr; | 
|  | size_t der_len = 0; | 
|  | }; | 
|  |  | 
|  | CMS_ContentInfo *CMS_sign(X509 *signcert, EVP_PKEY *pkey, STACK_OF(X509) *certs, | 
|  | BIO *data, uint32_t flags) { | 
|  | // We only support external signatures and do not support embedding | 
|  | // certificates in SignedData. | 
|  | if ((flags & CMS_DETACHED) == 0 || sk_X509_num(certs) != 0) { | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | bssl::UniquePtr<CMS_ContentInfo> cms( | 
|  | static_cast<CMS_ContentInfo *>(OPENSSL_zalloc(sizeof(CMS_ContentInfo)))); | 
|  | if (cms == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | if (pkey != nullptr && | 
|  | !CMS_add1_signer(cms.get(), signcert, pkey, /*md=*/nullptr, flags)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // We don't actually use streaming mode, but Linux passes |CMS_STREAM| to | 
|  | // |CMS_sign| and OpenSSL interprets it as an alias for |CMS_PARTIAL| in this | 
|  | // context. | 
|  | if ((flags & (CMS_PARTIAL | CMS_STREAM)) == 0 && | 
|  | !CMS_final(cms.get(), data, NULL, flags)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return cms.release(); | 
|  | } | 
|  |  | 
|  | void CMS_ContentInfo_free(CMS_ContentInfo *cms) { | 
|  | if (cms == nullptr) { | 
|  | return; | 
|  | } | 
|  | X509_free(cms->signer_info.signcert); | 
|  | EVP_PKEY_free(cms->signer_info.pkey); | 
|  | OPENSSL_free(cms->der); | 
|  | OPENSSL_free(cms); | 
|  | } | 
|  |  | 
|  | CMS_SignerInfo *CMS_add1_signer(CMS_ContentInfo *cms, X509 *signcert, | 
|  | EVP_PKEY *pkey, const EVP_MD *md, | 
|  | uint32_t flags) { | 
|  | if (  // Already finalized. | 
|  | cms->der_len != 0 || | 
|  | // We only support one signer. | 
|  | cms->has_signer_info || | 
|  | // We do not support configuring a signer in multiple steps. (In OpenSSL, | 
|  | // this is used to configure attributes. | 
|  | (flags & CMS_PARTIAL) != 0 || | 
|  | // We do not support embedding certificates in SignedData. | 
|  | (flags & CMS_NOCERTS) == 0 || | 
|  | // We do not support attributes in SignedData. | 
|  | (flags & CMS_NOATTR) == 0) { | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | if (signcert == nullptr || pkey == nullptr) { | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_PASSED_NULL_PARAMETER); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | if (!X509_check_private_key(signcert, pkey)) { | 
|  | OPENSSL_PUT_ERROR(CMS, CMS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Default to SHA-256. | 
|  | if (md == nullptr) { | 
|  | md = EVP_sha256(); | 
|  | } | 
|  |  | 
|  | // Save information for later. | 
|  | cms->has_signer_info = true; | 
|  | cms->signer_info.signcert = bssl::UpRef(signcert).release(); | 
|  | cms->signer_info.pkey = bssl::UpRef(pkey).release(); | 
|  | cms->signer_info.md = md; | 
|  | cms->signer_info.use_key_id = (flags & CMS_USE_KEYID) != 0; | 
|  | return &cms->signer_info; | 
|  | } | 
|  |  | 
|  | int CMS_final(CMS_ContentInfo *cms, BIO *data, BIO *dcont, uint32_t flags) { | 
|  | if (  // Already finalized. | 
|  | cms->der_len != 0 || | 
|  | // Require a SignerInfo. We do not support signature-less SignedDatas. | 
|  | !cms->has_signer_info || | 
|  | // We only support the straightforward passthrough mode, without S/MIME | 
|  | // translations. | 
|  | (flags & CMS_BINARY) == 0 || | 
|  | // We do not support |dcont|. It is unclear what it does. | 
|  | dcont != nullptr) { | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | bssl::ScopedCBB cbb; | 
|  | if (!CBB_init(cbb.get(), 2048) || | 
|  | !pkcs7_add_external_signature(cbb.get(), cms->signer_info.signcert, | 
|  | cms->signer_info.pkey, cms->signer_info.md, | 
|  | data, cms->signer_info.use_key_id) || | 
|  | !CBB_finish(cbb.get(), &cms->der, &cms->der_len)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | int i2d_CMS_bio(BIO *out, CMS_ContentInfo *cms) { | 
|  | if (cms->der_len == 0) { | 
|  | // Not yet finalized. | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return BIO_write_all(out, cms->der, cms->der_len); | 
|  | } | 
|  |  | 
|  | int i2d_CMS_bio_stream(BIO *out, CMS_ContentInfo *cms, BIO *in, int flags) { | 
|  | // We do not support streaming mode. | 
|  | if ((flags & CMS_STREAM) != 0 || in != nullptr) { | 
|  | OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return i2d_CMS_bio(out, cms); | 
|  | } |