1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
|
// 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) 2023- OpenVPN Inc.
// Copyright (C) 2021-2022 Selva Nair <selva.nair@gmail.com>
//
// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception OR GPL-2.0-only WITH openvpn-openssl-exception
//
#pragma once
#include <openssl/evp.h>
#include <openssl/provider.h>
#include <openvpn/pki/epkibase.hpp>
#include <openvpn/ssl/sslapi.hpp>
#include <openvpn/openssl/compat.hpp>
#include <openvpn/openssl/xkey/xkey_common.h>
namespace openvpn {
class XKeyExternalPKIImpl : public std::enable_shared_from_this<XKeyExternalPKIImpl>, public ExternalPKIImpl
{
private:
using OSSL_LIB_CTX_unique_ptr = std::unique_ptr<::OSSL_LIB_CTX, decltype(&::OSSL_LIB_CTX_free)>;
OSSL_LIB_CTX_unique_ptr tls_libctx{nullptr, ::OSSL_LIB_CTX_free};
ExternalPKIBase *external_pki;
std::string alias;
static void
xkey_logging_callback(const char *message, bool debug)
{
if (!debug)
OPENVPN_LOG(message);
}
static int
provider_load(OSSL_PROVIDER *prov, void *dest_libctx)
{
const char *name = OSSL_PROVIDER_get0_name(prov);
OSSL_PROVIDER_load(static_cast<OSSL_LIB_CTX *>(dest_libctx), name);
return 1;
}
static int
provider_unload(OSSL_PROVIDER *prov, [[maybe_unused]] void *unused)
{
OSSL_PROVIDER_unload(prov);
return 1;
}
void load_xkey_provider()
{
/* setup logging first to be able to see error while loading the provider */
xkey_set_logging_cb_function(xkey_logging_callback);
/* Make a new library context for use in TLS context */
if (!tls_libctx)
{
tls_libctx = OSSL_LIB_CTX_unique_ptr{::OSSL_LIB_CTX_new(), OSSL_LIB_CTX_free};
if (!tls_libctx)
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIImpl: OSSL_LIB_CTX_new");
/* Load all providers in default LIBCTX into this libctx.
* OpenSSL has a child libctx functionality to automate this,
* but currently that is usable only from within providers.
* So we do something close to it manually here.
*/
OSSL_PROVIDER_do_all(nullptr, provider_load, tls_libctx.get());
}
if (!OSSL_PROVIDER_available(tls_libctx.get(), "ovpn.xkey"))
{
OSSL_PROVIDER_add_builtin(tls_libctx.get(), "ovpn.xkey", xkey_provider_init);
if (!OSSL_PROVIDER_load(tls_libctx.get(), "ovpn.xkey"))
{
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIImpl: " << "failed loading external key provider: "
"Signing with external keys will not work.");
}
}
/* We only implement minimal functionality in ovpn.xkey, so we do not want
* methods in xkey to be picked unless absolutely required (i.e, when the key
* is external). Ensure this by setting a default propquery for the custom
* libctx that unprefers, but does not forbid, ovpn.xkey. See also man page
* of "property" in OpenSSL 3.0.
*/
EVP_set_default_properties(tls_libctx.get(), "?provider!=ovpn.xkey");
}
EVP_PKEY *
tls_ctx_use_external_key(::SSL_CTX *ctx, ::X509 *cert)
{
if (cert == nullptr)
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIImpl: pubcert undefined");
/* get the public key */
EVP_PKEY *pkey = X509_get0_pubkey(cert);
if (!pkey)
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIImpl: X509_get0_pubkey");
/* keep a refrence of XKeyExternalPKIImpl in the EVP_PKEY object, see also xkey_free_cb */
std::unique_ptr<decltype(shared_from_this())> thisptr{new std::shared_ptr(shared_from_this())};
EVP_PKEY *privkey = xkey_load_generic_key(tls_libctx.get(), thisptr.get(), pkey, xkey_sign_cb, xkey_free_cb);
if (!privkey
|| !SSL_CTX_use_PrivateKey(ctx, privkey))
{
EVP_PKEY_free(privkey);
return nullptr;
}
thisptr.release();
return privkey;
}
public:
[[nodiscard]] static std::shared_ptr<XKeyExternalPKIImpl> create(SSL_CTX *ssl_ctx, ::X509 *cert, ExternalPKIBase *external_pki, std::string alias)
{
auto ret = std::shared_ptr<XKeyExternalPKIImpl>{new XKeyExternalPKIImpl{external_pki, alias}};
ret->use_external_key(ssl_ctx, cert);
return ret;
}
~XKeyExternalPKIImpl()
{
if (tls_libctx)
{
OSSL_PROVIDER_do_all(tls_libctx.get(), provider_unload, nullptr);
}
}
XKeyExternalPKIImpl(ExternalPKIBase *external_pki, std::string alias)
: external_pki(external_pki), alias(alias)
{
}
void use_external_key(SSL_CTX *ssl_ctx, ::X509 *cert)
{
/* Ensure provider is loaded */
load_xkey_provider();
/* Set public key/certificate */
::EVP_PKEY *privkey = tls_ctx_use_external_key(ssl_ctx, cert);
if (!privkey)
{
OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIImpl: " << "SSL_CTX_use_PrivateKey");
}
EVP_PKEY_free(privkey);
}
private:
static int xkey_sign_cb(void *this_ptr,
unsigned char *sig,
size_t *siglen,
const unsigned char *tbs,
size_t tbslen,
XKEY_SIGALG alg)
{
return static_cast<std::shared_ptr<XKeyExternalPKIImpl> *>(this_ptr)->get()->xkey_sign(sig, siglen, tbs, tbslen, alg);
}
static void xkey_free_cb(void *this_ptr)
{
/* This method implements a reference counting for the library context.
* Normally objects in OpenSSL are refcounted and will only be freed
* when no object still uses that object. However library contexts are
* not reference counted. So we use the shared_ptr here to keep this
* objects and the \c tls_libctx alive as long as there still OpenSSL
* objects using it.The xkey provider will be kept alive as
* long as is still an object referencing it (like an ::EVP_PKEY).
*/
delete static_cast<std::shared_ptr<XKeyExternalPKIImpl> *>(this_ptr);
}
/**
* Signature callback for xkey_provider
*
* @param sig On successful return signature is in sig.
* @param siglen On entry *siglen has length of buffer sig,
* on successful return size of signature
* @param tbs hash or message to be signed
* @param tbslen len of data in dgst
* @param alg extra signature parameters
*
* @return signature length or -1 on error.
*/
int
xkey_sign(unsigned char *sig, size_t *siglen, const unsigned char *tbs, size_t tbslen, XKEY_SIGALG alg)
{
std::string algstr;
std::string hashalg;
std::string saltlen;
unsigned char enc[EVP_MAX_MD_SIZE + 32]; /* 32 bytes enough for digest info structure */
size_t enc_len = sizeof(enc);
if (!strcmp(alg.keytype, "ED448") || !strcmp(alg.keytype, "ED25519"))
{
algstr = alg.keytype;
hashalg = alg.mdname;
}
else if (!strcmp(alg.keytype, "EC"))
{
algstr = "ECDSA";
if (strcmp(alg.op, "Sign"))
{
hashalg = alg.mdname;
}
}
else if (!strcmp(alg.padmode, "pkcs1"))
{
/* assume RSA key */
algstr = "RSA_PKCS1_PADDING";
/* For Sign, interface expects a pkcs1 encoded digest -- add it */
if (!strcmp(alg.op, "Sign"))
{
if (!xkey_encode_pkcs1(enc, &enc_len, alg.mdname, tbs, tbslen))
{
return 0;
}
tbs = enc;
tbslen = enc_len;
}
else
{
/* For undigested message, add hashalg=digest parameter */
hashalg = alg.mdname;
}
}
else if (!strcmp(alg.padmode, "none") && !strcmp(alg.op, "Sign"))
{
/* NO_PADDING requires digested data */
algstr = "RSA_NO_PADDING";
}
else if (!strcmp(alg.padmode, "pss"))
{
algstr = "RSA_PKCS1_PSS_PADDING";
hashalg = alg.mdname;
saltlen = alg.saltlen;
}
else
{
OPENVPN_LOG("RSA padding mode not supported by external key " << alg.padmode);
return 0;
}
/* convert 'tbs' to base64 */
ConstBuffer from_buf(tbs, tbslen, true);
const std::string from_b64 = base64->encode(from_buf);
std::string sig_b64;
external_pki->sign(alias, from_b64, sig_b64, algstr, hashalg, saltlen);
Buffer sigbuf(static_cast<void *>(sig), *siglen, false);
base64->decode(sigbuf, sig_b64);
*siglen = sigbuf.size();
return (int)*siglen;
}
};
}; // namespace openvpn
|