]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
stir_shaken: CRL fixes and a new CLI command
authorGeorge Joseph <gjoseph@sangoma.com>
Fri, 19 Jul 2024 14:46:31 +0000 (08:46 -0600)
committerAsterisk Development Team <asteriskteam@digium.com>
Thu, 12 Sep 2024 18:46:27 +0000 (18:46 +0000)
* Fixed a bug in crypto_show_cli_store that was causing asterisk
to crash if there were certificate revocation lists in the
verification certificate store.  We're also now prefixing
certificates with "Cert:" and CRLs with "CRL:" to distinguish them
in the list.

* Added 'untrusted_cert_file' and 'untrusted_cert_path' options
to both verification and profile objects.  If you have CRLs that
are signed by a different CA than the incoming X5U certificate
(indirect CRL), you'll need to provide the certificate of the
CRL signer here.  Thse will show up as 'Untrusted" when showing
the verification or profile objects.

* Fixed loading of crl_path.  The OpenSSL API we were using to
load CRLs won't actually load them from a directory, only a file.
We now scan the directory ourselves and load the files one-by-one.

* Fixed the verification flags being set on the certificate store.
  - Removed the CRL_CHECK_ALL flag as this was causing all certificates
    to be checked for CRL extensions and failing to verify the cert if
    there was none.  This basically caused all certs to fail when a CRL
    was provided via crl_file or crl_path.
  - Added the EXTENDED_CRL_SUPPORT flag as it is required to handle
    indirect CRLs.

* Added a new CLI command...
`stir_shaken verify certificate_file <certificate_file> [ <profile> ]`
which will assist troubleshooting certificate problems by allowing
the user to manually verify a certificate file against either the
global verification certificate store or the store for a specific
profile.

* Updated the XML documentation and the sample config file.

Resolves: #809
(cherry picked from commit 2fb3215f031325c4ffb213f5d475771b6b45a849)

configs/samples/stir_shaken.conf.sample
res/res_stir_shaken/attestation_config.c
res/res_stir_shaken/common_config.c
res/res_stir_shaken/common_config.h
res/res_stir_shaken/crypto_utils.c
res/res_stir_shaken/crypto_utils.h
res/res_stir_shaken/profile_config.c
res/res_stir_shaken/stir_shaken_doc.xml
res/res_stir_shaken/verification_config.c

index c7ee89230e8e1d0568ec10e22cac4fcf4abb2a90..5e2b3b9219881150ed2229391d457b329ea2af63 100644 (file)
@@ -209,16 +209,22 @@ CA certififcate to you separately.
 Default: no
 
 -- ca_file -----------------------------------------------------------
-Path to a single file containing a CA certificate or certificate chain
-to be used to validate the certificates in incoming requests.
+Path to a file containing one or more CA certs in PEM format.
+These certs are used to verify the chain of trust for the
+certificate retrieved from the X5U Identity header parameter.  This
+file must have the root CA certificate, the certificate of the
+issuer of the X5U certificate, and any intermediate certificates
+between them.
 
 Default: none
 
 -- ca_path -----------------------------------------------------------
-Path to a directory containing one or more CA certificates to be used
-to validate the certificates in incoming requests.  The files in that
-directory must contain only one certificate each and the directory
-must be hashed using the OpenSSL 'c_rehash' utility.
+Path to a directory containing one or more hashed CA certs.
+See ca_file above.
+For this option, each certificate must be placed in its own
+PEM file in the directory specified and hashed with the
+following command:
+`openssl rehash <ca_path>`
 
 Default: none
 
@@ -226,21 +232,50 @@ NOTE:  Both ca_file and ca_path can be specified but at least one
 MUST be.
 
 -- crl_file -----------------------------------------------------------
-Path to a single file containing a CA certificate revocation list
-to be used to validate the certificates in incoming requests.
+Path to a file containing one or more CRLs in PEM format.
+If you with to check if the certificate in the X5U Identity header
+parameter has been revoked, you'll need the certificate revocation
+list generated by the issuer.
 
 Default: none
 
 -- crl_path -----------------------------------------------------------
-Path to a directory containing one or more CA certificate revocation
-lists to be used to validate the certificates in incoming requests.
-The files in that directory must contain only one certificate each and
-the directory must be hashed using the OpenSSL 'c_rehash' utility.
+Path to a directory containing one or more hashed CRLs.
+See crl_file above.
+For this option, each CRL must be placed in its own
+PEM file in the directory specified and hashed with the
+following command:
+`openssl rehash <crl_path>`
 
 Default: none
 
 NOTE:  Neither crl_file nor crl_path are required.
 
+-- untrusted_cert_file ------------------------------------------------
+Path to a file containing one or more untrusted certs in PEM format.
+Unfortunately, sometimes the CRLs are signed by a different CA
+than the certificate being verified.  In this case, you'll need to
+provide the certificate belonging to the issuer of the CRL.  That
+certificate is considered "untrusted" by OpenSSL and can't be placed
+in the ca_file or ca_path.  It has to be specified here.
+
+Default: none
+
+-- untrusted_cert_path ------------------------------------------------
+Path to a directory containing one or more hashed untrusted certs used
+to verify CRLs.
+See untrusted_cert_file above.
+For this option, each certificates must be placed in its own
+PEM file in the directory specified and hashed with the
+following command:
+`openssl rehash <ca_path>`
+
+Default: none
+
+NOTE:  Neither untrusted_cert_file nor untrusted_cert_path are required
+unless you're verifying CRLs that aren't signed by the same CA as the
+X5U certificate.
+
 -- cert_cache_dir -----------------------------------------------------
 Incoming Identity headers will have a URL pointing to the certificate
 used to sign the header.  To prevent us from having to retrieve the
index d7efc9e4752352fd44ffdbb836b8a16462ede269..7a5743c9f7411108876977a81a80d539649ec3d0 100644 (file)
@@ -245,6 +245,11 @@ static char *attestation_show(struct ast_cli_entry *e, int cmd, struct ast_cli_a
                return CLI_SHOWUSAGE;
        }
 
