Skip to content

Commit

Permalink
Add support for different pkey types and sizes (#342)
Browse files Browse the repository at this point in the history
Currently only RSA_2048, RSA_3072, RSA_4096, ECDSA_P256
is supported. If the pkey type is not specified in
config file, original server pkey type will be copied.
The default value is RSA_2048.

Signed-off-by: Luyao Zhong <[email protected]>
  • Loading branch information
Luyao Zhong authored Nov 22, 2022
1 parent 05d4e80 commit f7aee0c
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#protodoc-title: Local Certificate Provider]

// [#extension: envoy.certificate_providers.local_certificate]
// [#next-free-field: 7]
message LocalCertificate {
enum Pkey {
UNSPECIFIED = 0;
RSA_2048 = 1;
RSA_3072 = 2;
RSA_4096 = 3;
ECDSA_P256 = 4;
}

// Key and cert of root ca used to sign certificates.
config.core.v3.DataSource rootca_cert = 1;

Expand All @@ -28,6 +37,8 @@ message LocalCertificate {

config.core.v3.DataSource default_identity_key = 4;

// Indicates the time at which the certificate expires.
// Indicates the time at which the certificate expires.
google.protobuf.Timestamp expiration_time = 5;

Pkey pkey = 6;
}
10 changes: 10 additions & 0 deletions envoy/ssl/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ class ConnectionInfo {
* @return std::string the SNI used to establish the connection.
**/
virtual const std::string& sni() const PURE;

/**
* @return pkey id, EVP_PKEY_EC or EVP_PKEY_RSA
*/
virtual int pkeyTypePeerCertificate() const PURE;

/**
* @return EC curve name or RSA size
*/
virtual int pkeySizePeerCertificate() const PURE;
};

using ConnectionInfoConstSharedPtr = std::shared_ptr<const ConnectionInfo>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "source/extensions/certificate_providers/local_certificate/local_certificate.h"

#include "envoy/extensions/certificate_providers/local_certificate/v3/local_certificate.pb.h"
#include <openssl/x509.h>

#include "source/common/common/logger.h"
#include "source/common/config/datasource.h"
Expand All @@ -18,6 +18,7 @@ Provider::Provider(const envoy::extensions::certificate_providers::local_certifi
ca_key_ = Config::DataSource::read(config.rootca_key(), true, api);
default_identity_cert_ = Config::DataSource::read(config.default_identity_cert(), true, api);
default_identity_key_ = Config::DataSource::read(config.default_identity_key(), true, api);
pkey_ = config.pkey();

if (config.has_expiration_time()) {
auto seconds = google::protobuf::util::TimeUtil::TimestampToSeconds(config.expiration_time());
Expand Down Expand Up @@ -108,8 +109,6 @@ void Provider::runOnDemandUpdateCallback(const std::string& host,
}
}

// TODO: we meed to copy more information of original cert, such as SANs, the whole subject,
// expiration time, etc.
void Provider::signCertificate(const std::string sni,
::Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata,
Event::Dispatcher& thread_local_dispatcher) {
Expand All @@ -124,71 +123,21 @@ void Provider::signCertificate(const std::string sni,
ca_key.reset(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));

/********* generate identity certificate locally *****/
EVP_PKEY* key = EVP_PKEY_new();
X509* crt = X509_new();

// create a new CSR
X509_REQ* req = X509_REQ_new();
BIGNUM* bne = BN_new();
BN_set_word(bne, RSA_F4);
RSA* rsa = RSA_new();
RSA_generate_key_ex(rsa, 2048, bne, nullptr);
X509_REQ_set_version(req, 0);

X509_NAME* x509_name = X509_REQ_get_subject_name(req);
setSubject(metadata->connectionInfo()->subjectPeerCertificate().data(), x509_name);
X509_set_subject_name(crt, x509_name);

EVP_PKEY_assign_RSA(key, rsa);
X509_REQ_set_pubkey(req, key);
X509_REQ_sign(req, key, EVP_sha1()); // return x509_req->signature->length

X509_set_version(crt, 2);
auto gens = sk_GENERAL_NAME_new_null();

for (auto& dns : metadata->connectionInfo()->dnsSansPeerCertificate()) {
auto ia5 = ASN1_IA5STRING_new();
ASN1_STRING_set(ia5, dns.c_str(), -1);
auto gen = GENERAL_NAME_new();
GENERAL_NAME_set0_value(gen, GEN_DNS, ia5);
sk_GENERAL_NAME_push(gens, gen);
}

for (auto& uri : metadata->connectionInfo()->uriSanPeerCertificate()) {
auto ia5 = ASN1_IA5STRING_new();
ASN1_STRING_set(ia5, uri.c_str(), -1);
auto gen = GENERAL_NAME_new();
GENERAL_NAME_set0_value(gen, GEN_URI, ia5);
sk_GENERAL_NAME_push(gens, gen);
}

X509_add1_ext_i2d(crt, NID_subject_alt_name, gens, 0, 0);
//X509_EXTENSION* ext =
// X509V3_EXT_nconf_nid(nullptr, nullptr, NID_subject_alt_name, subAltName.c_str());
//X509_add_ext(crt, ext, -1);

setSubjectToCSR(metadata->connectionInfo()->subjectPeerCertificate().data(), req);
// creates a new, empty public-key object
EVP_PKEY* key = EVP_PKEY_new();
setPkeyToCSR(metadata, key, req);
// create a new certificate,
X509* crt = X509_new();
X509_set_version(crt, X509_VERSION_3);
X509_set_issuer_name(crt, X509_get_subject_name(ca_cert.get()));
X509_gmtime_adj(X509_get_notBefore(crt), 0);


// Compare expiration_time config with upstream cert expiration. Use smaller
// value of those two dates as expiration time of mimic cert.
auto now = std::chrono::system_clock::now();
auto cert_expiration = metadata->connectionInfo()->expirationPeerCertificate();
uint64_t valid_seconds;
if (expiration_config_) {
valid_seconds = std::chrono::duration_cast<std::chrono::seconds>(expiration_config_.value() - now).count();
}
if (cert_expiration) {
if (!expiration_config_ || cert_expiration.value() <= expiration_config_.value()) {
valid_seconds =
std::chrono::duration_cast<std::chrono::seconds>(cert_expiration.value() - now).count();
}
}

X509_gmtime_adj(X509_get_notAfter(crt), valid_seconds);
X509_set_subject_name(crt, X509_REQ_get_subject_name(req));
EVP_PKEY* req_pubkey = X509_REQ_get_pubkey(req);
X509_set_pubkey(crt, req_pubkey);
X509_set_pubkey(crt, X509_REQ_get_pubkey(req));
setSANs(metadata, crt);
setExpirationTime(metadata, crt);
X509_sign(crt, ca_key.get(), EVP_sha256());
/********* generate identity certificate locally *****/

Expand All @@ -204,40 +153,165 @@ void Provider::signCertificate(const std::string sni,
std::string key_pem(reinterpret_cast<const char*>(output), length);

// Generate TLSCertificate
envoy::extensions::transport_sockets::tls::v3::TlsCertificate* tls_certificate = new envoy::extensions::transport_sockets::tls::v3::TlsCertificate();
envoy::extensions::transport_sockets::tls::v3::TlsCertificate* tls_certificate =
new envoy::extensions::transport_sockets::tls::v3::TlsCertificate();
tls_certificate->mutable_certificate_chain()->set_inline_string(cert_pem);
tls_certificate->mutable_private_key()->set_inline_string(key_pem);

// Update certificates_ map
{
absl::WriterMutexLock writer_lock{&certificates_lock_};
certificates_.try_emplace(
sni, tls_certificate);
certificates_.try_emplace(sni, tls_certificate);
}

runAddUpdateCallback();
runOnDemandUpdateCallback(sni, thread_local_dispatcher, false);
}

void Provider::setSubject(absl::string_view subject, X509_NAME* x509_name) {
void Provider::setSubjectToCSR(absl::string_view subject, X509_REQ* req) {
X509_NAME* x509_name = X509_NAME_new();
// Parse the RFC 2253 format output of subjectPeerCertificate and set back to mimic cert.
const std::string delim = ",";
std::string item;
for (absl::string_view v: absl::StrSplit(subject, delim)) {
for (absl::string_view v : absl::StrSplit(subject, delim)) {
// This happens when peer subject from connectioninfo contains escaped comma,
// like O=Technology Co.\\, Ltd, have to remove the double backslash.
if (v.back() == '\\') {
absl::StrAppend(&item, v.substr(0, v.length() - 1), delim);
}
else {
} else {
absl::StrAppend(&item, v.substr(0, v.length()));
std::vector<std::string> entries = absl::StrSplit(item, "=");
X509_NAME_add_entry_by_txt(x509_name, entries[0].c_str(), MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(entries[1].c_str()),
-1, -1, 0);
reinterpret_cast<const unsigned char*>(entries[1].c_str()), -1, -1,
0);
item.clear();
}
}
X509_REQ_set_subject_name(req, x509_name);
}

void Provider::setPkeyToCSR(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata,
EVP_PKEY* key, X509_REQ* req) {
auto pkey_rsa = [&](const int size) {
// BN provides support for working with arbitrary sized integers
BIGNUM* bne = BN_new();
BN_set_word(bne, RSA_F4);
RSA* rsa = RSA_new();
// generates a new RSA key where the modulus has size |bits|=2048 and the public exponent is
// |e|=bne
RSA_generate_key_ex(rsa, size, bne, nullptr);

// set the underlying public key in an |EVP_PKEY| object
EVP_PKEY_assign_RSA(key, rsa);
ENVOY_LOG(debug, "Generating RSA key");
};

auto pkey_ecdsa = [&]() {
EC_KEY* ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(ec_key);
EVP_PKEY_assign_EC_KEY(key, ec_key);
ENVOY_LOG(debug, "Generating ECDSA key");
};

auto pkey = pkey_;
if (pkey == envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_UNSPECIFIED) {
if (metadata->connectionInfo()->pkeyTypePeerCertificate() == EVP_PKEY_RSA) {
switch (auto size = metadata->connectionInfo()->pkeySizePeerCertificate()) {
case 2048:
pkey = envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_2048;
break;
case 3072:
pkey = envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_3072;
break;
case 4096:
pkey = envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_4096;
break;
default:
ENVOY_LOG(debug, "Not supported PKEY RSA size '{}'", size);
}
}
if (metadata->connectionInfo()->pkeyTypePeerCertificate() == EVP_PKEY_EC) {
switch (auto size = metadata->connectionInfo()->pkeySizePeerCertificate()) {
case NID_X9_62_prime256v1:
pkey = envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_ECDSA_P256;
break;
default:
ENVOY_LOG(debug, "Not supported PKEY ECDSA nid '{}'", size);
}
}
}

switch (pkey) {
case envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_2048:
pkey_rsa(2048);
break;
case envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_3072:
pkey_rsa(3072);
break;
case envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_RSA_4096:
pkey_rsa(4096);
break;
case envoy::extensions::certificate_providers::local_certificate::v3::
LocalCertificate_Pkey_ECDSA_P256:
pkey_ecdsa();
break;
default:
pkey_rsa(2048);
break;
}

X509_REQ_set_pubkey(req, key);
// signs |req| with |pkey| and replaces the signature algorithm and signature fields
X509_REQ_sign(req, key, EVP_sha256()); // return x509_req->signature->length
}

void Provider::setExpirationTime(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata,
X509* crt) {
X509_gmtime_adj(X509_get_notBefore(crt), 0);
// Compare expiration_time config with upstream cert expiration. Use smaller
// value of those two dates as expiration time of mimic cert.
auto now = std::chrono::system_clock::now();
auto cert_expiration = metadata->connectionInfo()->expirationPeerCertificate();
uint64_t valid_seconds;
if (expiration_config_) {
valid_seconds =
std::chrono::duration_cast<std::chrono::seconds>(expiration_config_.value() - now).count();
}
if (cert_expiration) {
if (!expiration_config_ || cert_expiration.value() <= expiration_config_.value()) {
valid_seconds =
std::chrono::duration_cast<std::chrono::seconds>(cert_expiration.value() - now).count();
}
}
X509_gmtime_adj(X509_get_notAfter(crt), valid_seconds);
}

void Provider::setSANs(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata, X509* crt) {
auto gens = sk_GENERAL_NAME_new_null();
for (auto& dns : metadata->connectionInfo()->dnsSansPeerCertificate()) {
auto ia5 = ASN1_IA5STRING_new();
ASN1_STRING_set(ia5, dns.c_str(), -1);
auto gen = GENERAL_NAME_new();
GENERAL_NAME_set0_value(gen, GEN_DNS, ia5);
sk_GENERAL_NAME_push(gens, gen);
}

for (auto& uri : metadata->connectionInfo()->uriSanPeerCertificate()) {
auto ia5 = ASN1_IA5STRING_new();
ASN1_STRING_set(ia5, uri.c_str(), -1);
auto gen = GENERAL_NAME_new();
GENERAL_NAME_set0_value(gen, GEN_URI, ia5);
sk_GENERAL_NAME_push(gens, gen);
}
X509_add1_ext_i2d(crt, NID_subject_alt_name, gens, 0, 0);
}
} // namespace LocalCertificate
} // namespace CertificateProviders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "envoy/certificate_provider/certificate_provider.h"
#include "envoy/common/callback.h"
#include "envoy/event/dispatcher.h"
#include "envoy/extensions/certificate_providers/local_certificate/v3/local_certificate.pb.h"
#include "envoy/server/transport_socket_config.h"

#include "envoy/extensions/certificate_providers/local_certificate/v3/local_certificate.pb.h"
Expand Down Expand Up @@ -55,18 +56,25 @@ class Provider : public CertificateProvider::CertificateProvider,
void runAddUpdateCallback();
void runOnDemandUpdateCallback(const std::string& host,
Event::Dispatcher& thread_local_dispatcher, bool in_cache = true);
//void signCertificate(std::string sni, absl::Span<const std::string> dns_sans, const std::string subject,
// Event::Dispatcher& thread_local_dispatcher);
// void signCertificate(std::string sni, absl::Span<const std::string> dns_sans, const std::string
// subject,
// Event::Dispatcher& thread_local_dispatcher);
void signCertificate(const std::string sni,
Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata,
Event::Dispatcher& thread_local_dispatcher);
void setSubject(absl::string_view subject, X509_NAME* x509_name);

void setSubjectToCSR(absl::string_view subject, X509_REQ* req);
void setPkeyToCSR(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata, EVP_PKEY* key,
X509_REQ* req);
void setExpirationTime(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata, X509* crt);
void setSANs(Envoy::CertificateProvider::OnDemandUpdateMetadataPtr metadata, X509* crt);
Event::Dispatcher& main_thread_dispatcher_;
std::string ca_cert_;
std::string ca_key_;
std::string default_identity_cert_;
std::string default_identity_key_;
absl::optional<SystemTime> expiration_config_;
envoy::extensions::certificate_providers::local_certificate::v3::LocalCertificate_Pkey pkey_;

Common::CallbackManager<> update_callback_manager_;
absl::flat_hash_map<std::string, std::list<OnDemandUpdateHandleImpl*>>
Expand Down
Loading

0 comments on commit f7aee0c

Please sign in to comment.