Skip to content

Commit

Permalink
Adds a function to SubtleUtilBoringSsl that generates a new RSA key p…
Browse files Browse the repository at this point in the history
…air.

This will be used later to at least test the RsaSignBoringSSL class.

PiperOrigin-RevId: 208055588
GitOrigin-RevId: 5db7533b333088b10bb8d22656c6aad4114da005
  • Loading branch information
rlerm authored and chuckx committed Aug 9, 2018
1 parent 0c3ead7 commit 413b6b5
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 5 deletions.
1 change: 1 addition & 0 deletions cc/subtle/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
67 changes: 66 additions & 1 deletion cc/subtle/subtle_util_boringssl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -343,7 +345,7 @@ util::StatusOr<std::string> SubtleUtilBoringSSL::EcPointEncode(
return util::Status(util::error::INTERNAL, "EC_POINT_point2oct failed");
}
return std::string(reinterpret_cast<const char *>(encoded.get()),
1 + curve_size_in_bytes);
1 + curve_size_in_bytes);
}
default:
return util::Status(util::error::INTERNAL, "Unsupported point format");
Expand Down Expand Up @@ -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(RSA_new());
if (rsa == nullptr) {
return util::Status(util::error::INTERNAL, "Could not initialize RSA.");
}

bssl::UniquePtr<BIGNUM> 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
32 changes: 32 additions & 0 deletions cc/subtle/subtle_util_boringssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<bssl::UniquePtr<BIGNUM>> str2bn(absl::string_view s);
Expand Down Expand Up @@ -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
Expand Down
133 changes: 129 additions & 4 deletions cc/subtle/subtle_util_boringssl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@

#include "tink/subtle/subtle_util_boringssl.h"

#include <algorithm>
#include <string>
#include <vector>

#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;
Expand Down Expand Up @@ -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<BIGNUM> 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<BIGNUM> 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<BIGNUM> 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<BN_CTX> ctx(BN_CTX_new());

// Check n = p * q.
{
auto n_calc = bssl::UniquePtr<BIGNUM>(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<BIGNUM>(BN_dup(p.get()));
ASSERT_TRUE(BN_sub_word(pm1.get(), 1));
auto dp_calc = bssl::UniquePtr<BIGNUM>(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<BIGNUM>(BN_dup(q.get()));
ASSERT_TRUE(BN_sub_word(qm1.get(), 1));
auto dq_calc = bssl::UniquePtr<BIGNUM>(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<BIGNUM> e(BN_new());
BN_set_word(e.get(), RSA_F4);

std::vector<SubtleUtilBoringSSL::RsaPrivateKey> 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
Expand Down
13 changes: 13 additions & 0 deletions cc/util/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
52 changes: 52 additions & 0 deletions cc/util/test_matchers.h
Original file line number Diff line number Diff line change
@@ -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_

0 comments on commit 413b6b5

Please sign in to comment.