blob: d9c7bc072b03fbdfd5cc60ef407e688461524c26 [file]
// 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(&timestamp_resp_cbs,
reinterpret_cast<const uint8_t*>(timestamp_resp.data()),
timestamp_resp.length());
CBS seq;
if (!CBS_get_asn1(&timestamp_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