blob: fc2cc1823b659e504e1a295dcff84a11b6f31ba3 [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 "cose/simple_cms_parser.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.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/algorithms.h"
#include "crypto/default/cms/certificates.h"
#include "crypto/default/cms/cms_error_code.h"
#include "crypto/default/cms/cms_parser.h"
#include "crypto/default/cms/oids.h"
#include "crypto/default/cms/verify_signature.h"
#include "openssl/asn1.h"
#include "openssl/bytestring.h"
#include "openssl/mem.h"
#include "openssl/nid.h"
#include "openssl/obj.h"
#include "openssl/stack.h"
#include "openssl/x509.h"
#include "tsp/constants.h"
#include "tsp/parsed_timestamp_token.h"
namespace credentio {
namespace {
absl::StatusOr<std::string> GetDer(X509* x509) {
uint8_t* der_ptr = nullptr;
int der_len = i2d_X509(x509, &der_ptr);
bssl::UniquePtr<uint8_t> der(der_ptr);
if (der_len <= 0) {
return absl::InvalidArgumentError(
"cannot convert certificate within timestamp token to DER");
}
return std::string(reinterpret_cast<char*>(der_ptr), der_len);
}
} // namespace
SimpleParsedTimestampToken::SimpleParsedTimestampToken(absl::string_view data)
: data_(data) {}
absl::StatusOr<std::unique_ptr<SimpleParsedTimestampToken>>
SimpleParsedTimestampToken::Create(absl::string_view timestamp_token) {
auto token =
absl::WrapUnique(new SimpleParsedTimestampToken(timestamp_token));
std::string error_message;
credentio_cms::ErrorCode err = credentio_cms::ParseCms(
token->data_.data(), token->data_.size(), &token->cms_, &error_message);
if (err != credentio_cms::ErrorCode::OK) {
return absl::InvalidArgumentError(
absl::StrCat("could not parse TimeStampToken cms: ", error_message));
}
if (!credentio_cms::CompareOid(token->cms_.content_type.cbs(),
credentio_cms::kCtTstInfoOid,
sizeof(credentio_cms::kCtTstInfoOid))) {
return absl::InvalidArgumentError(
"TimeStampToken eContentType is not id-ct-TSTInfo");
}
return token;
}
absl::StatusOr<const credentio_cms::SignerInfo*>
SimpleParsedTimestampToken::GetSingleSignerInfo() const {
const std::vector<credentio_cms::SignerInfo>& signer_infos = cms_.signers;
if (signer_infos.size() != 1) {
return absl::InvalidArgumentError(
absl::StrFormat("wrong number of signer infos: timestamp has %d "
"signers; exactly 1 is required",
signer_infos.size()));
}
return &signer_infos[0];
}
absl::StatusOr<X509*> SimpleParsedTimestampToken::GetSignerCert(
const STACK_OF(X509) * certs,
const credentio_cms::SignerInfo& signer_info) const {
std::vector<X509*> tsa_certs =
credentio_cms::GetSignerCertificates(certs, signer_info);
if (tsa_certs.empty() || tsa_certs[0] == nullptr) {
return absl::UnauthenticatedError(
"timestamp does not contain TSA certificate");
}
return tsa_certs[0];
}
absl::Status SimpleParsedTimestampToken::VerifySignature() const {
ABSL_ASSIGN_OR_RETURN(auto signer_info, GetSingleSignerInfo());
bssl::UniquePtr<STACK_OF(X509)> certs(
credentio_cms::GetAllCertificates(cms_));
if (!certs) {
return absl::UnauthenticatedError("failed to parse certificates");
}
ABSL_ASSIGN_OR_RETURN(auto tsa_cert,
GetSignerCert(certs.get(), *signer_info));
return credentio_cms::VerifySignature(cms_, *signer_info, cms_.content,
*tsa_cert)
.status();
}
absl::StatusOr<HashAlgorithm>
SimpleParsedTimestampToken::GetMessageImprintHashAlgorithm() const {
ABSL_ASSIGN_OR_RETURN(auto signer_info, GetSingleSignerInfo());
return NidToHashAlgorithm(
OBJ_cbs2nid(signer_info->digest_algorithm.algorithm_oid.cbs_ptr()));
}
absl::StatusOr<std::vector<std::string>>
SimpleParsedTimestampToken::GetCertificateChain() const {
ABSL_ASSIGN_OR_RETURN(auto signer_info, GetSingleSignerInfo());
bssl::UniquePtr<STACK_OF(X509)> certs(
credentio_cms::GetAllCertificates(cms_));
if (!certs) {
return absl::UnauthenticatedError("failed to parse certificates");
}
ABSL_ASSIGN_OR_RETURN(auto tsa_cert,
GetSignerCert(certs.get(), *signer_info));
std::vector<std::string> chain;
chain.reserve(sk_X509_num(certs.get()) + 1);
ABSL_ASSIGN_OR_RETURN(auto tsa_der, GetDer(tsa_cert));
chain.push_back(std::move(tsa_der));
for (size_t i = 0; i < sk_X509_num(certs.get()); ++i) {
X509* x509 = sk_X509_value(certs.get(), i);
if (X509_cmp(x509, tsa_cert) != 0) {
ABSL_ASSIGN_OR_RETURN(auto der, GetDer(x509));
chain.push_back(std::move(der));
}
}
return chain;
}
absl::StatusOr<absl::string_view>
SimpleParsedTimestampToken::GetSigningCertificateV2Bytes() const {
ABSL_ASSIGN_OR_RETURN(auto signer_info, GetSingleSignerInfo());
const auto& attributes = signer_info->signed_attributes;
auto v2_attribute = absl::c_find_if(
attributes, [](const credentio_cms::Attribute& attribute) -> bool {
const CBS* cbs = &attribute.type.cbs();
bssl::UniquePtr<ASN1_OBJECT> oid(
ASN1_OBJECT_create(NID_undef, CBS_data(cbs), CBS_len(cbs),
/*sn=*/nullptr, /*ln=*/nullptr));
if (!oid) {
return false;
}
return OBJ_cmp(IdAaSigningCertificateV2(), oid.get()) == 0;
});
if (v2_attribute == attributes.end()) {
return absl::InvalidArgumentError(
"invalid signed attributes; TSA did not set the "
"\"SigningCertificateV2\" signed attribute");
}
if (v2_attribute->values.size() != 1) {
return absl::InvalidArgumentError(
absl::StrFormat("SigningCertificateV2 signed attribute contains the "
"wrong number of entries; got %d entries, expect 1",
v2_attribute->values.size()));
}
CBS signing_certificate_cbs = v2_attribute->values[0].cbs();
const char* data =
reinterpret_cast<const char*>(CBS_data(&signing_certificate_cbs));
size_t len = CBS_len(&signing_certificate_cbs);
return absl::string_view(data, len);
}
absl::StatusOr<absl::string_view> SimpleParsedTimestampToken::GetTstInfoBytes()
const {
if (cms_.content.size() != 1) {
return absl::InvalidArgumentError(
absl::StrFormat("TSTInfo content contains unexpected number of chunks; "
"got %d chunks, expect 1",
cms_.content.size()));
}
const CBS& cbs = cms_.content[0].cbs();
const char* data = reinterpret_cast<const char*>(CBS_data(&cbs));
size_t len = CBS_len(&cbs);
return absl::string_view(data, len);
}
absl::StatusOr<std::unique_ptr<ParsedTimestampToken>>
SimpleCmsParser::ParseTimestampToken(absl::string_view timestamp_token) const {
return SimpleParsedTimestampToken::Create(timestamp_token);
}
} // namespace credentio