+       if (!as_is_config_loaded()) {
+               ast_log(LOG_WARNING,"Stir/Shaken attestation service disabled.  Either there were errors in the 'attestation' object in stir_shaken.conf or it was missing altogether.\n");
+               return CLI_FAILURE;
+       }
+
        cfg = as_get_cfg();
        config_object_cli_show(cfg, a, &data, 0);
        ao2_cleanup(cfg);
index f753b41ca60ac11cea46d86bde466e0c98f8c2f8..627ea81e7e4e158ff5f328e8e0a26437149b9f6a 100644 (file)
@@ -259,6 +259,112 @@ char *config_object_tab_complete_name(const char *word, struct ao2_container *co
        return NULL;
 }
 
+
+/* Remove everything except 0-9, *, and # in telephone number according to RFC 8224
+ * (required by RFC 8225 as part of canonicalization) */
+char *canonicalize_tn(const char *tn, char *dest_tn)
+{
+       int i;
+       const char *s = tn;
+       size_t len = tn ? strlen(tn) : 0;
+       char *new_tn = dest_tn;
+       SCOPE_ENTER(3, "tn: %s\n", S_OR(tn, "(null)"));
+
+       if (ast_strlen_zero(tn)) {
+               *dest_tn = '\0';
+               SCOPE_EXIT_RTN_VALUE(NULL, "Empty TN\n");
+       }
+
+       if (!dest_tn) {
+               SCOPE_EXIT_RTN_VALUE(NULL, "No destination buffer\n");
+       }
+
+       for (i = 0; i < len; i++) {
+               if (isdigit(*s) || *s == '#' || *s == '*') { /* Only characters allowed */
+                       *new_tn++ = *s;
+               }
+               s++;
+       }
+       *new_tn = '\0';
+       SCOPE_EXIT_RTN_VALUE(dest_tn, "Canonicalized '%s' -> '%s'\n", tn, dest_tn);
+}
+
+char *canonicalize_tn_alloc(const char *tn)
+{
+       char *canon_tn = ast_strlen_zero(tn) ? NULL : ast_malloc(strlen(tn) + 1);
+       if (!canon_tn) {
+               return NULL;
+       }
+       return canonicalize_tn(tn, canon_tn);
+}
+
+static char *cli_verify_cert(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       RAII_VAR(struct profile_cfg *, profile, NULL, ao2_cleanup);
+       RAII_VAR(struct verification_cfg *, vs_cfg, NULL, ao2_cleanup);
+       struct crypto_cert_store *tcs;
+       X509 *cert = NULL;
+       const char *errmsg = NULL;
+
+       switch(cmd) {
+       case CLI_INIT:
+               e->command = "stir_shaken verify certificate_file";
+               e->usage =
+                       "Usage: stir_shaken verify certificate_file <certificate_file> [ <profile> ]\n"
+                       "       Verify an external certificate file against the global or profile verification store\n";
+               return NULL;
+       case CLI_GENERATE:
+               if (a->pos == 4) {
+                       return config_object_tab_complete_name(a->word, profile_get_all());
+               } else {
+                       return NULL;
+               }
+       }
+
+       if (a->argc < 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       if (a->argc == 5) {
+               profile = profile_get_cfg(a->argv[4]);
+               if (!profile) {
+                       ast_cli(a->fd, "Profile %s doesn't exist\n", a->argv[4]);
+                       return CLI_SUCCESS;
+               }
+               if (!profile->vcfg_common.tcs) {
+                       ast_cli(a->fd,"Profile %s doesn't have a certificate store\n", a->argv[4]);
+                       return CLI_SUCCESS;
+               }
+               tcs = profile->vcfg_common.tcs;
+       } else {
+               vs_cfg = vs_get_cfg();
+               if (!vs_cfg) {
+                       ast_cli(a->fd, "No verification store found\n");
+                       return CLI_SUCCESS;
+               }
+               tcs = vs_cfg->vcfg_common.tcs;
+       }
+
+       cert = crypto_load_cert_from_file(a->argv[3]);
+       if (!cert) {
+               ast_cli(a->fd, "Failed to load certificate from %s.  See log for details\n", a->argv[3]);
+               return CLI_SUCCESS;
+       }
+
+       if (crypto_is_cert_trusted(tcs, cert, &errmsg)) {
+               ast_cli(a->fd, "Certificate %s trusted\n", a->argv[3]);
+       } else {
+               ast_cli(a->fd, "Certificate %s NOT trusted: %s\n", a->argv[3], errmsg);
+       }
+       X509_free(cert);
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+       AST_CLI_DEFINE(cli_verify_cert, "Verify a certificate file against the global or a profile verification store"),
+};
+
 int common_config_reload(void)
 {
        SCOPE_ENTER(2, "Stir Shaken Reload\n");
@@ -283,6 +389,8 @@ int common_config_reload(void)
 
 int common_config_unload(void)
 {
+       ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
        profile_unload();
        tn_config_unload();
        as_unload();
@@ -348,44 +456,7 @@ int common_config_load(void)
                        named_acl_changed_sub, ast_named_acl_change_type());
        }
 
-       SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_SUCCESS, "Stir Shaken Load Done\n");
-}
-
+       ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
 
-/* Remove everything except 0-9, *, and # in telephone number according to RFC 8224
- * (required by RFC 8225 as part of canonicalization) */
-char *canonicalize_tn(const char *tn, char *dest_tn)
-{
-       int i;
-       const char *s = tn;
-       size_t len = tn ? strlen(tn) : 0;
-       char *new_tn = dest_tn;
-       SCOPE_ENTER(3, "tn: %s\n", S_OR(tn, "(null)"));
-
-       if (ast_strlen_zero(tn)) {
-               *dest_tn = '\0';
-               SCOPE_EXIT_RTN_VALUE(NULL, "Empty TN\n");
-       }
-
-       if (!dest_tn) {
-               SCOPE_EXIT_RTN_VALUE(NULL, "No destination buffer\n");
-       }
-
-       for (i = 0; i < len; i++) {
-               if (isdigit(*s) || *s == '#' || *s == '*') { /* Only characters allowed */
-                       *new_tn++ = *s;
-               }
-               s++;
-       }
-       *new_tn = '\0';
-       SCOPE_EXIT_RTN_VALUE(dest_tn, "Canonicalized '%s' -> '%s'\n", tn, dest_tn);
-}
-
-char *canonicalize_tn_alloc(const char *tn)
-{
-       char *canon_tn = ast_strlen_zero(tn) ? NULL : ast_malloc(strlen(tn) + 1);
-       if (!canon_tn) {
-               return NULL;
-       }
-       return canonicalize_tn(tn, canon_tn);
+       SCOPE_EXIT_RTN_VALUE(AST_MODULE_LOAD_SUCCESS, "Stir Shaken Load Done\n");
 }
