blob: 88aebac2f4f9098c141785ebcb31dc0408294c04 [file]
// Copyright 2024 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
//
// http://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 "tink/signature/internal/slh_dsa_verify_boringssl.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
// Every header in BoringSSL includes base.h, which in turn defines
// OPENSSL_IS_BORINGSSL. So we include this common header upfront here to
// "force" the definition of OPENSSL_IS_BORINGSSL in case BoringSSL is used.
#include "openssl/crypto.h"
#include "tink/internal/fips_utils.h" // IWYU pragma: keep
#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS)
#include "openssl/slhdsa.h"
#endif // defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS)
#include "tink/partial_key_access.h"
#include "tink/public_key_verify.h"
#include "tink/signature/internal/slh_dsa_parameter_set.h"
#include "tink/signature/slh_dsa_public_key.h"
namespace crypto {
namespace tink {
namespace internal {
namespace {
#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS)
// Public Key Verification using SLH-DSA-SHA2-128s implementation from
// BoringSSL.
namespace {
using VerifyFunc = int (*)(const uint8_t*, size_t, const uint8_t*,
const uint8_t*, size_t, const uint8_t*, size_t);
absl::StatusOr<VerifyFunc> GetVerifyFunc(
const SlhDsaParameterSet& parameter_set) {
if (parameter_set == SlhDsaParameterSet::Sha2_128s()) {
return SLHDSA_SHA2_128S_verify;
}
if (parameter_set == SlhDsaParameterSet::Shake_256f()) {
return SLHDSA_SHAKE_256F_verify;
}
return absl::Status(
absl::StatusCode::kInvalidArgument,
"SLH-DSA parameter combination is not supported by BoringSSL.");
}
absl::StatusOr<size_t> GetSignatureSize(
const SlhDsaParameterSet& parameter_set) {
if (parameter_set == SlhDsaParameterSet::Sha2_128s()) {
return SLHDSA_SHA2_128S_SIGNATURE_BYTES;
}
if (parameter_set == SlhDsaParameterSet::Shake_256f()) {
return SLHDSA_SHAKE_256F_SIGNATURE_BYTES;
}
return absl::Status(
absl::StatusCode::kInvalidArgument,
"SLH-DSA parameter combination is not supported by BoringSSL.");
}
} // namespace
class SlhDsaVerifyBoringSsl : public PublicKeyVerify {
public:
static constexpr crypto::tink::internal::FipsCompatibility kFipsStatus =
crypto::tink::internal::FipsCompatibility::kNotFips;
explicit SlhDsaVerifyBoringSsl(const SlhDsaPublicKey& public_key)
: public_key_(public_key) {}
~SlhDsaVerifyBoringSsl() override = default;
// Verifies that 'signature' is a digital signature for 'data'.
absl::Status Verify(absl::string_view signature,
absl::string_view data) const override {
absl::StatusOr<SlhDsaParameterSet> parameter_set =
GetSlhDsaParameterSet(public_key_.GetParameters());
if (!parameter_set.ok()) {
return parameter_set.status();
}
absl::StatusOr<VerifyFunc> verify_func = GetVerifyFunc(*parameter_set);
if (!verify_func.ok()) {
return verify_func.status();
}
absl::StatusOr<size_t> signature_size = GetSignatureSize(*parameter_set);
if (!signature_size.ok()) {
return signature_size.status();
}
if (signature.size() !=
*signature_size + public_key_.GetOutputPrefix().size()) {
return absl::Status(
absl::StatusCode::kInvalidArgument,
absl::StrFormat(
"Verification failed: invalid signature length; got "
"%d, expected %d",
signature.size(),
*signature_size + public_key_.GetOutputPrefix().size()));
}
if (!absl::StartsWith(signature, public_key_.GetOutputPrefix())) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"Verification failed: invalid output prefix");
}
if (1 !=
(*verify_func)(
reinterpret_cast<const uint8_t*>(
signature.data() + public_key_.GetOutputPrefix().size()),
*signature_size,
reinterpret_cast<const uint8_t*>(
public_key_.GetPublicKeyBytes(GetPartialKeyAccess()).data()),
reinterpret_cast<const uint8_t*>(data.data()), data.size(),
/* context = */ nullptr, /* context_len = */ 0)) {
return absl::Status(absl::StatusCode::kInvalidArgument,
"Signature is not valid");
}
return absl::OkStatus();
}
private:
SlhDsaPublicKey public_key_;
};
#endif
} // namespace
absl::StatusOr<std::unique_ptr<PublicKeyVerify>> NewSlhDsaVerifyBoringSsl(
const SlhDsaPublicKey& public_key) {
#if !defined(OPENSSL_IS_BORINGSSL) || defined(TINK_USE_ONLY_FIPS)
return absl::UnimplementedError(
"SLH-DSA is only supported in non-FIPS BoringSSL builds.");
#else
auto status = internal::CheckFipsCompatibility<SlhDsaVerifyBoringSsl>();
if (!status.ok()) {
return status;
}
absl::StatusOr<SlhDsaParameterSet> parameter_set =
GetSlhDsaParameterSet(public_key.GetParameters());
return {std::make_unique<SlhDsaVerifyBoringSsl>(std::move(public_key))};
#endif // OPENSSL_IS_BORINGSSL
}
} // namespace internal
} // namespace tink
} // namespace crypto