diff options
author | Tor Brede Vekterli <vekterli@verizonmedia.com> | 2020-02-26 10:43:40 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-26 10:43:40 +0100 |
commit | 16f36db67f619098dc3c97f60d697037face1ce2 (patch) | |
tree | 1ea27a1d65016f161738ee74a4b487bec8428791 /vespalib | |
parent | 250b2672975f7e01b2429bebd4dd437ea1a2d40e (diff) | |
parent | ae87223307b0fe3294e27d86719c3acb702ffed6 (diff) |
Merge pull request #12292 from vespa-engine/vekterli/move-crypto-utilty-code-out-to-vespalib
Move crypto utility code out into vespalib and use for test credentials
Diffstat (limited to 'vespalib')
26 files changed, 429 insertions, 264 deletions
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt index 9339cdacea0..979184acbae 100644 --- a/vespalib/CMakeLists.txt +++ b/vespalib/CMakeLists.txt @@ -30,6 +30,7 @@ vespa_define_module( src/tests/component src/tests/compress src/tests/compression + src/tests/crypto src/tests/data/databuffer src/tests/data/input_reader src/tests/data/lz4_encode_decode @@ -139,6 +140,7 @@ vespa_define_module( src/vespa/vespalib src/vespa/vespalib/btree src/vespa/vespalib/component + src/vespa/vespalib/crypto src/vespa/vespalib/data src/vespa/vespalib/data/slime src/vespa/vespalib/datastore diff --git a/vespalib/src/tests/crypto/CMakeLists.txt b/vespalib/src/tests/crypto/CMakeLists.txt new file mode 100644 index 00000000000..b930b5715b5 --- /dev/null +++ b/vespalib/src/tests/crypto/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_crypto_crypto_test_app TEST + SOURCES + crypto_test.cpp + DEPENDS + vespalib + gtest +) +vespa_add_test(NAME vespalib_crypto_crypto_test_app COMMAND vespalib_crypto_crypto_test_app) + diff --git a/vespalib/src/tests/crypto/crypto_test.cpp b/vespalib/src/tests/crypto/crypto_test.cpp new file mode 100644 index 00000000000..8daba954793 --- /dev/null +++ b/vespalib/src/tests/crypto/crypto_test.cpp @@ -0,0 +1,37 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/crypto/private_key.h> +#include <vespa/vespalib/crypto/x509_certificate.h> +#include <vespa/vespalib/gtest/gtest.h> +#include <gmock/gmock.h> + +using namespace ::testing; + +namespace vespalib::crypto { + +// FIXME these tests are very high level and simple since the current crypto utility API we provide +// is extremely simple and does not support loading PEMs, signing or verifying. + +TEST(CryptoTest, generated_p256_ec_private_key_can_be_exported_to_pem_format) { + auto key = PrivateKey::generate_p256_ec_key(); + auto pem = key->private_to_pem(); + EXPECT_THAT(pem, StartsWith("-----BEGIN PRIVATE KEY-----")); +} + +TEST(CryptoTest, generated_x509_certificate_can_be_exported_to_pem_format) { + auto dn = X509Certificate::DistinguishedName() + .country("NO").locality("Trondheim") + .organization("Cool Unit Test Writers") + .organizational_unit("Only the finest tests, yes") + .add_common_name("cooltests.example.com"); + auto subject = X509Certificate::SubjectInfo(std::move(dn)); + auto key = PrivateKey::generate_p256_ec_key(); + auto params = X509Certificate::Params::self_signed(std::move(subject), key); + auto cert = X509Certificate::generate_from(std::move(params)); + auto pem = cert->to_pem(); + EXPECT_THAT(pem, StartsWith("-----BEGIN CERTIFICATE-----")); +} + +} + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/vespalib/src/tests/net/tls/direct_buffer_bio/direct_buffer_bio_test.cpp b/vespalib/src/tests/net/tls/direct_buffer_bio/direct_buffer_bio_test.cpp index 134df9b17eb..a51cfb688c1 100644 --- a/vespalib/src/tests/net/tls/direct_buffer_bio/direct_buffer_bio_test.cpp +++ b/vespalib/src/tests/net/tls/direct_buffer_bio/direct_buffer_bio_test.cpp @@ -5,6 +5,7 @@ #include <cassert> using namespace vespalib; +using namespace vespalib::crypto; using namespace vespalib::net::tls::impl; struct Fixture { diff --git a/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt b/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt index e8f77d36e16..799e2291d7c 100644 --- a/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt +++ b/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. vespa_add_executable(vespalib_net_tls_openssl_impl_test_app TEST SOURCES - crypto_utils.cpp openssl_impl_test.cpp DEPENDS vespalib diff --git a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp index 54c8c19fc64..4586beef910 100644 --- a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp +++ b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp @@ -1,5 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "crypto_utils.h" +#include <vespa/vespalib/crypto/private_key.h> +#include <vespa/vespalib/crypto/x509_certificate.h> #include <vespa/vespalib/testkit/test_kit.h> #include <vespa/vespalib/data/smart_buffer.h> #include <vespa/vespalib/net/tls/authorization_mode.h> @@ -11,11 +12,11 @@ #include <vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h> #include <vespa/vespalib/test/make_tls_options_for_testing.h> #include <vespa/vespalib/test/peer_policy_utils.h> -#include <iostream> #include <stdexcept> #include <stdlib.h> using namespace vespalib; +using namespace vespalib::crypto; using namespace vespalib::net::tls; using namespace vespalib::net::tls::impl; diff --git a/vespalib/src/vespa/vespalib/CMakeLists.txt b/vespalib/src/vespa/vespalib/CMakeLists.txt index 4249f6333a4..95f6a407914 100644 --- a/vespalib/src/vespa/vespalib/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/CMakeLists.txt @@ -3,6 +3,7 @@ vespa_add_library(vespalib SOURCES $<TARGET_OBJECTS:vespalib_vespalib_btree> $<TARGET_OBJECTS:vespalib_vespalib_component> + $<TARGET_OBJECTS:vespalib_vespalib_crypto> $<TARGET_OBJECTS:vespalib_vespalib_data> $<TARGET_OBJECTS:vespalib_vespalib_data_slime> $<TARGET_OBJECTS:vespalib_vespalib_datastore> diff --git a/vespalib/src/vespa/vespalib/crypto/CMakeLists.txt b/vespalib/src/vespa/vespalib/crypto/CMakeLists.txt new file mode 100644 index 00000000000..6000156fcfa --- /dev/null +++ b/vespalib/src/vespa/vespalib/crypto/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(vespalib_vespalib_crypto OBJECT + SOURCES + crypto_exception.cpp + openssl_crypto_impl.cpp + private_key.cpp + x509_certificate.cpp + DEPENDS +) +find_package(OpenSSL) +target_include_directories(vespalib_vespalib_crypto PUBLIC ${OPENSSL_INCLUDE_DIR}) diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.cpp b/vespalib/src/vespa/vespalib/crypto/crypto_exception.cpp index 41bb2060c04..226d8664de6 100644 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.cpp +++ b/vespalib/src/vespa/vespalib/crypto/crypto_exception.cpp @@ -2,7 +2,7 @@ #include "crypto_exception.h" -namespace vespalib::net::tls { +namespace vespalib::crypto { VESPA_IMPLEMENT_EXCEPTION(CryptoException, Exception); diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.h b/vespalib/src/vespa/vespalib/crypto/crypto_exception.h index 696a158e058..0d0dcc8ceec 100644 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.h +++ b/vespalib/src/vespa/vespalib/crypto/crypto_exception.h @@ -3,7 +3,7 @@ #include <vespa/vespalib/util/exception.h> -namespace vespalib::net::tls { +namespace vespalib::crypto { VESPA_DEFINE_EXCEPTION(CryptoException, Exception); diff --git a/vespalib/src/tests/net/tls/openssl_impl/crypto_utils.cpp b/vespalib/src/vespa/vespalib/crypto/openssl_crypto_impl.cpp index 14755360b51..72efbb841c2 100644 --- a/vespalib/src/tests/net/tls/openssl_impl/crypto_utils.cpp +++ b/vespalib/src/vespa/vespalib/crypto/openssl_crypto_impl.cpp @@ -1,13 +1,12 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "crypto_utils.h" -#include <vespa/vespalib/net/tls/crypto_exception.h> +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "openssl_crypto_impl.h" +#include <vespa/vespalib/crypto/crypto_exception.h> #include <cassert> #include <openssl/bn.h> #include <openssl/rand.h> #include <openssl/x509v3.h> -namespace vespalib::net::tls::impl { +namespace vespalib::crypto::openssl_impl { namespace { @@ -61,6 +60,77 @@ BioPtr new_memory_bio() { return bio; } +} // anonymous namespace + +vespalib::string PrivateKeyImpl::private_to_pem() const { + BioPtr bio = new_memory_bio(); + // TODO this API is const-broken even on 1.1.1, revisit in the future... + auto* mutable_pkey = const_cast<::EVP_PKEY*>(_pkey.get()); + if (::PEM_write_bio_PrivateKey(bio.get(), mutable_pkey, nullptr, nullptr, + 0, nullptr, nullptr) != 1) { + throw CryptoException("PEM_write_bio_PrivateKey"); + } + return bio_to_string(*bio); +} + +std::shared_ptr<PrivateKeyImpl> PrivateKeyImpl::generate_openssl_p256_ec_key() { + // We first have to generate an EVP context for the keygen _parameters_... + EvpPkeyCtxPtr params_ctx(::EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); + if (!params_ctx) { + throw CryptoException("EVP_PKEY_CTX_new_id"); + } + if (::EVP_PKEY_paramgen_init(params_ctx.get()) != 1) { + throw CryptoException("EVP_PKEY_paramgen_init"); + } + // Set EC keygen parameters to use prime256v1, which is the same as P-256 + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(params_ctx.get(), NID_X9_62_prime256v1) <= 0) { + throw CryptoException("EVP_PKEY_CTX_set_ec_paramgen_curve_nid"); + } +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + // Must tag _explicitly_ as a named curve or many won't accept our pretty keys. + // If we don't do this, explicit curve parameters are included with the key, + // and this is not widely supported nor needed since we're generating a key on + // a standardized curve. + if (EVP_PKEY_CTX_set_ec_param_enc(params_ctx.get(), OPENSSL_EC_NAMED_CURVE) <= 0) { + throw CryptoException("EVP_PKEY_CTX_set_ec_param_enc"); + } +#endif + // Note: despite being an EVP_PKEY this is not an actual key, just key parameters! + ::EVP_PKEY* params_raw = nullptr; + if (::EVP_PKEY_paramgen(params_ctx.get(), ¶ms_raw) != 1) { + throw CryptoException("EVP_PKEY_paramgen"); + } + EvpPkeyPtr params(params_raw); + // Now we can create a context for the proper key generation + EvpPkeyCtxPtr key_ctx(::EVP_PKEY_CTX_new(params.get(), nullptr)); + if (!params_ctx) { + throw CryptoException("EVP_PKEY_CTX_new"); + } + if (::EVP_PKEY_keygen_init(key_ctx.get()) != 1) { + throw CryptoException("EVP_PKEY_keygen_init"); + } + // Finally, it's time to generate the key pair itself. + ::EVP_PKEY* pkey_raw = nullptr; + if (::EVP_PKEY_keygen(key_ctx.get(), &pkey_raw) != 1) { + throw CryptoException("EVP_PKEY_keygen"); + } + EvpPkeyPtr generated_key(pkey_raw); +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) + // On OpenSSL versions prior to 1.1.0, we must set the named curve ASN1 flag + // directly on the EC_KEY, as the EVP_PKEY wrapper doesn't exist (this is a + // half truth, as it exists on 1.0.2 stable, but not necessarily on all 1.0.2 + // versions, and certainly not on 1.0.1). + EcKeyPtr ec_key(::EVP_PKEY_get1_EC_KEY(generated_key.get())); // Bumps ref count, needs free + if (!ec_key) { + throw CryptoException("EVP_PKEY_get1_EC_KEY"); + } + ::EC_KEY_set_asn1_flag(ec_key.get(), OPENSSL_EC_NAMED_CURVE); +#endif + return std::make_shared<PrivateKeyImpl>(std::move(generated_key), Type::EC); +} + +namespace { + void assign_random_positive_serial_number(::X509& cert) { /* * From RFC3280, section 4.1.2.2: @@ -165,88 +235,23 @@ void add_any_subject_alternate_names(::X509& subject, ::X509& issuer, } } -} // anon ns - -vespalib::string PrivateKey::private_to_pem() const { - BioPtr bio = new_memory_bio(); - // TODO this API is const-broken even on 1.1.1, revisit in the future... - auto* mutable_pkey = const_cast<::EVP_PKEY*>(_pkey.get()); - if (::PEM_write_bio_PrivateKey(bio.get(), mutable_pkey, nullptr, nullptr, - 0, nullptr, nullptr) != 1) { - throw CryptoException("PEM_write_bio_PrivateKey"); - } - return bio_to_string(*bio); -} - -std::shared_ptr<PrivateKey> PrivateKey::generate_p256_ec_key() { - // We first have to generate an EVP context for the keygen _parameters_... - EvpPkeyCtxPtr params_ctx(::EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr)); - if (!params_ctx) { - throw CryptoException("EVP_PKEY_CTX_new_id"); - } - if (::EVP_PKEY_paramgen_init(params_ctx.get()) != 1) { - throw CryptoException("EVP_PKEY_paramgen_init"); - } - // Set EC keygen parameters to use prime256v1, which is the same as P-256 - if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(params_ctx.get(), NID_X9_62_prime256v1) <= 0) { - throw CryptoException("EVP_PKEY_CTX_set_ec_paramgen_curve_nid"); - } -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - // Must tag _explicitly_ as a named curve or many won't accept our pretty keys. - // If we don't do this, explicit curve parameters are included with the key, - // and this is not widely supported nor needed since we're generating a key on - // a standardized curve. - if (EVP_PKEY_CTX_set_ec_param_enc(params_ctx.get(), OPENSSL_EC_NAMED_CURVE) <= 0) { - throw CryptoException("EVP_PKEY_CTX_set_ec_param_enc"); - } -#endif - // Note: despite being an EVP_PKEY this is not an actual key, just key parameters! - ::EVP_PKEY* params_raw = nullptr; - if (::EVP_PKEY_paramgen(params_ctx.get(), ¶ms_raw) != 1) { - throw CryptoException("EVP_PKEY_paramgen"); - } - EvpPkeyPtr params(params_raw); - // Now we can create a context for the proper key generation - EvpPkeyCtxPtr key_ctx(::EVP_PKEY_CTX_new(params.get(), nullptr)); - if (!params_ctx) { - throw CryptoException("EVP_PKEY_CTX_new"); - } - if (::EVP_PKEY_keygen_init(key_ctx.get()) != 1) { - throw CryptoException("EVP_PKEY_keygen_init"); - } - // Finally, it's time to generate the key pair itself. - ::EVP_PKEY* pkey_raw = nullptr; - if (::EVP_PKEY_keygen(key_ctx.get(), &pkey_raw) != 1) { - throw CryptoException("EVP_PKEY_keygen"); - } - EvpPkeyPtr generated_key(pkey_raw); -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - // On OpenSSL versions prior to 1.1.0, we must set the named curve ASN1 flag - // directly on the EC_KEY, as the EVP_PKEY wrapper doesn't exist (this is a - // half truth, as it exists on 1.0.2 stable, but not necessarily on all 1.0.2 - // versions, and certainly not on 1.0.1). - EcKeyPtr ec_key(::EVP_PKEY_get1_EC_KEY(generated_key.get())); // Bumps ref count, needs free - if (!ec_key) { - throw CryptoException("EVP_PKEY_get1_EC_KEY"); - } - ::EC_KEY_set_asn1_flag(ec_key.get(), OPENSSL_EC_NAMED_CURVE); -#endif - return std::make_shared<PrivateKey>(std::move(generated_key), Type::EC); -} +} // anonymous namespace // Some references: // https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl // https://opensource.apple.com/source/OpenSSL/OpenSSL-22/openssl/demos/x509/mkcert.c -std::shared_ptr<X509Certificate> X509Certificate::generate_from(Params params) { +std::shared_ptr<X509CertificateImpl> X509CertificateImpl::generate_openssl_x509_from(Params params) { X509Ptr cert(::X509_new()); if (!cert) { throw CryptoException("X509_new"); } + // FIXME make this const, currently is not due to OpenSSL API const issues (ugh). + auto& subject_key_impl = dynamic_cast<PrivateKeyImpl&>(*params.subject_key); ::X509_set_version(cert.get(), 2); // 2 actually means v3 :) assign_random_positive_serial_number(*cert); set_certificate_expires_from_now(*cert, params.valid_for); // Internal key copy; does not take ownership - if (::X509_set_pubkey(cert.get(), params.subject_key->native_key()) != 1) { + if (::X509_set_pubkey(cert.get(), subject_key_impl.native_key()) != 1) { throw CryptoException("X509_set_pubkey"); } // The "subject" is the target entity the certificate is intended to, well, certify. @@ -259,13 +264,14 @@ std::shared_ptr<X509Certificate> X509Certificate::generate_from(Params params) { // If we _do_ have an issuer, we'll record its Subject name as our Issuer name. // Note that it's legal to have a self-signed non-CA certificate, though it obviously // cannot be used to sign any subordinate certificates. - ::X509_NAME* issuer_name = (params.issuer - ? ::X509_get_subject_name(params.issuer->native_cert()) + auto* issuer_cert_impl = dynamic_cast<X509CertificateImpl*>(params.issuer.get()); // May be nullptr. + ::X509_NAME* issuer_name = (issuer_cert_impl + ? ::X509_get_subject_name(issuer_cert_impl->native_cert()) : subj_name); if (::X509_set_issuer_name(cert.get(), issuer_name) != 1) { // Makes internal copy throw CryptoException("X509_set_issuer_name"); } - ::X509& issuer_cert = params.issuer ? *params.issuer->native_cert() : *cert; + ::X509& issuer_cert = issuer_cert_impl ? *issuer_cert_impl->native_cert() : *cert; const char* basic_constraints = params.is_ca ? "critical,CA:TRUE" : "critical,CA:FALSE"; const char* key_usage = params.is_ca ? "critical,keyCertSign,digitalSignature" @@ -278,13 +284,14 @@ std::shared_ptr<X509Certificate> X509Certificate::generate_from(Params params) { add_v3_ext(*cert, issuer_cert, NID_authority_key_identifier, "keyid:always"); add_any_subject_alternate_names(*cert, issuer_cert, params.subject_info.subject_alt_names); - if (::X509_sign(cert.get(), params.issuer_key->native_key(), ::EVP_sha256()) == 0) { + auto& issuer_key_impl = dynamic_cast<PrivateKeyImpl&>(*params.issuer_key); + if (::X509_sign(cert.get(), issuer_key_impl.native_key(), ::EVP_sha256()) == 0) { throw CryptoException("X509_sign"); } - return std::make_shared<X509Certificate>(std::move(cert)); + return std::make_shared<X509CertificateImpl>(std::move(cert)); } -vespalib::string X509Certificate::to_pem() const { +vespalib::string X509CertificateImpl::to_pem() const { BioPtr bio = new_memory_bio(); // TODO this API is const-broken, revisit in the future... auto* mutable_cert = const_cast<::X509*>(_cert.get()); @@ -295,47 +302,4 @@ vespalib::string X509Certificate::to_pem() const { } -X509Certificate::DistinguishedName::DistinguishedName() = default; -X509Certificate::DistinguishedName::DistinguishedName(const DistinguishedName&) = default; -X509Certificate::DistinguishedName& X509Certificate::DistinguishedName::operator=(const DistinguishedName&) = default; -X509Certificate::DistinguishedName::DistinguishedName(DistinguishedName&&) = default; -X509Certificate::DistinguishedName& X509Certificate::DistinguishedName::operator=(DistinguishedName&&) = default; -X509Certificate::DistinguishedName::~DistinguishedName() = default; - -X509Certificate::Params::Params() = default; -X509Certificate::Params::~Params() = default; - -X509Certificate::Params -X509Certificate::Params::self_signed(SubjectInfo subject, - std::shared_ptr<PrivateKey> key) { - Params params; - params.subject_info = std::move(subject); - params.subject_key = key; - params.issuer_key = std::move(key); // self-signed, subject == issuer - params.is_ca = true; - return params; -} - -X509Certificate::Params -X509Certificate::Params::issued_by(SubjectInfo subject, - std::shared_ptr<PrivateKey> subject_key, - std::shared_ptr<X509Certificate> issuer, - std::shared_ptr<PrivateKey> issuer_key) { - Params params; - params.subject_info = std::move(subject); - params.issuer = std::move(issuer); - params.subject_key = std::move(subject_key); - params.issuer_key = std::move(issuer_key); - params.is_ca = false; // By default, caller can change for intermediate CAs - return params; -} - -CertKeyWrapper::CertKeyWrapper(std::shared_ptr<X509Certificate> cert_, - std::shared_ptr<PrivateKey> key_) - : cert(std::move(cert_)), - key(std::move(key_)) -{} - -CertKeyWrapper::~CertKeyWrapper() = default; - } diff --git a/vespalib/src/vespa/vespalib/crypto/openssl_crypto_impl.h b/vespalib/src/vespa/vespalib/crypto/openssl_crypto_impl.h new file mode 100644 index 00000000000..87e4ee14e65 --- /dev/null +++ b/vespalib/src/vespa/vespalib/crypto/openssl_crypto_impl.h @@ -0,0 +1,44 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/crypto/openssl_typedefs.h> +#include "private_key.h" +#include "x509_certificate.h" + +namespace vespalib::crypto::openssl_impl { + +class PrivateKeyImpl : public PrivateKey { + EvpPkeyPtr _pkey; + Type _type; +public: + PrivateKeyImpl(EvpPkeyPtr pkey, Type type) + : _pkey(std::move(pkey)), + _type(type) + {} + ~PrivateKeyImpl() override = default; + + ::EVP_PKEY* native_key() noexcept { return _pkey.get(); } + const ::EVP_PKEY* native_key() const noexcept { return _pkey.get(); } + + Type type() const noexcept override { return _type; } + vespalib::string private_to_pem() const override; + + static std::shared_ptr<PrivateKeyImpl> generate_openssl_p256_ec_key(); +}; + +class X509CertificateImpl : public X509Certificate { + X509Ptr _cert; +public: + explicit X509CertificateImpl(X509Ptr cert) : _cert(std::move(cert)) {} + ~X509CertificateImpl() = default; + + ::X509* native_cert() noexcept { return _cert.get(); } + const ::X509* native_cert() const noexcept { return _cert.get(); } + + vespalib::string to_pem() const override; + + // Generates an X509 certificate using a SHA-256 digest + static std::shared_ptr<X509CertificateImpl> generate_openssl_x509_from(Params params); +}; + +} diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_typedefs.h b/vespalib/src/vespa/vespalib/crypto/openssl_typedefs.h index afafe556338..2986a4515f7 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_typedefs.h +++ b/vespalib/src/vespa/vespalib/crypto/openssl_typedefs.h @@ -6,7 +6,7 @@ #include <openssl/crypto.h> #include <openssl/x509.h> -namespace vespalib::net::tls::impl { +namespace vespalib::crypto { struct BioDeleter { void operator()(::BIO* bio) const noexcept { diff --git a/vespalib/src/vespa/vespalib/crypto/private_key.cpp b/vespalib/src/vespa/vespalib/crypto/private_key.cpp new file mode 100644 index 00000000000..7ece9418bef --- /dev/null +++ b/vespalib/src/vespa/vespalib/crypto/private_key.cpp @@ -0,0 +1,11 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "private_key.h" +#include "openssl_crypto_impl.h" + +namespace vespalib::crypto { + +std::shared_ptr<PrivateKey> PrivateKey::generate_p256_ec_key() { + return openssl_impl::PrivateKeyImpl::generate_openssl_p256_ec_key(); +} + +} diff --git a/vespalib/src/vespa/vespalib/crypto/private_key.h b/vespalib/src/vespa/vespalib/crypto/private_key.h new file mode 100644 index 00000000000..7ac5c31502c --- /dev/null +++ b/vespalib/src/vespa/vespalib/crypto/private_key.h @@ -0,0 +1,34 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <vespa/vespalib/stllike/string.h> +#include <memory> + +namespace vespalib::crypto { + +/* + * Represents an asymmetric cryptographic private key. + * + * Can only be used for private/public key crypto, not for secret key (e.g. AES) crypto. + * Currently only supports generating EC keys on the standard P-256 curve. + */ +class PrivateKey { +public: + enum class Type { + EC, + RSA // TODO implement support..! + }; + + virtual ~PrivateKey() = default; + + virtual Type type() const noexcept = 0; + // TODO should have a wrapper for this that takes care to securely erase + // string memory on destruction. + virtual vespalib::string private_to_pem() const = 0; + + static std::shared_ptr<PrivateKey> generate_p256_ec_key(); +protected: + PrivateKey() = default; +}; + +} diff --git a/vespalib/src/vespa/vespalib/crypto/x509_certificate.cpp b/vespalib/src/vespa/vespalib/crypto/x509_certificate.cpp new file mode 100644 index 00000000000..ecd061e573a --- /dev/null +++ b/vespalib/src/vespa/vespalib/crypto/x509_certificate.cpp @@ -0,0 +1,62 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "x509_certificate.h" +#include "openssl_crypto_impl.h" + +namespace vespalib::crypto { + +X509Certificate::DistinguishedName::DistinguishedName() = default; +X509Certificate::DistinguishedName::DistinguishedName(const DistinguishedName&) = default; +X509Certificate::DistinguishedName& X509Certificate::DistinguishedName::operator=(const DistinguishedName&) = default; +X509Certificate::DistinguishedName::DistinguishedName(DistinguishedName&&) noexcept = default; +X509Certificate::DistinguishedName& X509Certificate::DistinguishedName::operator=(DistinguishedName&&) noexcept = default; +X509Certificate::DistinguishedName::~DistinguishedName() = default; + +X509Certificate::Params::Params() = default; +X509Certificate::Params::~Params() = default; + +X509Certificate::Params::Params(const Params&) = default; +X509Certificate::Params& X509Certificate::Params::operator=(const Params&) = default; +X509Certificate::Params::Params(Params&&) noexcept = default; +X509Certificate::Params& X509Certificate::Params::operator=(Params&&) noexcept = default; + +X509Certificate::Params +X509Certificate::Params::self_signed(SubjectInfo subject, + std::shared_ptr<PrivateKey> key) +{ + Params params; + params.subject_info = std::move(subject); + params.subject_key = key; + params.issuer_key = std::move(key); // self-signed, subject == issuer + params.is_ca = true; + return params; +} + +X509Certificate::Params +X509Certificate::Params::issued_by(SubjectInfo subject, + std::shared_ptr<PrivateKey> subject_key, + std::shared_ptr<X509Certificate> issuer, + std::shared_ptr<PrivateKey> issuer_key) +{ + Params params; + params.subject_info = std::move(subject); + params.issuer = std::move(issuer); + params.subject_key = std::move(subject_key); + params.issuer_key = std::move(issuer_key); + params.is_ca = false; // By default, caller can change for intermediate CAs + return params; +} + +std::shared_ptr<X509Certificate> X509Certificate::generate_from(Params params) { + return openssl_impl::X509CertificateImpl::generate_openssl_x509_from(std::move(params)); +} + +CertKeyWrapper::CertKeyWrapper(std::shared_ptr<X509Certificate> cert_, + std::shared_ptr<PrivateKey> key_) + : cert(std::move(cert_)), + key(std::move(key_)) +{} + +CertKeyWrapper::~CertKeyWrapper() = default; + +} diff --git a/vespalib/src/tests/net/tls/openssl_impl/crypto_utils.h b/vespalib/src/vespa/vespalib/crypto/x509_certificate.h index 017dfbdbc12..9eea423c5a0 100644 --- a/vespalib/src/tests/net/tls/openssl_impl/crypto_utils.h +++ b/vespalib/src/vespa/vespalib/crypto/x509_certificate.h @@ -1,52 +1,32 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once +#include "private_key.h" #include <vespa/vespalib/stllike/string.h> -#include <vespa/vespalib/net/tls/impl/openssl_typedefs.h> #include <chrono> #include <memory> #include <vector> -// TODOs -// - add unit testing -// - extend interfaces (separate PublicKey etc) -// - hide all OpenSSL details from header -// - move to appropriate new namespace/directory somewhere under vespalib - -namespace vespalib::net::tls::impl { - -class PrivateKey { -public: - enum class Type { - EC, - RSA // TODO implement support..! - }; -private: - EvpPkeyPtr _pkey; - Type _type; -public: - PrivateKey(EvpPkeyPtr pkey, Type type) - : _pkey(std::move(pkey)), - _type(type) - {} - - ::EVP_PKEY* native_key() noexcept { return _pkey.get(); } - const ::EVP_PKEY* native_key() const noexcept { return _pkey.get(); } - - Type type() const noexcept { return _type; } - vespalib::string private_to_pem() const; - - static std::shared_ptr<PrivateKey> generate_p256_ec_key(); -}; - - +namespace vespalib::crypto { + +/** + * Represents an X509 certificate instance and provides utility methods + * for generating new certificates on the fly. Certificates can be created + * for both Certificate Authorities and regular hosts (leaves). + * + * This implementation aims to ensure that best cryptographic practices are + * followed automatically. In particular: + * - The certificate digest is always SHA-256, never SHA-1 or MD5 + * - The certificate serial number is a 160-bit secure random sequence + * (technically 159 bits since the MSB is always zero) rather than a + * collision-prone or predictable sequence number. + * + */ class X509Certificate { - X509Ptr _cert; public: - explicit X509Certificate(X509Ptr cert) : _cert(std::move(cert)) {} + virtual ~X509Certificate() = default; - ::X509* native_cert() noexcept { return _cert.get(); } - const ::X509* native_cert() const noexcept { return _cert.get(); } + virtual vespalib::string to_pem() const = 0; struct DistinguishedName { vespalib::string _country; // "C" @@ -61,9 +41,8 @@ public: DistinguishedName(); DistinguishedName(const DistinguishedName&); DistinguishedName& operator=(const DistinguishedName&); - // TODO make these noexcept once vespalib::string has noexcept move.. or move at all! - DistinguishedName(DistinguishedName&&); - DistinguishedName& operator=(DistinguishedName&&); + DistinguishedName(DistinguishedName&&) noexcept; + DistinguishedName& operator=(DistinguishedName&&) noexcept; ~DistinguishedName(); // TODO could add rvalue overloads as well... @@ -101,8 +80,13 @@ public: Params(); ~Params(); + Params(const Params&); + Params& operator=(const Params&); + Params(Params&&) noexcept; + Params& operator=(Params&&) noexcept; + SubjectInfo subject_info; - // TODO make public key, but private key has both and this is currently just for testing. + // TODO make public key, but private key has both. std::shared_ptr<PrivateKey> subject_key; std::shared_ptr<X509Certificate> issuer; // May be nullptr for self-signed certs std::shared_ptr<PrivateKey> issuer_key; @@ -119,9 +103,14 @@ public: // Generates an X509 certificate using a SHA-256 digest static std::shared_ptr<X509Certificate> generate_from(Params params); - vespalib::string to_pem() const; +protected: + X509Certificate() = default; }; +/* + * Simple wrapper for storing both a X509 certificate and the private key + * that signed it. Useful for testing. + */ struct CertKeyWrapper { std::shared_ptr<X509Certificate> cert; std::shared_ptr<PrivateKey> key; diff --git a/vespalib/src/vespa/vespalib/net/crypto_engine.cpp b/vespalib/src/vespa/vespalib/net/crypto_engine.cpp index a92b0e06bbe..7bbc4b7523c 100644 --- a/vespalib/src/vespa/vespalib/net/crypto_engine.cpp +++ b/vespalib/src/vespa/vespalib/net/crypto_engine.cpp @@ -2,9 +2,9 @@ #include "crypto_engine.h" #include <vespa/vespalib/data/smart_buffer.h> +#include <vespa/vespalib/crypto/crypto_exception.h> #include <vespa/vespalib/net/tls/authorization_mode.h> #include <vespa/vespalib/net/tls/auto_reloading_tls_crypto_engine.h> -#include <vespa/vespalib/net/tls/crypto_exception.h> #include <vespa/vespalib/net/tls/maybe_tls_crypto_engine.h> #include <vespa/vespalib/net/tls/statistics.h> #include <vespa/vespalib/net/tls/tls_crypto_engine.h> @@ -232,7 +232,7 @@ CryptoEngine::SP create_default_crypto_engine() { CryptoEngine::SP try_create_default_crypto_engine() { try { return create_default_crypto_engine(); - } catch (net::tls::CryptoException &e) { + } catch (crypto::CryptoException &e) { LOG(error, "failed to create default crypto engine: %s", e.what()); std::_Exit(78); } diff --git a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt index 6dc48db68e4..b7801f40959 100644 --- a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt @@ -5,7 +5,6 @@ vespa_add_library(vespalib_vespalib_net_tls OBJECT auto_reloading_tls_crypto_engine.cpp crypto_codec.cpp crypto_codec_adapter.cpp - crypto_exception.cpp maybe_tls_crypto_engine.cpp maybe_tls_crypto_socket.cpp peer_credentials.cpp diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.cpp index 614722a9769..d7d02534242 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.cpp @@ -1,11 +1,10 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "direct_buffer_bio.h" -#include <vespa/vespalib/net/tls/crypto_exception.h> +#include <vespa/vespalib/crypto/crypto_exception.h> +#include <vespa/vespalib/util/backtrace.h> #include <utility> #include <cassert> -#include <vespa/vespalib/util/backtrace.h> - #include <vespa/log/log.h> LOG_SETUP(".vespalib.net.tls.impl.direct_buffer_bio"); @@ -19,6 +18,8 @@ LOG_SETUP(".vespalib.net.tls.impl.direct_buffer_bio"); * - https://github.com/indutny/uv_ssl_t/blob/master/src/bio.c */ +using namespace vespalib::crypto; + namespace vespalib::net::tls::impl { namespace { diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.h b/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.h index 581d43d6f29..8492bf2c436 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.h +++ b/vespalib/src/vespa/vespalib/net/tls/impl/direct_buffer_bio.h @@ -1,7 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include "openssl_typedefs.h" +#include <vespa/vespalib/crypto/openssl_typedefs.h> #include <openssl/bio.h> /* @@ -22,8 +22,8 @@ namespace vespalib::net::tls::impl { -BioPtr new_mutable_direct_buffer_bio(); -BioPtr new_const_direct_buffer_bio(); +crypto::BioPtr new_mutable_direct_buffer_bio(); +crypto::BioPtr new_const_direct_buffer_bio(); struct MutableBufferView { // Could use a pointer pair instead (or just modify the ptr), but being explicit is good for readability. diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp index 6a79caa8264..1d87a50190e 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp @@ -3,8 +3,8 @@ #include "openssl_tls_context_impl.h" #include "direct_buffer_bio.h" +#include <vespa/vespalib/crypto/crypto_exception.h> #include <vespa/vespalib/net/tls/crypto_codec.h> -#include <vespa/vespalib/net/tls/crypto_exception.h> #include <vespa/vespalib/net/tls/statistics.h> #include <mutex> @@ -36,6 +36,8 @@ LOG_SETUP(".vespalib.net.tls.openssl_crypto_codec_impl"); * light fades and turns to all-enveloping darkness. */ +using namespace vespalib::crypto; + namespace vespalib::net::tls::impl { namespace { diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h index ec8df853c16..80f3e12786a 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h @@ -1,7 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include "openssl_typedefs.h" +#include <vespa/vespalib/crypto/openssl_typedefs.h> #include <vespa/vespalib/net/socket_address.h> #include <vespa/vespalib/net/socket_spec.h> #include <vespa/vespalib/net/tls/transport_security_options.h> @@ -47,12 +47,12 @@ class OpenSslCryptoCodecImpl : public CryptoCodec { // The context maintains shared verification callback state, so it must be // kept alive explictly for at least as long as any codecs. std::shared_ptr<OpenSslTlsContextImpl> _ctx; - SocketSpec _peer_spec; - SocketAddress _peer_address; - SslPtr _ssl; - ::BIO* _input_bio; // Owned by _ssl - ::BIO* _output_bio; // Owned by _ssl - Mode _mode; + SocketSpec _peer_spec; + SocketAddress _peer_address; + crypto::SslPtr _ssl; + ::BIO* _input_bio; // Owned by _ssl + ::BIO* _output_bio; // Owned by _ssl + Mode _mode; std::optional<DeferredHandshakeParams> _deferred_handshake_params; std::optional<HandshakeResult> _deferred_handshake_result; public: diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp index 98675ec6b0b..e66baf87999 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp @@ -1,9 +1,9 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "iana_cipher_map.h" -#include "openssl_typedefs.h" #include "openssl_tls_context_impl.h" #include "openssl_crypto_codec_impl.h" -#include <vespa/vespalib/net/tls/crypto_exception.h> +#include <vespa/vespalib/crypto/crypto_exception.h> +#include <vespa/vespalib/crypto/openssl_typedefs.h> #include <vespa/vespalib/net/tls/statistics.h> #include <vespa/vespalib/net/tls/transport_security_options.h> #include <vespa/vespalib/util/stringfmt.h> @@ -26,6 +26,8 @@ LOG_SETUP(".vespalib.net.tls.openssl_tls_context_impl"); # error "Provided OpenSSL version is too darn old, need at least 1.0.0" #endif +using namespace vespalib::crypto; + namespace vespalib::net::tls::impl { namespace { diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h index c519b1ae874..badfe8306d1 100644 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h +++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h @@ -1,7 +1,7 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #pragma once -#include "openssl_typedefs.h" +#include <vespa/vespalib/crypto/openssl_typedefs.h> #include <vespa/vespalib/net/socket_address.h> #include <vespa/vespalib/net/tls/tls_context.h> #include <vespa/vespalib/net/tls/transport_security_options.h> @@ -13,7 +13,7 @@ namespace vespalib::net::tls::impl { class OpenSslTlsContextImpl : public TlsContext { - SslCtxPtr _ctx; + crypto::SslCtxPtr _ctx; AuthorizationMode _authorization_mode; std::shared_ptr<CertificateVerificationCallback> _cert_verify_callback; TransportSecurityOptions _redacted_transport_options; diff --git a/vespalib/src/vespa/vespalib/test/make_tls_options_for_testing.cpp b/vespalib/src/vespa/vespalib/test/make_tls_options_for_testing.cpp index b31f2830976..ad46e7272cb 100644 --- a/vespalib/src/vespa/vespalib/test/make_tls_options_for_testing.cpp +++ b/vespalib/src/vespa/vespalib/test/make_tls_options_for_testing.cpp @@ -1,82 +1,77 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "make_tls_options_for_testing.h" +#include <vespa/vespalib/crypto/private_key.h> +#include <vespa/vespalib/crypto/x509_certificate.h> -/* - * Generated with the following commands: - * - * openssl ecparam -name prime256v1 -genkey -noout -out ca.key - * - * openssl req -new -x509 -nodes -key ca.key \ - * -sha256 -out ca.pem \ - * -subj '/C=US/L=LooneyVille/O=ACME/OU=ACME test CA/CN=acme.example.com' \ - * -days 10000 - * - * openssl ecparam -name prime256v1 -genkey -noout -out host.key - * - * openssl req -new -key host.key -out host.csr \ - * -subj '/C=US/L=LooneyVille/O=Wile. E. Coyote, Ltd./CN=wile.example.com' \ - * -sha256 - * - * openssl x509 -req -in host.csr \ - * -CA ca.pem \ - * -CAkey ca.key \ - * -CAcreateserial \ - * -out host.pem \ - * -days 10000 \ - * -sha256 - * - * TODO generate keypairs and certs at test-time to avoid any hard-coding - * There certs are valid until 2046, so that buys us some time..! - */ - -// ca.pem -constexpr const char* ca_pem = R"(-----BEGIN CERTIFICATE----- -MIIBuDCCAV4CCQDpVjQIixTxvDAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU -MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD -TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx -MDU3NDVaFw00NjAxMTYxMDU3NDVaMGQxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM -b29uZXlWaWxsZTENMAsGA1UECgwEQUNNRTEVMBMGA1UECwwMQUNNRSB0ZXN0IENB -MRkwFwYDVQQDDBBhY21lLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAE1L7IzCN5pbyVnBATIHieuxq+hf9kWyn5yfjkXMhD52T5ITz1huq4nbiN -YtRoRP7XmipI60R/uiCHzERcsVz4rDAKBggqhkjOPQQDAgNIADBFAiEA6wmZDBca -y0aJ6ABtjbjx/vlmVDxdkaSZSgO8h2CkvIECIFktCkbZhDFfSvbqUScPOGuwkdGQ -L/EW2Bxp+1BPcYoZ ------END CERTIFICATE-----)"; - -// host.pem -constexpr const char* cert_pem = R"(-----BEGIN CERTIFICATE----- -MIIBsTCCAVgCCQD6GfDh0ltpsjAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU -MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD -TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx -MDU3NDVaFw00NjAxMTYxMDU3NDVaMF4xCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM -b29uZXlWaWxsZTEeMBwGA1UECgwVV2lsZS4gRS4gQ295b3RlLCBMdGQuMRkwFwYD -VQQDDBB3aWxlLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -e+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2npxYSKVCyo3a -/Vo33V8/H0WgOXioKEZJxDAKBggqhkjOPQQDAgNHADBEAiAN+87hQuGv3z0Ja2BV -b8PHq2vp3BJHjeMuxWu4BFPn0QIgYlvIHikspgGatXRNMZ1gPC0oCccsJFcie+Cw -zL06UPI= ------END CERTIFICATE-----)"; - -// host.key -constexpr const char* key_pem = R"(-----BEGIN EC PRIVATE KEY----- -MHcCAQEEID6di2PFYn8hPrxPbkFDGkSqF+K8L520In7nx3g0jwzOoAoGCCqGSM49 -AwEHoUQDQgAEe+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2 -npxYSKVCyo3a/Vo33V8/H0WgOXioKEZJxA== ------END EC PRIVATE KEY-----)"; +namespace { + +using namespace vespalib::crypto; + +struct TransientCryptoCredentials { + CertKeyWrapper root_ca; + CertKeyWrapper host_creds; + vespalib::net::tls::TransportSecurityOptions cached_transport_options; + + TransientCryptoCredentials(); + ~TransientCryptoCredentials(); + + static CertKeyWrapper make_root_ca() { + auto dn = X509Certificate::DistinguishedName() + .country("US").state("CA").locality("Sunnyvale") + .organization("ACME, Inc.") + .organizational_unit("ACME Root CA") + .add_common_name("acme.example.com"); + auto subject = X509Certificate::SubjectInfo(std::move(dn)); + auto key = PrivateKey::generate_p256_ec_key(); + auto params = X509Certificate::Params::self_signed(std::move(subject), key); + auto cert = X509Certificate::generate_from(std::move(params)); + return {std::move(cert), std::move(key)}; + } + + static CertKeyWrapper make_host_creds(const CertKeyWrapper& root_ca_creds) { + auto dn = X509Certificate::DistinguishedName() + .country("US").state("CA").locality("Sunnyvale") + .organization("Wile E. Coyote, Ltd.") + .organizational_unit("Unit Testing and Anvil Dropping Division") + .add_common_name("localhost"); // Should technically not be needed, but including it anyway. + auto subject = X509Certificate::SubjectInfo(std::move(dn)); + subject.add_subject_alt_name("DNS:localhost"); + auto key = PrivateKey::generate_p256_ec_key(); + auto params = X509Certificate::Params::issued_by(std::move(subject), key, root_ca_creds.cert, root_ca_creds.key); + params.valid_for = std::chrono::hours(1); + auto cert = X509Certificate::generate_from(std::move(params)); + return {std::move(cert), std::move(key)}; + } + + static const TransientCryptoCredentials& instance(); +}; + +TransientCryptoCredentials::TransientCryptoCredentials() + : root_ca(make_root_ca()), + host_creds(make_host_creds(root_ca)), + cached_transport_options(vespalib::net::tls::TransportSecurityOptions::Params(). + ca_certs_pem(root_ca.cert->to_pem()). + cert_chain_pem(host_creds.cert->to_pem()). + private_key_pem(host_creds.key->private_to_pem()). + authorized_peers(vespalib::net::tls::AuthorizedPeers::allow_all_authenticated())) +{} + +TransientCryptoCredentials::~TransientCryptoCredentials() = default; + +const TransientCryptoCredentials& TransientCryptoCredentials::instance() { + static TransientCryptoCredentials test_creds; + return test_creds; +} + +} namespace vespalib::test { SocketSpec local_spec("tcp/localhost:123"); vespalib::net::tls::TransportSecurityOptions make_tls_options_for_testing() { - auto ts_builder = vespalib::net::tls::TransportSecurityOptions::Params(). - ca_certs_pem(ca_pem). - cert_chain_pem(cert_pem). - private_key_pem(key_pem). - authorized_peers(vespalib::net::tls::AuthorizedPeers::allow_all_authenticated()). - disable_hostname_validation(true); // FIXME this is to avoid mass breakage of TLS'd networking tests. - return vespalib::net::tls::TransportSecurityOptions(std::move(ts_builder)); + return TransientCryptoCredentials::instance().cached_transport_options; } } // namespace vespalib::test |