index b4154757c3620280820ade52dfe52057f27e632a..6a8659b8cd390465ec53d6327be34d508165aa6f 100644 (file)
@@ -334,6 +334,8 @@ struct verification_cfg_common {
                AST_STRING_FIELD(ca_path);
                AST_STRING_FIELD(crl_file);
                AST_STRING_FIELD(crl_path);
+               AST_STRING_FIELD(untrusted_cert_file);
+               AST_STRING_FIELD(untrusted_cert_path);
                AST_STRING_FIELD(cert_cache_dir);
        );
        unsigned int curl_timeout;
@@ -414,7 +416,9 @@ struct profile_cfg {
 };
 
 struct profile_cfg *profile_get_cfg(const char *id);
+struct ao2_container *profile_get_all(void);
 struct profile_cfg *eprofile_get_cfg(const char *id);
+struct ao2_container *eprofile_get_all(void);
 int profile_load(void);
 int profile_reload(void);
 int profile_unload(void);
@@ -496,6 +500,8 @@ int tn_config_unload(void);
        stringfield_option_register(sorcery, CONFIG_TYPE, object, ca_path, vcfg_common.ca_path, nodoc); \
        stringfield_option_register(sorcery, CONFIG_TYPE, object, crl_file, vcfg_common.crl_file, nodoc); \
        stringfield_option_register(sorcery, CONFIG_TYPE, object, crl_path, vcfg_common.crl_path, nodoc); \
+       stringfield_option_register(sorcery, CONFIG_TYPE, object, untrusted_cert_file, vcfg_common.untrusted_cert_file, nodoc); \
+       stringfield_option_register(sorcery, CONFIG_TYPE, object, untrusted_cert_path, vcfg_common.untrusted_cert_path, nodoc); \
        stringfield_option_register(sorcery, CONFIG_TYPE, object, cert_cache_dir, vcfg_common.cert_cache_dir, nodoc); \
 \
        uint_option_register(sorcery, CONFIG_TYPE, object, curl_timeout, vcfg_common.curl_timeout, nodoc);\
index 7c4667fbb1572472e3f6c13ca72d09f7bd37e992..9ba26688fbf9a40c424fd04ed58608d4a4e22611 100644 (file)
@@ -16,6 +16,8 @@
  * at the top of the source tree.
  */
 
+#include <sys/stat.h>
+
 #include <openssl/err.h>
 #include <openssl/ssl.h>
 #include <openssl/evp.h>
@@ -30,6 +32,8 @@
 #include "crypto_utils.h"
 
 #include "asterisk.h"
+#include "asterisk/cli.h"
+#include "asterisk/file.h"
 #include "asterisk/logger.h"
 #include "asterisk/module.h"
 #include "asterisk/stringfields.h"
@@ -158,6 +162,30 @@ EVP_PKEY *crypto_load_privkey_from_file(const char *filename)
        return key;
 }
 
+X509_CRL *crypto_load_crl_from_file(const char *filename)
+{
+       FILE *fp;
+       X509_CRL *crl = NULL;
+
+       if (ast_strlen_zero(filename)) {
+               ast_log(LOG_ERROR, "filename was null or empty\n");
+               return NULL;
+       }
+
+       fp = fopen(filename, "r");
+       if (!fp) {
+               ast_log(LOG_ERROR, "Failed to open %s: %s\n", filename, strerror(errno));
+               return NULL;
+       }
+
+       crl = PEM_read_X509_CRL(fp, &crl, NULL, NULL);
+       fclose(fp);
+       if (!crl) {
+               crypto_log_openssl(LOG_ERROR, "Failed to create CRL from %s\n", filename);
+       }
+       return crl;
+}
+
 X509 *crypto_load_cert_from_file(const char *filename)
 {
        FILE *fp;
@@ -303,12 +331,59 @@ int crypto_extract_raw_privkey(EVP_PKEY *key, unsigned char **buffer)
        return dump_mem_bio(bio, buffer);
 }
 
+/*
+ * Notes on the crypto_cert_store object:
+ *
+ * We've discoverd a few issues with the X509_STORE object in OpenSSL
+ * that requires us to a bit more work to get the desired behavior.
+ *
+ * Basically, although X509_STORE_load_locations() and X509_STORE_load_path()
+ * work file for trusted certs, they refuse to load either CRLs or
+ * untrusted certs from directories, which is needed to support the
+ * crl_path and untrusted_cert_path options.  So we have to brute force
+ * it a bit.  We now use PEM_read_X509() and PEM_read_X509_CRL() to load
+ * the objects from files and then use X509_STORE_add_cert() and
+ * X509_STORE_add_crl() to add them to the store.  This is a bit more
+ * work but it gets the job done.  To load from directories, we
+ * simply use ast_file_read_dirs() with a callback that calls
+ * those functions.  This also fixes an issue where certificates
+ * loaded using ca_path don't show up when displaying the
+ * verification or profile objects from the CLI.
+ *
+ * NOTE: X509_STORE_load_file() could have been used instead of
+ * PEM_read_X509()/PEM_read_X509_CRL() and
+ * X509_STORE_add_cert()/X509_STORE_add_crl() but X509_STORE_load_file()
+ * didn't appear in OpenSSL until version 1.1.1. :(
+ *
+ * Another issue we have is that, while X509_verify_cert() can use
+ * an X509_STORE of CA certificates directly, it can't use X509_STOREs
+ * of untrusted certs or CRLs.  Instead, it needs a stack of X509
+ * objects for untrusted certs and a stack of X509_CRL objects for CRLs.
+ * So we need to extract the untrusted certs and CRLs from their
+ * stores and push them onto the stacks when the configuration is
+ * loaded.  We still use the stores as intermediaries because they
+ * make it easy to load the certs and CRLs from files and directories
+ * and they handle freeing the objects when the store is freed.
+ */
+
 static void crypto_cert_store_destructor(void *obj)
 {
        struct crypto_cert_store *store = obj;
 
-       if (store->store) {
-               X509_STORE_free(store->store);
+       if (store->certs) {
+               X509_STORE_free(store->certs);
+       }
+       if (store->untrusted) {
+               X509_STORE_free(store->untrusted);
+       }
+       if (store->untrusted_stack) {
+               sk_X509_free(store->untrusted_stack);
+       }
+       if (store->crls) {
+               X509_STORE_free(store->crls);
+       }
+       if (store->crl_stack) {
+               sk_X509_CRL_free(store->crl_stack);
        }
 }
 
@@ -319,61 +394,321 @@ struct crypto_cert_store *crypto_create_cert_store(void)
                ast_log(LOG_ERROR, "Failed to create crypto_cert_store\n");
                return NULL;
        }
