| // 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 "tsp/timestamp_parsing.h" |
| |
| #include <cstdint> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/time.h" |
| #include "crypto/algorithms.h" |
| #include "crypto/cbs_utils.h" |
| #include "openssl/base.h" |
| #include "openssl/bytestring.h" |
| #include "openssl/obj.h" |
| #include "tsp/constants.h" |
| #include "tsp/status_codes.h" |
| |
| namespace credentio { |
| namespace { |
| |
| // See RFC 3161, section 2.4.2. |
| struct FailureInfo { |
| int value; |
| absl::string_view description; |
| }; |
| |
| constexpr FailureInfo kFailureInfo[] = { |
| {0, R"((badAlg) unrecognized or unsupported Algorithm Identifier)"}, |
| {2, R"((badRequest) transaction not permitted or supported)"}, |
| {5, R"((badDataFormat) the data submitted has the wrong format)"}, |
| {14, R"((timeNotAvailable) the TSA's time source is not available)"}, |
| {15, |
| R"((unacceptedPolicy) the requested TSA policy is not supported by the TSA)"}, |
| {16, |
| R"((unacceptedExtension) the requested extension is not supported by the TSA)"}, |
| {17, |
| R"((addInfoNotAvailable) the additional information requested could not be understood or is not available)"}, |
| {25, |
| R"((systemFailure) the request cannot be handled due to system failure)"}, |
| }; |
| |
| // Converts a CBS containing a FailureInfo bitstring to a human-readable string. |
| std::string FailureInfoString(const CBS& failure_info) { |
| std::vector<absl::string_view> elements; |
| for (const auto& info : kFailureInfo) { |
| if (CBS_asn1_bitstring_has_bit(&failure_info, info.value)) { |
| elements.push_back(info.description); |
| } |
| } |
| return absl::StrJoin(elements, "; "); |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<absl::string_view> ParseTimestampResp( |
| absl::string_view timestamp_resp) { |
| CBS timestamp_resp_cbs; |
| CBS_init(×tamp_resp_cbs, |
| reinterpret_cast<const uint8_t*>(timestamp_resp.data()), |
| timestamp_resp.length()); |
| CBS seq; |
| if (!CBS_get_asn1(×tamp_resp_cbs, &seq, CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError( |
| "cannot parse top-level DER SEQUENCE while parsing TimeStampResp"); |
| } |
| CBS pki_status_info; |
| if (!CBS_get_asn1(&seq, &pki_status_info, CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError("cannot parse TimeStampResp.status"); |
| } |
| |
| int64_t raw_status_code; |
| if (!CBS_get_asn1_int64(&pki_status_info, &raw_status_code)) { |
| return absl::InvalidArgumentError( |
| "cannot parse TimeStampResp.status.status"); |
| } |
| |
| ABSL_ASSIGN_OR_RETURN(TspPkiStatus status_code, |
| GetTspPkiStatus(raw_status_code)); |
| |
| if (!IsTspPkiStatusOk(status_code)) { |
| CBS status_string; |
| std::string status_string_joined; |
| if (CBS_peek_asn1_tag(&pki_status_info, CBS_ASN1_SEQUENCE) && |
| CBS_get_asn1(&pki_status_info, &status_string, CBS_ASN1_SEQUENCE)) { |
| CBS status_string_element; |
| std::vector<std::string> status_string_elements; |
| while (CBS_get_asn1(&status_string, &status_string_element, |
| CBS_ASN1_UTF8STRING)) { |
| status_string_elements.push_back(std::string( |
| reinterpret_cast<const char*>(CBS_data(&status_string_element)), |
| CBS_len(&status_string_element))); |
| } |
| status_string_joined = absl::StrJoin(status_string_elements, "; "); |
| } |
| |
| CBS failure_info; |
| std::string failure_info_string; |
| if (CBS_get_asn1(&pki_status_info, &failure_info, CBS_ASN1_BITSTRING)) { |
| failure_info_string = FailureInfoString(failure_info); |
| } |
| |
| std::string error_message = absl::StrCat( |
| "TimestampResp.status contains an error; status=", status_code); |
| if (!failure_info_string.empty()) { |
| absl::StrAppend(&error_message, "; ", failure_info_string); |
| } |
| if (!status_string_joined.empty()) { |
| absl::StrAppend(&error_message, "; ", status_string_joined); |
| } |
| return absl::InvalidArgumentError(error_message); |
| } |
| // We extract the entire TimeStampToken element (including the header |
| // bytes) because the CMS library expects a complete DER construct. |
| CBS ts_token; |
| if (!CBS_get_asn1_element(&seq, &ts_token, CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError( |
| "cannot extract TimeStampToken from TimeStampResp"); |
| } |
| return absl::string_view(reinterpret_cast<const char*>(ts_token.data), |
| ts_token.len); |
| } |
| |
| absl::Status ParseTstInfo(CBS cbs, absl::Time* time, |
| std::string* message_imprint_hash, |
| HashAlgorithm* hash_algorithm, std::string* nonce) { |
| CBS sequence_cbs; |
| if (!CBS_get_asn1(&cbs, /*out=*/&sequence_cbs, CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError("could not read top-level sequence"); |
| } |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/nullptr, CBS_ASN1_INTEGER)) { |
| return absl::InvalidArgumentError("could not skip `version`"); |
| } |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/nullptr, CBS_ASN1_OBJECT)) { |
| return absl::InvalidArgumentError("could not skip `policy`"); |
| } |
| |
| // Format of MessageImprint defined by RFC 3161 section 2.4.1 |
| CBS messageimprint_cbs; |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/&messageimprint_cbs, |
| CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError("could not read `messageImprint`"); |
| } |
| |
| // Format of AlgorithmIdentifier defined by RFC 5280 section 4.1.1.2 |
| CBS algorithm_identifier_cbs; |
| if (!CBS_get_asn1(&messageimprint_cbs, /*out=*/&algorithm_identifier_cbs, |
| CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError( |
| "could not read `messageImprint.hashAlgorithm`"); |
| } |
| |
| CBS oid_cbs; |
| if (!CBS_get_asn1(&algorithm_identifier_cbs, /*out=*/&oid_cbs, |
| CBS_ASN1_OBJECT)) { |
| return absl::InvalidArgumentError( |
| "could not read `messageImprint.hashAlgorithm.algorithm`"); |
| } |
| |
| ABSL_ASSIGN_OR_RETURN( |
| *hash_algorithm, NidToHashAlgorithm(OBJ_cbs2nid(&oid_cbs)), |
| _.SetPrepend() << "unsupported message imprint hash algorithm: "); |
| |
| CBS hash_cbs; |
| if (!CBS_get_asn1(&messageimprint_cbs, &hash_cbs, CBS_ASN1_OCTETSTRING)) { |
| return absl::InvalidArgumentError( |
| "could not read `messageImprint.hashedMessage`"); |
| } |
| |
| *message_imprint_hash = ToStringView(hash_cbs); |
| |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/nullptr, CBS_ASN1_INTEGER)) { |
| return absl::InvalidArgumentError("could not skip `serialNumber`"); |
| } |
| |
| // Note that we can't use the OpenSSL function to decode the time value |
| // because it enforces the limits imposed in RFC 5280, which is more |
| // restrictive than RFC 3161. |
| CBS gentime_cbs; |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/&gentime_cbs, |
| CBS_ASN1_GENERALIZEDTIME)) { |
| return absl::InvalidArgumentError("could not read `genTime`"); |
| } |
| absl::string_view gentime_view( |
| reinterpret_cast<const char*>(gentime_cbs.data), gentime_cbs.len); |
| if (!absl::EndsWith(gentime_view, "Z")) { |
| return absl::InvalidArgumentError( |
| "`genTime` value is not specified in UTC (needs \"Z\" suffix)"); |
| } |
| if (gentime_view.find_first_of('Z') != gentime_view.length() - 1) { |
| return absl::InvalidArgumentError( |
| "`genTime` is not a valid timestamp (contains \"Z\" that is not at " |
| "end)"); |
| } |
| std::string error; |
| if (!absl::ParseTime(kGenTimeFormat, gentime_view, time, &error)) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("could not parse `genTime` value: ", error)); |
| } |
| |
| // Skip over `accuracy` if exists. |
| if (CBS_peek_asn1_tag(&sequence_cbs, CBS_ASN1_SEQUENCE)) { |
| if (!CBS_get_asn1(&sequence_cbs, /*out=*/nullptr, CBS_ASN1_SEQUENCE)) { |
| return absl::InvalidArgumentError("could not skip `accuracy`"); |
| } |
| } |
| // Skip over `ordering` if exists. |
| if (CBS_peek_asn1_tag(&sequence_cbs, CBS_ASN1_BOOLEAN)) { |
| int ordering; |
| if (!CBS_get_asn1_bool(&sequence_cbs, &ordering)) { |
| return absl::InvalidArgumentError("could not skip `ordering`"); |
| } |
| } |
| |
| // Retrieve `nonce` if exists. |
| if (CBS_peek_asn1_tag(&sequence_cbs, CBS_ASN1_INTEGER)) { |
| CBS nonce_cbs; |
| if (CBS_get_asn1_element(&sequence_cbs, &nonce_cbs, CBS_ASN1_INTEGER)) { |
| *nonce = std::string(reinterpret_cast<const char*>(CBS_data(&nonce_cbs)), |
| CBS_len(&nonce_cbs)); |
| } else { |
| return absl::InvalidArgumentError("could not read `nonce`"); |
| } |
| } else { |
| nonce->clear(); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| } // namespace credentio |