From 413b6b51f5d8df19c81fbdaef7787619530d4081 Mon Sep 17 00:00:00 2001 From: Rafael Lerm Date: Thu, 9 Aug 2018 08:53:54 -0700 Subject: [PATCH] Adds a function to SubtleUtilBoringSsl that generates a new RSA key pair. This will be used later to at least test the RsaSignBoringSSL class. PiperOrigin-RevId: 208055588 GitOrigin-RevId: 5db7533b333088b10bb8d22656c6aad4114da005 --- cc/subtle/BUILD.bazel | 1 + cc/subtle/subtle_util_boringssl.cc | 67 +++++++++++- cc/subtle/subtle_util_boringssl.h | 32 ++++++ cc/subtle/subtle_util_boringssl_test.cc | 133 +++++++++++++++++++++++- cc/util/BUILD.bazel | 13 +++ cc/util/test_matchers.h | 52 +++++++++ 6 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 cc/util/test_matchers.h diff --git a/cc/subtle/BUILD.bazel b/cc/subtle/BUILD.bazel index 1c3595f11a..b1684aab66 100644 --- a/cc/subtle/BUILD.bazel +++ b/cc/subtle/BUILD.bazel @@ -577,6 +577,7 @@ cc_test( ":wycheproof_util", "//cc/util:status", "//cc/util:statusor", + "//cc/util:test_matchers", "//cc/util:test_util", "@boringssl//:crypto", "@com_google_absl//absl/strings", diff --git a/cc/subtle/subtle_util_boringssl.cc b/cc/subtle/subtle_util_boringssl.cc index fa204145c0..b423f50f24 100644 --- a/cc/subtle/subtle_util_boringssl.cc +++ b/cc/subtle/subtle_util_boringssl.cc @@ -15,10 +15,12 @@ /////////////////////////////////////////////////////////////////////////////// #include "tink/subtle/subtle_util_boringssl.h" +#include "absl/strings/str_cat.h" #include "absl/strings/substitute.h" #include "openssl/bn.h" #include "openssl/ec.h" #include "openssl/err.h" +#include "openssl/rsa.h" #include "tink/subtle/common_enums.h" namespace crypto { @@ -343,7 +345,7 @@ util::StatusOr SubtleUtilBoringSSL::EcPointEncode( return util::Status(util::error::INTERNAL, "EC_POINT_point2oct failed"); } return std::string(reinterpret_cast(encoded.get()), - 1 + curve_size_in_bytes); + 1 + curve_size_in_bytes); } default: return util::Status(util::error::INTERNAL, "Unsupported point format"); @@ -385,6 +387,69 @@ absl::string_view SubtleUtilBoringSSL::EnsureNonNull(absl::string_view str) { return str; } +util::Status SubtleUtilBoringSSL::GetNewRsaKeyPair( + int modulus_size_in_bits, const BIGNUM *e, + SubtleUtilBoringSSL::RsaPrivateKey *private_key, + SubtleUtilBoringSSL::RsaPublicKey *public_key) { + bssl::UniquePtr rsa(RSA_new()); + if (rsa == nullptr) { + return util::Status(util::error::INTERNAL, "Could not initialize RSA."); + } + + bssl::UniquePtr e_copy(BN_new()); + if (BN_copy(e_copy.get(), e) == nullptr) { + return util::Status(util::error::INTERNAL, GetErrors()); + } + if (RSA_generate_key_ex(rsa.get(), modulus_size_in_bits, e_copy.get(), + /*cb=*/nullptr) != 1) { + return util::Status( + util::error::INTERNAL, + absl::StrCat("Error generating private key: ", GetErrors())); + } + + const BIGNUM *n_bn, *e_bn, *d_bn; + RSA_get0_key(rsa.get(), &n_bn, &e_bn, &d_bn); + + // Save exponents. + auto n_str = bn2str(n_bn, BN_num_bytes(n_bn)); + auto e_str = bn2str(e_bn, BN_num_bytes(e_bn)); + auto d_str = bn2str(d_bn, BN_num_bytes(d_bn)); + if (!n_str.ok()) return n_str.status(); + if (!e_str.ok()) return e_str.status(); + if (!d_str.ok()) return d_str.status(); + private_key->n = std::move(n_str.ValueOrDie()); + private_key->e = std::move(e_str.ValueOrDie()); + private_key->d = std::move(d_str.ValueOrDie()); + + public_key->n = private_key->n; + public_key->e = private_key->e; + + // Save factors. + const BIGNUM *p_bn, *q_bn; + RSA_get0_factors(rsa.get(), &p_bn, &q_bn); + auto p_str = bn2str(p_bn, BN_num_bytes(p_bn)); + auto q_str = bn2str(q_bn, BN_num_bytes(q_bn)); + if (!p_str.ok()) return p_str.status(); + if (!q_str.ok()) return q_str.status(); + private_key->p = std::move(p_str.ValueOrDie()); + private_key->q = std::move(q_str.ValueOrDie()); + + // Save CRT parameters. + const BIGNUM *dp_bn, *dq_bn, *crt_bn; + RSA_get0_crt_params(rsa.get(), &dp_bn, &dq_bn, &crt_bn); + auto dp_str = bn2str(dp_bn, BN_num_bytes(dp_bn)); + auto dq_str = bn2str(dq_bn, BN_num_bytes(dq_bn)); + auto crt_str = bn2str(crt_bn, BN_num_bytes(crt_bn)); + if (!dp_str.ok()) return dp_str.status(); + if (!dq_str.ok()) return dq_str.status(); + if (!crt_str.ok()) return crt_str.status(); + private_key->dp = std::move(dp_str.ValueOrDie()); + private_key->dq = std::move(dq_str.ValueOrDie()); + private_key->crt = std::move(crt_str.ValueOrDie()); + + return util::OkStatus(); +} + } // namespace subtle } // namespace tink } // namespace crypto diff --git a/cc/subtle/subtle_util_boringssl.h b/cc/subtle/subtle_util_boringssl.h index fd75027dde..f6d3398e0d 100644 --- a/cc/subtle/subtle_util_boringssl.h +++ b/cc/subtle/subtle_util_boringssl.h @@ -61,6 +61,32 @@ class SubtleUtilBoringSSL { int salt_length; }; + // RSA private key representation. + struct RsaPrivateKey { + // Modulus. + std::string n; + // Public exponent. + std::string e; + // Private exponent. + // Unsigned big integer in bigendian representation. + std::string d; + + // The prime factor p of n. + // Unsigned big integer in bigendian representation. + std::string p; + // The prime factor q of n. + // Unsigned big integer in bigendian representation. + std::string q; + // d mod (p - 1). + std::string dp; + // d mod (q - 1). + // Unsigned big integer in bigendian representation. + std::string dq; + // Chinese Remainder Theorem coefficient q^(-1) mod p. + // Unsigned big integer in bigendian representation. + std::string crt; + }; + // Returns BoringSSL's BIGNUM constructed from bigendian std::string // representation. static util::StatusOr> str2bn(absl::string_view s); @@ -127,6 +153,12 @@ class SubtleUtilBoringSSL { // Return an empty std::string if str.data() is nullptr; otherwise return str. static absl::string_view EnsureNonNull(absl::string_view str); + + // Creates a new RSA public and private key pair. + static util::Status GetNewRsaKeyPair(int modulus_size_in_bits, + const BIGNUM *e, + RsaPrivateKey *private_key, + RsaPublicKey *public_key); }; } // namespace subtle diff --git a/cc/subtle/subtle_util_boringssl_test.cc b/cc/subtle/subtle_util_boringssl_test.cc index 4690dcba1b..f17176d996 100644 --- a/cc/subtle/subtle_util_boringssl_test.cc +++ b/cc/subtle/subtle_util_boringssl_test.cc @@ -16,26 +16,32 @@ #include "tink/subtle/subtle_util_boringssl.h" +#include #include #include +#include "gtest/gtest.h" #include "absl/strings/str_cat.h" +#include "openssl/ec.h" +#include "openssl/evp.h" +#include "openssl/x509.h" #include "include/rapidjson/document.h" #include "tink/subtle/common_enums.h" #include "tink/subtle/ec_util.h" #include "tink/subtle/wycheproof_util.h" #include "tink/util/status.h" #include "tink/util/statusor.h" +#include "tink/util/test_matchers.h" #include "tink/util/test_util.h" -#include "gtest/gtest.h" -#include "openssl/ec.h" -#include "openssl/evp.h" -#include "openssl/x509.h" namespace crypto { namespace tink { namespace subtle { namespace { +using ::crypto::tink::test::IsOk; +using ::crypto::tink::test::StatusIs; +using ::testing::IsEmpty; +using ::testing::Not; struct EncodingTestVector { EcPointFormat format; @@ -266,6 +272,125 @@ TEST(SubtleUtilBoringSSLTest, testComputeEcdhSharedSecretWithWycheproofTest) { ::ReadTestVectors("ecdh_secp521r1_test.json"))); } +TEST(CreatesNewRsaKeyPairTest, BasicSanityChecks) { + SubtleUtilBoringSSL::RsaPublicKey public_key; + SubtleUtilBoringSSL::RsaPrivateKey private_key; + + bssl::UniquePtr e(BN_new()); + BN_set_word(e.get(), RSA_F4); + ASSERT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(2048, e.get(), &private_key, + &public_key), + IsOk()); + EXPECT_THAT(private_key.n, Not(IsEmpty())); + EXPECT_THAT(private_key.e, Not(IsEmpty())); + EXPECT_THAT(private_key.d, Not(IsEmpty())); + + EXPECT_THAT(private_key.p, Not(IsEmpty())); + EXPECT_THAT(private_key.q, Not(IsEmpty())); + EXPECT_THAT(private_key.dp, Not(IsEmpty())); + EXPECT_THAT(private_key.dq, Not(IsEmpty())); + EXPECT_THAT(private_key.crt, Not(IsEmpty())); + + EXPECT_THAT(public_key.n, Not(IsEmpty())); + EXPECT_THAT(public_key.e, Not(IsEmpty())); + + EXPECT_EQ(public_key.n, private_key.n); + EXPECT_EQ(public_key.e, private_key.e); +} + +TEST(CreatesNewRsaKeyPairTest, FailsOnLargeE) { + // OpenSSL requires the "e" value to be at most 32 bits. + SubtleUtilBoringSSL::RsaPublicKey public_key; + SubtleUtilBoringSSL::RsaPrivateKey private_key; + + bssl::UniquePtr e(BN_new()); + BN_set_word(e.get(), 1L << 33); + ASSERT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(2048, e.get(), &private_key, + &public_key), + StatusIs(util::error::INTERNAL)); +} + +TEST(CreatesNewRsaKeyPairTest, KeyIsWellFormed) { + SubtleUtilBoringSSL::RsaPublicKey public_key; + SubtleUtilBoringSSL::RsaPrivateKey private_key; + bssl::UniquePtr e(BN_new()); + BN_set_word(e.get(), RSA_F4); + ASSERT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(2048, e.get(), &private_key, + &public_key), + IsOk()); + auto n = std::move(SubtleUtilBoringSSL::str2bn(private_key.n).ValueOrDie()); + auto d = std::move(SubtleUtilBoringSSL::str2bn(private_key.d).ValueOrDie()); + auto p = std::move(SubtleUtilBoringSSL::str2bn(private_key.p).ValueOrDie()); + auto q = std::move(SubtleUtilBoringSSL::str2bn(private_key.q).ValueOrDie()); + auto dp = std::move(SubtleUtilBoringSSL::str2bn(private_key.dp).ValueOrDie()); + auto dq = std::move(SubtleUtilBoringSSL::str2bn(private_key.dq).ValueOrDie()); + bssl::UniquePtr ctx(BN_CTX_new()); + + // Check n = p * q. + { + auto n_calc = bssl::UniquePtr(BN_new()); + ASSERT_TRUE(BN_mul(n_calc.get(), p.get(), q.get(), ctx.get())); + ASSERT_TRUE(BN_equal_consttime(n_calc.get(), n.get())); + } + + // Check n size >= 2048 bit. + EXPECT_GE(BN_num_bits(n.get()), 2048); + + // dp = d mod (p - 1) + { + auto pm1 = bssl::UniquePtr(BN_dup(p.get())); + ASSERT_TRUE(BN_sub_word(pm1.get(), 1)); + auto dp_calc = bssl::UniquePtr(BN_new()); + ASSERT_TRUE(BN_mod(dp_calc.get(), d.get(), pm1.get(), ctx.get())); + + ASSERT_TRUE(BN_equal_consttime(dp_calc.get(), dp.get())); + } + + // dq = d mod (q - 1) + { + auto qm1 = bssl::UniquePtr(BN_dup(q.get())); + ASSERT_TRUE(BN_sub_word(qm1.get(), 1)); + auto dq_calc = bssl::UniquePtr(BN_new()); + ASSERT_TRUE(BN_mod(dq_calc.get(), d.get(), qm1.get(), ctx.get())); + + ASSERT_TRUE(BN_equal_consttime(dq_calc.get(), dq.get())); + } +} + +TEST(CreatesNewRsaKeyPairTest, GeneratesDifferentKeysEveryTime) { + SubtleUtilBoringSSL::RsaPublicKey public_key; + bssl::UniquePtr e(BN_new()); + BN_set_word(e.get(), RSA_F4); + + std::vector generated_keys; + std::generate_n(std::back_inserter(generated_keys), 4, [&]() { + SubtleUtilBoringSSL::RsaPrivateKey private_key; + EXPECT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair( + 2048, e.get(), &private_key, &public_key), + IsOk()); + return private_key; + }); + + // Iterate through a two-element sliding windows, comparing two consecutive + // elements in the list. + for (int i = 0; i < generated_keys.size() - 1; ++i) { + const auto& left = generated_keys[i]; + const auto& right = generated_keys[i + 1]; + + // The only fieldthat should be equal. + ASSERT_EQ(left.e, right.e); + + ASSERT_NE(left.n, right.n); + ASSERT_NE(left.d, right.d); + + ASSERT_NE(left.p, right.p); + ASSERT_NE(left.q, right.q); + ASSERT_NE(left.dp, right.dp); + ASSERT_NE(left.dq, right.dq); + ASSERT_NE(left.crt, right.crt); + } +} + } // namespace } // namespace subtle } // namespace tink diff --git a/cc/util/BUILD.bazel b/cc/util/BUILD.bazel index 5dd2f7c61a..0ad2c63d29 100644 --- a/cc/util/BUILD.bazel +++ b/cc/util/BUILD.bazel @@ -94,6 +94,19 @@ cc_library( ], ) +cc_library( + name = "test_matchers", + testonly = 1, + srcs = [], + hdrs = ["test_matchers.h"], + include_prefix = "tink", + strip_include_prefix = "/cc", + deps = [ + ":status", + "@com_google_googletest//:gtest", + ], +) + cc_library( name = "keyset_util", srcs = ["keyset_util.cc"], diff --git a/cc/util/test_matchers.h b/cc/util/test_matchers.h new file mode 100644 index 0000000000..2d510785f7 --- /dev/null +++ b/cc/util/test_matchers.h @@ -0,0 +1,52 @@ +// Copyright 2018 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef TINK_CC_UTIL_TEST_MATCHERS_H_ +#define TINK_CC_UTIL_TEST_MATCHERS_H_ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "tink/util/status.h" + +namespace crypto { +namespace tink { +namespace test { + +// Matches a util::Status value. This is better than +// EXPECT_TRUE(status.ok()) +// because the error message is a part of the failure messsage. +MATCHER(IsOk, "is a Status with an OK value") { + if (arg.ok()) { + return true; + } + *result_listener << arg.ToString(); + return false; +} + +MATCHER_P(StatusIs, code, + "is a Status with a " + util::ErrorCodeString(code) + " code") { + if (arg.CanonicalCode() == code) { + return true; + } + *result_listener << ::testing::PrintToString(arg); + return false; +} + +} // namespace test +} // namespace tink +} // namespace crypto + +#endif // TINK_CC_UTIL_TEST_MATCHERS_H_