You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by to...@apache.org on 2017/02/01 02:54:16 UTC
[2/3] kudu git commit: [security] sign/verify data using RSA key pair
[security] sign/verify data using RSA key pair
Added functionality to make a signature of a data chunk and
verify signature of a data chunk using RSA keys. As for now,
supporting SHA256 and SHA512 message digests with test coverage
for SHA512.
Change-Id: I3fa04bb7d09aa416363998e2f8d7ccbdea625e4f
Reviewed-on: http://gerrit.cloudera.org:8080/5805
Reviewed-by: Dan Burkert <da...@apache.org>
Tested-by: Kudu Jenkins
Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/e6e3029c
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/e6e3029c
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/e6e3029c
Branch: refs/heads/master
Commit: e6e3029c2f460fe838af70e07faa138962ee3b55
Parents: 9136c8a
Author: Alexey Serbin <as...@cloudera.com>
Authored: Thu Jan 26 12:57:35 2017 -0800
Committer: Alexey Serbin <as...@cloudera.com>
Committed: Wed Feb 1 02:01:16 2017 +0000
----------------------------------------------------------------------
src/kudu/security/crypto-test.cc | 59 +++++++++++++++++-
src/kudu/security/crypto.cc | 100 ++++++++++++++++++++++++++++--
src/kudu/security/crypto.h | 20 ++++++
src/kudu/security/test/test_certs.cc | 53 ++++++++++++++++
src/kudu/security/test/test_certs.h | 9 +++
5 files changed, 234 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/kudu/blob/e6e3029c/src/kudu/security/crypto-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/crypto-test.cc b/src/kudu/security/crypto-test.cc
index 9eb4386..e8136f9 100644
--- a/src/kudu/security/crypto-test.cc
+++ b/src/kudu/security/crypto-test.cc
@@ -19,6 +19,7 @@
#include <functional>
#include <mutex>
#include <utility>
+#include <vector>
#include "kudu/gutil/strings/strip.h"
#include "kudu/gutil/strings/substitute.h"
@@ -31,8 +32,11 @@
#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
#include "kudu/util/test_util.h"
+#include "kudu/util/url-coding.h"
+using std::pair;
using std::string;
+using std::vector;
using strings::Substitute;
namespace kudu {
@@ -141,8 +145,8 @@ TEST_F(CryptoTest, CorruptedRsaPublicKeyInputPEM) {
}
}
-// Check extraction of the public part out from RSA private keys par.
-TEST_F(CryptoTest, RSAExtractPublicPartFromPrivateKey) {
+// Check extraction of the public part from RSA private keys par.
+TEST_F(CryptoTest, RsaExtractPublicPartFromPrivateKey) {
// Load the reference RSA private key.
PrivateKey private_key;
ASSERT_OK(private_key.FromString(kCaPrivateKey, DataFormat::PEM));
@@ -184,5 +188,56 @@ INSTANTIATE_TEST_CASE_P(
DataFormats, CryptoKeySerDesTest,
::testing::Values(DataFormat::DER, DataFormat::PEM));
+// Check making crypto signatures against the reference data.
+TEST_F(CryptoTest, MakeVerifySignatureRef) {
+ static const vector<pair<string, string>> kRefElements = {
+ { kDataTiny, kSignatureTinySHA512 },
+ { kDataShort, kSignatureShortSHA512 },
+ { kDataLong, kSignatureLongSHA512 },
+ };
+
+ // Load the reference RSA private key.
+ PrivateKey private_key;
+ ASSERT_OK(private_key.FromString(kCaPrivateKey, DataFormat::PEM));
+
+ // Load the reference RSA public key.
+ PublicKey public_key;
+ ASSERT_OK(public_key.FromString(kCaPublicKey, DataFormat::PEM));
+
+ for (const auto& e : kRefElements) {
+ string sig;
+ ASSERT_OK(private_key.MakeSignature(DigestType::SHA512, e.first, &sig));
+
+ // Ad-hoc verification: check the produced signature matches the reference.
+ string sig_base64;
+ Base64Encode(sig, &sig_base64);
+ EXPECT_EQ(e.second, sig_base64);
+
+ // Verify the signature cryptographically.
+ EXPECT_OK(public_key.VerifySignature(DigestType::SHA512, e.first, sig));
+ }
+}
+
+TEST_F(CryptoTest, VerifySignatureWrongData) {
+ static const vector<string> kRefSignatures = {
+ kSignatureTinySHA512,
+ kSignatureShortSHA512,
+ kSignatureLongSHA512,
+ };
+
+ // Load the reference RSA public key.
+ PublicKey key;
+ ASSERT_OK(key.FromString(kCaPublicKey, DataFormat::PEM));
+
+ for (const auto& e : kRefSignatures) {
+ string signature;
+ ASSERT_TRUE(Base64Decode(e, &signature));
+ Status s = key.VerifySignature(DigestType::SHA512,
+ "non-expected-data", signature);
+ EXPECT_TRUE(s.IsCorruption()) << s.ToString();
+ }
+}
+
+
} // namespace security
} // namespace kudu
http://git-wip-us.apache.org/repos/asf/kudu/blob/e6e3029c/src/kudu/security/crypto.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/crypto.cc b/src/kudu/security/crypto.cc
index 856ddda..f34c270 100644
--- a/src/kudu/security/crypto.cc
+++ b/src/kudu/security/crypto.cc
@@ -81,9 +81,39 @@ struct RsaPublicKeyTraits : public SslTypeTraits<EVP_PKEY> {
template<> struct SslTypeTraits<RSA> {
static constexpr auto free = &RSA_free;
};
-template<> struct SslTypeTraits<void> {
- static constexpr auto free = &CRYPTO_free;
-};
+
+namespace {
+
+const char* GetDigestName(DigestType digest_type) {
+ switch (digest_type) {
+ case DigestType::SHA256:
+ return "SHA256";
+ case DigestType::SHA512:
+ return "SHA512";
+ }
+ return "UNKNOWN_DIGEST";
+}
+
+Status PrepareDigest(DigestType digest,
+ const EVP_MD** md,
+ EVP_MD_CTX** mctx,
+ c_unique_ptr<BIO>* bmd_out) {
+ InitializeOpenSSL();
+ const char* md_name = GetDigestName(digest);
+ *md = EVP_get_digestbyname(md_name);
+ if (!*md) {
+ return Status::InvalidArgument(Substitute("error getting digest '$0': $1",
+ md_name, GetOpenSSLErrors()));
+ }
+ auto bmd = ssl_make_unique(BIO_new(BIO_f_md()));
+ OPENSSL_RET_NOT_OK(BIO_get_md_ctx(bmd.get(), mctx),
+ "error initializing message digest context");
+ bmd_out->swap(bmd);
+
+ return Status::OK();
+}
+
+} // anonymous namespace
Status PublicKey::FromString(const std::string& data, DataFormat format) {
@@ -106,6 +136,43 @@ Status PublicKey::FromBIO(BIO* bio, DataFormat format) {
bio, format, &data_);
}
+// Modeled after code in $OPENSSL_ROOT/apps/dgst.c
+Status PublicKey::VerifySignature(DigestType digest,
+ const std::string& data,
+ const std::string& signature) const {
+ const EVP_MD* md = nullptr;
+ EVP_MD_CTX* mctx = nullptr;
+ c_unique_ptr<BIO> bmd;
+ RETURN_NOT_OK(PrepareDigest(digest, &md, &mctx, &bmd));
+
+ OPENSSL_RET_NOT_OK(
+ EVP_DigestVerifyInit(mctx, nullptr, md, nullptr, GetRawData()),
+ "error initializing verification digest");
+ OPENSSL_RET_NOT_OK(
+ EVP_DigestVerifyUpdate(mctx, data.data(), data.size()),
+ "error verifying data signature");
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ unsigned char* sig_data = reinterpret_cast<unsigned char*>(
+ const_cast<char*>(signature.data()));
+#else
+ const unsigned char* sig_data = reinterpret_cast<const unsigned char*>(
+ signature.data());
+#endif
+ // The success is indicated by return code 1. All other values means
+ // either wrong signature or error while performing signature verification.
+ const int rc = EVP_DigestVerifyFinal(mctx, sig_data, signature.size());
+ if (rc < 0 || rc > 1) {
+ return Status::RuntimeError(
+ Substitute("error verifying data signature: $0",
+ GetOpenSSLErrors()));
+ }
+ if (rc == 0) {
+ return Status::Corruption("data signature verification failed");
+ }
+
+ return Status::OK();
+}
+
Status PrivateKey::FromString(const std::string& data, DataFormat format) {
return ::kudu::security::FromString<RawDataType, RsaPrivateKeyTraits>(
data, format, &data_);
@@ -141,6 +208,29 @@ Status PrivateKey::GetPublicKey(PublicKey* public_key) {
return Status::OK();
}
+// Modeled after code in $OPENSSL_ROOT/apps/dgst.c
+Status PrivateKey::MakeSignature(DigestType digest,
+ const std::string& data,
+ std::string* signature) const {
+ CHECK(signature);
+ const EVP_MD* md = nullptr;
+ EVP_MD_CTX* mctx = nullptr;
+ c_unique_ptr<BIO> bmd;
+ RETURN_NOT_OK(PrepareDigest(digest, &md, &mctx, &bmd));
+ OPENSSL_RET_NOT_OK(
+ EVP_DigestSignInit(mctx, nullptr, md, nullptr, GetRawData()),
+ "error initializing signing digest");
+ OPENSSL_RET_NOT_OK(EVP_DigestSignUpdate(mctx, data.data(), data.size()),
+ "error signing data");
+ size_t sig_len = EVP_PKEY_size(GetRawData());
+ unsigned char buf[4 * 1024];
+ OPENSSL_RET_NOT_OK(EVP_DigestSignFinal(mctx, buf, &sig_len),
+ "error finalizing data signature");
+ *signature = string(reinterpret_cast<char*>(buf), sig_len);
+
+ return Status::OK();
+}
+
Status GeneratePrivateKey(int num_bits, PrivateKey* ret) {
CHECK(ret);
InitializeOpenSSL();
@@ -152,8 +242,8 @@ Status GeneratePrivateKey(int num_bits, PrivateKey* ret) {
OPENSSL_RET_NOT_OK(
RSA_generate_key_ex(rsa.get(), num_bits, bn.get(), nullptr),
"error generating RSA key");
- OPENSSL_RET_NOT_OK(EVP_PKEY_set1_RSA(key.get(), rsa.get()),
- "error assigning RSA key");
+ OPENSSL_RET_NOT_OK(
+ EVP_PKEY_set1_RSA(key.get(), rsa.get()), "error assigning RSA key");
}
ret->AdoptRawData(key.release());
http://git-wip-us.apache.org/repos/asf/kudu/blob/e6e3029c/src/kudu/security/crypto.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/crypto.h b/src/kudu/security/crypto.h
index 1da5de3..6063f37 100644
--- a/src/kudu/security/crypto.h
+++ b/src/kudu/security/crypto.h
@@ -31,6 +31,12 @@ class Status;
namespace security {
+// Supported message digests for data signing and signature verification.
+enum class DigestType {
+ SHA256,
+ SHA512,
+};
+
// A class with generic public key interface, but actually it represents
// an RSA key.
class PublicKey : public RawDataWrapper<EVP_PKEY> {
@@ -42,6 +48,13 @@ class PublicKey : public RawDataWrapper<EVP_PKEY> {
Status FromFile(const std::string& fpath, DataFormat format);
Status FromBIO(BIO* bio, DataFormat format);
+
+ // Using the key, verify data signature using the specified message
+ // digest algorithm for signature verification.
+ // The input signature should be in in raw format (i.e. no base64 encoding).
+ Status VerifySignature(DigestType digest,
+ const std::string& data,
+ const std::string& signature) const;
};
// A class with generic private key interface, but actually it represents
@@ -57,6 +70,13 @@ class PrivateKey : public RawDataWrapper<EVP_PKEY> {
// Output the public part of the keypair into the specified placeholder.
Status GetPublicKey(PublicKey* public_key);
+
+ // Using the key, generate data signature using the specified
+ // message digest algorithm. The result signature is in raw format
+ // (i.e. no base64 encoding).
+ Status MakeSignature(DigestType digest,
+ const std::string& data,
+ std::string* signature) const;
};
// Utility method to generate private keys.
http://git-wip-us.apache.org/repos/asf/kudu/blob/e6e3029c/src/kudu/security/test/test_certs.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/test_certs.cc b/src/kudu/security/test/test_certs.cc
index a7911a3..5432906 100644
--- a/src/kudu/security/test/test_certs.cc
+++ b/src/kudu/security/test/test_certs.cc
@@ -240,6 +240,59 @@ OwIDAQAB
-----END PUBLIC KEY-----
)***";
+//
+// The reference signatures were obtained by using the following sequence:
+// 0. The reference private key was saved into /tmp/ca.pkey.pem file.
+// 1. Put the input data into /tmp/in.txt file.
+// 2. To sign the input data, run
+// openssl dgst -sign /tmp/ca.pkey.pem -sha512 -out /tmp/out /tmp/in.txt
+// 3. To capture the signature in text format, run
+// base64 -b 60 /tmp/out
+//
+const char kDataTiny[] = "Tiny";
+const char kSignatureTinySHA512[] =
+ "omtvSpfj9tKo0RdI4zJwasWSQnXl++aKVjhH19ABJCd0haKT8RXNuhnxcbZU"
+ "Y1ILE5F9YjVj+tN/7ah5WQZR5qlJ6GMFfCFBhOzvi/vf5PSbUrFfwFvFD6sq"
+ "Bu0PWdwKM3t8/YFE2HcZWSzGCcasKlG/aw2eQCN3Kdv8QVMlC28CFA/EqQBt"
+ "8Sfye1DLba33SzDpJqR2DduTFrEW2UffumpYIbkEcMwUSBFzfdp5hgWPowFb"
+ "LrnKvyWKpEPMFGQmf5siyXSkbBIfL774tynhWN/lAUWykwXSUfGgi2G0NQvj"
+ "xmuHhbxWpbW/31uMGssw92OfVQ/+aQ4pNmY9GbibcA==";
+
+const char kDataShort[] = "ShortRefInputData";
+const char kSignatureShortSHA512[] =
+ "BHaDipr8ibn40BMD6+DlatKsjbmsGZsJIDlheppBjqv66eBDLKOVjpmpMLl9"
+ "9lXCGUlVS+cNcVP4RPDzXNoXkpzUOJD3UQSnxCAm6tV1eGjD3SHi3fk6PCNc"
+ "MhM/+09fA0WHdIdZm93cpHt6c9MFzB/dUjHJByhQ7Csmz2zdITyMIl3/D+bi"
+ "ocW0aIibk0wNGn/FmXfgFDP+3pBS2bpS0AdFnckX8AqXHFMJnvqKYODpYCW8"
+ "NWFSD1TgZOumu/gzxm+HySPezQ2j9tdR6nb9swfShvN+o0oBVGq5vgtgZMTM"
+ "7Ws+BrasLfvQFkvtGMWB9VeH/rDlGOym8RwUrCIJJQ==";
+
+const char kDataLong[] =
+R"***(Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you 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.
+)***";
+const char kSignatureLongSHA512[] =
+ "kc62qPHApVFbueR1xSCQJR5NomqDRzVA+4Xi9egVyfkKgpVhDAYGxbMl8OTY/YCb"
+ "eQuwY+B7RGxF9sj3gvsq/dvrbIjLT3QDhs0bv+lXTtBQ5r9zrals3de0tEFrPoLr"
+ "CkKPhVZaG+zwmUVltfsdlsqvepy6rNW7BocehvgpPTbzxgsZg4nUANsjSy8HBoDb"
+ "xWyfbkMgBY4aWIH1g+wksq1DHzdTNdZCYstupRwVw/ESC+zrFQiZPFeRE/wCSeG/"
+ "bd0L8TcotQHJchZ8THW0rEbuCg79I7Crd1KQYljBpOOhMYZEDEdM9L19JlaMlw+Z"
+ "leyLfL8Bw3wCg9cMfNmQfQ==";
+
Status CreateTestSSLCerts(const string& dir,
string* cert_file,
http://git-wip-us.apache.org/repos/asf/kudu/blob/e6e3029c/src/kudu/security/test/test_certs.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/test_certs.h b/src/kudu/security/test/test_certs.h
index cb63ad2..763d9ed 100644
--- a/src/kudu/security/test/test_certs.h
+++ b/src/kudu/security/test/test_certs.h
@@ -44,6 +44,15 @@ extern const char kCaExpiredPrivateKey[];
// The public part of the abovementioned private key.
extern const char kCaExpiredPublicKey[];
+extern const char kDataTiny[];
+extern const char kSignatureTinySHA512[];
+
+extern const char kDataShort[];
+extern const char kSignatureShortSHA512[];
+
+extern const char kDataLong[];
+extern const char kSignatureLongSHA512[];
+
// Creates a matching SSL certificate and private key file in 'dir', returning
// their paths in '*cert_file' and '*key_file'. The password associated with
// the private key is stored in '*key_password'.