| // 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 |