-       store->store = X509_STORE_new();
 
-       if (!store->store) {
+       store->certs = X509_STORE_new();
+       if (!store->certs) {
                crypto_log_openssl(LOG_ERROR, "Failed to create X509_STORE\n");
                ao2_ref(store, -1);
                return NULL;
        }
 
+       store->untrusted = X509_STORE_new();
+       if (!store->untrusted) {
+               crypto_log_openssl(LOG_ERROR, "Failed to create untrusted X509_STORE\n");
+               ao2_ref(store, -1);
+               return NULL;
+       }
+       store->untrusted_stack = sk_X509_new_null();
+       if (!store->untrusted_stack) {
+               crypto_log_openssl(LOG_ERROR, "Failed to create untrusted stack\n");
+               ao2_ref(store, -1);
+               return NULL;
+       }
+
+       store->crls = X509_STORE_new();
+       if (!store->crls) {
+               crypto_log_openssl(LOG_ERROR, "Failed to create CRL X509_STORE\n");
+               ao2_ref(store, -1);
+               return NULL;
+       }
+       store->crl_stack = sk_X509_CRL_new_null();
+       if (!store->crl_stack) {
+               crypto_log_openssl(LOG_ERROR, "Failed to create CRL stack\n");
+               ao2_ref(store, -1);
+               return NULL;
+       }
+
        return store;
 }
 
