mirror of
https://github.com/amnezia-vpn/OpenVPNAdapter.git
synced 2026-06-18 08:13:27 +03:00
2148 lines
61 KiB
C++
2148 lines
61 KiB
C++
// OpenVPN -- An application to securely tunnel IP networks
|
|
// over a single port, with support for SSL/TLS-based
|
|
// session authentication and key exchange,
|
|
// packet encryption, packet authentication, and
|
|
// packet compression.
|
|
//
|
|
// Copyright (C) 2012-2020 OpenVPN Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License Version 3
|
|
// as published by the Free Software Foundation.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program in the COPYING file.
|
|
// If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Wrap the OpenSSL SSL API as defined in <openssl/ssl.h>
|
|
// so that it can be used as the SSL layer by the OpenVPN core.
|
|
|
|
#ifndef OPENVPN_OPENSSL_SSL_SSLCTX_H
|
|
#define OPENVPN_OPENSSL_SSL_SSLCTX_H
|
|
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/x509v3.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/dsa.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/bn.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#include <openvpn/common/size.hpp>
|
|
#include <openvpn/common/exception.hpp>
|
|
#include <openvpn/common/mode.hpp>
|
|
#include <openvpn/common/options.hpp>
|
|
#include <openvpn/common/base64.hpp>
|
|
#include <openvpn/common/string.hpp>
|
|
#include <openvpn/common/uniqueptr.hpp>
|
|
#include <openvpn/common/hexstr.hpp>
|
|
#include <openvpn/common/to_string.hpp>
|
|
#include <openvpn/common/unicode.hpp>
|
|
#include <openvpn/frame/frame.hpp>
|
|
#include <openvpn/buffer/buffer.hpp>
|
|
#include <openvpn/pki/cclist.hpp>
|
|
#include <openvpn/pki/epkibase.hpp>
|
|
#include <openvpn/ssl/kuparse.hpp>
|
|
#include <openvpn/ssl/nscert.hpp>
|
|
#include <openvpn/ssl/tlsver.hpp>
|
|
#include <openvpn/ssl/tls_remote.hpp>
|
|
#include <openvpn/ssl/verify_x509_name.hpp>
|
|
#include <openvpn/ssl/sslconsts.hpp>
|
|
#include <openvpn/ssl/sslapi.hpp>
|
|
#include <openvpn/ssl/ssllog.hpp>
|
|
#include <openvpn/ssl/sni_handler.hpp>
|
|
#include <openvpn/ssl/iana_ciphers.hpp>
|
|
#include <openvpn/openssl/util/error.hpp>
|
|
#include <openvpn/openssl/pki/extpki.hpp>
|
|
#include <openvpn/openssl/pki/x509.hpp>
|
|
#include <openvpn/openssl/pki/crl.hpp>
|
|
#include <openvpn/openssl/pki/pkey.hpp>
|
|
#include <openvpn/openssl/pki/dh.hpp>
|
|
#include <openvpn/openssl/pki/x509store.hpp>
|
|
#include <openvpn/openssl/pki/x509certinfo.hpp>
|
|
#include <openvpn/openssl/bio/bio_memq_stream.hpp>
|
|
#include <openvpn/openssl/ssl/sess_cache.hpp>
|
|
#include <openvpn/openssl/ssl/tlsver.hpp>
|
|
|
|
|
|
#ifdef HAVE_JSON
|
|
#include <openvpn/common/jsonhelper.hpp>
|
|
#endif
|
|
|
|
// An SSL Context is essentially a configuration that can be used
|
|
// to generate an arbitrary number of actual SSL connections objects.
|
|
|
|
// OpenSSLContext is an SSL Context implementation that uses the
|
|
// OpenSSL library as a backend.
|
|
|
|
namespace openvpn {
|
|
|
|
// Represents an SSL configuration that can be used
|
|
// to instantiate actual SSL sessions.
|
|
class OpenSSLContext : public SSLFactoryAPI
|
|
{
|
|
public:
|
|
typedef RCPtr<OpenSSLContext> Ptr;
|
|
typedef CertCRLListTemplate<OpenSSLPKI::X509List, OpenSSLPKI::CRLList> CertCRLList;
|
|
|
|
enum {
|
|
MAX_CIPHERTEXT_IN = 64 // maximum number of queued input ciphertext packets
|
|
};
|
|
|
|
// The data needed to construct an OpenSSLContext.
|
|
class Config : public SSLConfigAPI
|
|
{
|
|
friend class OpenSSLContext;
|
|
|
|
public:
|
|
typedef RCPtr<Config> Ptr;
|
|
|
|
SSLFactoryAPI::Ptr new_factory() override
|
|
{
|
|
return SSLFactoryAPI::Ptr(new OpenSSLContext(this));
|
|
}
|
|
|
|
void set_mode(const Mode& mode_arg) override
|
|
{
|
|
mode = mode_arg;
|
|
}
|
|
|
|
const Mode& get_mode() const override
|
|
{
|
|
return mode;
|
|
}
|
|
|
|
// if this callback is defined, no private key needs to be loaded
|
|
void set_external_pki_callback(ExternalPKIBase* external_pki_arg) override
|
|
{
|
|
external_pki = external_pki_arg;
|
|
}
|
|
|
|
// server side
|
|
void set_session_ticket_handler(TLSSessionTicketBase* session_ticket_handler_arg) override
|
|
{
|
|
session_ticket_handler = session_ticket_handler_arg;
|
|
}
|
|
|
|
// client side
|
|
void set_client_session_tickets(const bool v) override
|
|
{
|
|
client_session_tickets = v;
|
|
}
|
|
|
|
// server side
|
|
void set_sni_handler(SNI::HandlerBase* sni_handler_arg) override
|
|
{
|
|
sni_handler = sni_handler_arg;
|
|
}
|
|
|
|
// client side
|
|
void set_sni_name(const std::string& sni_name_arg) override
|
|
{
|
|
sni_name = sni_name_arg;
|
|
}
|
|
|
|
void set_private_key_password(const std::string& pwd) override
|
|
{
|
|
pkey.set_private_key_password(pwd);
|
|
}
|
|
|
|
void load_ca(const std::string& ca_txt, bool strict) override
|
|
{
|
|
ca.parse_pem(ca_txt, "ca");
|
|
}
|
|
|
|
void load_crl(const std::string& crl_txt) override
|
|
{
|
|
ca.parse_pem(crl_txt, "crl");
|
|
}
|
|
|
|
void load_cert(const std::string& cert_txt) override
|
|
{
|
|
cert.parse_pem(cert_txt, "cert");
|
|
}
|
|
|
|
void load_cert(const std::string& cert_txt, const std::string& extra_certs_txt) override
|
|
{
|
|
load_cert(cert_txt);
|
|
if (!extra_certs_txt.empty())
|
|
CertCRLList::from_string(extra_certs_txt, "extra-certs", &extra_certs, nullptr);
|
|
}
|
|
|
|
void load_private_key(const std::string& key_txt) override
|
|
{
|
|
pkey.parse_pem(key_txt, "private key");
|
|
}
|
|
|
|
void load_dh(const std::string& dh_txt) override
|
|
{
|
|
dh.parse_pem(dh_txt);
|
|
}
|
|
|
|
std::string extract_ca() const override
|
|
{
|
|
return ca.certs.render_pem();
|
|
}
|
|
|
|
std::string extract_crl() const override
|
|
{
|
|
return ca.crls.render_pem();
|
|
}
|
|
|
|
std::string extract_cert() const override
|
|
{
|
|
return cert.render_pem();
|
|
}
|
|
|
|
std::vector<std::string> extract_extra_certs() const override
|
|
{
|
|
std::vector<std::string> ret;
|
|
|
|
for (auto const& cert : extra_certs)
|
|
ret.push_back(cert.render_pem());
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::string extract_private_key() const override
|
|
{
|
|
return pkey.render_pem();
|
|
}
|
|
|
|
std::string extract_dh() const override
|
|
{
|
|
return dh.render_pem();
|
|
}
|
|
|
|
PKType::Type private_key_type() const override
|
|
{
|
|
if (!pkey.defined())
|
|
return PKType::PK_NONE;
|
|
return pkey.key_type();
|
|
}
|
|
|
|
size_t private_key_length() const override
|
|
{
|
|
return pkey.key_length();
|
|
}
|
|
|
|
void set_frame(const Frame::Ptr& frame_arg) override
|
|
{
|
|
frame = frame_arg;
|
|
}
|
|
|
|
void set_debug_level(const int debug_level) override
|
|
{
|
|
ssl_debug_level = debug_level;
|
|
}
|
|
|
|
void set_flags(const unsigned int flags_arg) override
|
|
{
|
|
flags = flags_arg;
|
|
}
|
|
|
|
void set_ns_cert_type(const NSCert::Type ns_cert_type_arg) override
|
|
{
|
|
ns_cert_type = ns_cert_type_arg;
|
|
}
|
|
|
|
void set_remote_cert_tls(const KUParse::TLSWebType wt) override
|
|
{
|
|
KUParse::remote_cert_tls(wt, ku, eku);
|
|
}
|
|
|
|
void set_tls_remote(const std::string& tls_remote_arg) override
|
|
{
|
|
tls_remote = tls_remote_arg;
|
|
}
|
|
|
|
void set_tls_version_min(const TLSVersion::Type tvm) override
|
|
{
|
|
tls_version_min = tvm;
|
|
}
|
|
|
|
void set_tls_version_min_override(const std::string& override) override
|
|
{
|
|
TLSVersion::apply_override(tls_version_min, override);
|
|
}
|
|
|
|
void set_tls_cert_profile(const TLSCertProfile::Type type) override
|
|
{
|
|
tls_cert_profile = type;
|
|
}
|
|
|
|
void set_tls_cert_profile_override(const std::string& override) override
|
|
{
|
|
TLSCertProfile::apply_override(tls_cert_profile, override);
|
|
}
|
|
|
|
virtual void set_tls_cipher_list(const std::string& override)
|
|
{
|
|
if(!override.empty())
|
|
tls_cipher_list = override;
|
|
}
|
|
|
|
virtual void set_tls_ciphersuite_list(const std::string& override)
|
|
{
|
|
if(!override.empty())
|
|
tls_ciphersuite_list = override;
|
|
}
|
|
|
|
virtual void set_tls_groups(const std::string& groups)
|
|
{
|
|
if (!groups.empty())
|
|
tls_groups = groups;
|
|
}
|
|
|
|
void set_local_cert_enabled(const bool v) override
|
|
{
|
|
local_cert_enabled = v;
|
|
}
|
|
|
|
void set_x509_track(X509Track::ConfigSet x509_track_config_arg) override
|
|
{
|
|
x509_track_config = std::move(x509_track_config_arg);
|
|
}
|
|
|
|
void set_rng(const RandomAPI::Ptr& rng_arg) override
|
|
{
|
|
// Not implemented (other than assert_crypto check)
|
|
// because OpenSSL is hardcoded to use its own RNG.
|
|
rng_arg->assert_crypto();
|
|
}
|
|
|
|
std::string validate_cert(const std::string& cert_txt) const override
|
|
{
|
|
OpenSSLPKI::X509 cert(cert_txt, "cert");
|
|
return cert.render_pem();
|
|
}
|
|
|
|
std::string validate_cert_list(const std::string& certs_txt) const override
|
|
{
|
|
CertCRLList certs(certs_txt, "cert list");
|
|
return certs.render_pem();
|
|
}
|
|
|
|
std::string validate_private_key(const std::string& key_txt) const override
|
|
{
|
|
OpenSSLPKI::PKey pkey(key_txt, "private key");
|
|
return pkey.render_pem();
|
|
}
|
|
|
|
std::string validate_dh(const std::string& dh_txt) const override
|
|
{
|
|
OpenSSLPKI::DH dh(dh_txt);
|
|
return dh.render_pem();
|
|
}
|
|
|
|
std::string validate_crl(const std::string& crl_txt) const override
|
|
{
|
|
OpenSSLPKI::CRL crl(crl_txt);
|
|
return crl.render_pem();
|
|
}
|
|
|
|
void load(const OptionList& opt, const unsigned int lflags) override
|
|
{
|
|
// client/server
|
|
if (lflags & LF_PARSE_MODE)
|
|
mode = opt.exists("client") ? Mode(Mode::CLIENT) : Mode(Mode::SERVER);
|
|
|
|
// possibly disable peer cert verification
|
|
if ((lflags & LF_ALLOW_CLIENT_CERT_NOT_REQUIRED)
|
|
&& opt.exists("client-cert-not-required"))
|
|
flags |= SSLConst::NO_VERIFY_PEER;
|
|
|
|
// sni
|
|
{
|
|
const std::string name = opt.get_optional("sni", 1, 256);
|
|
if (!name.empty())
|
|
set_sni_name(name);
|
|
}
|
|
|
|
// ca
|
|
{
|
|
std::string ca_txt = opt.cat("ca");
|
|
if (lflags & LF_RELAY_MODE)
|
|
ca_txt += opt.cat("relay-extra-ca");
|
|
load_ca(ca_txt, true);
|
|
}
|
|
|
|
// CRL
|
|
{
|
|
const std::string crl_txt = opt.cat("crl-verify");
|
|
if (!crl_txt.empty())
|
|
load_crl(crl_txt);
|
|
}
|
|
|
|
// local cert/key
|
|
if (local_cert_enabled)
|
|
{
|
|
// cert
|
|
{
|
|
const std::string& cert_txt = opt.get("cert", 1, Option::MULTILINE);
|
|
const std::string ec_txt = opt.cat("extra-certs");
|
|
load_cert(cert_txt, ec_txt);
|
|
}
|
|
|
|
// private key
|
|
if (!external_pki)
|
|
{
|
|
const std::string& key_txt = opt.get("key", 1, Option::MULTILINE);
|
|
load_private_key(key_txt);
|
|
}
|
|
}
|
|
|
|
// DH
|
|
if (mode.is_server())
|
|
{
|
|
const std::string& dh_txt = opt.get("dh", 1, Option::MULTILINE);
|
|
load_dh(dh_txt);
|
|
}
|
|
|
|
// relay mode
|
|
std::string relay_prefix;
|
|
if (lflags & LF_RELAY_MODE)
|
|
relay_prefix = "relay-";
|
|
|
|
// ns-cert-type
|
|
ns_cert_type = NSCert::ns_cert_type(opt, relay_prefix);
|
|
|
|
// parse remote-cert-x options
|
|
KUParse::remote_cert_tls(opt, relay_prefix, ku, eku);
|
|
KUParse::remote_cert_ku(opt, relay_prefix, ku);
|
|
KUParse::remote_cert_eku(opt, relay_prefix, eku);
|
|
|
|
// parse tls-remote
|
|
tls_remote = opt.get_optional(relay_prefix + "tls-remote", 1, 256);
|
|
|
|
// parse verify-x509-name
|
|
verify_x509_name.init(opt, relay_prefix);
|
|
|
|
// Parse tls-version-min option.
|
|
tls_version_min = TLSVersion::parse_tls_version_min(opt, relay_prefix, maxver());
|
|
|
|
// parse tls-cert-profile
|
|
tls_cert_profile = TLSCertProfile::parse_tls_cert_profile(opt, relay_prefix);
|
|
|
|
// Overrides for tls cipher suites
|
|
if (opt.exists("tls-cipher"))
|
|
tls_cipher_list = opt.get_optional("tls-cipher", 1, 256);
|
|
|
|
if (opt.exists("tls-ciphersuites"))
|
|
tls_ciphersuite_list = opt.get_optional("tls-ciphersuites", 1, 256);
|
|
|
|
if (opt.exists("tls-groups"))
|
|
tls_groups = opt.get_optional("tls-groups", 1, 256);
|
|
|
|
// unsupported cert checkers
|
|
{
|
|
}
|
|
}
|
|
|
|
#ifdef OPENVPN_JSON_INTERNAL
|
|
// The get_string_ref methods require internal JSON and do not work with jsoncpp
|
|
SSLConfigAPI::Ptr json_override(const Json::Value& root, const bool load_cert_key) const override
|
|
{
|
|
static const char title[] = "json_override";
|
|
|
|
Config::Ptr ret(new Config);
|
|
|
|
// inherit from self
|
|
ret->mode = mode;
|
|
ret->dh = dh;
|
|
ret->frame = frame;
|
|
ret->ssl_debug_level = ssl_debug_level;
|
|
ret->flags = flags;
|
|
ret->local_cert_enabled = local_cert_enabled;
|
|
|
|
// ca
|
|
{
|
|
const std::string& ca_txt = json::get_string_ref(root, "ca", title);
|
|
ret->load_ca(ca_txt, true);
|
|
}
|
|
|
|
// CRL
|
|
{
|
|
const std::string crl_txt = json::get_string_optional(root, "crl_verify", std::string(), title);
|
|
if (!crl_txt.empty())
|
|
ret->load_crl(crl_txt);
|
|
}
|
|
|
|
// cert/key
|
|
if (load_cert_key && local_cert_enabled)
|
|
{
|
|
bool loaded_cert = false;
|
|
|
|
// cert/extra_certs
|
|
{
|
|
const std::string cert_txt = json::get_string_optional(root, "cert", std::string(), title);
|
|
if (!cert_txt.empty())
|
|
{
|
|
const std::string ec_txt = json::get_string_optional(root, "extra_certs", std::string(), title);
|
|
ret->load_cert(cert_txt, ec_txt);
|
|
loaded_cert = true;
|
|
}
|
|
else
|
|
{
|
|
ret->cert = cert;
|
|
ret->extra_certs = extra_certs;
|
|
}
|
|
}
|
|
|
|
// private key
|
|
if (loaded_cert && !external_pki)
|
|
{
|
|
const std::string& key_txt = json::get_string_ref(root, "key", title);
|
|
if (!key_txt.empty())
|
|
ret->load_private_key(key_txt);
|
|
else
|
|
ret->pkey = pkey;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// inherit from self
|
|
ret->cert = cert;
|
|
ret->extra_certs = extra_certs;
|
|
ret->pkey = pkey;
|
|
}
|
|
|
|
// ns_cert_type
|
|
{
|
|
const std::string ct = json::get_string_optional(root, "ns_cert_type", std::string(), title);
|
|
if (!ct.empty())
|
|
ret->ns_cert_type = NSCert::ns_cert_type(ct);
|
|
}
|
|
|
|
// ku, eku
|
|
{
|
|
const std::string ct = json::get_string_optional(root, "remote_cert_tls", std::string(), title);
|
|
if (!ct.empty())
|
|
KUParse::remote_cert_tls(ct, ret->ku, ret->eku);
|
|
}
|
|
|
|
// tls_version_min
|
|
{
|
|
const std::string tvm = json::get_string_optional(root, "tls_version_min", std::string(), title);
|
|
if (!tvm.empty())
|
|
ret->tls_version_min = TLSVersion::parse_tls_version_min(tvm, false, maxver());
|
|
}
|
|
|
|
// tls_cert_profile
|
|
{
|
|
const std::string prof = json::get_string_optional(root, "tls_cert_profile", std::string(), title);
|
|
if (!prof.empty())
|
|
ret->tls_cert_profile = TLSCertProfile::parse_tls_cert_profile(prof);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
static TLSVersion::Type maxver()
|
|
{
|
|
// Return maximum TLS version supported by OpenSSL.
|
|
// Assume that presence of SSL_OP_NO_TLSvX macro indicates
|
|
// that local OpenSSL library implements TLSvX.
|
|
#if defined(SSL_OP_NO_TLSv1_3)
|
|
return TLSVersion::V1_3;
|
|
#elif defined(SSL_OP_NO_TLSv1_2)
|
|
return TLSVersion::V1_2;
|
|
#elif defined(SSL_OP_NO_TLSv1_1)
|
|
return TLSVersion::V1_1;
|
|
#else
|
|
return TLSVersion::V1_0;
|
|
#endif
|
|
}
|
|
|
|
Mode mode;
|
|
CertCRLList ca; // from OpenVPN "ca" and "crl-verify" option
|
|
OpenSSLPKI::X509 cert; // from OpenVPN "cert" option
|
|
OpenSSLPKI::X509List extra_certs; // from OpenVPN "extra-certs" option
|
|
OpenSSLPKI::PKey pkey; // private key
|
|
OpenSSLPKI::DH dh; // diffie-hellman parameters (only needed in server mode)
|
|
ExternalPKIBase* external_pki = nullptr;
|
|
TLSSessionTicketBase* session_ticket_handler = nullptr; // server side only
|
|
SNI::HandlerBase* sni_handler = nullptr; // server side only
|
|
Frame::Ptr frame;
|
|
int ssl_debug_level = 0;
|
|
unsigned int flags = 0; // defined in sslconsts.hpp
|
|
std::string sni_name; // client side only
|
|
NSCert::Type ns_cert_type{NSCert::NONE};
|
|
std::vector<unsigned int> ku; // if defined, peer cert X509 key usage must match one of these values
|
|
std::string eku; // if defined, peer cert X509 extended key usage must match this OID/string
|
|
std::string tls_remote;
|
|
VerifyX509Name verify_x509_name; // --verify-x509-name feature
|
|
TLSVersion::Type tls_version_min{TLSVersion::UNDEF}; // minimum TLS version that we will negotiate
|
|
TLSCertProfile::Type tls_cert_profile{TLSCertProfile::UNDEF};
|
|
std::string tls_cipher_list;
|
|
std::string tls_ciphersuite_list;
|
|
std::string tls_groups;
|
|
X509Track::ConfigSet x509_track_config;
|
|
bool local_cert_enabled = true;
|
|
bool client_session_tickets = false;
|
|
};
|
|
|
|
// Represents an actual SSL session.
|
|
// Normally instantiated by OpenSSLContext::ssl().
|
|
class SSL : public SSLAPI
|
|
{
|
|
friend class OpenSSLContext;
|
|
|
|
public:
|
|
typedef RCPtr<SSL> Ptr;
|
|
|
|
void start_handshake() override
|
|
{
|
|
SSL_do_handshake(ssl);
|
|
}
|
|
|
|
ssize_t write_cleartext_unbuffered(const void *data, const size_t size) override
|
|
{
|
|
const int status = BIO_write(ssl_bio, data, size);
|
|
if (status < 0)
|
|
{
|
|
if (status == -1 && BIO_should_retry(ssl_bio))
|
|
return SSLConst::SHOULD_RETRY;
|
|
else
|
|
{
|
|
mark_no_cache();
|
|
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::SSL::write_cleartext: BIO_write failed, size=" << size << " status=" << status);
|
|
}
|
|
}
|
|
else
|
|
return status;
|
|
}
|
|
|
|
ssize_t read_cleartext(void *data, const size_t capacity) override
|
|
{
|
|
if (!overflow)
|
|
{
|
|
const int status = BIO_read(ssl_bio, data, capacity);
|
|
if (status < 0)
|
|
{
|
|
if (status == -1 && BIO_should_retry(ssl_bio))
|
|
return SSLConst::SHOULD_RETRY;
|
|
else
|
|
{
|
|
mark_no_cache();
|
|
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::SSL::read_cleartext: BIO_read failed, cap=" << capacity << " status=" << status);
|
|
}
|
|
}
|
|
else
|
|
return status;
|
|
}
|
|
else
|
|
throw ssl_ciphertext_in_overflow();
|
|
}
|
|
|
|
bool read_cleartext_ready() const override
|
|
{
|
|
return !bmq_stream::memq_from_bio(ct_in)->empty() || SSL_pending(ssl) > 0;
|
|
}
|
|
|
|
void write_ciphertext(const BufferPtr& buf) override
|
|
{
|
|
bmq_stream::MemQ* in = bmq_stream::memq_from_bio(ct_in);
|
|
if (in->size() < MAX_CIPHERTEXT_IN)
|
|
in->write_buf(buf);
|
|
else
|
|
overflow = true;
|
|
}
|
|
|
|
void write_ciphertext_unbuffered(const unsigned char *data, const size_t size) override
|
|
{
|
|
bmq_stream::MemQ* in = bmq_stream::memq_from_bio(ct_in);
|
|
if (in->size() < MAX_CIPHERTEXT_IN)
|
|
in->write(data, size);
|
|
else
|
|
overflow = true;
|
|
}
|
|
|
|
bool read_ciphertext_ready() const override
|
|
{
|
|
return !bmq_stream::memq_from_bio(ct_out)->empty();
|
|
}
|
|
|
|
BufferPtr read_ciphertext() override
|
|
{
|
|
return bmq_stream::memq_from_bio(ct_out)->read_buf();
|
|
}
|
|
|
|
std::string ssl_handshake_details() const override
|
|
{
|
|
return ssl_handshake_details(ssl);
|
|
}
|
|
|
|
virtual bool export_keying_material(const std::string& label, unsigned char *dest, size_t size) override
|
|
{
|
|
return SSL_export_keying_material(ssl, dest, size, label.c_str(), label.size(), nullptr, 0, 0) == 1;
|
|
}
|
|
|
|
// Return true if we did a full SSL handshake/negotiation.
|
|
// Return false for cached, reused, or persisted sessions.
|
|
// Also returns false if previously called on this session.
|
|
virtual bool did_full_handshake() override
|
|
{
|
|
if (called_did_full_handshake)
|
|
return false;
|
|
called_did_full_handshake = true;
|
|
return !SSL_session_reused(ssl);
|
|
}
|
|
|
|
const AuthCert::Ptr& auth_cert() const override
|
|
{
|
|
// Reused sessions don't call the cert verify callbacks,
|
|
// so we must use an alternative method to build authcert.
|
|
if (authcert && authcert->is_uninitialized())
|
|
rebuild_authcert();
|
|
return authcert;
|
|
}
|
|
|
|
void mark_no_cache() override
|
|
{
|
|
sess_cache_key.reset();
|
|
}
|
|
|
|
~SSL()
|
|
{
|
|
ssl_erase();
|
|
}
|
|
|
|
static void init_static()
|
|
{
|
|
bmq_stream::init_static();
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_NO_EC)
|
|
ExternalPKIECImpl::init_static();
|
|
#endif
|
|
|
|
|
|
ssl_data_index = SSL_get_ex_new_index(0, (char *)"OpenSSLContext::SSL", nullptr, nullptr, nullptr);
|
|
context_data_index = SSL_get_ex_new_index(0, (char *)"OpenSSLContext", nullptr, nullptr, nullptr);
|
|
|
|
/*
|
|
* We actually override some of the OpenSSL SSLv23 methods here,
|
|
* in particular the ssl_pending method. We want ssl_pending
|
|
* to return 0 until the SSL negotiation establishes the
|
|
* actual method. The default OpenSSL SSLv23 ssl_pending method
|
|
* (ssl_undefined_const_function) triggers an OpenSSL error condition
|
|
* when calling SSL_pending early which is not what we want.
|
|
*
|
|
* This depends on SSL23 being a generic method and OpenSSL later
|
|
* switching to a spefic TLS method (TLS10method etc..) with
|
|
* ssl23_get_client_method that has the proper ssl3_pending pending method.
|
|
*
|
|
* OpenSSL 1.1.x does not allow hacks like this anymore. So overriding is not
|
|
* possible. Fortunately OpenSSL 1.1 also always defines ssl_pending method to
|
|
* be ssl3_pending, so this hack is no longer needed.
|
|
*/
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
ssl23_method_client_ = *SSLv23_client_method();
|
|
ssl23_method_client_.ssl_pending = ssl_pending_override;
|
|
|
|
ssl23_method_server_ = *SSLv23_server_method();
|
|
ssl23_method_server_.ssl_pending = ssl_pending_override;
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
SSL(const OpenSSLContext& ctx, const std::string* hostname, const std::string* cache_key)
|
|
{
|
|
ssl_clear();
|
|
try {
|
|
// init SSL objects
|
|
ssl = SSL_new(ctx.ctx);
|
|
if (!ssl)
|
|
throw OpenSSLException("OpenSSLContext::SSL: SSL_new failed");
|
|
|
|
// release unneeded buffers
|
|
SSL_set_mode(ssl, SSL_MODE_RELEASE_BUFFERS);
|
|
|
|
// verify hostname
|
|
if (hostname && !(ctx.config->flags & SSLConst::NO_VERIFY_HOSTNAME))
|
|
{
|
|
X509_VERIFY_PARAM *param = SSL_get0_param(ssl);
|
|
X509_VERIFY_PARAM_set_hostflags(param, 0);
|
|
X509_VERIFY_PARAM_set1_host(param, hostname->c_str(), 0);
|
|
}
|
|
|
|
// init BIOs
|
|
ssl_bio = BIO_new(BIO_f_ssl());
|
|
if (!ssl_bio)
|
|
throw OpenSSLException("OpenSSLContext::SSL: BIO_new BIO_f_ssl failed");
|
|
ct_in = mem_bio(ctx.config->frame);
|
|
ct_out = mem_bio(ctx.config->frame);
|
|
|
|
// set client/server mode
|
|
if (ctx.config->mode.is_server())
|
|
{
|
|
SSL_set_accept_state(ssl);
|
|
authcert.reset(new AuthCert());
|
|
if (!ctx.config->x509_track_config.empty())
|
|
authcert->x509_track.reset(new X509Track::Set);
|
|
}
|
|
else if (ctx.config->mode.is_client())
|
|
{
|
|
if (cache_key && ctx.sess_cache)
|
|
{
|
|
// see if a cached session already exists for our cache_key
|
|
ctx.sess_cache->extract(*cache_key, [this](SSL_SESSION* sess) {
|
|
if (!SSL_set_session(ssl, sess))
|
|
throw OpenSSLException("SSL_set_session failed");
|
|
});
|
|
|
|
// cache the session before its end-of-life if no errors occur
|
|
sess_cache_key.reset(new OpenSSLSessionCache::Key(*cache_key, ctx.sess_cache));
|
|
}
|
|
SSL_set_connect_state(ssl);
|
|
|
|
// client-side SNI
|
|
if (!ctx.config->sni_name.empty())
|
|
{
|
|
if (SSL_set_tlsext_host_name(ssl, ctx.config->sni_name.c_str()) != 1)
|
|
throw OpenSSLException("OpenSSLContext::SSL: SSL_set_tlsext_host_name failed (sni_name)");
|
|
}
|
|
else if ((ctx.config->flags & SSLConst::ENABLE_CLIENT_SNI) && hostname)
|
|
{
|
|
if (SSL_set_tlsext_host_name(ssl, hostname->c_str()) != 1)
|
|
throw OpenSSLException("OpenSSLContext::SSL: SSL_set_tlsext_host_name failed (hostname)");
|
|
}
|
|
}
|
|
else
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext::SSL: unknown client/server mode");
|
|
|
|
// effect SSL/BIO linkage
|
|
ssl_bio_linkage = true; // after this point, no need to explicitly BIO_free ct_in/ct_out
|
|
SSL_set_bio (ssl, ct_in, ct_out);
|
|
BIO_set_ssl (ssl_bio, ssl, BIO_NOCLOSE);
|
|
|
|
if (ssl_data_index < 0)
|
|
throw ssl_context_error("OpenSSLContext::SSL: ssl_data_index is uninitialized");
|
|
SSL_set_ex_data (ssl, ssl_data_index, this);
|
|
set_parent(&ctx);
|
|
}
|
|
catch (...)
|
|
{
|
|
ssl_erase();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void set_parent(const OpenSSLContext* ctx)
|
|
{
|
|
if (context_data_index < 0)
|
|
throw ssl_context_error("OpenSSLContext::SSL: context_data_index is uninitialized");
|
|
SSL_set_ex_data(ssl, context_data_index, (void *)ctx);
|
|
}
|
|
|
|
void rebuild_authcert() const
|
|
{
|
|
::X509 *cert = SSL_get_peer_certificate(ssl);
|
|
if (cert)
|
|
{
|
|
// save the issuer cert fingerprint
|
|
static_assert(sizeof(AuthCert::issuer_fp) == SHA_DIGEST_LENGTH, "size inconsistency");
|
|
|
|
unsigned int md_len = sizeof(AuthCert::issuer_fp);
|
|
X509_digest (cert, EVP_sha1 (), authcert->issuer_fp, &md_len);
|
|
|
|
// save the Common Name
|
|
authcert->cn = OpenSSLPKI::x509_get_field(cert, NID_commonName);
|
|
|
|
// save the leaf cert serial number
|
|
const ASN1_INTEGER *ai = X509_get_serialNumber(cert);
|
|
authcert->sn = extract_serial_number(ai);
|
|
|
|
X509_free (cert);
|
|
}
|
|
}
|
|
|
|
// Indicate no data available for our custom SSLv23 method
|
|
static int ssl_pending_override(const ::SSL *)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Print a one line summary of SSL/TLS session handshake.
|
|
static std::string ssl_handshake_details (const ::SSL *c_ssl)
|
|
{
|
|
std::ostringstream os;
|
|
|
|
::X509 *cert = SSL_get_peer_certificate (c_ssl);
|
|
|
|
if (cert)
|
|
os << "peer certificate: CN=" << OpenSSLPKI::x509_get_field(cert, NID_commonName);
|
|
|
|
if (cert != nullptr)
|
|
{
|
|
EVP_PKEY *pkey = X509_get_pubkey (cert);
|
|
if (pkey != nullptr)
|
|
{
|
|
#ifndef OPENSSL_NO_EC
|
|
if ((EVP_PKEY_id(pkey) == EVP_PKEY_EC) && (EVP_PKEY_get0_EC_KEY(pkey) != nullptr &&
|
|
EVP_PKEY_get0_EC_KEY(pkey) != nullptr))
|
|
{
|
|
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey);
|
|
const EC_GROUP* group = EC_KEY_get0_group(ec);
|
|
const char* curve = nullptr;
|
|
|
|
int nid = EC_GROUP_get_curve_name(group);
|
|
|
|
if (nid != 0)
|
|
{
|
|
curve = OBJ_nid2sn(nid);
|
|
}
|
|
|
|
if(!curve)
|
|
{
|
|
curve = "Error getting curve name";
|
|
}
|
|
|
|
os << ", " << EC_GROUP_order_bits(group) << " bit EC, curve:" << curve;
|
|
}
|
|
|
|
else
|
|
#endif
|
|
{
|
|
int pkeyId = EVP_PKEY_id(pkey);
|
|
const char* pkeySN = OBJ_nid2sn(pkeyId);
|
|
if (!pkeySN)
|
|
pkeySN = "Unknown";
|
|
|
|
// Nicer names instead of rsaEncryption and dsaEncryption
|
|
if (pkeyId == EVP_PKEY_RSA)
|
|
pkeySN = "RSA";
|
|
else if (pkeyId == EVP_PKEY_DSA)
|
|
pkeySN = "DSA";
|
|
|
|
os << ", " << EVP_PKEY_bits(pkey) << " bit " << pkeySN;
|
|
}
|
|
EVP_PKEY_free(pkey);
|
|
}
|
|
X509_free (cert);
|
|
}
|
|
|
|
const SSL_CIPHER *ciph = SSL_get_current_cipher (c_ssl);
|
|
if (ciph)
|
|
{
|
|
char* desc = SSL_CIPHER_description(ciph, nullptr, 0);
|
|
if (!desc)
|
|
{
|
|
os << ", cipher: Error getting TLS cipher description from SSL_CIPHER_description";
|
|
}
|
|
else
|
|
{
|
|
os << ", cipher: " << desc;
|
|
OPENSSL_free(desc);
|
|
}
|
|
}
|
|
// This has been changed in upstream SSL to have a const
|
|
// parameter, so we cast away const for older versions compatibility
|
|
// (Upstream commit: c04b66b18d1a90f0c6326858e4b8367be5444582)
|
|
if (SSL_session_reused(const_cast<::SSL *>(c_ssl)))
|
|
os << " [REUSED]";
|
|
return os.str();
|
|
}
|
|
|
|
void ssl_clear()
|
|
{
|
|
ssl_bio_linkage = false;
|
|
ssl = nullptr;
|
|
ssl_bio = nullptr;
|
|
ct_in = nullptr;
|
|
ct_out = nullptr;
|
|
overflow = false;
|
|
called_did_full_handshake = false;
|
|
sess_cache_key.reset();
|
|
}
|
|
|
|
void ssl_erase()
|
|
{
|
|
if (!ssl_bio_linkage)
|
|
{
|
|
if (ct_in)
|
|
BIO_free(ct_in);
|
|
if (ct_out)
|
|
BIO_free(ct_out);
|
|
}
|
|
if (ssl_bio)
|
|
BIO_free_all(ssl_bio);
|
|
if (ssl)
|
|
{
|
|
if (sess_cache_key)
|
|
{
|
|
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
|
|
sess_cache_key->commit(SSL_get1_session(ssl));
|
|
}
|
|
SSL_free(ssl);
|
|
}
|
|
openssl_clear_error_stack();
|
|
ssl_clear();
|
|
}
|
|
|
|
static BIO* mem_bio(const Frame::Ptr& frame)
|
|
{
|
|
BIO *bio = BIO_new(bmq_stream::BIO_s_memq());
|
|
if (!bio)
|
|
throw OpenSSLException("OpenSSLContext::SSL: BIO_new failed on bmq_stream");
|
|
bmq_stream::memq_from_bio(bio)->set_frame(frame);
|
|
return bio;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
/*
|
|
* Return modified OpenSSL SSLv23 methods,
|
|
* as configured in init_static().
|
|
*/
|
|
|
|
static const SSL_METHOD* tls_method_client()
|
|
{
|
|
return &ssl23_method_client_;
|
|
}
|
|
|
|
static const SSL_METHOD* tls_method_server()
|
|
{
|
|
return &ssl23_method_server_;
|
|
}
|
|
#else
|
|
static const SSL_METHOD* tls_method_client ()
|
|
{
|
|
return TLS_client_method ();
|
|
}
|
|
|
|
static const SSL_METHOD* tls_method_server ()
|
|
{
|
|
return TLS_server_method ();
|
|
}
|
|
#endif
|
|
::SSL *ssl; // OpenSSL SSL object
|
|
BIO *ssl_bio; // read/write cleartext from here
|
|
BIO *ct_in; // write ciphertext to here
|
|
BIO *ct_out; // read ciphertext from here
|
|
AuthCert::Ptr authcert;
|
|
OpenSSLSessionCache::Key::UPtr sess_cache_key; // client-side only
|
|
OpenSSLContext::Ptr sni_ctx;
|
|
bool ssl_bio_linkage;
|
|
bool overflow;
|
|
bool called_did_full_handshake;
|
|
|
|
// Helps us to store pointer to self in ::SSL object
|
|
static int ssl_data_index;
|
|
static int context_data_index;
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
// Modified SSLv23 methods
|
|
static SSL_METHOD ssl23_method_client_;
|
|
static SSL_METHOD ssl23_method_server_;
|
|
#endif
|
|
};
|
|
|
|
/////// start of main class implementation
|
|
static std::string translate_cipher_list(std::string cipherlist)
|
|
{
|
|
// OpenVPN 2.x accepts IANA ciphers instead in the cipher list, we need
|
|
// to do the same
|
|
|
|
std::stringstream cipher_list_ss(cipherlist);
|
|
std::string ciphersuite;
|
|
|
|
std::stringstream result;
|
|
|
|
|
|
while(std::getline(cipher_list_ss, ciphersuite, ':'))
|
|
{
|
|
const tls_cipher_name_pair* pair = tls_get_cipher_name_pair(ciphersuite);
|
|
|
|
if (!result.str().empty())
|
|
result << ":";
|
|
|
|
if (pair)
|
|
{
|
|
if (pair->iana_name != ciphersuite)
|
|
{
|
|
OPENVPN_LOG_SSL("OpenSSLContext: Deprecated cipher suite name '"
|
|
<< pair->openssl_name << "' please use IANA name ' "
|
|
<< pair->iana_name << "'");
|
|
}
|
|
result << pair->openssl_name;
|
|
}
|
|
else
|
|
{
|
|
result << ciphersuite;
|
|
}
|
|
}
|
|
|
|
return result.str();
|
|
}
|
|
|
|
private:
|
|
OpenSSLContext(Config* config_arg)
|
|
: config(config_arg)
|
|
{
|
|
try
|
|
{
|
|
// Create new SSL_CTX for server or client mode
|
|
if (config->mode.is_server())
|
|
{
|
|
ctx = SSL_CTX_new(SSL::tls_method_server());
|
|
if (ctx == nullptr)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_new failed for server method");
|
|
|
|
// Set DH object
|
|
if (!config->dh.defined())
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: DH not defined");
|
|
if (!SSL_CTX_set_tmp_dh(ctx, config->dh.obj()))
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_set_tmp_dh failed");
|
|
if (config->flags & SSLConst::SERVER_TO_SERVER)
|
|
SSL_CTX_set_purpose(ctx, X509_PURPOSE_SSL_SERVER);
|
|
|
|
// server-side SNI
|
|
if (config->sni_handler)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
|
#define OPENSSL_SERVER_SNI
|
|
SSL_CTX_set_client_hello_cb(ctx, client_hello_callback, nullptr);
|
|
#else
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: server-side SNI requires OpenSSL 1.1 or higher");
|
|
#endif
|
|
}
|
|
}
|
|
else if (config->mode.is_client())
|
|
{
|
|
ctx = SSL_CTX_new(SSL::tls_method_client());
|
|
if (ctx == nullptr)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_new failed for client method");
|
|
}
|
|
else
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: unknown config->mode");
|
|
|
|
// Set SSL options
|
|
if (!(config->flags & SSLConst::NO_VERIFY_PEER))
|
|
{
|
|
int vf = SSL_VERIFY_PEER;
|
|
if (!(config->flags & SSLConst::PEER_CERT_OPTIONAL))
|
|
vf |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
SSL_CTX_set_verify(ctx, vf,
|
|
config->mode.is_client() ? verify_callback_client : verify_callback_server);
|
|
SSL_CTX_set_verify_depth(ctx, 16);
|
|
}
|
|
|
|
/* Disable SSLv2 and SSLv3, might be a noop but does not hurt */
|
|
long sslopt = SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
|
|
|
|
if (config->mode.is_server())
|
|
{
|
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
|
|
if (config->session_ticket_handler)
|
|
{
|
|
const std::string sess_id_context = config->session_ticket_handler->session_id_context();
|
|
if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)sess_id_context.c_str(), sess_id_context.length()))
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_set_session_id_context failed");
|
|
|
|
if (!SSL_CTX_set_tlsext_ticket_key_cb(ctx, tls_ticket_key_callback))
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_set_tlsext_ticket_key_cb failed");
|
|
}
|
|
else
|
|
sslopt |= SSL_OP_NO_TICKET;
|
|
|
|
// send a client CA list to the client
|
|
if (config->flags & SSLConst::SEND_CLIENT_CA_LIST)
|
|
{
|
|
for (const auto& e : config->ca.certs)
|
|
{
|
|
if (SSL_CTX_add_client_CA(ctx, e.obj()) != 1)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_add_client_CA failed");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (config->client_session_tickets)
|
|
{
|
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT);
|
|
sess_cache.reset(new OpenSSLSessionCache);
|
|
}
|
|
else
|
|
{
|
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
|
|
sslopt |= SSL_OP_NO_TICKET;
|
|
}
|
|
}
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
if (config->tls_version_min > TLSVersion::V1_0)
|
|
{
|
|
SSL_CTX_set_min_proto_version(ctx, TLSVersion::toTLSVersion(config->tls_version_min));
|
|
}
|
|
#else
|
|
if (config->tls_version_min > TLSVersion::V1_0)
|
|
sslopt |= SSL_OP_NO_TLSv1;
|
|
#ifdef SSL_OP_NO_TLSv1_1
|
|
if (config->tls_version_min > TLSVersion::V1_1)
|
|
sslopt |= SSL_OP_NO_TLSv1_1;
|
|
#endif
|
|
#ifdef SSL_OP_NO_TLSv1_2
|
|
if (config->tls_version_min > TLSVersion::V1_2)
|
|
sslopt |= SSL_OP_NO_TLSv1_2;
|
|
#endif
|
|
#ifdef SSL_OP_NO_TLSv1_3
|
|
if (config->tls_version_min > TLSVersion::V1_3)
|
|
sslopt |= SSL_OP_NO_TLSv1_3;
|
|
#endif
|
|
#endif
|
|
SSL_CTX_set_options(ctx, sslopt);
|
|
|
|
if (!config->tls_groups.empty())
|
|
{
|
|
set_openssl_tls_groups(config->tls_groups);
|
|
}
|
|
#if defined(TLS1_3_VERSION)
|
|
if (!config->tls_ciphersuite_list.empty())
|
|
{
|
|
if(!SSL_CTX_set_ciphersuites(ctx, config->tls_ciphersuite_list.c_str()))
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: SSL_CTX_set_ciphersuites_list failed");
|
|
}
|
|
#endif
|
|
const char* tls_cipher_list =
|
|
/* default list as a basis */
|
|
"DEFAULT"
|
|
/* Disable export ciphers, low and medium */
|
|
":!EXP:!LOW:!MEDIUM"
|
|
/* Disable static (EC)DH keys (no forward secrecy) */
|
|
":!kDH:!kECDH"
|
|
/* Disable DSA private keys */
|
|
":!DSS"
|
|
/* Disable RC4 cipher */
|
|
":!RC4"
|
|
/* Disable MD5 */
|
|
":!MD5"
|
|
/* Disable unsupported TLS modes */
|
|
":!PSK:!SRP:!kRSA"
|
|
/* Disable SSLv2 cipher suites*/
|
|
":!SSLv2";
|
|
|
|
std::string translated_cipherlist;
|
|
|
|
if (!config->tls_cipher_list.empty())
|
|
{
|
|
translated_cipherlist = translate_cipher_list(config->tls_cipher_list);
|
|
|
|
tls_cipher_list = translated_cipherlist.c_str();
|
|
}
|
|
|
|
if (!SSL_CTX_set_cipher_list(ctx, tls_cipher_list))
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: SSL_CTX_set_cipher_list failed");
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
SSL_CTX_set_ecdh_auto(ctx, 1); // this method becomes a no-op in OpenSSL 1.1
|
|
#endif
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
switch(TLSCertProfile::default_if_undef(config->tls_cert_profile))
|
|
{
|
|
case TLSCertProfile::UNDEF:
|
|
OPENVPN_THROW(ssl_context_error,
|
|
"OpenSSLContext: undefined tls-cert-profile");
|
|
break;
|
|
#ifdef OPENVPN_USE_TLS_MD5
|
|
case TLSCertProfile::INSECURE:
|
|
SSL_CTX_set_security_level(ctx, 0);
|
|
break;
|
|
#endif
|
|
case TLSCertProfile::LEGACY:
|
|
SSL_CTX_set_security_level(ctx, 1);
|
|
break;
|
|
case TLSCertProfile::PREFERRED:
|
|
SSL_CTX_set_security_level(ctx, 2);
|
|
break;
|
|
case TLSCertProfile::SUITEB:
|
|
SSL_CTX_set_security_level(ctx, 3);
|
|
break;
|
|
default:
|
|
OPENVPN_THROW(ssl_context_error,
|
|
"OpenSSLContext: unexpected tls-cert-profile value");
|
|
break;
|
|
}
|
|
#else
|
|
// when OpenSSL does not CertProfile support we force the user to set 'legacy'
|
|
if (TLSCertProfile::default_if_undef(config->tls_cert_profile) != TLSCertProfile::LEGACY)
|
|
{
|
|
OPENVPN_THROW(ssl_context_error,
|
|
"OpenSSLContext: tls-cert-profile not supported by this OpenSSL build. Use 'legacy' instead");
|
|
}
|
|
#endif
|
|
|
|
if (config->local_cert_enabled)
|
|
{
|
|
// Set certificate
|
|
if (!config->cert.defined())
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: cert not defined");
|
|
if (SSL_CTX_use_certificate(ctx, config->cert.obj()) != 1)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_use_certificate failed");
|
|
|
|
// Set private key
|
|
if (config->external_pki)
|
|
{
|
|
auto certType = EVP_PKEY_id (X509_get0_pubkey(config->cert.obj()));
|
|
if (certType == EVP_PKEY_RSA )
|
|
{
|
|
epki = new ExternalPKIRsaImpl (ctx, config->cert.obj(), config->external_pki);
|
|
}
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_NO_EC)
|
|
else if (certType == EVP_PKEY_EC)
|
|
{
|
|
epki = new ExternalPKIECImpl (ctx, config->cert.obj(), config->external_pki);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
throw OpenSSLException("OpenSSLContext: pkey is neither RSA nor EC. Unsupported with external pki");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!config->pkey.defined())
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: private key not defined");
|
|
if (SSL_CTX_use_PrivateKey(ctx, config->pkey.obj()) != 1)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_use_PrivateKey failed");
|
|
|
|
// Check cert/private key compatibility
|
|
if (!SSL_CTX_check_private_key(ctx))
|
|
throw OpenSSLException("OpenSSLContext: private key does not match the certificate");
|
|
}
|
|
|
|
// Set extra certificates that are part of our own certificate
|
|
// chain but shouldn't be included in the verify chain.
|
|
if (config->extra_certs.defined())
|
|
{
|
|
for (const auto& e : config->extra_certs)
|
|
{
|
|
if (SSL_CTX_add_extra_chain_cert(ctx, e.obj_dup()) != 1)
|
|
throw OpenSSLException("OpenSSLContext: SSL_CTX_add_extra_chain_cert failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set CAs/CRLs
|
|
if (config->ca.certs.defined())
|
|
update_trust(config->ca);
|
|
else if (!(config->flags & SSLConst::NO_VERIFY_PEER))
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: CA not defined");
|
|
|
|
// Show handshake debugging info
|
|
if (config->ssl_debug_level)
|
|
SSL_CTX_set_info_callback (ctx, info_callback);
|
|
}
|
|
catch (...)
|
|
{
|
|
erase();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public:
|
|
// create a new SSL instance
|
|
virtual SSLAPI::Ptr ssl()
|
|
{
|
|
return SSL::Ptr(new SSL(*this, nullptr, nullptr));
|
|
}
|
|
|
|
// like ssl() above but verify hostname against cert CommonName and/or SubjectAltName
|
|
virtual SSLAPI::Ptr ssl(const std::string* hostname, const std::string* cache_key)
|
|
{
|
|
return SSL::Ptr(new SSL(*this, hostname, cache_key));
|
|
}
|
|
|
|
void update_trust(const CertCRLList& cc)
|
|
{
|
|
OpenSSLPKI::X509Store store(cc);
|
|
SSL_CTX_set_cert_store(ctx, store.release());
|
|
}
|
|
|
|
~OpenSSLContext()
|
|
{
|
|
erase();
|
|
}
|
|
|
|
virtual const Mode& mode() const
|
|
{
|
|
return config->mode;
|
|
}
|
|
|
|
constexpr static bool support_key_material_export()
|
|
{
|
|
return true;
|
|
}
|
|
private:
|
|
// ns-cert-type verification
|
|
|
|
bool ns_cert_type_defined() const
|
|
{
|
|
return config->ns_cert_type != NSCert::NONE;
|
|
}
|
|
|
|
bool verify_ns_cert_type(::X509* cert) const
|
|
{
|
|
if (config->ns_cert_type == NSCert::SERVER)
|
|
return X509_check_purpose (cert, X509_PURPOSE_SSL_SERVER, 0);
|
|
else if (config->ns_cert_type == NSCert::CLIENT)
|
|
return X509_check_purpose (cert, X509_PURPOSE_SSL_CLIENT, 0);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
|
|
void set_openssl_tls_groups(const std::string& tls_groups)
|
|
{
|
|
auto num_groups = std::count(tls_groups.begin(), tls_groups.end(), ':') + 1;
|
|
|
|
std::unique_ptr<int[]> glist(new int[num_groups]);
|
|
|
|
std::stringstream groups_ss(tls_groups);
|
|
std::string group;
|
|
|
|
int glistlen = 0;
|
|
while (std::getline(groups_ss, group, ':'))
|
|
{
|
|
/* Dance around that the fact that even though OpenSSL authors
|
|
* call this group secp256r1 in their own source code, OpenSSL
|
|
* externally only knows this by prime256v1 or P-256 */
|
|
if (group == "secp256r1")
|
|
{
|
|
group = "prime256v1";
|
|
}
|
|
|
|
int nid = OBJ_sn2nid(group.c_str());
|
|
if (nid != 0)
|
|
{
|
|
glist[glistlen] = nid;
|
|
glistlen++;
|
|
}
|
|
else
|
|
{
|
|
OPENVPN_LOG_SSL("OpenSSL -- warning ignoring unknown group '"
|
|
<< group << "' in tls-groups");
|
|
}
|
|
}
|
|
|
|
if (!SSL_CTX_set1_groups(ctx, glist.get(), glistlen))
|
|
OPENVPN_THROW(ssl_context_error, "OpenSSLContext: SSL_CTX_set1_groups failed");
|
|
|
|
}
|
|
|
|
// remote-cert-ku verification
|
|
|
|
bool x509_cert_ku_defined() const
|
|
{
|
|
return config->ku.size() > 0;
|
|
}
|
|
|
|
bool verify_x509_cert_ku(::X509 *cert) const
|
|
{
|
|
bool found = false;
|
|
ASN1_BIT_STRING *ku = (ASN1_BIT_STRING *)X509_get_ext_d2i(cert, NID_key_usage, nullptr, nullptr);
|
|
|
|
if (ku)
|
|
{
|
|
// Extract key usage bits
|
|
unsigned int nku = 0;
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (ASN1_BIT_STRING_get_bit(ku, i))
|
|
nku |= 1 << (7 - i);
|
|
}
|
|
}
|
|
|
|
// Fixup if no LSB bits
|
|
if ((nku & 0xff) == 0)
|
|
nku >>= 8;
|
|
|
|
// Validating certificate key usage
|
|
{
|
|
for (std::vector<unsigned int>::const_iterator i = config->ku.begin(); i != config->ku.end(); ++i)
|
|
{
|
|
if (nku == *i)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASN1_BIT_STRING_free(ku);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// remote-cert-eku verification
|
|
|
|
bool x509_cert_eku_defined() const
|
|
{
|
|
return !config->eku.empty();
|
|
}
|
|
|
|
bool verify_x509_cert_eku(::X509 *cert) const
|
|
{
|
|
bool found = false;
|
|
EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr);
|
|
|
|
if (eku)
|
|
{
|
|
// Validating certificate extended key usage
|
|
for (int i = 0; !found && i < sk_ASN1_OBJECT_num(eku); i++)
|
|
{
|
|
ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(eku, i);
|
|
char oid_str[256];
|
|
|
|
if (!found && OBJ_obj2txt(oid_str, sizeof(oid_str), oid, 0) != -1)
|
|
{
|
|
// Compare EKU against string
|
|
if (config->eku == oid_str)
|
|
found = true;
|
|
}
|
|
|
|
if (!found && OBJ_obj2txt(oid_str, sizeof(oid_str), oid, 1) != -1)
|
|
{
|
|
// Compare EKU against OID
|
|
if (config->eku == oid_str)
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static void x509_track_extract_nid(const X509Track::Type xt_type,
|
|
const int nid,
|
|
::X509 *cert,
|
|
const int depth,
|
|
X509Track::Set& xts)
|
|
{
|
|
const std::string value = OpenSSLPKI::x509_get_field(cert, nid);
|
|
if (!value.empty())
|
|
xts.emplace_back(xt_type, depth, OpenSSLPKI::x509_get_field(cert, nid));
|
|
}
|
|
|
|
static void x509_track_extract_from_cert(::X509 *cert,
|
|
const int depth,
|
|
const X509Track::ConfigSet& cs,
|
|
X509Track::Set& xts)
|
|
{
|
|
for (auto &c : cs)
|
|
{
|
|
if (c.depth_match(depth))
|
|
{
|
|
switch (c.type)
|
|
{
|
|
case X509Track::SERIAL:
|
|
xts.emplace_back(X509Track::SERIAL,
|
|
depth,
|
|
OpenSSLPKI::x509_get_serial(cert));
|
|
break;
|
|
case X509Track::SERIAL_HEX:
|
|
xts.emplace_back(X509Track::SERIAL_HEX,
|
|
depth,
|
|
OpenSSLPKI::x509_get_serial_hex(cert));
|
|
break;
|
|
case X509Track::SHA1:
|
|
{
|
|
unsigned char buf[EVP_MAX_MD_SIZE];
|
|
unsigned int len = EVP_MAX_MD_SIZE;
|
|
X509_digest (cert, EVP_sha1 (), buf, &len);
|
|
xts.emplace_back (X509Track::SHA1,
|
|
depth,
|
|
render_hex_sep (buf, len, ':', true));
|
|
}
|
|
break;
|
|
case X509Track::CN:
|
|
x509_track_extract_nid(X509Track::CN, NID_commonName, cert, depth, xts);
|
|
break;
|
|
case X509Track::C:
|
|
x509_track_extract_nid(X509Track::C, NID_countryName, cert, depth, xts);
|
|
break;
|
|
case X509Track::L:
|
|
x509_track_extract_nid(X509Track::L, NID_localityName, cert, depth, xts);
|
|
break;
|
|
case X509Track::ST:
|
|
x509_track_extract_nid(X509Track::ST, NID_stateOrProvinceName, cert, depth, xts);
|
|
break;
|
|
case X509Track::O:
|
|
x509_track_extract_nid(X509Track::O, NID_organizationName, cert, depth, xts);
|
|
break;
|
|
case X509Track::OU:
|
|
x509_track_extract_nid(X509Track::OU, NID_organizationalUnitName, cert, depth, xts);
|
|
break;
|
|
case X509Track::EMAIL:
|
|
x509_track_extract_nid(X509Track::EMAIL, NID_pkcs9_emailAddress, cert, depth, xts);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::int64_t extract_serial_number(const ASN1_INTEGER *ai)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
std::int64_t ret;
|
|
if (ASN1_INTEGER_get_int64(&ret, ai) == 0)
|
|
return -1;
|
|
return ret;
|
|
#else
|
|
return ai ? ASN1_INTEGER_get(ai) : -1;
|
|
#endif
|
|
}
|
|
|
|
static std::string cert_status_line(int preverify_ok,
|
|
int depth,
|
|
int err,
|
|
const std::string& signature,
|
|
const std::string& subject)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(128);
|
|
ret = "VERIFY";
|
|
if (preverify_ok)
|
|
ret += " OK";
|
|
else
|
|
ret += " FAIL";
|
|
ret += ": depth=";
|
|
ret += openvpn::to_string(depth);
|
|
ret += ", ";
|
|
if (!subject.empty())
|
|
ret += subject;
|
|
else
|
|
ret += "NO_SUBJECT";
|
|
ret += ", signature: " + signature;
|
|
if (!preverify_ok)
|
|
{
|
|
ret += " [";
|
|
ret += X509_verify_cert_error_string(err);
|
|
ret += ']';
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static AuthCert::Fail::Type cert_fail_code(const int openssl_err)
|
|
{
|
|
// NOTE: this method should never return OK
|
|
switch (openssl_err)
|
|
{
|
|
case X509_V_ERR_CERT_HAS_EXPIRED:
|
|
return AuthCert::Fail::EXPIRED;
|
|
default:
|
|
return AuthCert::Fail::CERT_FAIL;
|
|
}
|
|
}
|
|
|
|
|
|
static int check_cert_warnings(const X509* cert)
|
|
{
|
|
int nid = X509_get_signature_nid(cert);
|
|
|
|
switch (nid) {
|
|
case NID_ecdsa_with_SHA1:
|
|
case NID_dsaWithSHA:
|
|
case NID_dsaWithSHA1:
|
|
case NID_sha1WithRSAEncryption:
|
|
return SSLAPI::TLS_WARN_SIG_SHA1;
|
|
case NID_md5WithRSA:
|
|
case NID_md5WithRSAEncryption:
|
|
return SSLAPI::TLS_WARN_SIG_MD5;
|
|
default:
|
|
return SSLAPI::TLS_WARN_NONE;
|
|
}
|
|
}
|
|
|
|
static int verify_callback_client(int preverify_ok, X509_STORE_CTX *ctx)
|
|
{
|
|
// get the OpenSSL SSL object
|
|
::SSL* ssl = (::SSL*) X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
|
|
// get OpenSSLContext
|
|
const OpenSSLContext* self = (OpenSSLContext*) SSL_get_ex_data (ssl, SSL::context_data_index);
|
|
|
|
// get OpenSSLContext::SSL
|
|
SSL* self_ssl = (SSL *) SSL_get_ex_data (ssl, SSL::ssl_data_index);
|
|
|
|
// get depth
|
|
const int depth = X509_STORE_CTX_get_error_depth(ctx);
|
|
|
|
// get current certificate
|
|
X509* current_cert = X509_STORE_CTX_get_current_cert (ctx);
|
|
|
|
// log subject
|
|
const std::string subject = OpenSSLPKI::x509_get_subject(current_cert);
|
|
auto signature = OpenSSLPKI::x509_get_signature_algorithm(current_cert);
|
|
if (self->config->flags & SSLConst::LOG_VERIFY_STATUS)
|
|
OPENVPN_LOG_SSL(cert_status_line(preverify_ok, depth, X509_STORE_CTX_get_error(ctx),
|
|
signature, subject));
|
|
|
|
// Add warnings if Cert parameters are wrong
|
|
self_ssl->tls_warnings |= self->check_cert_warnings(current_cert);
|
|
|
|
// leaf-cert verification
|
|
if (depth == 0)
|
|
{
|
|
// verify ns-cert-type
|
|
if (self->ns_cert_type_defined() && !self->verify_ns_cert_type(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad ns-cert-type in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
// verify X509 key usage
|
|
if (self->x509_cert_ku_defined() && !self->verify_x509_cert_ku(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad X509 key usage in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
// verify X509 extended key usage
|
|
if (self->x509_cert_eku_defined() && !self->verify_x509_cert_eku(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad X509 extended key usage in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
// verify-x509-name
|
|
const VerifyX509Name& verify_x509 = self->config->verify_x509_name;
|
|
if (verify_x509.get_mode() != VerifyX509Name::VERIFY_X509_NONE)
|
|
{
|
|
switch (verify_x509.get_mode())
|
|
{
|
|
case VerifyX509Name::VERIFY_X509_SUBJECT_DN:
|
|
preverify_ok = verify_x509.verify(OpenSSLPKI::x509_get_subject(current_cert, true));
|
|
break;
|
|
|
|
case VerifyX509Name::VERIFY_X509_SUBJECT_RDN:
|
|
case VerifyX509Name::VERIFY_X509_SUBJECT_RDN_PREFIX:
|
|
preverify_ok = verify_x509.verify(OpenSSLPKI::x509_get_field(current_cert, NID_commonName));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (!preverify_ok)
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- verify-x509-name failed");
|
|
}
|
|
}
|
|
|
|
// verify tls-remote
|
|
if (!self->config->tls_remote.empty())
|
|
{
|
|
const std::string subj = TLSRemote::sanitize_x509_name(subject);
|
|
const std::string common_name = TLSRemote::sanitize_common_name(OpenSSLPKI::x509_get_field(current_cert, NID_commonName));
|
|
TLSRemote::log(self->config->tls_remote, subj, common_name);
|
|
if (!TLSRemote::test(self->config->tls_remote, subj, common_name))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- tls-remote match failed");
|
|
preverify_ok = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return preverify_ok;
|
|
}
|
|
|
|
static int verify_callback_server(int preverify_ok, X509_STORE_CTX *ctx)
|
|
{
|
|
// get the OpenSSL SSL object
|
|
::SSL* ssl = (::SSL*) X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
|
|
// get OpenSSLContext
|
|
const OpenSSLContext* self = (OpenSSLContext*) SSL_get_ex_data (ssl, SSL::context_data_index);
|
|
|
|
// get OpenSSLContext::SSL
|
|
SSL* self_ssl = (SSL *) SSL_get_ex_data (ssl, SSL::ssl_data_index);
|
|
|
|
// get error code
|
|
const int err = X509_STORE_CTX_get_error(ctx);
|
|
|
|
// get depth
|
|
const int depth = X509_STORE_CTX_get_error_depth(ctx);
|
|
|
|
// get current certificate
|
|
X509* current_cert = X509_STORE_CTX_get_current_cert (ctx);
|
|
|
|
// log subject
|
|
if (self->config->flags & SSLConst::LOG_VERIFY_STATUS)
|
|
OPENVPN_LOG_SSL(cert_status_line(preverify_ok, depth, err,
|
|
OpenSSLPKI::x509_get_subject(current_cert),
|
|
OpenSSLPKI::x509_get_signature_algorithm(current_cert)));
|
|
|
|
// record cert error in authcert
|
|
if (!preverify_ok && self_ssl->authcert)
|
|
self_ssl->authcert->add_fail(depth, cert_fail_code(err), X509_verify_cert_error_string(err));
|
|
|
|
if (depth == 1) // issuer cert
|
|
{
|
|
// save the issuer cert fingerprint
|
|
if (self_ssl->authcert)
|
|
{
|
|
static_assert(sizeof(AuthCert::issuer_fp) == SHA_DIGEST_LENGTH, "size inconsistency");
|
|
unsigned int digest_len = sizeof(AuthCert::issuer_fp);
|
|
if (!X509_digest (current_cert, EVP_sha1 (), self_ssl->authcert->issuer_fp, &digest_len))
|
|
preverify_ok = false;
|
|
}
|
|
}
|
|
else if (depth == 0) // leaf cert
|
|
{
|
|
// verify ns-cert-type
|
|
if (self->ns_cert_type_defined() && !self->verify_ns_cert_type(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad ns-cert-type in leaf certificate");
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->add_fail(depth, AuthCert::Fail::BAD_CERT_TYPE, "bad ns-cert-type in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
// verify X509 key usage
|
|
if (self->x509_cert_ku_defined() && !self->verify_x509_cert_ku(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad X509 key usage in leaf certificate");
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->add_fail(depth, AuthCert::Fail::BAD_CERT_TYPE, "bad X509 key usage in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
// verify X509 extended key usage
|
|
if (self->x509_cert_eku_defined() && !self->verify_x509_cert_eku(current_cert))
|
|
{
|
|
OPENVPN_LOG_SSL("VERIFY FAIL -- bad X509 extended key usage in leaf certificate");
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->add_fail(depth, AuthCert::Fail::BAD_CERT_TYPE, "bad X509 extended key usage in leaf certificate");
|
|
preverify_ok = false;
|
|
}
|
|
|
|
if (self_ssl->authcert)
|
|
{
|
|
// save the Common Name
|
|
self_ssl->authcert->cn = OpenSSLPKI::x509_get_field(current_cert, NID_commonName);
|
|
|
|
// save the leaf cert serial number
|
|
const ASN1_INTEGER *ai = X509_get_serialNumber(current_cert);
|
|
self_ssl->authcert->sn = extract_serial_number(ai);
|
|
}
|
|
}
|
|
|
|
// x509-track enabled?
|
|
if (self_ssl->authcert && self_ssl->authcert->x509_track)
|
|
x509_track_extract_from_cert(current_cert,
|
|
depth,
|
|
self->config->x509_track_config,
|
|
*self_ssl->authcert->x509_track);
|
|
|
|
return preverify_ok || self->deferred_cert_verify_failsafe(*self_ssl);
|
|
}
|
|
|
|
// Print debugging information on SSL/TLS session negotiation.
|
|
static void info_callback (const ::SSL *s, int where, int ret)
|
|
{
|
|
if (where & SSL_CB_LOOP)
|
|
{
|
|
OPENVPN_LOG_SSL("SSL state (" << (where & SSL_ST_CONNECT ? "connect" : where & SSL_ST_ACCEPT ? "accept" : "undefined") << "): " << SSL_state_string_long(s));
|
|
}
|
|
else if (where & SSL_CB_ALERT)
|
|
{
|
|
OPENVPN_LOG_SSL("SSL alert (" << (where & SSL_CB_READ ? "read" : "write") << "): " << SSL_alert_type_string_long(ret) << ": " << SSL_alert_desc_string_long(ret));
|
|
}
|
|
}
|
|
|
|
static int tls_ticket_key_callback(::SSL *ssl,
|
|
unsigned char key_name[16],
|
|
unsigned char iv[EVP_MAX_IV_LENGTH],
|
|
::EVP_CIPHER_CTX *ctx,
|
|
::HMAC_CTX *hctx,
|
|
int enc)
|
|
{
|
|
// get OpenSSLContext
|
|
const OpenSSLContext* self = (OpenSSLContext*) SSL_get_ex_data (ssl, SSL::context_data_index);
|
|
if (!self)
|
|
return -1;
|
|
|
|
// get user-defined session ticket handler
|
|
TLSSessionTicketBase* t = self->config->session_ticket_handler;
|
|
if (!t)
|
|
return -1;
|
|
|
|
if (enc)
|
|
{
|
|
// create new ticket
|
|
TLSSessionTicketBase::Name name;
|
|
TLSSessionTicketBase::Key key;
|
|
|
|
switch (t->create_session_ticket_key(name, key))
|
|
{
|
|
case TLSSessionTicketBase::NO_TICKET:
|
|
case TLSSessionTicketBase::TICKET_EXPIRING: // doesn't really make sense for enc==1?
|
|
// NOTE: OpenSSL may segfault on a zero return.
|
|
// This appears to be fixed by:
|
|
// commit dbdb96617cce2bd4356d57f53ecc327d0e31f2ad
|
|
// Author: Todd Short <tshort@akamai.com>
|
|
// Date: Thu May 12 18:16:52 2016 -0400
|
|
// Fix session ticket and SNI
|
|
//OPENVPN_LOG("tls_ticket_key_callback: create: no ticket or expiring ticket");
|
|
#if OPENSSL_VERSION_NUMBER < 0x1000212fL // 1.0.2r
|
|
if (!randomize_name_key(name, key))
|
|
return -1;
|
|
// fallthrough
|
|
#else
|
|
return 0;
|
|
#endif
|
|
case TLSSessionTicketBase::TICKET_AVAILABLE:
|
|
if (!RAND_bytes(iv, EVP_MAX_IV_LENGTH))
|
|
return -1;
|
|
if (!tls_ticket_init_cipher_hmac(key, iv, ctx, hctx, enc))
|
|
return -1;
|
|
static_assert(TLSSessionTicketBase::Name::SIZE == 16, "unexpected name size");
|
|
std::memcpy(key_name, name.value_, TLSSessionTicketBase::Name::SIZE);
|
|
//OPENVPN_LOG("tls_ticket_key_callback: created ticket");
|
|
return 1;
|
|
default:
|
|
//OPENVPN_LOG("tls_ticket_key_callback: create: bad ticket");
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// lookup existing ticket
|
|
static_assert(TLSSessionTicketBase::Name::SIZE == 16, "unexpected name size");
|
|
const TLSSessionTicketBase::Name name(key_name);
|
|
TLSSessionTicketBase::Key key;
|
|
|
|
switch (t->lookup_session_ticket_key(name, key))
|
|
{
|
|
case TLSSessionTicketBase::TICKET_AVAILABLE:
|
|
if (!tls_ticket_init_cipher_hmac(key, iv, ctx, hctx, enc))
|
|
return -1;
|
|
//OPENVPN_LOG("tls_ticket_key_callback: found ticket");
|
|
return 1;
|
|
case TLSSessionTicketBase::TICKET_EXPIRING:
|
|
if (!tls_ticket_init_cipher_hmac(key, iv, ctx, hctx, enc))
|
|
return -1;
|
|
//OPENVPN_LOG("tls_ticket_key_callback: expiring ticket");
|
|
return 2;
|
|
case TLSSessionTicketBase::NO_TICKET:
|
|
//OPENVPN_LOG("tls_ticket_key_callback: lookup: no ticket");
|
|
return 0;
|
|
default:
|
|
//OPENVPN_LOG("tls_ticket_key_callback: lookup: bad ticket");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool tls_ticket_init_cipher_hmac(const TLSSessionTicketBase::Key& key,
|
|
unsigned char iv[EVP_MAX_IV_LENGTH],
|
|
::EVP_CIPHER_CTX *ctx,
|
|
::HMAC_CTX *hctx,
|
|
const int enc)
|
|
{
|
|
static_assert(TLSSessionTicketBase::Key::CIPHER_KEY_SIZE == 32, "unexpected cipher key size");
|
|
if (!EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.cipher_value_, iv, enc))
|
|
return false;
|
|
if (!HMAC_Init_ex(hctx, key.hmac_value_, TLSSessionTicketBase::Key::HMAC_KEY_SIZE, EVP_sha256(), nullptr))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool randomize_name_key(TLSSessionTicketBase::Name& name,
|
|
TLSSessionTicketBase::Key& key)
|
|
{
|
|
if (!RAND_bytes(name.value_, TLSSessionTicketBase::Name::SIZE))
|
|
return false;
|
|
if (!RAND_bytes(key.cipher_value_, TLSSessionTicketBase::Key::CIPHER_KEY_SIZE))
|
|
return false;
|
|
if (!RAND_bytes(key.hmac_value_, TLSSessionTicketBase::Key::HMAC_KEY_SIZE))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
|
|
|
static int client_hello_callback(::SSL *s, int *al, void *)
|
|
{
|
|
std::string sni_name;
|
|
|
|
// get OpenSSLContext
|
|
OpenSSLContext* self = (OpenSSLContext*) SSL_get_ex_data(s, SSL::context_data_index);
|
|
|
|
// get OpenSSLContext::SSL
|
|
SSL* self_ssl = (SSL *) SSL_get_ex_data(s, SSL::ssl_data_index);
|
|
|
|
try {
|
|
// get the SNI from the client hello
|
|
sni_name = client_hello_get_sni(s);
|
|
|
|
// process the SNI name, if provided
|
|
if (!sni_name.empty())
|
|
{
|
|
// save the SNI name in authcert
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->sni = sni_name;
|
|
|
|
// ignore the SNI if no handler was provided
|
|
if (self->config->sni_handler)
|
|
{
|
|
// get an alternative SSLFactoryAPI from the sni_handler
|
|
SSLFactoryAPI::Ptr fapi;
|
|
try {
|
|
SNI::Metadata::UPtr sm;
|
|
fapi = self->config->sni_handler->sni_hello(sni_name, sm, self->config);
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->sni_metadata = std::move(sm);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
OPENVPN_LOG("SNI HANDLER ERROR: " << e.what());
|
|
return sni_error(e.what(), SSL_AD_INTERNAL_ERROR, self, self_ssl, al);
|
|
}
|
|
if (!fapi)
|
|
return sni_error("SNI name not found", SSL_AD_UNRECOGNIZED_NAME, self, self_ssl, al);
|
|
|
|
// make sure that returned SSLFactoryAPI is an OpenSSLContext
|
|
self_ssl->sni_ctx = fapi.dynamic_pointer_cast<OpenSSLContext>();
|
|
if (!self_ssl->sni_ctx)
|
|
throw Exception("sni_handler returned wrong kind of SSLFactoryAPI");
|
|
|
|
// don't modify SSL CTX if the returned SSLFactoryAPI is ourself
|
|
if (fapi.get() != self)
|
|
{
|
|
SSL_set_SSL_CTX(s, self_ssl->sni_ctx->ctx);
|
|
self_ssl->set_parent(self_ssl->sni_ctx.get());
|
|
}
|
|
}
|
|
}
|
|
return SSL_CLIENT_HELLO_SUCCESS;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
OPENVPN_LOG("SNI exception in OpenSSLContext, SNI=" << sni_name << " : " << e.what());
|
|
*al = SSL_AD_INTERNAL_ERROR;
|
|
return SSL_CLIENT_HELLO_ERROR;
|
|
}
|
|
}
|
|
|
|
static int sni_error(std::string err,
|
|
const int ssl_ad_error,
|
|
OpenSSLContext* self,
|
|
SSL* self_ssl,
|
|
int* al)
|
|
{
|
|
if (self_ssl->authcert)
|
|
self_ssl->authcert->add_fail(0, AuthCert::Fail::SNI_ERROR, std::move(err));
|
|
if (self->deferred_cert_verify_failsafe(*self_ssl))
|
|
return SSL_CLIENT_HELLO_SUCCESS;
|
|
*al = ssl_ad_error;
|
|
return SSL_CLIENT_HELLO_ERROR;
|
|
}
|
|
|
|
static size_t sni_get_len(ConstBuffer& buf)
|
|
{
|
|
size_t ret = buf.pop_front() << 8;
|
|
ret += buf.pop_front();
|
|
return ret;
|
|
}
|
|
|
|
static std::string client_hello_get_sni(::SSL* s)
|
|
{
|
|
const unsigned char *p;
|
|
size_t remaining;
|
|
if (!SSL_client_hello_get0_ext(s, TLSEXT_TYPE_server_name, &p, &remaining))
|
|
return std::string();
|
|
|
|
// For safety, map a ConstBuffer onto returned OpenSSL TLSEXT_TYPE_server_name data.
|
|
ConstBuffer buf(p, remaining, true);
|
|
|
|
// Extract the length of the supplied list of names,
|
|
// and check that it matches size of remaining data
|
|
// in buf.
|
|
{
|
|
const size_t len = sni_get_len(buf);
|
|
if (len != buf.size())
|
|
throw Exception("bad name list size");
|
|
}
|
|
|
|
// Next byte must be TLSEXT_NAMETYPE_host_name.
|
|
if (buf.pop_front() != TLSEXT_NAMETYPE_host_name)
|
|
throw Exception("expecting TLSEXT_NAMETYPE_host_name");
|
|
|
|
// Now try to extract the SNI name.
|
|
{
|
|
const size_t len = sni_get_len(buf);
|
|
if (len > buf.size())
|
|
throw Exception("bad name size");
|
|
if (!Unicode::is_valid_utf8_uchar_buf(buf.c_data(), len, 1024 | Unicode::UTF8_NO_CTRL))
|
|
throw Exception("invalid UTF-8");
|
|
return std::string((const char *)buf.c_data(), len);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Return true if we should continue with authentication
|
|
// even though there was an error, because the user has
|
|
// enabled SSLConst::DEFERRED_CERT_VERIFY and wants the
|
|
// error to be logged in authcert so that it can be handled
|
|
// by a higher layer.
|
|
bool deferred_cert_verify_failsafe(const SSL& ssl) const
|
|
{
|
|
return (config->flags & SSLConst::DEFERRED_CERT_VERIFY)
|
|
&& ssl.authcert // failsafe: don't defer error unless
|
|
&& ssl.authcert->is_fail(); // authcert has recorded it
|
|
}
|
|
|
|
void erase()
|
|
{
|
|
if (epki)
|
|
{
|
|
delete epki;
|
|
epki = nullptr;
|
|
}
|
|
if (ctx)
|
|
{
|
|
SSL_CTX_free(ctx);
|
|
ctx = nullptr;
|
|
}
|
|
}
|
|
|
|
Config::Ptr config;
|
|
SSL_CTX* ctx = nullptr;
|
|
ExternalPKIImpl* epki = nullptr;
|
|
OpenSSLSessionCache::Ptr sess_cache; // client-side only
|
|
};
|
|
|
|
#ifdef OPENVPN_NO_EXTERN
|
|
int OpenSSLContext::SSL::ssl_data_index = -1;
|
|
int OpenSSLContext::SSL::context_data_index = -1;
|
|
#endif
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L && defined(OPENVPN_NO_EXTERN)
|
|
SSL_METHOD OpenSSLContext::SSL::ssl23_method_client_;
|
|
SSL_METHOD OpenSSLContext::SSL::ssl23_method_server_;
|
|
#endif
|
|
|
|
inline const std::string get_ssl_library_version()
|
|
{
|
|
return OPENSSL_VERSION_TEXT;
|
|
}
|
|
}
|
|
#endif
|