Add SlhDsaPrivateKey::CreateFromSeed method. PiperOrigin-RevId: 959035397 Change-Id: I96fa98f6d0dc52263f7967b87e397a0c777fa70f
diff --git a/tink/signature/BUILD.bazel b/tink/signature/BUILD.bazel index fc937b2..8603097 100644 --- a/tink/signature/BUILD.bazel +++ b/tink/signature/BUILD.bazel
@@ -1002,10 +1002,13 @@ "//tink:key", "//tink:partial_key_access_token", "//tink:restricted_data", + "//tink:secret_data", "//tink/internal:call_with_core_dump_protection", "//tink/internal:dfsan_forwarders", "//tink/internal:fips_utils", + "//tink/internal:secret_buffer", "//tink/signature/internal:slh_dsa_parameter_set", + "//tink/util:secret_data", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings:string_view", @@ -2010,8 +2013,13 @@ "//tink:insecure_secret_key_access", "//tink:key", "//tink:partial_key_access", + "//tink:public_key_sign", + "//tink:public_key_verify", "//tink:restricted_data", "//tink/internal:fips_utils", + "//tink/signature/internal:slh_dsa_parameter_set", + "//tink/signature/internal:slh_dsa_sign_boringssl", + "//tink/signature/internal:slh_dsa_verify_boringssl", "//tink/subtle:random", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:status_matchers",
diff --git a/tink/signature/CMakeLists.txt b/tink/signature/CMakeLists.txt index 78ff21d..08a18e9 100644 --- a/tink/signature/CMakeLists.txt +++ b/tink/signature/CMakeLists.txt
@@ -1896,10 +1896,13 @@ tink::core::key tink::core::partial_key_access_token tink::core::restricted_data + tink::core::secret_data tink::internal::call_with_core_dump_protection tink::internal::dfsan_forwarders tink::internal::fips_utils + tink::internal::secret_buffer tink::signature::internal::slh_dsa_parameter_set + tink::util::secret_data ) tink_cc_test( @@ -1919,8 +1922,13 @@ tink::core::insecure_secret_key_access tink::core::key tink::core::partial_key_access + tink::core::public_key_sign + tink::core::public_key_verify tink::core::restricted_data tink::internal::fips_utils + tink::signature::internal::slh_dsa_parameter_set + tink::signature::internal::slh_dsa_sign_boringssl + tink::signature::internal::slh_dsa_verify_boringssl tink::subtle::random TAGS exclude_if_openssl
diff --git a/tink/signature/internal/slh_dsa_parameter_set.h b/tink/signature/internal/slh_dsa_parameter_set.h index 9d90404..00b3d64 100644 --- a/tink/signature/internal/slh_dsa_parameter_set.h +++ b/tink/signature/internal/slh_dsa_parameter_set.h
@@ -30,6 +30,9 @@ static SlhDsaParameterSet Shake_256f(); SlhDsaParameters::HashType GetHashType() const { return hash_type_; } + int GetPrivateSeedSizeInBytes() const { + return private_key_size_in_bytes_ * 3 / 4; + } int GetPrivateKeySizeInBytes() const { return private_key_size_in_bytes_; } int GetPublicKeySizeInBytes() const { return private_key_size_in_bytes_ / 2; } SlhDsaParameters::SignatureType GetSignatureType() const {
diff --git a/tink/signature/internal/slh_dsa_parameter_set_test.cc b/tink/signature/internal/slh_dsa_parameter_set_test.cc index a9876c2..8e8c59c 100644 --- a/tink/signature/internal/slh_dsa_parameter_set_test.cc +++ b/tink/signature/internal/slh_dsa_parameter_set_test.cc
@@ -35,6 +35,7 @@ struct TestCase { SlhDsaParameterSet parameter_set; SlhDsaParameters::HashType hash_type; + int private_seed_size_in_bytes; int private_key_size_in_bytes; int public_key_size_in_bytes; SlhDsaParameters::SignatureType signature_type; @@ -47,16 +48,18 @@ ::testing::Values( // SLH-DSA-SHA2-128s TestCase{SlhDsaParameterSet::Sha2_128s(), - SlhDsaParameters::HashType::kSha2, 64, 32, + SlhDsaParameters::HashType::kSha2, 48, 64, 32, SlhDsaParameters::SignatureType::kSmallSignature}, // SLH-DSA-SHAKE-256f TestCase{SlhDsaParameterSet::Shake_256f(), - SlhDsaParameters::HashType::kShake, 128, 64, + SlhDsaParameters::HashType::kShake, 96, 128, 64, SlhDsaParameters::SignatureType::kFastSigning})); TEST_P(SlhDsaParameterSetTest, Getters) { const TestCase& test_case = GetParam(); EXPECT_THAT(test_case.parameter_set.GetHashType(), Eq(test_case.hash_type)); + EXPECT_THAT(test_case.parameter_set.GetPrivateSeedSizeInBytes(), + Eq(test_case.private_seed_size_in_bytes)); EXPECT_THAT(test_case.parameter_set.GetPrivateKeySizeInBytes(), Eq(test_case.private_key_size_in_bytes)); EXPECT_THAT(test_case.parameter_set.GetPublicKeySizeInBytes(),
diff --git a/tink/signature/internal/slh_dsa_sign_boringssl.cc b/tink/signature/internal/slh_dsa_sign_boringssl.cc index 7a2ff74..0e6f81b 100644 --- a/tink/signature/internal/slh_dsa_sign_boringssl.cc +++ b/tink/signature/internal/slh_dsa_sign_boringssl.cc
@@ -29,13 +29,13 @@ // 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" -#ifdef OPENSSL_IS_BORINGSSL +#include "tink/internal/fips_utils.h" // IWYU pragma: keep +#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) #include "openssl/slhdsa.h" -#endif // OPENSSL_IS_BORINGSSL +#endif #include "tink/insecure_secret_key_access.h" #include "tink/internal/call_with_core_dump_protection.h" #include "tink/internal/dfsan_forwarders.h" -#include "tink/internal/fips_utils.h" #include "tink/partial_key_access.h" #include "tink/public_key_sign.h" #include "tink/signature/internal/slh_dsa_parameter_set.h" @@ -48,7 +48,7 @@ namespace { -#ifdef OPENSSL_IS_BORINGSSL +#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) using SignFunc = int (*)(uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, size_t); @@ -133,15 +133,15 @@ return signature; } -#endif // OPENSSL_IS_BORINGSSL +#endif } // namespace absl::StatusOr<std::unique_ptr<PublicKeySign>> NewSlhDsaSignBoringSsl( const SlhDsaPrivateKey& private_key) { -#ifndef OPENSSL_IS_BORINGSSL +#if !defined(OPENSSL_IS_BORINGSSL) || defined(TINK_USE_ONLY_FIPS) return absl::UnimplementedError( - "SLH-DSA is only supported in BoringSSL builds."); + "SLH-DSA is only supported in non-FIPS BoringSSL builds."); #else auto status = internal::CheckFipsCompatibility<SlhDsaSignBoringSsl>(); if (!status.ok()) { @@ -154,7 +154,7 @@ } return {std::make_unique<SlhDsaSignBoringSsl>(std::move(private_key))}; -#endif // OPENSSL_IS_BORINGSSL +#endif } } // namespace internal
diff --git a/tink/signature/internal/slh_dsa_verify_boringssl.cc b/tink/signature/internal/slh_dsa_verify_boringssl.cc index eae3783..88aebac 100644 --- a/tink/signature/internal/slh_dsa_verify_boringssl.cc +++ b/tink/signature/internal/slh_dsa_verify_boringssl.cc
@@ -30,10 +30,10 @@ // 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" -#ifdef OPENSSL_IS_BORINGSSL +#include "tink/internal/fips_utils.h" // IWYU pragma: keep +#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) #include "openssl/slhdsa.h" -#endif // OPENSSL_IS_BORINGSSL -#include "tink/internal/fips_utils.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" @@ -45,7 +45,7 @@ namespace { -#ifdef OPENSSL_IS_BORINGSSL +#if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) // Public Key Verification using SLH-DSA-SHA2-128s implementation from // BoringSSL. namespace { @@ -143,15 +143,15 @@ private: SlhDsaPublicKey public_key_; }; -#endif // OPENSSL_IS_BORINGSSL +#endif } // namespace absl::StatusOr<std::unique_ptr<PublicKeyVerify>> NewSlhDsaVerifyBoringSsl( const SlhDsaPublicKey& public_key) { -#ifndef OPENSSL_IS_BORINGSSL +#if !defined(OPENSSL_IS_BORINGSSL) || defined(TINK_USE_ONLY_FIPS) return absl::UnimplementedError( - "SLH-DSA is only supported in BoringSSL builds."); + "SLH-DSA is only supported in non-FIPS BoringSSL builds."); #else auto status = internal::CheckFipsCompatibility<SlhDsaVerifyBoringSsl>(); if (!status.ok()) {
diff --git a/tink/signature/slh_dsa_private_key.cc b/tink/signature/slh_dsa_private_key.cc index 4052a1f..9efc627 100644 --- a/tink/signature/slh_dsa_private_key.cc +++ b/tink/signature/slh_dsa_private_key.cc
@@ -28,6 +28,10 @@ // "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 +#include "tink/internal/secret_buffer.h" +#include "tink/secret_data.h" +#include "tink/signature/slh_dsa_parameters.h" +#include "tink/util/secret_data.h" #if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) #include "openssl/mem.h" #include "openssl/slhdsa.h" @@ -50,15 +54,41 @@ namespace { #if defined(OPENSSL_IS_BORINGSSL) && !defined(TINK_USE_ONLY_FIPS) -using PublicFromPrivateFunc = void (*)(uint8_t*, const uint8_t*); - -absl::StatusOr<PublicFromPrivateFunc> GetPublicFromPrivateFunc( - const internal::SlhDsaParameterSet& parameter_set) { +absl::Status GetPublicFromPrivate( + const internal::SlhDsaParameterSet& parameter_set, + std::string& public_key_bytes, const SecretData& private_key_bytes) { if (parameter_set == internal::SlhDsaParameterSet::Sha2_128s()) { - return SLHDSA_SHA2_128S_public_from_private; + SLHDSA_SHA2_128S_public_from_private( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + private_key_bytes.data()); + return absl::OkStatus(); } if (parameter_set == internal::SlhDsaParameterSet::Shake_256f()) { - return SLHDSA_SHAKE_256F_public_from_private; + SLHDSA_SHAKE_256F_public_from_private( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + private_key_bytes.data()); + return absl::OkStatus(); + } + return absl::Status( + absl::StatusCode::kInvalidArgument, + "SLH-DSA parameter combination is not supported by BoringSSL."); +} + +absl::Status GetKeyPairFromSeed( + const internal::SlhDsaParameterSet& parameter_set, + std::string& public_key_bytes, internal::SecretBuffer& private_key_bytes, + const SecretData& private_seed_bytes) { + if (parameter_set == internal::SlhDsaParameterSet::Sha2_128s()) { + SLHDSA_SHA2_128S_generate_key_from_seed( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + private_key_bytes.data(), private_seed_bytes.data()); + return absl::OkStatus(); + } + if (parameter_set == internal::SlhDsaParameterSet::Shake_256f()) { + SLHDSA_SHAKE_256F_generate_key_from_seed( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + private_key_bytes.data(), private_seed_bytes.data()); + return absl::OkStatus(); } return absl::Status( absl::StatusCode::kInvalidArgument, @@ -97,29 +127,23 @@ int public_key_size = parameter_set->GetPublicKeySizeInBytes(); - absl::StatusOr<PublicFromPrivateFunc> public_from_private_func = - GetPublicFromPrivateFunc(*parameter_set); - if (!public_from_private_func.ok()) { - return public_from_private_func.status(); - } - PublicFromPrivateFunc public_from_private_function = - *public_from_private_func; - // Confirm that the private key and public key are a valid SLH-DSA key pair. std::string public_key_bytes_regen; public_key_bytes_regen.resize(public_key_size); + absl::Status status = absl::OkStatus(); internal::CallWithCoreDumpProtection([&]() { internal::ScopedAssumeRegionCoreDumpSafe scope(&public_key_bytes_regen[0], public_key_size); - public_from_private_function( - reinterpret_cast<uint8_t*>(&public_key_bytes_regen[0]), - reinterpret_cast<const uint8_t*>( - private_key_bytes.GetSecret(InsecureSecretKeyAccess::Get()) - .data())); + status = GetPublicFromPrivate( + *parameter_set, public_key_bytes_regen, + private_key_bytes.Get(InsecureSecretKeyAccess::Get())); internal::DfsanClearLabel(&public_key_bytes_regen[0], public_key_size); }); + if (!status.ok()) { + return status; + } absl::string_view expected_public_key_bytes = public_key.GetPublicKeyBytes(token); @@ -135,8 +159,7 @@ } absl::StatusOr<SlhDsaPrivateKey> SlhDsaPrivateKey::Create( - const SlhDsaParameters& parameters, - const RestrictedData& private_key_bytes, + const SlhDsaParameters& parameters, const RestrictedData& private_key_bytes, absl::optional<int> id_requirement, PartialKeyAccessToken token) { #if !defined(OPENSSL_IS_BORINGSSL) || defined(TINK_USE_ONLY_FIPS) return absl::UnimplementedError( @@ -163,28 +186,22 @@ int public_key_size = parameter_set->GetPublicKeySizeInBytes(); - absl::StatusOr<PublicFromPrivateFunc> public_from_private_func = - GetPublicFromPrivateFunc(*parameter_set); - if (!public_from_private_func.ok()) { - return public_from_private_func.status(); - } - PublicFromPrivateFunc public_from_private_function = - *public_from_private_func; - std::string public_key_bytes; public_key_bytes.resize(public_key_size); + absl::Status status = absl::OkStatus(); internal::CallWithCoreDumpProtection([&]() { internal::ScopedAssumeRegionCoreDumpSafe scope(&public_key_bytes[0], public_key_size); - public_from_private_function( - reinterpret_cast<uint8_t*>(&public_key_bytes[0]), - reinterpret_cast<const uint8_t*>( - private_key_bytes.GetSecret(InsecureSecretKeyAccess::Get()) - .data())); + absl::Status status = GetPublicFromPrivate( + *parameter_set, public_key_bytes, + private_key_bytes.Get(InsecureSecretKeyAccess::Get())); internal::DfsanClearLabel(&public_key_bytes[0], public_key_size); }); + if (!status.ok()) { + return status; + } absl::StatusOr<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( parameters, public_key_bytes, id_requirement, token); @@ -196,6 +213,67 @@ #endif } +absl::StatusOr<SlhDsaPrivateKey> SlhDsaPrivateKey::CreateFromSeed( + const SlhDsaParameters& parameters, + const RestrictedData& private_seed_bytes, + absl::optional<int> id_requirement, PartialKeyAccessToken token) { +#if !defined(OPENSSL_IS_BORINGSSL) || defined(TINK_USE_ONLY_FIPS) + return absl::UnimplementedError( + "SLH-DSA is only supported in non-FIPS BoringSSL builds."); +#else + // Only 48-byte, 72-byte and 96-byte private keys are supported. + if (private_seed_bytes.size() != 48 && private_seed_bytes.size() != 72 && + private_seed_bytes.size() != 96) { + return absl::Status( + absl::StatusCode::kInvalidArgument, + "SLH-DSA private seed length must be 48, 72, or 96 bytes."); + } + + absl::StatusOr<internal::SlhDsaParameterSet> parameter_set = + internal::GetSlhDsaParameterSet(parameters); + if (!parameter_set.ok()) { + return parameter_set.status(); + } + + if (parameter_set->GetPrivateSeedSizeInBytes() != private_seed_bytes.size()) { + return absl::Status(absl::StatusCode::kInvalidArgument, + "Private key size does not match parameters"); + } + + int public_key_size = parameter_set->GetPublicKeySizeInBytes(); + int private_key_size = parameter_set->GetPrivateKeySizeInBytes(); + + std::string public_key_bytes; + public_key_bytes.resize(public_key_size); + internal::SecretBuffer private_key_bytes(private_key_size); + + absl::Status status = absl::OkStatus(); + internal::CallWithCoreDumpProtection([&]() { + internal::ScopedAssumeRegionCoreDumpSafe scope(&public_key_bytes[0], + public_key_size); + + status = GetKeyPairFromSeed( + *parameter_set, public_key_bytes, private_key_bytes, + private_seed_bytes.Get(InsecureSecretKeyAccess::Get())); + internal::DfsanClearLabel(&public_key_bytes[0], public_key_size); + }); + if (!status.ok()) { + return status; + } + + absl::StatusOr<SlhDsaPublicKey> public_key = SlhDsaPublicKey::Create( + parameters, public_key_bytes, id_requirement, token); + if (!public_key.ok()) { + return public_key.status(); + } + + return SlhDsaPrivateKey( + *public_key, + RestrictedData(util::internal::AsSecretData(private_key_bytes), + InsecureSecretKeyAccess::Get())); +#endif +} + bool SlhDsaPrivateKey::operator==(const Key& other) const { const SlhDsaPrivateKey* that = dynamic_cast<const SlhDsaPrivateKey*>(&other); if (that == nullptr) {
diff --git a/tink/signature/slh_dsa_private_key.h b/tink/signature/slh_dsa_private_key.h index dd64a88..259fdbb 100644 --- a/tink/signature/slh_dsa_private_key.h +++ b/tink/signature/slh_dsa_private_key.h
@@ -52,11 +52,24 @@ // `parameters`. // // This function unconditionally returns an error in non-BoringSSL builds. + // + // Warning: This function assumes that `private_key_bytes` is a valid private + // key. In particular, it cannot be filled with random bytes. If you need to + // create a private key from a random seed, use `CreateFromSeed` instead. static absl::StatusOr<SlhDsaPrivateKey> Create( const SlhDsaParameters& parameters, const RestrictedData& private_key_bytes, absl::optional<int> id_requirement, PartialKeyAccessToken token); + // Creates a new SLH-DSA private key from `private_seed_bytes` and + // `parameters`. + // + // This function unconditionally returns an error in non-BoringSSL builds. + static absl::StatusOr<SlhDsaPrivateKey> CreateFromSeed( + const SlhDsaParameters& parameters, + const RestrictedData& private_seed_bytes, + absl::optional<int> id_requirement, PartialKeyAccessToken token); + const RestrictedData& GetPrivateKeyBytes(PartialKeyAccessToken token) const { return private_key_bytes_; }
diff --git a/tink/signature/slh_dsa_private_key_test.cc b/tink/signature/slh_dsa_private_key_test.cc index 600d450..f677666 100644 --- a/tink/signature/slh_dsa_private_key_test.cc +++ b/tink/signature/slh_dsa_private_key_test.cc
@@ -34,7 +34,12 @@ #include "tink/insecure_secret_key_access.h" #include "tink/key.h" #include "tink/partial_key_access.h" +#include "tink/public_key_sign.h" +#include "tink/public_key_verify.h" #include "tink/restricted_data.h" +#include "tink/signature/internal/slh_dsa_parameter_set.h" +#include "tink/signature/internal/slh_dsa_sign_boringssl.h" +#include "tink/signature/internal/slh_dsa_verify_boringssl.h" #include "tink/signature/slh_dsa_parameters.h" #include "tink/signature/slh_dsa_public_key.h" #include "tink/subtle/random.h" @@ -95,6 +100,40 @@ #endif } +absl::StatusOr<KeyPair> GenerateKeyPairFromSeed( + SlhDsaParameters::HashType hash_type, RestrictedData seed, + int private_key_size_in_bytes, int public_key_size_in_bytes) { +#ifdef TINK_USE_ONLY_FIPS + return absl::UnimplementedError( + "SLH-DSA is only supported in non-FIPS BoringSSL builds."); +#else + std::string public_key_bytes; + public_key_bytes.resize(public_key_size_in_bytes); + std::string private_key_bytes; + private_key_bytes.resize(private_key_size_in_bytes); + + if (hash_type == SlhDsaParameters::HashType::kSha2) { + SLHDSA_SHA2_128S_generate_key_from_seed( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + reinterpret_cast<uint8_t*>(&private_key_bytes[0]), + reinterpret_cast<const uint8_t*>( + seed.GetSecret(InsecureSecretKeyAccess::Get()).data())); + } else { + SLHDSA_SHAKE_256F_generate_key_from_seed( + reinterpret_cast<uint8_t*>(&public_key_bytes[0]), + reinterpret_cast<uint8_t*>(&private_key_bytes[0]), + reinterpret_cast<const uint8_t*>( + seed.GetSecret(InsecureSecretKeyAccess::Get()).data())); + } + + RestrictedData restricted_private_key_bytes = + RestrictedData(private_key_bytes, InsecureSecretKeyAccess::Get()); + + KeyPair key_pair = {public_key_bytes, restricted_private_key_bytes}; + return key_pair; +#endif +} + using SlhDsaPrivateKeyTest = TestWithParam<TestCase>; static constexpr int kSlhDsa128sPrivateKeyBytes = 64; @@ -146,6 +185,28 @@ HasSubstr( "SLH-DSA is only supported in non-FIPS BoringSSL builds."))); } + +TEST_P(SlhDsaPrivateKeyTest, CreateFromSeedFipsFails) { + TestCase test_case = GetParam(); + + absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + test_case.hash_type, test_case.private_key_size_in_bytes, + test_case.signature_type, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + absl::StatusOr<internal::SlhDsaParameterSet> parameter_set = + internal::GetSlhDsaParameterSet(*parameters); + ASSERT_THAT(parameter_set, IsOk()); + + RestrictedData seed(parameter_set->GetPrivateSeedSizeInBytes()); + + EXPECT_THAT( + SlhDsaPrivateKey::CreateFromSeed( + *parameters, seed, test_case.id_requirement, GetPartialKeyAccess()), + StatusIs(absl::StatusCode::kUnimplemented, + HasSubstr("SLH-DSA is only supported in non-FIPS " + "BoringSSL builds."))); +} #else TEST_P(SlhDsaPrivateKeyTest, CreateSucceeds) { TestCase test_case = GetParam(); @@ -208,6 +269,137 @@ Eq(key_pair->private_key_bytes)); } +TEST_P(SlhDsaPrivateKeyTest, CreateFromSeedSucceeds) { + TestCase test_case = GetParam(); + + absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + test_case.hash_type, test_case.private_key_size_in_bytes, + test_case.signature_type, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + absl::StatusOr<internal::SlhDsaParameterSet> parameter_set = + internal::GetSlhDsaParameterSet(*parameters); + ASSERT_THAT(parameter_set, IsOk()); + + RestrictedData seed(parameter_set->GetPrivateSeedSizeInBytes()); + + absl::StatusOr<KeyPair> expected_key_pair = GenerateKeyPairFromSeed( + test_case.hash_type, seed, test_case.private_key_size_in_bytes, + test_case.public_key_size_in_bytes); + ASSERT_THAT(expected_key_pair, IsOk()); + + absl::StatusOr<SlhDsaPublicKey> expected_public_key = + SlhDsaPublicKey::Create(*parameters, expected_key_pair->public_key_bytes, + test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(expected_public_key, IsOk()); + + absl::StatusOr<SlhDsaPrivateKey> private_key = + SlhDsaPrivateKey::CreateFromSeed( + *parameters, seed, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + EXPECT_THAT(private_key->GetParameters(), Eq(*parameters)); + EXPECT_THAT(private_key->GetIdRequirement(), Eq(test_case.id_requirement)); + EXPECT_THAT(private_key->GetPublicKey(), Eq(*expected_public_key)); + EXPECT_THAT(private_key->GetOutputPrefix(), Eq(test_case.output_prefix)); + EXPECT_THAT(private_key->GetPrivateKeyBytes(GetPartialKeyAccess()), + Eq(expected_key_pair->private_key_bytes)); +} + +TEST_P(SlhDsaPrivateKeyTest, CreateFromSeedGeneratesConsistentKeyPair) { + TestCase test_case = GetParam(); + + absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + test_case.hash_type, test_case.private_key_size_in_bytes, + test_case.signature_type, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + absl::StatusOr<internal::SlhDsaParameterSet> parameter_set = + internal::GetSlhDsaParameterSet(*parameters); + ASSERT_THAT(parameter_set, IsOk()); + + RestrictedData seed(parameter_set->GetPrivateSeedSizeInBytes()); + + absl::StatusOr<SlhDsaPrivateKey> private_key = + SlhDsaPrivateKey::CreateFromSeed( + *parameters, seed, test_case.id_requirement, GetPartialKeyAccess()); + ASSERT_THAT(private_key, IsOk()); + + absl::StatusOr<std::unique_ptr<PublicKeySign> > signer = + internal::NewSlhDsaSignBoringSsl(*private_key); + ASSERT_THAT(signer, IsOk()); + + absl::StatusOr<std::unique_ptr<PublicKeyVerify> > verifier = + internal::NewSlhDsaVerifyBoringSsl(private_key->GetPublicKey()); + ASSERT_THAT(verifier, IsOk()); + + std::string message = "Self-verify test message"; + absl::StatusOr<std::string> signature = (*signer)->Sign(message); + ASSERT_THAT(signature, IsOk()); + + EXPECT_THAT((*verifier)->Verify(*signature, message), IsOk()); +} + +TEST(SlhDsaPrivateKeyTest, CreateFromSeedWithInvalidSeedLengthFails) { + absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + SlhDsaParameters::HashType::kSha2, + /*private_key_size_in_bytes=*/SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES, + SlhDsaParameters::SignatureType::kSmallSignature, + SlhDsaParameters::Variant::kTink); + ASSERT_THAT(parameters, IsOk()); + + // General invalid seed length (47 bytes, not 48, 72, or 96 bytes) + RestrictedData invalid_seed(47); + EXPECT_THAT( + SlhDsaPrivateKey::CreateFromSeed(*parameters, invalid_seed, + /*id_requirement=*/123, + GetPartialKeyAccess()) + .status(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr( + "SLH-DSA private seed length must be 48, 72, or 96 bytes."))); + + // Mismatched seed size (96 bytes, but parameters expect 48 bytes) + RestrictedData mismatched_seed(96); + EXPECT_THAT( + SlhDsaPrivateKey::CreateFromSeed(*parameters, mismatched_seed, + /*id_requirement=*/123, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Private key size does not match parameters"))); +} + +TEST_P(SlhDsaPrivateKeyTest, CreateFromSeedWithMismatchedIdRequirementFails) { + TestCase test_case = GetParam(); + + absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( + test_case.hash_type, test_case.private_key_size_in_bytes, + test_case.signature_type, test_case.variant); + ASSERT_THAT(parameters, IsOk()); + + absl::StatusOr<internal::SlhDsaParameterSet> parameter_set = + internal::GetSlhDsaParameterSet(*parameters); + ASSERT_THAT(parameter_set, IsOk()); + + RestrictedData seed(parameter_set->GetPrivateSeedSizeInBytes()); + + if (parameters->HasIdRequirement()) { + EXPECT_THAT(SlhDsaPrivateKey::CreateFromSeed( + *parameters, seed, + /*id_requirement=*/std::nullopt, GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + } else { + EXPECT_THAT(SlhDsaPrivateKey::CreateFromSeed(*parameters, seed, + /*id_requirement=*/123, + GetPartialKeyAccess()) + .status(), + StatusIs(absl::StatusCode::kInvalidArgument)); + } +} + TEST(SlhDsaPrivateKeyTest, CreateFromParametersWithInvalidPrivateKeyLengthFails) { absl::StatusOr<SlhDsaParameters> parameters = SlhDsaParameters::Create( @@ -217,8 +409,7 @@ SlhDsaParameters::Variant::kTink); ASSERT_THAT(parameters, IsOk()); - RestrictedData restricted_private_key_bytes = RestrictedData( - subtle::Random::GetRandomBytes(63), InsecureSecretKeyAccess::Get()); + RestrictedData restricted_private_key_bytes(63); EXPECT_THAT( SlhDsaPrivateKey::Create(*parameters, restricted_private_key_bytes, /*id_requirement=*/123, GetPartialKeyAccess()) @@ -271,8 +462,7 @@ /*id_requirement=*/123, GetPartialKeyAccess()); ASSERT_THAT(public_key, IsOk()); - RestrictedData restricted_private_key_bytes = RestrictedData( - subtle::Random::GetRandomBytes(63), InsecureSecretKeyAccess::Get()); + RestrictedData restricted_private_key_bytes(63); EXPECT_THAT( SlhDsaPrivateKey::Create(*public_key, restricted_private_key_bytes, GetPartialKeyAccess())