+static int crypto_load_store_from_cert_file(X509_STORE *store, const char *file)
+{
+       X509 *cert;
+       int rc = 0;
+
+       if (ast_strlen_zero(file)) {
+               ast_log(LOG_ERROR, "file was null or empty\n");
+               return -1;
+       }
+
+       cert = crypto_load_cert_from_file(file);
+       if (!cert) {
+               return -1;
+       }
+       rc = X509_STORE_add_cert(store, cert);
+       X509_free(cert);
+       if (!rc) {
+               crypto_log_openssl(LOG_ERROR, "Failed to load store from file '%s'\n", file);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int crypto_load_store_from_crl_file(X509_STORE *store, const char *file)
+{
+       X509_CRL *crl;
+       int rc = 0;
+
+       if (ast_strlen_zero(file)) {
+               ast_log(LOG_ERROR, "file was null or empty\n");
+               return -1;
+       }
+
+       crl = crypto_load_crl_from_file(file);
+       if (!crl) {
+               return -1;
+       }
+       rc = X509_STORE_add_crl(store, crl);
+       X509_CRL_free(crl);
+       if (!rc) {
+               crypto_log_openssl(LOG_ERROR, "Failed to load store from file '%s'\n", file);
+               return -1;
+       }
+
+       return 0;
+}
+
+struct pem_file_cb_data {
+       X509_STORE *store;
+       int is_crl;
+};
+
+static int pem_file_cb(const char *dir_name, const char *filename, void *obj)
+{
+       struct pem_file_cb_data* data = obj;
+       char *filename_merged = NULL;
+       struct stat statbuf;
+       int rc = 0;
+
+       if (ast_asprintf(&filename_merged, "%s/%s", dir_name, filename) < 0) {
+               return -1;
+       }
+
+       if (lstat(filename_merged, &statbuf)) {
+               printf("Error reading path stats - %s: %s\n",
+                                       filename_merged, strerror(errno));
+               return -1;
+       }
+
+       /* We only want the symlinks from the directory */
+       if (!S_ISLNK(statbuf.st_mode)) {
+               return 0;
+       }
+
+       if (data->is_crl) {
+               rc = crypto_load_store_from_crl_file(data->store, filename_merged);
+       } else {
+               rc = crypto_load_store_from_cert_file(data->store, filename_merged);
+       }
+
+       return rc;
+}
+
+static int _crypto_load_cert_store(X509_STORE *store, const char *file, const char *path)
+{
+       int rc = 0;
+
+       if (!ast_strlen_zero(file)) {
+               rc = crypto_load_store_from_cert_file(store, file);
+               if (rc != 0) {
+                       return -1;
+               }
+       }
+
+       if (!ast_strlen_zero(path)) {
+               struct pem_file_cb_data data = { .store = store, .is_crl = 0 };
+               if (ast_file_read_dirs(path, pem_file_cb, &data, 0)) {
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static int _crypto_load_crl_store(X509_STORE *store, const char *file, const char *path)
+{
+       int rc = 0;
+
+       if (!ast_strlen_zero(file)) {
+               rc = crypto_load_store_from_crl_file(store, file);
+               if (rc != 0) {
+                       return -1;
+               }
+       }
+
+       if (!ast_strlen_zero(path)) {
+               struct pem_file_cb_data data = { .store = store, .is_crl = 1 };
+               if (ast_file_read_dirs(path, pem_file_cb, &data, 0)) {
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
 int crypto_load_cert_store(struct crypto_cert_store *store, const char *file,
        const char *path)
 {
        if (ast_strlen_zero(file) && ast_strlen_zero(path)) {
-               ast_log(LOG_ERROR, "Both file and path can't be NULL");
+               ast_log(LOG_ERROR, "Both file and path can't be NULL\n");
+               return -1;
+       }
+
+       if (!store || !store->certs) {
+               ast_log(LOG_ERROR, "store or store->certs is NULL\n");
+               return -1;
+       }
+
+       return _crypto_load_cert_store(store->certs, file, path);
+}
+
+int crypto_load_untrusted_cert_store(struct crypto_cert_store *store, const char *file,
+       const char *path)
+{
+       int rc = 0;
+       STACK_OF(X509_OBJECT) *objs = NULL;
+       int count = 0;
+       int i = 0;
+
+       if (ast_strlen_zero(file) && ast_strlen_zero(path)) {
+               ast_log(LOG_ERROR, "Both file and path can't be NULL\n");
                return -1;
        }
 
-       if (!store || !store->store) {
-               ast_log(LOG_ERROR, "store is NULL");
+       if (!store || !store->untrusted || !store->untrusted_stack) {
+               ast_log(LOG_ERROR, "store wasn't initialized properly\n");
                return -1;
        }
 
+       rc = _crypto_load_cert_store(store->untrusted, file, path);
+       if (rc != 0) {
+               return rc;
+       }
+
        /*
-        * If the file or path are empty strings, we need to pass NULL
-        * so openssl ignores it otherwise it'll try to open a file or
-        * path named ''.
+        * We need to extract the certs from the store and push them onto the
+        * untrusted stack.  This is because the verification context needs
+        * a stack of untrusted certs and not the store.
+        * The store holds the references to the certs so we can't
+        * free it.
         */
-       if (!X509_STORE_load_locations(store->store, S_OR(file, NULL), S_OR(path, NULL))) {
-               crypto_log_openssl(LOG_ERROR, "Failed to load store from file '%s' or path '%s'\n",
-                       S_OR(file, "N/A"), S_OR(path, "N/A"));
+       objs = X509_STORE_get0_objects(store->untrusted);
+       count = sk_X509_OBJECT_num(objs);
+       for (i = 0; i < count ; i++) {
+               X509_OBJECT *o = sk_X509_OBJECT_value(objs, i);
+               if (X509_OBJECT_get_type(o) == X509_LU_X509) {
+                       X509 *c = X509_OBJECT_get0_X509(o);
+                       sk_X509_push(store->untrusted_stack, c);
+               }
+       }
+
+       return 0;
+}
+
+int crypto_load_crl_store(struct crypto_cert_store *store, const char *file,
+       const char *path)
+{
+       int rc = 0;
+       STACK_OF(X509_OBJECT) *objs = NULL;
+       int count = 0;
+       int i = 0;
+
+       if (ast_strlen_zero(file) && ast_strlen_zero(path)) {
+               ast_log(LOG_ERROR, "Both file and path can't be NULL\n");
+               return -1;
+       }
+
+       if (!store || !store->untrusted || !store->untrusted_stack) {
+               ast_log(LOG_ERROR, "store wasn't initialized properly\n");
                return -1;
        }
 
+       rc = _crypto_load_crl_store(store->crls, file, path);
+       if (rc != 0) {
+               return rc;
+       }
+
+       /*
+        * We need to extract the CRLs from the store and push them onto the
+        * crl stack.  This is because the verification context needs
+        * a stack of CRLs and not the store.
+        * The store holds the references to the CRLs so we can't
+        * free it.
+        */
+       objs = X509_STORE_get0_objects(store->crls);
+       count = sk_X509_OBJECT_num(objs);
+       for (i = 0; i < count ; i++) {
+               X509_OBJECT *o = sk_X509_OBJECT_value(objs, i);
+               if (X509_OBJECT_get_type(o) == X509_LU_CRL) {
+                       X509_CRL *c = X509_OBJECT_get0_X509_CRL(o);
+                       sk_X509_CRL_push(store->crl_stack, c);
+               }
+       }
+
        return 0;
 }
 
 int crypto_show_cli_store(struct crypto_cert_store *store, int fd)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-       STACK_OF(X509_OBJECT) *certs = NULL;
+       STACK_OF(X509_OBJECT) *objs = NULL;
        int count = 0;
+       int untrusted_count = 0;
+       int crl_count = 0;
        int i = 0;
        char subj[1024];
 
-       certs = X509_STORE_get0_objects(store->store);
-       count = sk_X509_OBJECT_num(certs);
+       /*
+        * The CA certificates are stored in the certs store.
+        */
+       objs = X509_STORE_get0_objects(store->certs);
+       count = sk_X509_OBJECT_num(objs);
+
        for (i = 0; i < count ; i++) {
-               X509_OBJECT *o = sk_X509_OBJECT_value(certs, i);
-               X509 *c = X509_OBJECT_get0_X509(o);
+               X509_OBJECT *o = sk_X509_OBJECT_value(objs, i);
+               if (X509_OBJECT_get_type(o) == X509_LU_X509) {
+                       X509 *c = X509_OBJECT_get0_X509(o);
+                       X509_NAME_oneline(X509_get_subject_name(c), subj, 1024);
+                       ast_cli(fd, "Cert: %s\n", subj);
+               } else {
+                       ast_log(LOG_ERROR, "CRLs are not allowed in the CA cert store\n");
+               }
+       }
+
+       /*
+        * Although the untrusted certs are stored in the untrusted store,
+        * we already have the stack of certificates so we can just
+        * list them directly.
+        */
+       untrusted_count = sk_X509_num(store->untrusted_stack);
+       for (i = 0; i < untrusted_count ; i++) {
+               X509 *c = sk_X509_value(store->untrusted_stack, i);
                X509_NAME_oneline(X509_get_subject_name(c), subj, 1024);
-               ast_cli(fd, "%s\n", subj);
+               ast_cli(fd, "Untrusted: %s\n", subj);
        }
-       return count;
+
+       /*
+        * Same for the CRLs.
+        */
+       crl_count = sk_X509_CRL_num(store->crl_stack);
+       for (i = 0; i < crl_count ; i++) {
+               X509_CRL *crl = sk_X509_CRL_value(store->crl_stack, i);
+               X509_NAME_oneline(X509_CRL_get_issuer(crl), subj, 1024);
+               ast_cli(fd, "CRL: %s\n", subj);
+       }
+
+       return count + untrusted_count + crl_count;
 #else
        ast_cli(fd, "This command is not supported until OpenSSL 1.1.0\n");
        return 0;
@@ -409,12 +744,13 @@ int crypto_is_cert_trusted(struct crypto_cert_store *store, X509 *cert, const ch
                return 0;
        }
 
-       if (X509_STORE_CTX_init(verify_ctx, store->store, cert, NULL) != 1) {
+       if (X509_STORE_CTX_init(verify_ctx, store->certs, cert, store->untrusted_stack) != 1) {
                X509_STORE_CTX_cleanup(verify_ctx);
                X509_STORE_CTX_free(verify_ctx);
                crypto_log_openssl(LOG_ERROR, "Unable to initialize verify_ctx\n");
                return 0;
        }
+       X509_STORE_CTX_set0_crls(verify_ctx, store->crl_stack);
 
        rc = X509_verify_cert(verify_ctx);
        if (rc != 1 && err_msg != NULL) {
index 1f475c6521f68ae44ade82b776569c33b0dfa966..692f25abb9ea116f5fc22e342b20a343bd314c97 100644 (file)
@@ -82,6 +82,15 @@ ASN1_OCTET_STRING *crypto_get_cert_extension_data(X509 *cert, int nid,
  */
 X509 *crypto_load_cert_from_file(const char *filename);
 
+/*!
+ * \brief Load an X509 CRL from a PEM file
+ *
+ * \param filename PEM file
+ *
+ * \returns X509_CRL* or NULL on error
+ */
+X509_CRL *crypto_load_crl_from_file(const char *filename);
+
 /*!
  * \brief Load a private key from memory
  *
@@ -168,7 +177,13 @@ EVP_PKEY *crypto_load_privkey_from_file(const char *filename);
  * \brief ao2 object wrapper for X509_STORE that provides locking and refcounting
  */
 struct crypto_cert_store {
-       X509_STORE *store;
+       X509_STORE *certs;
+       X509_STORE *crls;
+       /*!< The verification context needs a stack of CRLs, not the store */
+       STACK_OF(X509_CRL) *crl_stack;
+       X509_STORE *untrusted;
+       /*!< The verification context needs a stack of untrusted certs, not the store */
+       STACK_OF(X509) *untrusted_stack;
 };
 
 /*!
@@ -211,6 +226,36 @@ int crypto_show_cli_store(struct crypto_cert_store *store, int fd);
 int crypto_load_cert_store(struct crypto_cert_store *store, const char *file,
        const char *path);
 
+/*!
+ * \brief Load an X509 Store with certificate revocation lists
+ *
+ * \param store X509 Store to load
+ * \param file CRL file to load or NULL
+ * \param path Path to directory with hashed CRLs to load or NULL
+ *
+ * \note At least 1 file or path must be specified.
+ *
+ * \retval <= 0 failure
+ * \retval 0 success
+ */
+int crypto_load_crl_store(struct crypto_cert_store *store, const char *file,
+       const char *path);
+
+/*!
+ * \brief Load an X509 Store with untrusted certificates
+ *
+ * \param store X509 Store to load
+ * \param file Certificate file to load or NULL
+ * \param path Path to directory with hashed certs to load or NULL
+ *
+ * \note At least 1 file or path must be specified.
+ *
+ * \retval <= 0 failure
+ * \retval 0 success
+ */
+int crypto_load_untrusted_cert_store(struct crypto_cert_store *store, const char *file,
+       const char *path);
+
 /*!
  * \brief Locks an X509 Store
  *
index e892fb9991cb801226fcb7c2236ca1708a4e4166..6e5a78a4482b653ba8a1c251874330be931f2577 100644 (file)
@@ -34,6 +34,8 @@
 #define DEFAULT_ca_path NULL
 #define DEFAULT_crl_file NULL
 #define DEFAULT_crl_path NULL
+#define DEFAULT_untrusted_cert_file NULL
+#define DEFAULT_untrusted_cert_path NULL
 #define DEFAULT_cert_cache_dir NULL
 
 #define DEFAULT_curl_timeout 0
@@ -100,7 +102,7 @@ static void *profile_alloc(const char *name)
        return profile;
 }
 
-static struct ao2_container *profile_get_all(void)
+struct ao2_container *profile_get_all(void)
 {
        return ast_sorcery_retrieve_by_fields(get_sorcery(), CONFIG_TYPE,
                AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
@@ -114,7 +116,7 @@ struct profile_cfg *profile_get_cfg(const char *id)
        return ast_sorcery_retrieve_by_id(get_sorcery(), CONFIG_TYPE, id);
 }
 
-static struct ao2_container *eprofile_get_all(void)
+struct ao2_container *eprofile_get_all(void)
 {
        return ast_sorcery_retrieve_by_fields(get_sorcery(), "eprofile",
                AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
index e14d1d2d08db748146a31d2d3ac144174aacd2c1..6663ce9a16c908998a26ff5d88de54374c88c51b 100644 (file)
                                        <synopsis>A boolean indicating whether trusted CA certificates should be loaded from the system</synopsis>
                                </configOption>
                                <configOption name="ca_file" default="">
-                                       <synopsis>Path to a file containing one or more CA certs</synopsis>
+                                       <synopsis>Path to a file containing one or more CA certs in PEM format</synopsis>
+                                       <description>
+                                               <para>These certs are used to verify the chain of trust for the
+                                               certificate retrieved from the X5U Identity header parameter.  This
+                                               file must have the root CA certificate, the certificate of the
+                                               issuer of the X5U certificate, and any intermediate certificates
+                                               between them.</para>
+                                               <para>
+                                               See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                               </para>
+                                       </description>
                                </configOption>
                                <configOption name="ca_path" default="">
                                        <synopsis>Path to a directory containing one or more hashed CA certs</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='ca_file']/description/node())" />
+                                               <para>For this option, the individual certificates must be placed in
+                                               the directory specified and hashed using the <literal>openssl rehash</literal>
+                                               command.</para>
+                                               <para>
+                                               See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                               </para>
+                                       </description>
                                </configOption>
                                <configOption name="crl_file" default="">
-                                       <synopsis>Path to a file containing a CRL</synopsis>
+                                       <synopsis>Path to a file containing one or more CRLs in PEM format</synopsis>
+                                       <description>
+                                               <para>If you with to check if the certificate in the X5U Identity header
+                                               parameter has been revoked, you'll need the certificate revocation
+                                               list generated by the issuer.</para>
+                                               <para>
+                                               See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                               </para>
+                                       </description>
                                </configOption>
                                <configOption name="crl_path" default="">
                                        <synopsis>Path to a directory containing one or more hashed CRLs</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='crl_file']/description/node())" />
+                                               <para>For this option, the individual CRLs must be placed in
+                                               the directory specified and hashed using the <literal>openssl rehash</literal>
+                                               command.</para>
+                                               <para>
+                                               See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                               </para>
+                                       </description>
+                               </configOption>
+                               <configOption name="untrusted_cert_file" default="">
+                                       <synopsis>Path to a file containing one or more untrusted cert in PEM format used to verify CRLs</synopsis>
+                                       <description>
+                                       <para>If you with to check if the certificate in the X5U Identity header
+                                       parameter has been revoked, you'll need the certificate revocation
+                                       list generated by the issuer.  Unfortunately, sometimes the CRLs are signed by a
+                                       different CA than the certificate being verified.  In this case, you
+                                       may need to provide the untrusted certificate to verify the CRL.</para>
+                                       <para>
+                                       See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                       </para>
+                                       </description>
+                               </configOption>
+                               <configOption name="untrusted_cert_path" default="">
+                                       <synopsis>Path to a directory containing one or more hashed untrusted certs used to verify CRLs</synopsis>
+                                       <description>
+                                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='untrusted_cert_file']/description/node())" />
+                                               <para>For this option, the individual certificates must be placed in
+                                               the directory specified and hashed using the <literal>openssl rehash</literal>
+                                               command.</para>
+                                               <para>
+                                               See https://docs.asterisk.org/Deployment/STIR-SHAKEN/ for more information.
+                                               </para>
+                                       </description>
                                </configOption>
                                <configOption name="cert_cache_dir" default="">
                                        <synopsis>Directory to cache retrieved verification certs</synopsis>
                                <configOption name="type">
                                        <synopsis>Must be of type 'profile'.</synopsis>
                                </configOption>
-                               <configOption name="load_system_certs" default="">
-                                       <synopsis>A boolean indicating whether trusted CA certificates should be loaded from the system</synopsis>
-                               </configOption>
-                               <configOption name="ca_file" default="">
-                                       <synopsis>Path to a file containing one or more CA certs</synopsis>
-                               </configOption>
-                               <configOption name="ca_path" default="">
-                                       <synopsis>Path to a directory containing one or more hashed CA certs</synopsis>
-                               </configOption>
-                               <configOption name="crl_file" default="">
-                                       <synopsis>Path to a file containing a CRL</synopsis>
-                               </configOption>
-                               <configOption name="crl_path" default="">
-                                       <synopsis>Path to a directory containing one or more hashed CRLs</synopsis>
-                               </configOption>
-                               <configOption name="cert_cache_dir" default="">
-                                       <synopsis>Directory to cache retrieved verification certs</synopsis>
-                               </configOption>
-                               <configOption name="curl_timeout" default="2">
-                                       <synopsis>Maximum time to wait to CURL certificates</synopsis>
-                               </configOption>
-                               <configOption name="max_iat_age" default="15">
-                                       <synopsis>Number of seconds an iat grant may be behind current time</synopsis>
-                               </configOption>
-                               <configOption name="max_date_header_age" default="15">
-                                       <synopsis>Number of seconds a SIP Date header may be behind current time</synopsis>
-                               </configOption>
-                               <configOption name="max_cache_entry_age" default="60">
-                                       <synopsis>Number of seconds a cache entry may be behind current time</synopsis>
-                               </configOption>
-                               <configOption name="max_cache_size" default="1000">
-                                       <synopsis>Maximum size to use for caching public keys</synopsis>
-                               </configOption>
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='load_system_certs'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='ca_file'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='ca_path'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='crl_file'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='crl_path'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='untrusted_cert_file'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='untrusted_cert_path'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='cert_cache_dir'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='curl_timeout'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='max_iat_age'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='max_date_header_age'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='max_cache_entry_age'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='max_cache_size'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='failure_action'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='use_rfc9410_responses'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='relax_x5u_port_scheme_restrictions'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='relax_x5u_path_restrictions'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='x5u_acl'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='x5u_permit'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='verification']/configOption[@name='x5u_deny'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='check_tn_cert_public_url'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='private_key_file'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='public_cert_url'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='attest_level'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='send_mky'])" />
                                <configOption name="endpoint_behavior" default="off">
                                        <synopsis>Actions performed when an endpoint references this profile</synopsis>
                                        <description>
                                                </enumlist>
                                        </description>
                                </configOption>
-                               <configOption name="failure_action" default="continue">
-                                       <synopsis>What do do when a verification fails</synopsis>
-                                       <description>
-                                               <enumlist>
-                                                       <enum name="continue">
-                                                       <para>If set to <literal>continue</literal>, continue and let
-                                                       the dialplan decide what action to take.</para>
-                                                       </enum>
-                                                       <enum name="reject_request">
-                                                       <para>If set to <literal>reject_request</literal>, reject the incoming
-                                                       request with response codes defined in RFC8224.
-                                                       </para>
-                                                       </enum>
-                                                       <enum name="return_reason">
-                                                       <para>If set to <literal>return_reason</literal>, continue to the
-                                                       dialplan but add a <literal>Reason</literal> header to the sender in
-                                                       the next provisional response.</para>
-                                                       </enum>
-                                               </enumlist>
-                                       </description>
-                               </configOption>
-                               <configOption name="use_rfc9410_responses" default="no">
-                                       <synopsis>RFC9410 uses the STIR protocol on Reason headers
-                                       instead of the SIP protocol</synopsis>
-                               </configOption>
-                               <configOption name="relax_x5u_port_scheme_restrictions" default="no">
-                                       <synopsis>Relaxes check for "https" and port 443 or 8443
-                                       in incoming Identity header x5u URLs.</synopsis>
-                               </configOption>
-                               <configOption name="relax_x5u_path_restrictions" default="no">
-                                       <synopsis>Relaxes check for query parameters, user/password, etc.
-                                       in incoming Identity header x5u URLs.</synopsis>
-                               </configOption>
-                               <configOption name="x5u_acl" default="">
-                                       <synopsis>An existing ACL from acl.conf to use when checking
-                                       hostnames in incoming Identity header x5u URLs.</synopsis>
-                               </configOption>
-                               <configOption name="x5u_permit" default="">
-                                       <synopsis>An IP or subnet to permit when checking
-                                       hostnames in incoming Identity header x5u URLs.</synopsis>
-                               </configOption>
-                               <configOption name="x5u_deny" default="">
-                                       <synopsis>An IP or subnet to deny  checking
-                                       hostnames in incoming Identity header x5u URLs.</synopsis>
-                               </configOption>
-                               <configOption name="check_tn_cert_public_url" default="false">
-                                       <synopsis>On load, Retrieve all TN's certificates and validate their dates</synopsis>
-                               </configOption>
-                               <configOption name="private_key_file" default="">
-                                       <synopsis>File path to a certificate</synopsis>
-                               </configOption>
-                               <configOption name="public_cert_url" default="">
-                                       <synopsis>URL to the public certificate</synopsis>
-                                       <description><para>
-                                        Must be a valid http, or https, URL.
-                                       </para></description>
-                               </configOption>
-                               <configOption name="attest_level">
-                                       <synopsis>Attestation level</synopsis>
-                               </configOption>
-                               <configOption name="send_mky" default="no">
-                                       <synopsis>Send a media key (mky) grant in the attestation for DTLS calls.
-                                       (not common)</synopsis>
-                               </configOption>
                        </configObject>
                </configFile>
        </configInfo>
index 0cade6bd52b5ec5e319ac69b7dc15dd39ea9b2a6..ef68ffc83ec461152dc8f0ec995a5a1184ea0ff7 100644 (file)
@@ -29,6 +29,8 @@
 #define DEFAULT_ca_path NULL
 #define DEFAULT_crl_file NULL
 #define DEFAULT_crl_path NULL
+#define DEFAULT_untrusted_cert_file NULL
+#define DEFAULT_untrusted_cert_path NULL
 static char DEFAULT_cert_cache_dir[PATH_MAX];
 
 #define DEFAULT_curl_timeout 2
@@ -129,6 +131,8 @@ int vs_copy_cfg_common(const char *id, struct verification_cfg_common *cfg_dst,
                cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, ca_path);
                cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, crl_file);
                cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, crl_path);
+               cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, untrusted_cert_file);
+               cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, untrusted_cert_path);
                ao2_bump(cfg_src->tcs);
                cfg_dst->tcs = cfg_src->tcs;
        }
@@ -188,6 +192,20 @@ int vs_check_common_config(const char *id,
                        id, vcfg_common->crl_path);
        }
 
+       if (!ast_strlen_zero(vcfg_common->untrusted_cert_file)
+               && !ast_file_is_readable(vcfg_common->untrusted_cert_file)) {
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
+                       "%s: untrusted_cert_file '%s' not found, or is unreadable\n",
+                       id, vcfg_common->untrusted_cert_file);
+       }
+
+       if (!ast_strlen_zero(vcfg_common->untrusted_cert_path)
+               && !ast_file_is_readable(vcfg_common->untrusted_cert_path)) {
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
+                       "%s: untrusted_cert_path '%s' not found, or is unreadable\n",
+                       id, vcfg_common->untrusted_cert_path);
+       }
+
        if (!ast_strlen_zero(vcfg_common->ca_file)
                || !ast_strlen_zero(vcfg_common->ca_path)) {
                int rc = 0;
@@ -219,7 +237,7 @@ int vs_check_common_config(const char *id,
                                        "%s: Unable to create CA cert store\n", id);
                        }
                }
-               rc = crypto_load_cert_store(vcfg_common->tcs,
+               rc = crypto_load_crl_store(vcfg_common->tcs,
                        vcfg_common->crl_file, vcfg_common->crl_path);
                if (rc != 0) {
                        SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
@@ -228,14 +246,34 @@ int vs_check_common_config(const char *id,
                }
        }
 
+       if (!ast_strlen_zero(vcfg_common->untrusted_cert_file)
+               || !ast_strlen_zero(vcfg_common->untrusted_cert_path)) {
+               int rc = 0;
+
+               if (!vcfg_common->tcs) {
+                       vcfg_common->tcs = crypto_create_cert_store();
+                       if (!vcfg_common->tcs) {
+                               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
+                                       "%s: Unable to create CA cert store\n", id);
+                       }
+               }
+               rc = crypto_load_untrusted_cert_store(vcfg_common->tcs,
+                       vcfg_common->untrusted_cert_file, vcfg_common->untrusted_cert_path);
+               if (rc != 0) {
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
+                               "%s: Unable to load CA CRL store from '%s' or '%s'\n",
+                               id, vcfg_common->untrusted_cert_file, vcfg_common->untrusted_cert_path);
+               }
+       }
+
        if (vcfg_common->tcs) {
                if (ENUM_BOOL(vcfg_common->load_system_certs, load_system_certs)) {
-                       X509_STORE_set_default_paths(vcfg_common->tcs->store);
+                       X509_STORE_set_default_paths(vcfg_common->tcs->certs);
                }
 
                if (!ast_strlen_zero(vcfg_common->crl_file)
                        || !ast_strlen_zero(vcfg_common->crl_path)) {
-                       X509_STORE_set_flags(vcfg_common->tcs->store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
+                       X509_STORE_set_flags(vcfg_common->tcs->certs, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_EXTENDED_CRL_SUPPORT);
                }
        }
 
@@ -355,6 +393,11 @@ static char *cli_verification_show(struct ast_cli_entry *e, int cmd, struct ast_
                return CLI_SHOWUSAGE;
        }
 
+       if (!vs_is_config_loaded()) {
+               ast_log(LOG_WARNING,"Stir/Shaken verification service disabled.  Either there were errors in the 'verification' object in stir_shaken.conf or it was missing altogether.\n");
+               return CLI_FAILURE;
+       }
+
        cfg = vs_get_cfg();
        config_object_cli_show(cfg, a, &data, 0);