blob: 73a45b42dd1dc61ae53472d78a6bfd79e300fb62 [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 "cose/ocsp_verifier.h"
#include <string>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "absl/types/span.h"
#include "constants/labels.h"
#include "constants/status_codes.h"
#include "cose/validation_status_util.h"
#include "crypto/crypto_read_handler.h"
#include "proto/validation_status.pb.h"
namespace credentio {
void RecordSkippedOcspCheck(ValidationStatusSet* status_set) {
RecordStatus(status_set,
InformationalStatusCode::kSigningCredentialOcspSkipped,
{.url = kClaimSignatureLabel});
}
bool OcspVerifier::VerifyOcspResponses(
absl::Span<const std::string> ocsp_responses,
absl::Span<const std::string> trust_chain, absl::Time asserted_time,
ValidationStatusSet* status_set) const {
bool has_good_signing_cert = false;
// The manifest has an attested time provided by a valid signed
// time-stamp and `rVals` header contains at least one OCSP response.
for (int i = 0; i < ocsp_responses.size(); ++i) {
absl::string_view ocsp_value = ocsp_responses[i];
for (int j = 1; j < trust_chain.size(); ++j) {
absl::string_view certificate = trust_chain[j - 1];
// The issuer certificate is the next certificate in the chain.
absl::string_view issuer_certificate = trust_chain[j];
auto revocation_status = crypto_read_handler_->VerifyOcspResponse(
ocsp_value, certificate, issuer_certificate, asserted_time);
if (revocation_status.ok()) {
if (*revocation_status == OCSPRevocationStatus::kGood) {
// According to the C2PA spec, if at least one OCSP response is
// `good`, then the credential is not revoked.
if (j == 1) {
has_good_signing_cert = true;
}
break;
} else if (*revocation_status == OCSPRevocationStatus::kRevoked) {
// According to the C2PA spec, if at least one OCSP response is
// `revoked`, then the credential is revoked.
if (j == 1) {
RecordStatus(status_set,
FailureStatusCode::kSigningCredentialOcspRevoked,
{.url = kClaimSignatureLabel});
} else {
RecordStatus(status_set,
FailureStatusCode::kSigningCredentialUntrusted,
{.url = kClaimSignatureLabel});
}
return false;
}
} else {
if (revocation_status.status().code() ==
absl::StatusCode::kOutOfRange) {
// OCSP response is produced at a time outside of the
// corresponding certificate's validity period or the timestamp
// time (i.e., the verify time) falls outside of the validity
// window of the stapled OCSP response.
RecordStatus(
status_set,
InformationalStatusCode::kSigningCredentialOcspOutsideValidity,
{.url = kClaimSignatureLabel});
}
}
}
}
if (has_good_signing_cert) {
RecordStatus(status_set,
SuccessStatusCode::kSigningCredentialOcspNotRevoked,
{.url = kClaimSignatureLabel});
} else {
// None of the OCSP responses satisfied the conditions in
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#ocsp_stapled,
// or no OCSP responses were provided. The validator chooses not to perform
// online revocation checks so we record a `signingCredential.ocsp.skipped`
// informational code per
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#ocsp_online.
RecordSkippedOcspCheck(status_set);
}
return true;
}
} // namespace credentio