OpenVPN
xkey_helper.c
Go to the documentation of this file.
1 /*
2  * OpenVPN -- An application to securely tunnel IP networks
3  * over a single TCP/UDP 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) 2021-2023 Selva Nair <selva.nair@gmail.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation, either version 2 of the License,
13  * or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28 
29 #include "syshead.h"
30 #include "error.h"
31 #include "buffer.h"
32 #include "xkey_common.h"
33 #include "manage.h"
34 #include "base64.h"
35 
36 #ifdef HAVE_XKEY_PROVIDER
37 
38 #include <openssl/provider.h>
39 #include <openssl/params.h>
40 #include <openssl/core_dispatch.h>
41 #include <openssl/core_object.h>
42 #include <openssl/core_names.h>
43 #include <openssl/store.h>
44 #include <openssl/evp.h>
45 #include <openssl/err.h>
46 
47 static const char *const props = XKEY_PROV_PROPS;
48 
49 XKEY_EXTERNAL_SIGN_fn xkey_management_sign;
50 
51 static void
52 print_openssl_errors()
53 {
54  unsigned long e;
55  while ((e = ERR_get_error()))
56  {
57  msg(M_WARN, "OpenSSL error %lu: %s\n", e, ERR_error_string(e, NULL));
58  }
59 }
60 
62 int
63 xkey_digest(const unsigned char *src, size_t srclen, unsigned char *buf,
64  size_t *buflen, const char *mdname)
65 {
66  dmsg(D_XKEY, "In xkey_digest");
67  EVP_MD *md = EVP_MD_fetch(NULL, mdname, NULL); /* from default context */
68  if (!md)
69  {
70  msg(M_WARN, "WARN: xkey_digest: MD_fetch failed for <%s>", mdname);
71  return 0;
72  }
73 
74  unsigned int len = (unsigned int) *buflen;
75  if (EVP_Digest(src, srclen, buf, &len, md, NULL) != 1)
76  {
77  msg(M_WARN, "WARN: xkey_digest: EVP_Digest failed");
78  return 0;
79  }
80  EVP_MD_free(md);
81 
82  *buflen = len;
83  return 1;
84 }
85 
86 #ifdef ENABLE_MANAGEMENT
87 
94 EVP_PKEY *
95 xkey_load_management_key(OSSL_LIB_CTX *libctx, EVP_PKEY *pubkey)
96 {
97  ASSERT(pubkey);
98 
99  /* Management interface doesn't require any handle to be
100  * stored in the key. We use a dummy pointer as we do need a
101  * non-NULL value to indicate private key is available.
102  */
103  void *dummy = &"dummy";
104 
105  XKEY_EXTERNAL_SIGN_fn *sign_op = xkey_management_sign;
106 
107  return xkey_load_generic_key(libctx, dummy, pubkey, sign_op, NULL);
108 }
109 #endif
110 
116 EVP_PKEY *
117 xkey_load_generic_key(OSSL_LIB_CTX *libctx, void *handle, EVP_PKEY *pubkey,
118  XKEY_EXTERNAL_SIGN_fn *sign_op, XKEY_PRIVKEY_FREE_fn *free_op)
119 {
120  EVP_PKEY *pkey = NULL;
121  const char *origin = "external";
122 
123  /* UTF8 string pointers in here are only read from, so cast is safe */
124  OSSL_PARAM params[] = {
125  {"xkey-origin", OSSL_PARAM_UTF8_STRING, (char *) origin, 0, 0},
126  {"pubkey", OSSL_PARAM_OCTET_STRING, &pubkey, sizeof(pubkey), 0},
127  {"handle", OSSL_PARAM_OCTET_PTR, &handle, sizeof(handle), 0},
128  {"sign_op", OSSL_PARAM_OCTET_PTR, (void **) &sign_op, sizeof(sign_op), 0},
129  {"free_op", OSSL_PARAM_OCTET_PTR, (void **) &free_op, sizeof(free_op), 0},
130  {NULL, 0, NULL, 0, 0}
131  };
132 
133  /* Do not use EVP_PKEY_new_from_pkey as that will take keymgmt from pubkey */
134  EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(libctx, EVP_PKEY_get0_type_name(pubkey), props);
135  if (!ctx
136  || EVP_PKEY_fromdata_init(ctx) != 1
137  || EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1)
138  {
139  print_openssl_errors();
140  msg(M_FATAL, "OpenSSL error: failed to load key into ovpn.xkey provider");
141  }
142  if (ctx)
143  {
144  EVP_PKEY_CTX_free(ctx);
145  }
146 
147  return pkey;
148 }
149 
150 #ifdef ENABLE_MANAGEMENT
151 
164 int
165 xkey_management_sign(void *unused, unsigned char *sig, size_t *siglen,
166  const unsigned char *tbs, size_t tbslen, XKEY_SIGALG alg)
167 {
168  dmsg(D_XKEY, "In xkey_management_sign with keytype = %s, op = %s",
169  alg.keytype, alg.op);
170 
171  (void) unused;
172  char alg_str[128];
173  unsigned char buf[EVP_MAX_MD_SIZE]; /* for computing digest if required */
174  size_t buflen = sizeof(buf);
175 
176  unsigned char enc[EVP_MAX_MD_SIZE + 32]; /* 32 bytes enough for digest info structure */
177  size_t enc_len = sizeof(enc);
178 
179  unsigned int flags = management->settings.flags;
180  bool is_message = !strcmp(alg.op, "DigestSign"); /* tbs is message, not digest */
181 
182  /* if management client cannot do digest -- we do it here */
183  if (!strcmp(alg.op, "DigestSign") && !(flags & MF_EXTERNAL_KEY_DIGEST)
184  && strcmp(alg.mdname, "none"))
185  {
186  dmsg(D_XKEY, "xkey_management_sign: computing digest");
187  if (xkey_digest(tbs, tbslen, buf, &buflen, alg.mdname))
188  {
189  tbs = buf;
190  tbslen = buflen;
191  alg.op = "Sign";
192  is_message = false;
193  }
194  else
195  {
196  return 0;
197  }
198  }
199 
200  if (!strcmp(alg.keytype, "EC"))
201  {
202  if (!strcmp(alg.op, "Sign"))
203  {
204  strncpynt(alg_str, "ECDSA", sizeof(alg_str));
205  }
206  else
207  {
208  openvpn_snprintf(alg_str, sizeof(alg_str), "ECDSA,hashalg=%s", alg.mdname);
209  }
210  }
211  else if (!strcmp(alg.keytype, "ED448") || !strcmp(alg.keytype, "ED25519"))
212  {
213  strncpynt(alg_str, alg.keytype, sizeof(alg_str));
214  }
215  /* else assume RSA key */
216  else if (!strcmp(alg.padmode, "pkcs1") && (flags & MF_EXTERNAL_KEY_PKCS1PAD))
217  {
218  /* For Sign, management interface expects a pkcs1 encoded digest -- add it */
219  if (!strcmp(alg.op, "Sign"))
220  {
221  if (!encode_pkcs1(enc, &enc_len, alg.mdname, tbs, tbslen))
222  {
223  return 0;
224  }
225  tbs = enc;
226  tbslen = enc_len;
227  strncpynt(alg_str, "RSA_PKCS1_PADDING", sizeof(alg_str));
228  }
229  /* For undigested message, add hashalg=digest parameter */
230  else
231  {
232  openvpn_snprintf(alg_str, sizeof(alg_str), "%s,hashalg=%s",
233  "RSA_PKCS1_PADDING", alg.mdname);
234  }
235  }
236  else if (!strcmp(alg.padmode, "none") && (flags & MF_EXTERNAL_KEY_NOPADDING)
237  && !strcmp(alg.op, "Sign")) /* NO_PADDING requires digested data */
238  {
239  strncpynt(alg_str, "RSA_NO_PADDING", sizeof(alg_str));
240  }
241  else if (!strcmp(alg.padmode, "pss") && (flags & MF_EXTERNAL_KEY_PSSPAD))
242  {
243  openvpn_snprintf(alg_str, sizeof(alg_str), "%s,hashalg=%s,saltlen=%s",
244  "RSA_PKCS1_PSS_PADDING", alg.mdname, alg.saltlen);
245  }
246  else
247  {
248  msg(M_NONFATAL, "RSA padding mode not supported by management-client <%s>",
249  alg.padmode);
250  return 0;
251  }
252 
253  if (is_message)
254  {
255  strncat(alg_str, ",data=message", sizeof(alg_str) - strlen(alg_str) - 1);
256  }
257 
258  dmsg(D_LOW, "xkey management_sign: requesting sig with algorithm <%s>", alg_str);
259 
260  char *in_b64 = NULL;
261  char *out_b64 = NULL;
262  int len = -1;
263 
264  int bencret = openvpn_base64_encode(tbs, (int) tbslen, &in_b64);
265 
266  if (management && bencret > 0)
267  {
268  out_b64 = management_query_pk_sig(management, in_b64, alg_str);
269  }
270  if (out_b64)
271  {
272  len = openvpn_base64_decode(out_b64, sig, (int) *siglen);
273  }
274  free(in_b64);
275  free(out_b64);
276 
277  *siglen = (len > 0) ? len : 0;
278 
279  return (*siglen > 0);
280 }
281 #endif /* ENABLE MANAGEMENT */
282 
298 bool
299 encode_pkcs1(unsigned char *enc, size_t *enc_len, const char *mdname,
300  const unsigned char *tbs, size_t tbslen)
301 {
302  ASSERT(enc_len != NULL);
303  ASSERT(tbs != NULL);
304 
305  /* Tabulate the digest info header for expected hash algorithms
306  * These were pre-computed using the DigestInfo definition:
307  * DigestInfo ::= SEQUENCE {
308  * digestAlgorithm DigestAlgorithmIdentifier,
309  * digest Digest }
310  * Also see the table in RFC 8017 section 9.2, Note 1.
311  */
312 
313  const unsigned char sha1[] = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b,
314  0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14};
315  const unsigned char sha256[] = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
316  0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
317  const unsigned char sha384[] = {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
318  0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30};
319  const unsigned char sha512[] = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
320  0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
321  const unsigned char sha224[] = {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
322  0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c};
323  const unsigned char sha512_224[] = {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
324  0x01, 0x65, 0x03, 0x04, 0x02, 0x05, 0x05, 0x00, 0x04, 0x1c};
325  const unsigned char sha512_256[] = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
326  0x01, 0x65, 0x03, 0x04, 0x02, 0x06, 0x05, 0x00, 0x04, 0x20};
327 
328  typedef struct {
329  const int nid;
330  const unsigned char *header;
331  size_t sz;
332  } DIG_INFO;
333 
334 #define MAKE_DI(x) {NID_ ## x, x, sizeof(x)}
335 
336  DIG_INFO dinfo[] = {MAKE_DI(sha1), MAKE_DI(sha256), MAKE_DI(sha384),
337  MAKE_DI(sha512), MAKE_DI(sha224), MAKE_DI(sha512_224),
338  MAKE_DI(sha512_256), {0, NULL, 0}};
339 
340  int out_len = 0;
341  int ret = 0;
342 
343  int nid = OBJ_sn2nid(mdname);
344  if (nid == NID_undef)
345  {
346  /* try harder -- name variants like SHA2-256 doesn't work */
347  nid = EVP_MD_type(EVP_get_digestbyname(mdname));
348  if (nid == NID_undef)
349  {
350  msg(M_WARN, "Error: encode_pkcs11: invalid digest name <%s>", mdname);
351  goto done;
352  }
353  }
354 
355  if (tbslen != EVP_MD_size(EVP_get_digestbyname(mdname)))
356  {
357  msg(M_WARN, "Error: encode_pkcs11: invalid input length <%d>", (int)tbslen);
358  goto done;
359  }
360 
361  if (nid == NID_md5_sha1) /* no encoding needed -- just copy */
362  {
363  if (enc && (*enc_len >= tbslen))
364  {
365  memcpy(enc, tbs, tbslen);
366  ret = true;
367  }
368  out_len = tbslen;
369  goto done;
370  }
371 
372  /* locate entry for nid in dinfo table */
373  DIG_INFO *di = dinfo;
374  while ((di->nid != nid) && (di->nid != 0))
375  {
376  di++;
377  }
378  if (di->nid != nid) /* not found in our table */
379  {
380  msg(M_WARN, "Error: encode_pkcs11: unsupported hash algorithm <%s>", mdname);
381  goto done;
382  }
383 
384  out_len = tbslen + di->sz;
385 
386  if (enc && (out_len <= (int) *enc_len))
387  {
388  /* combine header and digest */
389  memcpy(enc, di->header, di->sz);
390  memcpy(enc + di->sz, tbs, tbslen);
391  dmsg(D_XKEY, "encode_pkcs1: digest length = %d encoded length = %d",
392  (int) tbslen, (int) out_len);
393  ret = true;
394  }
395 
396 done:
397  *enc_len = out_len; /* assignment safe as out_len is > 0 at this point */
398 
399  return ret;
400 }
401 
408 int
409 ecdsa_bin2der(unsigned char *buf, int len, size_t capacity)
410 {
411  ECDSA_SIG *ecsig = NULL;
412  int rlen = len/2;
413  BIGNUM *r = BN_bin2bn(buf, rlen, NULL);
414  BIGNUM *s = BN_bin2bn(buf+rlen, rlen, NULL);
415  if (!r || !s)
416  {
417  goto err;
418  }
419  ecsig = ECDSA_SIG_new(); /* this does not allocate r, s */
420  if (!ecsig)
421  {
422  goto err;
423  }
424  if (!ECDSA_SIG_set0(ecsig, r, s)) /* ecsig takes ownership of r and s */
425  {
426  ECDSA_SIG_free(ecsig);
427  goto err;
428  }
429 
430  int derlen = i2d_ECDSA_SIG(ecsig, NULL);
431  if (derlen > (int) capacity)
432  {
433  ECDSA_SIG_free(ecsig);
434  msg(M_NONFATAL, "Error: DER encoded ECDSA signature is too long (%d)\n", derlen);
435  return 0;
436  }
437  derlen = i2d_ECDSA_SIG(ecsig, &buf);
438  ECDSA_SIG_free(ecsig);
439  return derlen;
440 
441 err:
442  BN_free(r); /* it is ok to free NULL BN */
443  BN_free(s);
444  return 0;
445 }
446 
447 #endif /* HAVE_XKEY_PROVIDER */
openvpn_base64_decode
int openvpn_base64_decode(const char *str, void *data, int size)
Definition: base64.c:158
MF_EXTERNAL_KEY_PSSPAD
#define MF_EXTERNAL_KEY_PSSPAD
Definition: manage.h:44
management::settings
struct man_settings settings
Definition: manage.h:338
MF_EXTERNAL_KEY_PKCS1PAD
#define MF_EXTERNAL_KEY_PKCS1PAD
Definition: manage.h:39
error.h
MF_EXTERNAL_KEY_NOPADDING
#define MF_EXTERNAL_KEY_NOPADDING
Definition: manage.h:38
EVP_MD_fetch
static const EVP_MD * EVP_MD_fetch(void *ctx, const char *algorithm, const char *properties)
Definition: openssl_compat.h:788
M_FATAL
#define M_FATAL
Definition: error.h:95
M_NONFATAL
#define M_NONFATAL
Definition: error.h:96
manage.h
xkey_common.h
openvpn_base64_encode
int openvpn_base64_encode(const void *data, int size, char **str)
Definition: base64.c:52
dmsg
#define dmsg(flags,...)
Definition: error.h:154
D_LOW
#define D_LOW
Definition: errlevel.h:97
EVP_MD_free
static void EVP_MD_free(const EVP_MD *md)
Definition: openssl_compat.h:802
ASSERT
#define ASSERT(x)
Definition: error.h:201
MF_EXTERNAL_KEY_DIGEST
#define MF_EXTERNAL_KEY_DIGEST
Definition: manage.h:45
M_WARN
#define M_WARN
Definition: error.h:97
base64.h
management_query_pk_sig
char * management_query_pk_sig(struct management *man, const char *b64_data, const char *algorithm)
Definition: manage.c:3747
buffer.h
syshead.h
D_XKEY
#define D_XKEY
Definition: errlevel.h:117
strncpynt
static void strncpynt(char *dest, const char *src, size_t maxlen)
Definition: buffer.h:361
openvpn_snprintf
bool openvpn_snprintf(char *str, size_t size, const char *format,...)
Definition: buffer.c:294
management
Definition: manage.h:335
config.h
msg
#define msg(flags,...)
Definition: error.h:150
OSSL_LIB_CTX
void OSSL_LIB_CTX
Definition: openssl_compat.h:774
man_settings::flags
unsigned int flags
Definition: manage.h:246