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