Asterisk - The Open Source Telephony Project  21.4.1
crypto_utils.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2023, Sangoma Technologies Corporation
5  *
6  * George Joseph <gjoseph@sangoma.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 #include <openssl/err.h>
20 #include <openssl/ssl.h>
21 #include <openssl/evp.h>
22 #include <openssl/md5.h>
23 #include <openssl/sha.h>
24 #include <openssl/bio.h>
25 #include <openssl/obj_mac.h>
26 #include <openssl/x509.h>
27 #include <openssl/x509v3.h>
28 #include <openssl/x509_vfy.h>
29 
30 #include "crypto_utils.h"
31 
32 #include "asterisk.h"
33 #include "asterisk/logger.h"
34 #include "asterisk/module.h"
35 #include "asterisk/stringfields.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/vector.h"
38 #include "asterisk/cli.h"
39 
40 void __attribute__((format(printf, 5, 6)))
41 crypto_log_openssl(int level, char *file, int line, const char *function,
42  const char *fmt, ...)
43 {
44  FILE *fp;
45  char *buffer;
46  size_t length;
47  va_list ap;
48  char *tmp_fmt;
49 
50  fp = open_memstream(&buffer, &length);
51  if (!fp) {
52  return;
53  }
54 
55  va_start(ap, fmt);
56  if (!ast_strlen_zero(fmt)) {
57  size_t fmt_len = strlen(fmt);
58  if (fmt[fmt_len - 1] == '\n') {
59  tmp_fmt = ast_strdupa(fmt);
60  tmp_fmt[fmt_len - 1] = '\0';
61  fmt = tmp_fmt;
62  }
63  }
64  vfprintf(fp, fmt, ap);
65  fputs(": ", fp);
66  ERR_print_errors_fp(fp);
67  fclose(fp);
68 
69  if (length) {
70  ast_log(level, file, line, function, "%s\n", buffer);
71  }
72 
73  ast_std_free(buffer);
74 }
75 
76 int crypto_register_x509_extension(const char *oid, const char *short_name,
77  const char *long_name)
78 {
79  int nid = 0;
80 
81  if (ast_strlen_zero(oid) || ast_strlen_zero(short_name) ||
82  ast_strlen_zero(long_name)) {
83  ast_log(LOG_ERROR, "One or more of oid, short_name or long_name are NULL or empty\n");
84  return -1;
85  }
86 
87  nid = OBJ_sn2nid(short_name);
88  if (nid != NID_undef) {
89  ast_log(LOG_NOTICE, "NID %d, object %s already registered\n", nid, short_name);
90  return nid;
91  }
92 
93  nid = OBJ_create(oid, short_name, long_name);
94  if (nid == NID_undef) {
95  crypto_log_openssl(LOG_ERROR, "Couldn't register %s X509 extension\n", short_name);
96  return -1;
97  }
98  ast_log(LOG_NOTICE, "Registered object %s as NID %d\n", short_name, nid);
99 
100  return nid;
101 }
102 
103 ASN1_OCTET_STRING *crypto_get_cert_extension_data(X509 *cert,
104  int nid, const char *short_name)
105 {
106  int ex_idx;
107  X509_EXTENSION *ex;
108 
109  if (nid <= 0) {
110  nid = OBJ_sn2nid(short_name);
111  if (nid == NID_undef) {
112  ast_log(LOG_ERROR, "Extension object for %s not found\n", short_name);
113  return NULL;
114  }
115  } else {
116  const char *tmp = OBJ_nid2sn(nid);
117  if (!tmp) {
118  ast_log(LOG_ERROR, "Extension object for NID %d not found\n", nid);
119  return NULL;
120  }
121  }
122 
123  ex_idx = X509_get_ext_by_NID(cert, nid, -1);
124  if (ex_idx < 0) {
125  ast_log(LOG_ERROR, "Extension index not found in certificate\n");
126  return NULL;
127  }
128  ex = X509_get_ext(cert, ex_idx);
129  if (!ex) {
130  ast_log(LOG_ERROR, "Extension not found in certificate\n");
131  return NULL;
132  }
133 
134  return X509_EXTENSION_get_data(ex);
135 }
136 
137 EVP_PKEY *crypto_load_privkey_from_file(const char *filename)
138 {
139  EVP_PKEY *key = NULL;
140  FILE *fp;
141 
142  if (ast_strlen_zero(filename)) {
143  ast_log(LOG_ERROR, "filename was null or empty\n");
144  return NULL;
145  }
146 
147  fp = fopen(filename, "r");
148  if (!fp) {
149  ast_log(LOG_ERROR, "Failed to open %s: %s\n", filename, strerror(errno));
150  return NULL;
151  }
152 
153  key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
154  fclose(fp);
155  if (!key) {
156  crypto_log_openssl(LOG_ERROR, "Failed to load private key from %s\n", filename);
157  }
158  return key;
159 }
160 
161 X509 *crypto_load_cert_from_file(const char *filename)
162 {
163  FILE *fp;
164  X509 *cert = NULL;
165 
166  if (ast_strlen_zero(filename)) {
167  ast_log(LOG_ERROR, "filename was null or empty\n");
168  return NULL;
169  }
170 
171  fp = fopen(filename, "r");
172  if (!fp) {
173  ast_log(LOG_ERROR, "Failed to open %s: %s\n", filename, strerror(errno));
174  return NULL;
175  }
176 
177  cert = PEM_read_X509(fp, &cert, NULL, NULL);
178  fclose(fp);
179  if (!cert) {
180  crypto_log_openssl(LOG_ERROR, "Failed to create cert from %s\n", filename);
181  }
182  return cert;
183 }
184 
185 X509 *crypto_load_cert_from_memory(const char *buffer, size_t size)
186 {
187  RAII_VAR(BIO *, bio, NULL, BIO_free_all);
188  X509 *cert = NULL;
189 
190  if (ast_strlen_zero(buffer) || size <= 0) {
191  ast_log(LOG_ERROR, "buffer was null or empty\n");
192  return NULL;
193  }
194 
195  bio = BIO_new_mem_buf(buffer, size);
196  if (!bio) {
197  crypto_log_openssl(LOG_ERROR, "Unable to create memory BIO\n");
198  return NULL;
199  }
200 
201  cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
202  if (!cert) {
203  crypto_log_openssl(LOG_ERROR, "Failed to create cert from BIO\n");
204  }
205  return cert;
206 }
207 
208 static EVP_PKEY *load_private_key_from_memory(const char *buffer, size_t size)
209 {
210  RAII_VAR(BIO *, bio, NULL, BIO_free_all);
211  EVP_PKEY *key = NULL;
212 
213  if (ast_strlen_zero(buffer) || size <= 0) {
214  ast_log(LOG_ERROR, "buffer was null or empty\n");
215  return NULL;
216  }
217 
218  bio = BIO_new_mem_buf(buffer, size);
219  if (!bio) {
220  crypto_log_openssl(LOG_ERROR, "Unable to create memory BIO\n");
221  return NULL;
222  }
223 
224  key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
225 
226  return key;
227 }
228 
229 EVP_PKEY *crypto_load_private_key_from_memory(const char *buffer, size_t size)
230 {
231  EVP_PKEY *key = load_private_key_from_memory(buffer, size);
232  if (!key) {
233  crypto_log_openssl(LOG_ERROR, "Unable to load private key from memory\n");
234  }
235  return key;
236 }
237 
238 int crypto_has_private_key_from_memory(const char *buffer, size_t size)
239 {
240  RAII_VAR(EVP_PKEY *, key, load_private_key_from_memory(buffer, size), EVP_PKEY_free);
241 
242  return key ? 1 : 0;
243 }
244 
245 static int dump_mem_bio(BIO *bio, unsigned char **buffer)
246 {
247  char *temp_ptr;
248  int raw_key_len;
249 
250  raw_key_len = BIO_get_mem_data(bio, &temp_ptr);
251  if (raw_key_len <= 0) {
252  crypto_log_openssl(LOG_ERROR, "Unable to extract raw public key\n");
253  return -1;
254  }
255  *buffer = ast_malloc(raw_key_len);
256  if (!*buffer) {
257  ast_log(LOG_ERROR, "Unable to allocate memory for raw public key\n");
258  return -1;
259  }
260  memcpy(*buffer, temp_ptr, raw_key_len);
261 
262  return raw_key_len;
263 }
264 
265 int crypto_extract_raw_pubkey(EVP_PKEY *key, unsigned char **buffer)
266 {
267  RAII_VAR(BIO *, bio, NULL, BIO_free_all);
268 
269  bio = BIO_new(BIO_s_mem());
270 
271  if (!bio || (PEM_write_bio_PUBKEY(bio, key) <= 0)) {
272  crypto_log_openssl(LOG_ERROR, "Unable to write pubkey to BIO\n");
273  return -1;
274  }
275 
276  return dump_mem_bio(bio, buffer);
277 }
278 
279 int crypto_get_raw_pubkey_from_cert(X509 *cert,
280  unsigned char **buffer)
281 {
282  RAII_VAR(EVP_PKEY *, public_key, X509_get_pubkey(cert), EVP_PKEY_free);
283 
284  if (!public_key) {
285  crypto_log_openssl(LOG_ERROR, "Unable to retrieve pubkey from cert\n");
286  return -1;
287  }
288 
289  return crypto_extract_raw_pubkey(public_key, buffer);
290 }
291 
292 int crypto_extract_raw_privkey(EVP_PKEY *key, unsigned char **buffer)
293 {
294  RAII_VAR(BIO *, bio, NULL, BIO_free_all);
295 
296  bio = BIO_new(BIO_s_mem());
297 
298  if (!bio || (PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL) <= 0)) {
299  crypto_log_openssl(LOG_ERROR, "Unable to write privkey to BIO\n");
300  return -1;
301  }
302 
303  return dump_mem_bio(bio, buffer);
304 }
305 
306 static void crypto_cert_store_destructor(void *obj)
307 {
308  struct crypto_cert_store *store = obj;
309 
310  if (store->store) {
311  X509_STORE_free(store->store);
312  }
313 }
314 
315 struct crypto_cert_store *crypto_create_cert_store(void)
316 {
317  struct crypto_cert_store *store = ao2_alloc(sizeof(*store), crypto_cert_store_destructor);
318  if (!store) {
319  ast_log(LOG_ERROR, "Failed to create crypto_cert_store\n");
320  return NULL;
321  }
322  store->store = X509_STORE_new();
323 
324  if (!store->store) {
325  crypto_log_openssl(LOG_ERROR, "Failed to create X509_STORE\n");
326  ao2_ref(store, -1);
327  return NULL;
328  }
329 
330  return store;
331 }
332 
333 int crypto_load_cert_store(struct crypto_cert_store *store, const char *file,
334  const char *path)
335 {
336  if (ast_strlen_zero(file) && ast_strlen_zero(path)) {
337  ast_log(LOG_ERROR, "Both file and path can't be NULL");
338  return -1;
339  }
340 
341  if (!store || !store->store) {
342  ast_log(LOG_ERROR, "store is NULL");
343  return -1;
344  }
345 
346  /*
347  * If the file or path are empty strings, we need to pass NULL
348  * so openssl ignores it otherwise it'll try to open a file or
349  * path named ''.
350  */
351  if (!X509_STORE_load_locations(store->store, S_OR(file, NULL), S_OR(path, NULL))) {
352  crypto_log_openssl(LOG_ERROR, "Failed to load store from file '%s' or path '%s'\n",
353  S_OR(file, "N/A"), S_OR(path, "N/A"));
354  return -1;
355  }
356 
357  return 0;
358 }
359 
360 int crypto_show_cli_store(struct crypto_cert_store *store, int fd)
361 {
362 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
363  STACK_OF(X509_OBJECT) *certs = NULL;
364  int count = 0;
365  int i = 0;
366  char subj[1024];
367 
368  certs = X509_STORE_get0_objects(store->store);
369  count = sk_X509_OBJECT_num(certs);
370  for (i = 0; i < count ; i++) {
371  X509_OBJECT *o = sk_X509_OBJECT_value(certs, i);
372  X509 *c = X509_OBJECT_get0_X509(o);
373  X509_NAME_oneline(X509_get_subject_name(c), subj, 1024);
374  ast_cli(fd, "%s\n", subj);
375  }
376  return count;
377 #else
378  ast_cli(fd, "This command is not supported until OpenSSL 1.1.0\n");
379  return 0;
380 #endif
381 }
382 
383 int crypto_is_cert_time_valid(X509*cert, time_t reftime)
384 {
385  ASN1_STRING *notbefore;
386  ASN1_STRING *notafter;
387 
388  if (!reftime) {
389  reftime = time(NULL);
390  }
391  notbefore = X509_get_notBefore(cert);
392  notafter = X509_get_notAfter(cert);
393  if (!notbefore || !notafter) {
394  ast_log(LOG_ERROR, "Either notbefore or notafter were not present in the cert\n");
395  return 0;
396  }
397 
398  return (X509_cmp_time(notbefore, &reftime) < 0 &&
399  X509_cmp_time(notafter, &reftime) > 0);
400 }
401 
402 int crypto_is_cert_trusted(struct crypto_cert_store *store, X509 *cert, const char **err_msg)
403 {
404  X509_STORE_CTX *verify_ctx = NULL;
405  int rc = 0;
406 
407  if (!(verify_ctx = X509_STORE_CTX_new())) {
408  crypto_log_openssl(LOG_ERROR, "Unable to create verify_ctx\n");
409  return 0;
410  }
411 
412  if (X509_STORE_CTX_init(verify_ctx, store->store, cert, NULL) != 1) {
413  X509_STORE_CTX_cleanup(verify_ctx);
414  X509_STORE_CTX_free(verify_ctx);
415  crypto_log_openssl(LOG_ERROR, "Unable to initialize verify_ctx\n");
416  return 0;
417  }
418 
419  rc = X509_verify_cert(verify_ctx);
420  if (rc != 1 && err_msg != NULL) {
421  int err = X509_STORE_CTX_get_error(verify_ctx);
422  *err_msg = X509_verify_cert_error_string(err);
423  }
424  X509_STORE_CTX_cleanup(verify_ctx);
425  X509_STORE_CTX_free(verify_ctx);
426 
427  return rc;
428 }
429 
430 #define SECS_PER_DAY 86400
431 time_t crypto_asn_time_as_time_t(ASN1_TIME *at)
432 {
433  int pday;
434  int psec;
435  time_t rt = time(NULL);
436 
437  if (!ASN1_TIME_diff(&pday, &psec, NULL, at)) {
438  crypto_log_openssl(LOG_ERROR, "Unable to calculate time diff\n");
439  return 0;
440  }
441 
442  rt += ((pday * SECS_PER_DAY) + psec);
443 
444  return rt;
445 }
446 #undef SECS_PER_DAY
447 
448 char *crypto_get_cert_subject(X509 *cert, const char *short_name)
449 {
450  size_t len = 0;
451  RAII_VAR(char *, buffer, NULL, ast_std_free);
452  char *search_buff = NULL;
453  char *search = NULL;
454  size_t search_len = 0;
455  char *rtn = NULL;
456  char *line = NULL;
457  /*
458  * If short_name was supplied, we want a multiline subject
459  * with each component on a separate line. This makes it easier
460  * to iterate over the components to find the one we want.
461  * Otherwise, we just want the whole subject on one line.
462  */
463  unsigned long flags =
464  short_name ? XN_FLAG_FN_SN | XN_FLAG_SEP_MULTILINE : XN_FLAG_ONELINE;
465  FILE *fp = open_memstream(&buffer, &len);
466  BIO *bio = fp ? BIO_new_fp(fp, BIO_CLOSE) : NULL;
467  X509_NAME *subject = X509_get_subject_name(cert);
468  int rc = 0;
469 
470  if (!fp || !bio || !subject) {
471  return NULL;
472  }
473 
474  rc = X509_NAME_print_ex(bio, subject, 0, flags);
475  BIO_free(bio);
476  if (rc < 0) {
477  return NULL;
478  }
479 
480  if (!short_name) {
481  rtn = ast_malloc(len + 1);
482  if (rtn) {
483  strcpy(rtn, buffer); /* Safe */
484  }
485  return rtn;
486  }
487 
488  search_len = strlen(short_name) + 1;
489  rc = ast_asprintf(&search, "%s=", short_name);
490  if (rc != search_len) {
491  return NULL;
492  }
493 
494  search_buff = buffer;
495  while((line = ast_read_line_from_buffer(&search_buff))) {
496  if (ast_begins_with(line, search)) {
497  rtn = ast_malloc(strlen(line) - search_len + 1);
498  if (rtn) {
499  strcpy(rtn, line + search_len); /* Safe */
500  }
501  break;
502  }
503  }
504 
505  ast_std_free(search);
506  return rtn;
507 }
508 
509 int crypto_load(void)
510 {
512 }
513 
514 int crypto_unload(void)
515 {
516  return 0;
517 }
518 
Asterisk main include file. File version handling, generic pbx functions.
Utility functions.
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
ao2 object wrapper for X509_STORE that provides locking and refcounting
Definition: crypto_utils.h:170
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
Support for logging to various files, console and syslog Configuration in file logger.conf.
static void crypto_load(int ifd, int ofd)
refresh RSA keys from file
Definition: res_crypto.c:819
Vector container support.
Standard Command Line Interface.
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
char * ast_read_line_from_buffer(char **buffer)
Read lines from a string buffer.
Definition: strings.c:371