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'.