/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "net.h" #include "str.h" #include "iostream-openssl.h" #include #include #include /* openssl_min_protocol_to_options() scans this array for name and returns version and opt. opt is used with SSL_set_options() and version is used with SSL_set_min_proto_version(). Using either method should enable the same SSL protocol versions. */ static const struct { const char *name; int version; long opt; } protocol_versions[] = { #ifdef TLS_ANY_VERSION { "ANY", TLS_ANY_VERSION, 0 }, #else { "ANY", SSL3_VERSION, 0 }, #endif { SSL_TXT_SSLV3, SSL3_VERSION, 0 }, { SSL_TXT_TLSV1, TLS1_VERSION, SSL_OP_NO_SSLv3 }, { SSL_TXT_TLSV1_1, TLS1_1_VERSION, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 }, { SSL_TXT_TLSV1_2, TLS1_2_VERSION, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 }, #if defined(TLS1_3_VERSION) { "TLSv1.3", TLS1_3_VERSION, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 }, #endif /* Use latest protocol version. If this is used on some ancient system which does not support ssl_min_protocol, ensure only TLSv1.2 is supported. */ #ifdef TLS_MAX_VERSION { "LATEST", TLS_MAX_VERSION, #else { "LATEST", 0, #endif SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 }, }; int openssl_min_protocol_to_options(const char *min_protocol, long *opt_r, int *version_r) { unsigned i = 0; for (; i < N_ELEMENTS(protocol_versions); i++) { if (strcasecmp(protocol_versions[i].name, min_protocol) == 0) break; } if (i >= N_ELEMENTS(protocol_versions)) return -1; if (opt_r != NULL) *opt_r = protocol_versions[i].opt; if (version_r != NULL) *version_r = protocol_versions[i].version; return 0; } #if !defined(HAVE_X509_CHECK_HOST) || !defined(HAVE_X509_CHECK_IP_ASC) static const char *asn1_string_to_c(ASN1_STRING *asn_str) { const char *cstr; unsigned int len; len = ASN1_STRING_length(asn_str); cstr = t_strndup(ASN1_STRING_get0_data(asn_str), len); if (strlen(cstr) != len) { /* NULs in the name - could be some MITM attack. never allow. */ return ""; } return cstr; } static const char *get_general_dns_name(const GENERAL_NAME *name) { if (ASN1_STRING_type(name->d.ia5) != V_ASN1_IA5STRING) return ""; return asn1_string_to_c(name->d.ia5); } static int get_general_ip_addr(const GENERAL_NAME *name, struct ip_addr *ip_r) { if (ASN1_STRING_type(name->d.ip) != V_ASN1_OCTET_STRING) return 0; const unsigned char *data = ASN1_STRING_get0_data(name->d.ip); if (name->d.ip->length == sizeof(ip_r->u.ip4.s_addr)) { ip_r->family = AF_INET; memcpy(&ip_r->u.ip4.s_addr, data, sizeof(ip_r->u.ip4.s_addr)); } else if (name->d.ip->length == sizeof(ip_r->u.ip6.s6_addr)) { ip_r->family = AF_INET6; memcpy(ip_r->u.ip6.s6_addr, data, sizeof(ip_r->u.ip6.s6_addr)); } else return -1; return 0; } static const char *get_cname(X509 *cert) { X509_NAME *name; X509_NAME_ENTRY *entry; ASN1_STRING *str; int cn_idx; name = X509_get_subject_name(cert); if (name == NULL) return ""; cn_idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1); if (cn_idx == -1) return ""; entry = X509_NAME_get_entry(name, cn_idx); i_assert(entry != NULL); str = X509_NAME_ENTRY_get_data(entry); i_assert(str != NULL); return asn1_string_to_c(str); } static bool openssl_hostname_equals(const char *ssl_name, const char *host) { const char *p; if (strcasecmp(ssl_name, host) == 0) return TRUE; /* check for *.example.com wildcard */ if (ssl_name[0] != '*' || ssl_name[1] != '.') return FALSE; p = strchr(host, '.'); return p != NULL && strcasecmp(ssl_name+2, p+1) == 0; } #endif bool openssl_cert_match_name(SSL *ssl, const char *verify_name, const char **reason_r) { X509 *cert; bool ret; *reason_r = NULL; cert = SSL_get_peer_certificate(ssl); i_assert(cert != NULL); #if defined(HAVE_X509_CHECK_HOST) && defined(HAVE_X509_CHECK_IP_ASC) char *peername; int check_res; /* First check DNS name agains CommonName or SubjectAltNames. If failed, check IP addresses. */ if ((check_res = X509_check_host(cert, verify_name, strlen(verify_name), X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, &peername)) == 1) { *reason_r = t_strdup_printf("Matched to %s", peername); free(peername); ret = TRUE; } else if (check_res == 0 && (check_res = X509_check_ip_asc(cert, verify_name, 0)) == 1) { *reason_r = t_strdup_printf("Matched to IP address %s", verify_name); ret = TRUE; } else if (check_res == 0) { *reason_r = "did not match to any IP or DNS fields"; ret = FALSE; } else { *reason_r = "Malformed input"; ret = FALSE; } #else STACK_OF(GENERAL_NAME) *gnames; const GENERAL_NAME *gn; struct ip_addr ip; const char *dnsname; bool dns_names = FALSE; unsigned int i, count; /* verify against SubjectAltNames */ gnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); count = gnames == NULL ? 0 : sk_GENERAL_NAME_num(gnames); i_zero(&ip); /* try to convert verify_name to IP */ if (inet_pton(AF_INET6, verify_name, &ip.u.ip6) == 1) ip.family = AF_INET6; else if (inet_pton(AF_INET, verify_name, &ip.u.ip4) == 1) ip.family = AF_INET; else i_zero(&ip); for (i = 0; i < count; i++) { gn = sk_GENERAL_NAME_value(gnames, i); if (gn->type == GEN_DNS) { dns_names = TRUE; dnsname = get_general_dns_name(gn); if (openssl_hostname_equals(dnsname, verify_name)) { *reason_r = t_strdup_printf( "Matches DNS name in SubjectAltNames: %s", dnsname); break; } } else if (gn->type == GEN_IPADD) { struct ip_addr ip_2; i_zero(&ip_2); dns_names = TRUE; if (get_general_ip_addr(gn, &ip_2) == 0 && net_ip_compare(&ip, &ip_2)) { *reason_r = t_strdup_printf( "Matches IP in SubjectAltNames: %s", net_ip2addr(&ip_2)); break; } } } sk_GENERAL_NAME_pop_free(gnames, GENERAL_NAME_free); /* verify against CommonName only when there wasn't any DNS SubjectAltNames */ if (dns_names) { i_assert(*reason_r != NULL || i == count); if (i == count) { *reason_r = t_strdup_printf( "No match to %u SubjectAltNames", count); ret = FALSE; } else { ret = TRUE; } } else { const char *cname = get_cname(cert); if (openssl_hostname_equals(cname, verify_name)) { ret = TRUE; *reason_r = t_strdup_printf( "Matches to CommonName: %s", cname); } else { *reason_r = t_strdup_printf( "No match to CommonName=%s or %u SubjectAltNames", cname, count); ret = FALSE; } } #endif X509_free(cert); return ret; } static const char *ssl_err2str(unsigned long err, const char *data, int flags) { const char *ret; char *buf; size_t err_size = 256; buf = t_malloc0(err_size); ERR_error_string_n(err, buf, err_size-1); ret = buf; if ((flags & ERR_TXT_STRING) != 0) ret = t_strdup_printf("%s: %s", buf, data); return ret; } const char *openssl_iostream_error(void) { string_t *errstr = NULL; unsigned long err; const char *data, *final_error; int flags; while ((err = ERR_get_error_line_data(NULL, NULL, &data, &flags)) != 0) { if (ERR_GET_REASON(err) == ERR_R_MALLOC_FAILURE) i_fatal_status(FATAL_OUTOFMEM, "OpenSSL malloc() failed"); if (ERR_peek_error() == 0) break; if (errstr == NULL) errstr = t_str_new(128); else str_append(errstr, ", "); str_append(errstr, ssl_err2str(err, data, flags)); } if (err == 0) { if (errno != 0) final_error = strerror(errno); else final_error = "Unknown error"; } else { final_error = ssl_err2str(err, data, flags); } if (errstr == NULL) return final_error; else { str_printfa(errstr, ", %s", final_error); return str_c(errstr); } } const char *openssl_iostream_key_load_error(void) { unsigned long err = ERR_peek_error(); if (ERR_GET_LIB(err) == ERR_LIB_X509 && ERR_GET_REASON(err) == X509_R_KEY_VALUES_MISMATCH) return "Key is for a different cert than ssl_cert"; else return openssl_iostream_error(); } static bool is_pem_key(const char *cert) { return strstr(cert, "PRIVATE KEY---") != NULL; } const char * openssl_iostream_use_certificate_error(const char *cert, const char *set_name) { unsigned long err; if (cert[0] == '\0') return "The certificate is empty"; err = ERR_peek_error(); if (ERR_GET_LIB(err) != ERR_LIB_PEM || ERR_GET_REASON(err) != PEM_R_NO_START_LINE) return openssl_iostream_error(); else if (is_pem_key(cert)) { return "The file contains a private key " "(you've mixed ssl_cert and ssl_key settings)"; } else if (set_name != NULL && strchr(cert, '\n') == NULL) { return t_strdup_printf("There is no valid PEM certificate. " "(You probably forgot '<' from %s=<%s)", set_name, cert); } else { return "There is no valid PEM certificate."; } } void openssl_iostream_clear_errors(void) { while (ERR_get_error() != 0) ; }