| // 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. |
| |
| #include <cstring> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include "kudu/gutil/strings/strip.h" |
| #include "kudu/gutil/strings/substitute.h" |
| #include "kudu/security/crypto.h" |
| #include "kudu/security/openssl_util.h" |
| #include "kudu/security/test/test_certs.h" |
| #include "kudu/util/env.h" |
| #include "kudu/util/path_util.h" |
| #include "kudu/util/slice.h" |
| #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 { |
| namespace security { |
| |
| // Test for various crypto-related functionality in the security library. |
| class CryptoTest : public KuduTest { |
| public: |
| CryptoTest() : |
| pem_dir_(GetTestPath("pem")), |
| private_key_file_(JoinPathSegments(pem_dir_, "private_key.pem")), |
| public_key_file_(JoinPathSegments(pem_dir_, "public_key.pem")), |
| corrupted_private_key_file_(JoinPathSegments(pem_dir_, |
| "corrupted.private_key.pem")), |
| corrupted_public_key_file_(JoinPathSegments(pem_dir_, |
| "corrupted.public_key.pem")) { |
| } |
| |
| void SetUp() override { |
| ASSERT_OK(env_->CreateDir(pem_dir_)); |
| ASSERT_OK(WriteStringToFile(env_, kCaPrivateKey, private_key_file_)); |
| ASSERT_OK(WriteStringToFile(env_, kCaPublicKey, public_key_file_)); |
| ASSERT_OK(WriteStringToFile(env_, |
| string(kCaPrivateKey, strlen(kCaPrivateKey) / 2), |
| corrupted_private_key_file_)); |
| ASSERT_OK(WriteStringToFile(env_, |
| string(kCaPublicKey, strlen(kCaPublicKey) / 2), |
| corrupted_public_key_file_)); |
| } |
| |
| protected: |
| template<typename Key> |
| void CheckToAndFromString(const Key& key_ref, DataFormat format) { |
| SCOPED_TRACE(Substitute("ToAndFromString for $0 format", |
| DataFormatToString(format))); |
| string key_ref_str; |
| ASSERT_OK(key_ref.ToString(&key_ref_str, format)); |
| Key key; |
| ASSERT_OK(key.FromString(key_ref_str, format)); |
| string key_str; |
| ASSERT_OK(key.ToString(&key_str, format)); |
| ASSERT_EQ(key_ref_str, key_str); |
| } |
| |
| const string pem_dir_; |
| |
| const string private_key_file_; |
| const string public_key_file_; |
| const string corrupted_private_key_file_; |
| const string corrupted_public_key_file_; |
| }; |
| |
| // Check input/output of RSA private keys in PEM format. |
| TEST_F(CryptoTest, RsaPrivateKeyInputOutputPEM) { |
| PrivateKey key; |
| ASSERT_OK(key.FromFile(private_key_file_, DataFormat::PEM)); |
| string key_str; |
| ASSERT_OK(key.ToString(&key_str, DataFormat::PEM)); |
| RemoveExtraWhitespace(&key_str); |
| |
| string ref_key_str(kCaPrivateKey); |
| RemoveExtraWhitespace(&ref_key_str); |
| EXPECT_EQ(ref_key_str, key_str); |
| } |
| |
| // Check input of corrupted RSA private keys in PEM format. |
| TEST_F(CryptoTest, CorruptedRsaPrivateKeyInputPEM) { |
| static const string kFiles[] = { |
| corrupted_private_key_file_, |
| public_key_file_, |
| corrupted_public_key_file_, |
| "/bin/sh" |
| }; |
| for (const auto& file : kFiles) { |
| PrivateKey key; |
| const Status s = key.FromFile(file, DataFormat::PEM); |
| EXPECT_TRUE(s.IsRuntimeError()) << s.ToString(); |
| } |
| } |
| |
| // Check input/output of RSA public keys in PEM format. |
| TEST_F(CryptoTest, RsaPublicKeyInputOutputPEM) { |
| PublicKey key; |
| ASSERT_OK(key.FromFile(public_key_file_, DataFormat::PEM)); |
| string key_str; |
| ASSERT_OK(key.ToString(&key_str, DataFormat::PEM)); |
| RemoveExtraWhitespace(&key_str); |
| |
| string ref_key_str(kCaPublicKey); |
| RemoveExtraWhitespace(&ref_key_str); |
| EXPECT_EQ(ref_key_str, key_str); |
| } |
| |
| // Check input of corrupted RSA public keys in PEM format. |
| TEST_F(CryptoTest, CorruptedRsaPublicKeyInputPEM) { |
| static const string kFiles[] = { |
| corrupted_public_key_file_, |
| private_key_file_, |
| corrupted_private_key_file_, |
| "/bin/sh" |
| }; |
| for (const auto& file : kFiles) { |
| PublicKey key; |
| const Status s = key.FromFile(file, DataFormat::PEM); |
| EXPECT_TRUE(s.IsRuntimeError()) << s.ToString(); |
| } |
| } |
| |
| // 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)); |
| |
| PublicKey public_key; |
| ASSERT_OK(private_key.GetPublicKey(&public_key)); |
| string str_public_key; |
| ASSERT_OK(public_key.ToString(&str_public_key, DataFormat::PEM)); |
| RemoveExtraWhitespace(&str_public_key); |
| |
| string ref_str_public_key(kCaPublicKey); |
| RemoveExtraWhitespace(&ref_str_public_key); |
| EXPECT_EQ(ref_str_public_key, str_public_key); |
| } |
| |
| class CryptoKeySerDesTest : |
| public CryptoTest, |
| public ::testing::WithParamInterface<DataFormat> { |
| }; |
| |
| // Check the transformation chains for RSA public/private keys: |
| // internal -> PEM -> internal -> PEM |
| // internal -> DER -> internal -> DER |
| TEST_P(CryptoKeySerDesTest, ToAndFromString) { |
| const auto format = GetParam(); |
| |
| // Generate private RSA key. |
| PrivateKey private_key; |
| ASSERT_OK(GeneratePrivateKey(2048, &private_key)); |
| NO_FATALS(CheckToAndFromString(private_key, format)); |
| |
| // Extract public part of the key. |
| PublicKey public_key; |
| ASSERT_OK(private_key.GetPublicKey(&public_key)); |
| NO_FATALS(CheckToAndFromString(public_key, format)); |
| } |
| |
| 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(); |
| } |
| } |
| |
| TEST_F(CryptoTest, TestGenerateNonce) { |
| string nonce; |
| ASSERT_OK(GenerateNonce(&nonce)); |
| |
| // Do some basic validation on the returned nonce. |
| ASSERT_EQ(kNonceSize, nonce.size()); |
| ASSERT_NE(string(kNonceSize, '\0'), nonce); |
| |
| // Nonces should be unique, by definition. |
| string another_nonce; |
| ASSERT_OK(GenerateNonce(&another_nonce)); |
| ASSERT_NE(nonce, another_nonce); |
| } |
| |
| } // namespace security |
| } // namespace kudu |