// 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 #include #include #include #include #include #include "../bytestring/internal.h" #include "../internal.h" #include "../mem_internal.h" #include "../pkcs7/internal.h" using namespace bssl; // 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) DECLARE_OPAQUE_STRUCT(CMS_SignerInfo_st, CMSSignerInfo) DECLARE_OPAQUE_STRUCT(CMS_ContentInfo_st, CMSContentInfo) BSSL_NAMESPACE_BEGIN class CMSSignerInfo : public CMS_SignerInfo_st { public: UniquePtr signcert; UniquePtr pkey; const EVP_MD *md = nullptr; bool use_key_id = false; }; class CMSContentInfo : public CMS_ContentInfo_st { public: static constexpr bool kAllowUniquePtr = true; bool has_signer_info = false; CMSSignerInfo signer_info; Array der; }; BSSL_NAMESPACE_END 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; } UniquePtr cms = MakeUnique(); 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, nullptr, flags)) { return nullptr; } return cms.release(); } void CMS_ContentInfo_free(CMS_ContentInfo *cms) { Delete(FromOpaque(cms)); } CMS_SignerInfo *CMS_add1_signer(CMS_ContentInfo *cms, X509 *signcert, EVP_PKEY *pkey, const EVP_MD *md, uint32_t flags) { auto *impl = FromOpaque(cms); if ( // Already finalized. !impl->der.empty() || // We only support one signer. impl->has_signer_info || // 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. impl->has_signer_info = true; impl->signer_info.signcert = UpRef(signcert); impl->signer_info.pkey = UpRef(pkey); impl->signer_info.md = md; impl->signer_info.use_key_id = (flags & CMS_USE_KEYID) != 0; return &impl->signer_info; } int CMS_final(CMS_ContentInfo *cms, BIO *data, BIO *dcont, uint32_t flags) { auto *impl = FromOpaque(cms); if ( // Already finalized. !impl->der.empty() || // Require a SignerInfo. We do not support signature-less SignedDatas. !impl->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; } ScopedCBB cbb; if (!CBB_init(cbb.get(), 2048) || !pkcs7_add_external_signature(cbb.get(), impl->signer_info.signcert.get(), impl->signer_info.pkey.get(), impl->signer_info.md, data, impl->signer_info.use_key_id) || !CBBFinishArray(cbb.get(), &impl->der)) { return 0; } return 1; } int i2d_CMS_bio(BIO *out, CMS_ContentInfo *cms) { auto *impl = FromOpaque(cms); if (impl->der.empty()) { // Not yet finalized. OPENSSL_PUT_ERROR(CMS, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return 0; } return BIO_write_all(out, impl->der.data(), impl->der.size()); } 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); }