Merge "Add X.509 subject alternative name support to TLS certificate verification."
authorJoshua Colp <jcolp@digium.com>
Fri, 15 May 2015 15:37:58 +0000 (10:37 -0500)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Fri, 15 May 2015 15:37:58 +0000 (10:37 -0500)
1  2 
main/tcptls.c

diff --combined main/tcptls.c
@@@ -555,6 -555,34 +555,34 @@@ static void session_instance_destructor
        ao2_cleanup(i->private_data);
  }
  
+ #ifdef DO_SSL
+ static int check_tcptls_cert_name(ASN1_STRING *cert_str, const char *hostname, const char *desc)
+ {
+       unsigned char *str;
+       int ret;
+       ret = ASN1_STRING_to_UTF8(&str, cert_str);
+       if (ret < 0 || !str) {
+               return -1;
+       }
+       if (strlen((char *) str) != ret) {
+               ast_log(LOG_WARNING, "Invalid certificate %s length (contains NULL bytes?)\n", desc);
+               ret = -1;
+       } else if (!strcasecmp(hostname, (char *) str)) {
+               ret = 0;
+       } else {
+               ret = -1;
+       }
+       ast_debug(3, "SSL %s compare s1='%s' s2='%s'\n", desc, hostname, str);
+       OPENSSL_free(str);
+       return ret;
+ }
+ #endif
  /*! \brief
  * creates a FILE * from the fd passed by the accept thread.
  * This operation is potentially expensive (certificate verification),
@@@ -631,8 -659,8 +659,8 @@@ static void *handle_tcptls_connection(v
                                }
                                if (!ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) {
                                        ASN1_STRING *str;
-                                       unsigned char *str2;
                                        X509_NAME *name = X509_get_subject_name(peer);
+                                       STACK_OF(GENERAL_NAME) *alt_names;
                                        int pos = -1;
                                        int found = 0;
  
                                                if (pos < 0) {
                                                        break;
                                                }
                                                str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, pos));
-                                               ret = ASN1_STRING_to_UTF8(&str2, str);
-                                               if (ret < 0) {
-                                                       continue;
+                                               if (!check_tcptls_cert_name(str, tcptls_session->parent->hostname, "common name")) {
+                                                       found = 1;
+                                                       break;
                                                }
+                                       }
+                                       if (!found) {
+                                               alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, NULL, NULL);
+                                               if (alt_names != NULL) {
+                                                       int alt_names_count = sk_GENERAL_NAME_num(alt_names);
  
-                                               if (str2) {
-                                                       if (strlen((char *) str2) != ret) {
-                                                               ast_log(LOG_WARNING, "Invalid certificate common name length (contains NULL bytes?)\n");
-                                                       } else if (!strcasecmp(tcptls_session->parent->hostname, (char *) str2)) {
-                                                               found = 1;
+                                                       for (pos = 0; pos < alt_names_count; pos++) {
+                                                               const GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(alt_names, pos);
+                                                               if (alt_name->type != GEN_DNS) {
+                                                                       continue;
+                                                               }
+                                                               if (!check_tcptls_cert_name(alt_name->d.dNSName, tcptls_session->parent->hostname, "alt name")) {
+                                                                       found = 1;
+                                                                       break;
+                                                               }
                                                        }
-                                                       ast_debug(3, "SSL Common Name compare s1='%s' s2='%s'\n", tcptls_session->parent->hostname, str2);
-                                                       OPENSSL_free(str2);
-                                               }
-                                               if (found) {
-                                                       break;
+                                                       sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free);
                                                }
                                        }
                                        if (!found) {
                                                ast_log(LOG_ERROR, "Certificate common name did not match (%s)\n", tcptls_session->parent->hostname);
                                                X509_free(peer);
@@@ -752,22 -791,6 +791,22 @@@ void *ast_tcptls_server_root(void *data
        return NULL;
  }
  
 +static void __ssl_setup_certs(struct ast_tls_config *cfg, const size_t cert_file_len, const char *key_type_extension, const char *key_type)
 +{
 +      char *cert_file = ast_strdupa(cfg->certfile);
 +
 +      memcpy(cert_file + cert_file_len - 8, key_type_extension, 5);
 +      if (access(cert_file, F_OK) == 0) {
 +              if (SSL_CTX_use_certificate_chain_file(cfg->ssl_ctx, cert_file) == 0) {
 +                      ast_log(LOG_WARNING, "TLS/SSL error loading public %s key (certificate) from <%s>.\n", key_type, cert_file);
 +              } else if (SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cert_file, SSL_FILETYPE_PEM) == 0) {
 +                      ast_log(LOG_WARNING, "TLS/SSL error loading private %s key from <%s>.\n", key_type, cert_file);
 +              } else if (SSL_CTX_check_private_key(cfg->ssl_ctx) == 0) {
 +                      ast_log(LOG_WARNING, "TLS/SSL error matching private %s key and certificate in <%s>.\n", key_type, cert_file);
 +              }
 +      }
 +}
 +
  static int __ssl_setup(struct ast_tls_config *cfg, int client)
  {
  #ifndef DO_SSL
                                return 0;
                        }
                }
 +              if (!client) {
 +                      size_t certfile_len = strlen(cfg->certfile);
 +
 +                      /* expects a file name which contains _rsa. like asterisk_rsa.pem
 +                       * ignores any 3-character file-extension like .pem, .cer, .crt
 +                       */
 +                      if (certfile_len >= 8 && !strncmp(cfg->certfile + certfile_len - 8, "_rsa.", 5)) {
 +                              __ssl_setup_certs(cfg, certfile_len, "_ecc.", "ECC");
 +                              __ssl_setup_certs(cfg, certfile_len, "_dsa.", "DSA");
 +                      }
 +              }
        }
        if (!ast_strlen_zero(cfg->cipher)) {
                if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {