blob: 1a011720525bd339c6f8969af3e9bf6583625a56 [file] [edit]
// Copyright 2026 Google LLC
//
// 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 "crypto/default/cms/verify_signature.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "crypto/default/cms/cms_parser.h"
#include "crypto/default/cms/oids.h"
#include "openssl/asn1.h"
#include "openssl/base.h"
#include "openssl/bytestring.h"
#include "openssl/crypto.h"
#include "openssl/digest.h"
#include "openssl/err.h"
#include "openssl/evp.h"
#include "openssl/mem.h"
#include "openssl/nid.h"
#include "openssl/obj.h"
#include "openssl/obj_mac.h"
#include "openssl/objects.h"
#include "openssl/rsa.h"
#include "openssl/x509.h"
using absl::StatusCode;
namespace credentio_cms {
namespace {
// Append the OpenSSL error strings to the status message.
absl::Status OpenSslError(absl::StatusCode code,
absl::string_view error_message) {
std::string message(error_message);
const char *file, *data;
int line, flags;
while (uint32_t err = ERR_get_error_line_data(&file, &line, &data, &flags)) {
if (file) {
absl::StrAppendFormat(&message, "\n%s:%d ", file, line);
} else {
absl::StrAppend(&message, "\n");
}
if (const char* reason = ERR_reason_error_string(err)) {
absl::StrAppend(&message, reason);
} else {
absl::StrAppend(&message, err);
}
if (data && (flags & ERR_TXT_STRING)) {
absl::StrAppend(&message, " - ", data);
}
}
return absl::Status(code, message);
}
// For PSS signatures, verify the parameters and apply them to 'pkey_ctx'.
// - 'certificate' is needed for the extra checks needed when a PSS
// certificate is used.
// - 'md' is the message digest algorithm used on the content (or signed
// attributes), it will be used to initialize the masking function.
absl::Status SetPssParameters(const SignerInfo& signer, const X509& certificate,
int content_digest_nid, const EVP_MD* md,
EVP_PKEY_CTX* pkey_ctx) {
// See https://tools.ietf.org/html/rfc4056 for the checks performed.
const unsigned char* p =
CBS_data(signer.signature_algorithm.parameter.cbs_ptr());
if (p == nullptr ||
CBS_len(signer.signature_algorithm.parameter.cbs_ptr()) == 0) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Missing PSS parameters");
}
bssl::UniquePtr<RSA_PSS_PARAMS> pss_params(d2i_RSA_PSS_PARAMS(
nullptr, &p, CBS_len(&(signer.signature_algorithm.parameter).cbs())));
if (pss_params == nullptr) {
// http://tools.ietf.org/html/rfc4056#section-2.2
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Missing PSS parameters");
}
if (pss_params->trailerField &&
ASN1_INTEGER_get(pss_params->trailerField) != 1) {
return OpenSslError(absl::StatusCode::kUnimplemented,
"Unsupported PSS trailer value.");
}
if (X509_get_signature_nid(&certificate) == NID_rsassaPss) {
return OpenSslError(
absl::StatusCode::kUnimplemented,
"PSS signatures are not supported with RSASSA-PSS certificates.");
}
if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) {
return OpenSslError(absl::StatusCode::kInternal,
"Failed to set PSS padding");
}
if (pss_params->hashAlgorithm) {
// http://tools.ietf.org/html/rfc4056#section-3
const ASN1_OBJECT* algorithm;
X509_ALGOR_get0(&algorithm, /*out_param_type=*/nullptr,
/*out_param_value=*/nullptr, pss_params->hashAlgorithm);
if (OBJ_obj2nid(algorithm) != content_digest_nid) {
return absl::Status(
absl::StatusCode::kInvalidArgument,
"The PSS Hash algorithm doesn't match the content hash algorithm.");
}
}
if (pss_params->maskGenAlgorithm) {
const ASN1_OBJECT* algorithm;
X509_ALGOR_get0(&algorithm, /*out_param_type=*/nullptr,
/*out_param_value=*/nullptr, pss_params->maskGenAlgorithm);
if (OBJ_obj2nid(algorithm) != NID_mgf1) {
return OpenSslError(absl::StatusCode::kUnimplemented,
"Unsupported PSS mask generation algorithm.");
}
}
if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, md) != 1) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Failed to set the PSS MGF1 hash");
}
int salt_length = 20;
if (pss_params->saltLength != nullptr) {
salt_length = ASN1_INTEGER_get(pss_params->saltLength);
}
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_length) != 1) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Invalid PSS parameters");
}
return absl::OkStatus();
}
// Check that the hash of the 'contents' matches the digest present in the
// authenticated attributes.
absl::Status VerifySignedAttributesHash(const SignerInfo& signer,
const std::vector<ByteString>& contents,
int digest_nid) {
const EVP_MD* md = EVP_get_digestbynid(digest_nid);
if (md == nullptr) {
return OpenSslError(absl::StatusCode::kUnimplemented,
"Failed to get the content hashing EVP_MD object.");
}
// When the authenticated attributes are present the signature is computed
// over them so we need two checks:
// 1) That the digest of the contents matches the attribute's
// message_digest.
// 2) That the signature of the authenticated attributes is valid.
bssl::ScopedEVP_MD_CTX md_context;
if (!EVP_DigestInit_ex(md_context.get(), md, nullptr)) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestInit_ex failed.");
}
for (const auto& chunk : contents) {
if (!EVP_DigestUpdate(md_context.get(), CBS_data(&(chunk).cbs()),
CBS_len(chunk.cbs_ptr()))) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestUpdate failed.");
}
}
uint8_t md_value[EVP_MAX_MD_SIZE];
unsigned md_len;
if (!EVP_DigestFinal_ex(md_context.get(), md_value, &md_len)) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestFinal_ex failed.");
}
if (CBS_len(&(signer.message_digest).cbs()) != md_len ||
CRYPTO_memcmp(md_value, CBS_data(&(signer.message_digest).cbs()),
CBS_len(&(signer.message_digest).cbs())) != 0) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Hash attribute mismatch");
}
return absl::OkStatus();
}
} // namespace
absl::StatusOr<SignatureInfo> VerifySignature(
const Content& cms_content, const SignerInfo& signer,
const std::vector<ByteString>& contents, const X509& certificate) {
ERR_clear_error(); // Clear OpenSSL's error queue for this thread.
// Signed attributes checks, see
// https://tools.ietf.org/html/rfc5652#section-5.6
if (!CompareOid(cms_content.content_type.cbs(), kDataOid, sizeof(kDataOid)) &&
CBS_len(signer.raw_signed_attributes.cbs_ptr()) == 0) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"The signed attributes are needed when the "
"encapsultated content type is not id-data.");
}
if (CBS_len(signer.raw_signed_attributes.cbs_ptr()) > 0) {
if (CBS_len(signer.content_type_signed.cbs_ptr()) == 0 ||
!CompareOid(signer.content_type_signed.cbs(),
cms_content.content_type.cbs())) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"Mismatch between the signed attributes content type "
"and the encapsultated content type.");
}
if (CBS_len(signer.message_digest.cbs_ptr()) == 0) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"Missing message digest in the signed attributes.");
}
}
bssl::UniquePtr<EVP_PKEY> key(
X509_get_pubkey(const_cast<X509*>(&certificate)));
if (key == nullptr) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Failed to get the public key from the certificate.");
}
int digest_nid = OBJ_cbs2nid(&signer.digest_algorithm.algorithm_oid.cbs());
if (digest_nid == NID_undef) {
return OpenSslError(absl::StatusCode::kUnimplemented,
"Unknown digest algorithm.");
}
int signature_algorithm_nid =
OBJ_cbs2nid(signer.signature_algorithm.algorithm_oid.cbs_ptr());
// Get the digest NID from the signature NID if possible, otherwise use the
// signer's digest algorithm.
int signature_digest_nid = NID_undef;
if (OBJ_find_sigid_algs(signature_algorithm_nid, &signature_digest_nid,
nullptr /* pkey nid */) != 1 ||
signature_digest_nid == NID_undef) {
signature_digest_nid = digest_nid;
}
// Check that the signature algorithm is supported.
switch (signature_algorithm_nid) {
case NID_rsassaPss:
case NID_rsaEncryption:
case NID_sha224WithRSAEncryption:
case NID_sha256WithRSAEncryption:
case NID_sha384WithRSAEncryption:
case NID_sha512WithRSAEncryption:
case NID_X9_62_id_ecPublicKey:
break;
// ECDSA support is defined in RFC 5753 and includes instructions for
// additional verification that `digest_nid` must match the value derived
// from `signature_algorithm_nid`. (See RFC 5753 section 2.1.1.) Note
// that the RFC permits the use of SHA-1 for ECDSA signatures, but we do
// not, as at the time of writing it had long been obsolete.
case NID_ecdsa_with_SHA224:
case NID_ecdsa_with_SHA256:
case NID_ecdsa_with_SHA384:
case NID_ecdsa_with_SHA512:
if (digest_nid != signature_digest_nid) {
return absl::InvalidArgumentError(absl::StrFormat(
R"(The digest algorithm does not match the value derived from the signature algorithm: digest_nid=%d, signature_digest_nid=%d)",
digest_nid, signature_digest_nid));
}
break;
default:
return OpenSslError(
absl::StatusCode::kUnimplemented,
absl::StrCat("Signature algorithm not implemented, NID: ",
signature_algorithm_nid));
}
const EVP_MD* signature_md = EVP_get_digestbynid(signature_digest_nid);
if (signature_md == nullptr) {
return OpenSslError(absl::StatusCode::kUnimplemented,
"Failed to get the signature EVP_MD object.");
}
bssl::ScopedEVP_MD_CTX md_context;
EVP_PKEY_CTX* pkey_ctx = nullptr;
if (EVP_DigestVerifyInit(md_context.get(), &pkey_ctx, signature_md,
nullptr /* engine */, key.get()) != 1) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestVerifyInit failed.");
}
if (signature_algorithm_nid == NID_rsassaPss) {
auto status = SetPssParameters(signer, certificate, digest_nid,
signature_md, pkey_ctx);
if (!status.ok()) {
return status;
}
}
if (CBS_len(signer.raw_signed_attributes.cbs_ptr()) > 0) {
auto status = VerifySignedAttributesHash(signer, contents, digest_nid);
if (!status.ok()) {
return status;
}
// The signature is computed after replacing the implicit tag.
// https://tools.ietf.org/html/rfc2315#section-9.3
uint8_t tag = 0x31; // SET
if (EVP_DigestVerifyUpdate(md_context.get(), &tag, 1) != 1 ||
EVP_DigestVerifyUpdate(
md_context.get(),
reinterpret_cast<const char*>(
CBS_data(signer.raw_signed_attributes.cbs_ptr()) + 1),
CBS_len(signer.raw_signed_attributes.cbs_ptr()) - 1) != 1) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestVerifyUpdate failed.");
}
} else {
// When there are no authenticated attributes the signature is computed on
// the contents.
for (const auto& chunk : contents) {
if (EVP_DigestVerifyUpdate(md_context.get(), CBS_data(chunk.cbs_ptr()),
CBS_len(chunk.cbs_ptr())) != 1) {
return OpenSslError(absl::StatusCode::kInternal,
"EVP_DigestVerifyUpdate failed.");
}
}
}
if (EVP_DigestVerifyFinal(md_context.get(),
const_cast<unsigned char*>(
CBS_data(signer.signature_value.cbs_ptr())),
CBS_len(signer.signature_value.cbs_ptr())) != 1) {
return OpenSslError(absl::StatusCode::kInvalidArgument,
"Invalid signature");
}
SignatureInfo signature_info;
signature_info.digest_algorithm_nid = digest_nid;
signature_info.signature_algorithm_id = signature_algorithm_nid;
return signature_info;
}
} // namespace credentio_cms