Mercurial > dovecot > core-2.2
view src/lib-ssl-iostream/iostream-openssl-common.c @ 23017:c1d36f2575c7 default tip
lib-imap: Fix "Don't accept strings with NULs" cherry-pick
author | Timo Sirainen <timo.sirainen@open-xchange.com> |
---|---|
date | Thu, 29 Aug 2019 09:55:25 +0300 |
parents | ad9c924ec91f |
children |
line wrap: on
line source
/* 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 <openssl/x509v3.h> #include <openssl/err.h> #include <arpa/inet.h> enum { DOVECOT_SSL_PROTO_SSLv2 = 0x01, DOVECOT_SSL_PROTO_SSLv3 = 0x02, DOVECOT_SSL_PROTO_TLSv1 = 0x04, DOVECOT_SSL_PROTO_TLSv1_1 = 0x08, DOVECOT_SSL_PROTO_TLSv1_2 = 0x10, DOVECOT_SSL_PROTO_ALL = 0x1f }; #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION static const struct { const char *name; int version; } protocol_versions[] = { { SSL_TXT_SSLV3, SSL3_VERSION }, { SSL_TXT_TLSV1, TLS1_VERSION }, { SSL_TXT_TLSV1_1, TLS1_1_VERSION }, { SSL_TXT_TLSV1_2, TLS1_2_VERSION }, }; int ssl_protocols_to_min_protocol(const char *ssl_protocols, int *min_protocol_r, const char **error_r) { /* Array where -1 = disable, 0 = not found, 1 = enable */ int protos[N_ELEMENTS(protocol_versions)]; memset(protos, 0, sizeof(protos)); bool explicit_enable = FALSE; const char *const *tmp = t_strsplit_spaces(ssl_protocols, ", "); for (; *tmp != NULL; tmp++) { const char *p = *tmp; bool enable = TRUE; if (p[0] == '!') { enable = FALSE; ++p; } for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) { if (strcmp(p, protocol_versions[i].name) == 0) { if (enable) { protos[i] = 1; explicit_enable = TRUE; } else { protos[i] = -1; } goto found; } } *error_r = t_strdup_printf("Unrecognized protocol '%s'", p); return -1; found:; } unsigned min = N_ELEMENTS(protocol_versions); for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) { if (explicit_enable) { if (protos[i] > 0) min = I_MIN(min, i); } else if (protos[i] == 0) min = I_MIN(min, i); } if (min == N_ELEMENTS(protocol_versions)) { *error_r = "All protocols disabled"; return -1; } *min_protocol_r = protocol_versions[min].version; return 0; } #endif /* HAVE_SSL_CTX_SET_MIN_PROTO_VERSION */ int openssl_get_protocol_options(const char *protocols) { const char *const *tmp; int proto, op = 0, include = 0, exclude = 0; bool neg; tmp = t_strsplit_spaces(protocols, ", "); for (; *tmp != NULL; tmp++) { const char *name = *tmp; if (*name != '!') neg = FALSE; else { name++; neg = TRUE; } #ifdef SSL_TXT_SSLV2 if (strcasecmp(name, SSL_TXT_SSLV2) == 0) proto = DOVECOT_SSL_PROTO_SSLv2; else #endif #ifdef SSL_TXT_SSLV3 if (strcasecmp(name, SSL_TXT_SSLV3) == 0) proto = DOVECOT_SSL_PROTO_SSLv3; else #endif if (strcasecmp(name, SSL_TXT_TLSV1) == 0) proto = DOVECOT_SSL_PROTO_TLSv1; #ifdef SSL_TXT_TLSV1_1 else if (strcasecmp(name, SSL_TXT_TLSV1_1) == 0) proto = DOVECOT_SSL_PROTO_TLSv1_1; #endif #ifdef SSL_TXT_TLSV1_2 else if (strcasecmp(name, SSL_TXT_TLSV1_2) == 0) proto = DOVECOT_SSL_PROTO_TLSv1_2; #endif else { i_fatal("Invalid ssl_protocols setting: " "Unknown protocol '%s'", name); } if (neg) exclude |= proto; else include |= proto; } if (include != 0) { /* exclude everything, except those that are included (and let excludes still override those) */ exclude |= DOVECOT_SSL_PROTO_ALL & ~include; } if ((exclude & DOVECOT_SSL_PROTO_SSLv2) != 0) op |= SSL_OP_NO_SSLv2; if ((exclude & DOVECOT_SSL_PROTO_SSLv3) != 0) op |= SSL_OP_NO_SSLv3; if ((exclude & DOVECOT_SSL_PROTO_TLSv1) != 0) op |= SSL_OP_NO_TLSv1; #ifdef SSL_OP_NO_TLSv1_1 if ((exclude & DOVECOT_SSL_PROTO_TLSv1_1) != 0) op |= SSL_OP_NO_TLSv1_1; #endif #ifdef SSL_OP_NO_TLSv1_2 if ((exclude & DOVECOT_SSL_PROTO_TLSv1_2) != 0) op |= SSL_OP_NO_TLSv1_2; #endif return op; } 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 (strcmp(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 && strcmp(ssl_name+2, p+1) == 0; } int openssl_cert_match_name(SSL *ssl, const char *verify_name) { X509 *cert; STACK_OF(GENERAL_NAME) *gnames; const GENERAL_NAME *gn; struct ip_addr ip; const char *dnsname; bool dns_names = FALSE; unsigned int i, count; int ret; cert = SSL_get_peer_certificate(ssl); i_assert(cert != NULL); /* 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)) 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)) break; } } sk_GENERAL_NAME_pop_free(gnames, GENERAL_NAME_free); /* verify against CommonName only when there wasn't any DNS SubjectAltNames */ if (dns_names) ret = i < count ? 0 : -1; else if (openssl_hostname_equals(get_cname(cert), verify_name)) ret = 0; else ret = -1; 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_malloc(err_size); buf[err_size-1] = '\0'; 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; 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) ; }