blob: 6e4662db3efbb5c0a0701bc9ba3876483f49ed42 [file] [log] [blame]
// 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 "kudu/security/ca/cert_management.h"
#include <memory>
#include <utility>
#include <vector>
#include "kudu/gutil/strings/substitute.h"
#include "kudu/gutil/strings/util.h"
#include "kudu/security/cert.h"
#include "kudu/security/openssl_util.h"
#include "kudu/security/security-test-util.h"
#include "kudu/security/test/test_certs.h"
#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
#include "kudu/util/test_util.h"
using std::string;
using std::vector;
using strings::Substitute;
namespace kudu {
namespace security {
namespace ca {
class CertManagementTest : public KuduTest {
public:
void SetUp() override {
ASSERT_OK(ca_cert_.FromString(kCaCert, DataFormat::PEM));
ASSERT_OK(ca_private_key_.FromString(kCaPrivateKey, DataFormat::PEM));
ASSERT_OK(ca_public_key_.FromString(kCaPublicKey, DataFormat::PEM));
ASSERT_OK(ca_exp_cert_.FromString(kCaExpiredCert, DataFormat::PEM));
ASSERT_OK(ca_exp_private_key_.FromString(kCaExpiredPrivateKey, DataFormat::PEM));
// Sanity checks.
ASSERT_OK(ca_cert_.CheckKeyMatch(ca_private_key_));
ASSERT_OK(ca_exp_cert_.CheckKeyMatch(ca_exp_private_key_));
}
protected:
CertRequestGenerator::Config PrepareConfig(
const string& hostname = "localhost.localdomain") {
return { hostname };
}
CaCertRequestGenerator::Config PrepareCaConfig(const string& cn) {
return { cn };
}
// Create a new private key in 'key' and return a CSR associated with that
// key.
template<class CSRGen = CertRequestGenerator>
CertSignRequest PrepareTestCSR(typename CSRGen::Config config,
PrivateKey* key) {
CHECK_OK(GeneratePrivateKey(512, key));
CSRGen gen(std::move(config));
CHECK_OK(gen.Init());
CertSignRequest req;
CHECK_OK(gen.GenerateRequest(*key, &req));
return req;
}
Cert ca_cert_;
PrivateKey ca_private_key_;
PublicKey ca_public_key_;
Cert ca_exp_cert_;
PrivateKey ca_exp_private_key_;
};
// Check for basic constraints while initializing CertRequestGenerator objects.
TEST_F(CertManagementTest, RequestGeneratorConstraints) {
const CertRequestGenerator::Config gen_config = PrepareConfig("");
CertRequestGenerator gen(gen_config);
const Status s = gen.Init();
const string err_msg = s.ToString();
ASSERT_TRUE(s.IsInvalidArgument()) << err_msg;
ASSERT_STR_CONTAINS(err_msg, "hostname must not be empty");
}
// Check for the basic functionality of the CertRequestGenerator class:
// check it's able to generate keys of expected number of bits and that it
// reports an error if trying to generate a key of unsupported number of bits.
TEST_F(CertManagementTest, RequestGeneratorBasics) {
const CertRequestGenerator::Config gen_config = PrepareConfig();
PrivateKey key;
ASSERT_OK(GeneratePrivateKey(1024, &key));
CertRequestGenerator gen(gen_config);
ASSERT_OK(gen.Init());
string key_str;
ASSERT_OK(key.ToString(&key_str, DataFormat::PEM));
// Check for non-supported number of bits for the key.
Status s = GeneratePrivateKey(7, &key);
ASSERT_TRUE(s.IsRuntimeError());
}
// Check that CertSigner behaves in a predictable way if given non-matching
// CA private key and certificate.
TEST_F(CertManagementTest, SignerInitWithMismatchedCertAndKey) {
PrivateKey key;
const auto& csr = PrepareTestCSR(PrepareConfig(), &key);
{
Cert cert;
Status s = CertSigner(&ca_cert_, &ca_exp_private_key_)
.Sign(csr, &cert);
const string err_msg = s.ToString();
ASSERT_TRUE(s.IsRuntimeError()) << err_msg;
ASSERT_STR_CONTAINS(err_msg, "certificate does not match private key");
}
{
Cert cert;
Status s = CertSigner(&ca_exp_cert_, &ca_private_key_)
.Sign(csr, &cert);
const string err_msg = s.ToString();
ASSERT_TRUE(s.IsRuntimeError()) << err_msg;
ASSERT_STR_CONTAINS(err_msg, "certificate does not match private key");
}
}
// Check how CertSigner behaves if given expired CA certificate
// and corresponding private key.
TEST_F(CertManagementTest, SignerInitWithExpiredCert) {
const CertRequestGenerator::Config gen_config = PrepareConfig();
PrivateKey key;
CertSignRequest req = PrepareTestCSR(gen_config, &key);
// Signer works fine even with expired CA certificate.
Cert cert;
ASSERT_OK(CertSigner(&ca_exp_cert_, &ca_exp_private_key_).Sign(req, &cert));
ASSERT_OK(cert.CheckKeyMatch(key));
}
// Generate X509 CSR and issue corresponding certificate putting the specified
// hostname into the SAN X509v3 extension field. The fix for KUDU-1981 addresses
// the issue of enabling Kudu server components on systems with FQDN longer than
// 64 characters. This test is a regression for KUDU-1981, so let's verify that
// CSRs and the result X509 cerificates with long hostnames in SAN are handled
// properly.
TEST_F(CertManagementTest, SignCertLongHostnameInSan) {
for (auto const& hostname :
{
"foo.bar.com",
"222222222222222222222222222222222222222222222222222222222222222."
"555555555555555555555555555555555555555555555555555555555555555."
"555555555555555555555555555555555555555555555555555555555555555."
"chaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaars",
}) {
CertRequestGenerator::Config gen_config;
gen_config.hostname = hostname;
gen_config.user_id = "test-uid";
PrivateKey key;
const auto& csr = PrepareTestCSR(gen_config, &key);
Cert cert;
ASSERT_OK(CertSigner(&ca_cert_, &ca_private_key_).Sign(csr, &cert));
ASSERT_OK(cert.CheckKeyMatch(key));
EXPECT_EQ("C = US, ST = CA, O = MyCompany, CN = MyName, emailAddress = my@email.com",
cert.IssuerName());
EXPECT_EQ("UID = test-uid", cert.SubjectName());
vector<string> hostnames = cert.Hostnames();
ASSERT_EQ(1, hostnames.size());
EXPECT_EQ(hostname, hostnames[0]);
}
}
// Generate X509 CSR and issues corresponding certificate.
TEST_F(CertManagementTest, SignCert) {
CertRequestGenerator::Config gen_config;
gen_config.hostname = "foo.bar.com";
gen_config.user_id = "test-uid";
gen_config.kerberos_principal = "kudu/foo.bar.com@bar.com";
PrivateKey key;
const auto& csr = PrepareTestCSR(gen_config, &key);
Cert cert;
ASSERT_OK(CertSigner(&ca_cert_, &ca_private_key_).Sign(csr, &cert));
ASSERT_OK(cert.CheckKeyMatch(key));
EXPECT_EQ("C = US, ST = CA, O = MyCompany, CN = MyName, emailAddress = my@email.com",
cert.IssuerName());
EXPECT_EQ("UID = test-uid", cert.SubjectName());
EXPECT_EQ(gen_config.user_id, *cert.UserId());
EXPECT_EQ(gen_config.kerberos_principal, *cert.KuduKerberosPrincipal());
vector<string> hostnames = cert.Hostnames();
ASSERT_EQ(1, hostnames.size());
EXPECT_EQ("foo.bar.com", hostnames[0]);
}
// Generate X509 CA CSR and sign the result certificate.
TEST_F(CertManagementTest, SignCaCert) {
const CaCertRequestGenerator::Config gen_config(PrepareCaConfig("self-ca"));
PrivateKey key;
const auto& csr = PrepareTestCSR<CaCertRequestGenerator>(gen_config, &key);
Cert cert;
ASSERT_OK(CertSigner(&ca_cert_, &ca_private_key_).Sign(csr, &cert));
ASSERT_OK(cert.CheckKeyMatch(key));
}
// Test the creation and use of a CA which uses a self-signed CA cert
// generated on the fly.
TEST_F(CertManagementTest, TestSelfSignedCA) {
PrivateKey ca_key;
Cert ca_cert;
ASSERT_OK(GenerateSelfSignedCAForTests(&ca_key, &ca_cert));
// Create a key and CSR for the tablet server.
const auto& config = PrepareConfig();
PrivateKey ts_key;
CertSignRequest ts_csr = PrepareTestCSR(config, &ts_key);
// Sign it using the self-signed CA.
Cert ts_cert;
ASSERT_OK(CertSigner(&ca_cert, &ca_key).Sign(ts_csr, &ts_cert));
ASSERT_OK(ts_cert.CheckKeyMatch(ts_key));
}
// Check the transformation chains for X509 CSRs:
// internal -> PEM -> internal -> PEM
// internal -> DER -> internal -> DER
TEST_F(CertManagementTest, X509CsrFromAndToString) {
static const DataFormat kFormats[] = { DataFormat::PEM, DataFormat::DER };
PrivateKey key;
ASSERT_OK(GeneratePrivateKey(1024, &key));
CertRequestGenerator gen(PrepareConfig());
ASSERT_OK(gen.Init());
CertSignRequest req_ref;
ASSERT_OK(gen.GenerateRequest(key, &req_ref));
for (auto format : kFormats) {
SCOPED_TRACE(Substitute("X509 CSR format: $0", DataFormatToString(format)));
string str_req_ref;
ASSERT_OK(req_ref.ToString(&str_req_ref, format));
CertSignRequest req;
ASSERT_OK(req.FromString(str_req_ref, format));
string str_req;
ASSERT_OK(req.ToString(&str_req, format));
ASSERT_EQ(str_req_ref, str_req);
}
}
// Check the transformation chains for X509 certs:
// internal -> PEM -> internal -> PEM
// internal -> DER -> internal -> DER
TEST_F(CertManagementTest, X509FromAndToString) {
static const DataFormat kFormats[] = { DataFormat::PEM, DataFormat::DER };
PrivateKey key;
ASSERT_OK(GeneratePrivateKey(1024, &key));
CertRequestGenerator gen(PrepareConfig());
ASSERT_OK(gen.Init());
CertSignRequest req;
ASSERT_OK(gen.GenerateRequest(key, &req));
Cert cert_ref;
ASSERT_OK(CertSigner(&ca_cert_, &ca_private_key_)
.Sign(req, &cert_ref));
for (auto format : kFormats) {
SCOPED_TRACE(Substitute("X509 format: $0", DataFormatToString(format)));
string str_cert_ref;
ASSERT_OK(cert_ref.ToString(&str_cert_ref, format));
Cert cert;
ASSERT_OK(cert.FromString(str_cert_ref, format));
string str_cert;
ASSERT_OK(cert.ToString(&str_cert, format));
ASSERT_EQ(str_cert_ref, str_cert);
}
}
} // namespace ca
} // namespace security
} // namespace kudu