blob: b2f19532b5f6aa13e19506f58bea5d9d723dccce [file] [log] [blame]
// 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);
}