| // 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 "claim/local_signer.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/log/absl_log.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/string_view.h" | |
| #include "absl/time/clock.h" | |
| #include "absl/time/time.h" | |
| #include "async/async_context.h" | |
| #include "claim/signer.h" | |
| #include "cose/sig_structure.h" | |
| #include "cose/timestamp_creator.h" | |
| #include "crypto/crypto_write_handler.h" | |
| #include "crypto/hash.h" | |
| #include "utils/cbor_padding.h" | |
| namespace credentio { | |
| namespace { | |
| absl::StatusOr<std::string> FinalizeSignature( | |
| const absl::StatusOr<std::string>& timestamp, | |
| TimeStampingMode time_stamping_mode, uint64_t max_timestamp_byte_size, | |
| CoseSign1TaggedStructure* cose_sign1, uint64_t max_signature_byte_size, | |
| std::optional<size_t> pad_to_size) { | |
| if (!timestamp.ok() && time_stamping_mode == TimeStampingMode::kRequired) { | |
| return absl::Status(timestamp.status().code(), | |
| absl::StrCat("timestamping is required but failed: ", | |
| timestamp.status().message())); | |
| } | |
| if (timestamp.ok()) { | |
| if (timestamp.value().size() > max_timestamp_byte_size) { | |
| if (time_stamping_mode == TimeStampingMode::kRequired) { | |
| return absl::InternalError( | |
| absl::StrCat("Timestamp size ", timestamp.value().size(), | |
| " is larger than the maximum allowed size ", | |
| max_timestamp_byte_size)); | |
| } else { | |
| ABSL_LOG_EVERY_N_SEC(WARNING, 60) | |
| << "Ignoring timestamp because the timestamp size " | |
| << timestamp.value().size() | |
| << " is larger than the maximum allowed size " | |
| << max_timestamp_byte_size; | |
| } | |
| } else if (!timestamp.value().empty()) { | |
| cose_sign1->unprotected_header.sig_tst2 = | |
| TstContainer{.tst_tokens = {{.val = timestamp.value()}}}; | |
| } | |
| } | |
| if (pad_to_size.has_value()) { | |
| // This logic is necessary for remote signing fallback to local where | |
| // the template and final signatures must be padded to the same size. | |
| // e.g. template uses remote signing but final uses local signing. | |
| ABSL_RETURN_IF_ERROR( | |
| PadCoseSign1TaggedStructure(*cose_sign1, *pad_to_size)); | |
| } else { | |
| ABSL_RETURN_IF_ERROR(PadCoseSign1TaggedStructure( | |
| *cose_sign1, max_signature_byte_size, max_timestamp_byte_size)); | |
| } | |
| return EncodeCoseSign1TaggedStructure(*cose_sign1); | |
| } | |
| } // namespace | |
| void LocalClaimSigner::Sign( | |
| absl::string_view claim, Options options, | |
| absl::AnyInvocable<void(absl::StatusOr<SignResult>) &&> cb) const { | |
| AsyncResolver<SignResult> resolver(options.async_context, std::move(cb)); | |
| auto& signer = crypto_writer_.GetSigner(); | |
| auto protected_header_res = EncodeProtectedHeader( | |
| ProtectedHeader{.alg = signer.algorithm(), | |
| .certificate_chain = signer.certificate_chain()}); | |
| if (!protected_header_res.ok()) { | |
| resolver.Return(protected_header_res.status()); | |
| return; | |
| } | |
| std::string protected_header = *std::move(protected_header_res); | |
| Sig1Structure sig_structure{ | |
| .context = "Signature1", | |
| .body_protected = std::move(protected_header), | |
| .payload = std::string(claim), | |
| }; | |
| auto sign_res = signer.Sign(EncodeSig1Structure(sig_structure)); | |
| if (!sign_res.ok()) { | |
| resolver.Return(sign_res.status()); | |
| return; | |
| } | |
| if (sign_res->signature.size() > signer.max_signature_byte_size()) { | |
| resolver.Return(absl::InternalError( | |
| absl::StrCat("Signature size ", sign_res->signature.size(), | |
| " is larger than the maximum allowed size ", | |
| signer.max_signature_byte_size()))); | |
| return; | |
| } | |
| auto cose_sign1 = std::make_shared<CoseSign1TaggedStructure>(); | |
| cose_sign1->protected_header = sig_structure.body_protected; | |
| cose_sign1->signature = std::move(sign_res->signature); | |
| // Add OCSP responses. | |
| const std::vector<std::string>& ocsp_responses = signer.ocsp_responses(); | |
| if (!ocsp_responses.empty()) { | |
| cose_sign1->unprotected_header.ocsp_responses = ocsp_responses; | |
| } | |
| // Create timestamp and finalize signature. | |
| uint64_t max_signature_byte_size = signer.max_signature_byte_size(); | |
| uint64_t max_timestamp_byte_size = | |
| crypto_writer_.max_timestamp_byte_size(hasher_factory_.algorithm()); | |
| auto time_stamping_mode = options.time_stamping_mode; | |
| if (time_stamping_mode == TimeStampingMode::kOmittedWithPadding) { | |
| auto signature = FinalizeSignature( | |
| /*timestamp=*/"", time_stamping_mode, max_timestamp_byte_size, | |
| cose_sign1.get(), max_signature_byte_size, pad_to_size_); | |
| if (!signature.ok()) { | |
| resolver.Return(signature.status()); | |
| return; | |
| } | |
| SignResult sign_result{.signature = std::move(*signature)}; | |
| resolver.Return(std::move(sign_result)); | |
| return; | |
| } | |
| TimestampCreator::Options timestamp_creator_options{ | |
| .async_context = options.async_context, | |
| }; | |
| auto create_timestamp_cb = | |
| [resolver = std::move(resolver), max_timestamp_byte_size, cose_sign1, | |
| max_signature_byte_size, pad_to_size = pad_to_size_, time_stamping_mode]( | |
| absl::StatusOr<std::string> timestamp_result) mutable { | |
| auto signature = FinalizeSignature( | |
| timestamp_result, time_stamping_mode, max_timestamp_byte_size, | |
| cose_sign1.get(), max_signature_byte_size, pad_to_size); | |
| if (!signature.ok()) { | |
| resolver.Return(signature.status()); | |
| return; | |
| } | |
| SignResult sign_result{.signature = std::move(*signature)}; | |
| resolver.Return(std::move(sign_result)); | |
| }; | |
| timestamp_creator_.CreateTimestamp(*cose_sign1, | |
| std::move(timestamp_creator_options), | |
| std::move(create_timestamp_cb)); | |
| } | |
| } // namespace credentio |