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