diff --git a/Makefile b/Makefile index 846c0c3b7..33835a7aa 100644 --- a/Makefile +++ b/Makefile @@ -542,7 +542,7 @@ OPTIONS_LDFLAGS += $(if $(SSL_LIB),-L$(SSL_LIB)) -lssl -lcrypto ifneq ($(USE_DL),) OPTIONS_LDFLAGS += -ldl endif -OPTIONS_OBJS += src/ssl_sample.o src/ssl_sock.o src/ssl_crtlist.o src/ssl_ckch.o src/cfgparse-ssl.o +OPTIONS_OBJS += src/ssl_sample.o src/ssl_sock.o src/ssl_crtlist.o src/ssl_ckch.o src/ssl_utils.o src/cfgparse-ssl.o endif # The private cache option affect the way the shctx is built diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h index 690199a12..101d86810 100644 --- a/include/proto/ssl_sock.h +++ b/include/proto/ssl_sock.h @@ -107,20 +107,12 @@ void ssl_async_fd_handler(int fd); void ssl_async_fd_free(int fd); #endif struct issuer_chain* ssl_get0_issuer_chain(X509 *cert); -int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out); -int ssl_sock_get_serial(X509 *crt, struct buffer *out); -int cert_get_pkey_algo(X509 *crt, struct buffer *out); int ssl_load_global_issuer_from_BIO(BIO *in, char *fp, char **err); int ssl_sock_load_cert(char *path, struct bind_conf *bind_conf, char **err); void ssl_free_global_issuers(void); int ssl_sock_load_cert_list_file(char *file, int dir, struct bind_conf *bind_conf, struct proxy *curproxy, char **err); int ssl_init_single_engine(const char *engine_id, const char *def_algorithms); int ssl_store_load_locations_file(char *path); -int ssl_sock_crt2der(X509 *crt, struct buffer *out); -int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out); -int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out); -int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, - struct buffer *out); /* ssl shctx macro */ diff --git a/include/proto/ssl_utils.h b/include/proto/ssl_utils.h new file mode 100644 index 000000000..be14a0d1b --- /dev/null +++ b/include/proto/ssl_utils.h @@ -0,0 +1,40 @@ +/* + * include/proto/ssl_utils.h + * + * Utility functions for SSL: + * Mostly generic functions that retrieve information from certificates + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TYPES_SSL_UTILS_H +#define _TYPES_SSL_UTILS_H +#ifdef USE_OPENSSL + +int cert_get_pkey_algo(X509 *crt, struct buffer *out); +int ssl_sock_get_serial(X509 *crt, struct buffer *out); +int ssl_sock_crt2der(X509 *crt, struct buffer *out); +int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out); +int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, + struct buffer *out); +int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out); +int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out); + +#endif /* _TYPES_SSL_UTILS_H */ +#endif /* USE_OPENSSL */ + diff --git a/src/ssl_ckch.c b/src/ssl_ckch.c index 484997d7f..c59364930 100644 --- a/src/ssl_ckch.c +++ b/src/ssl_ckch.c @@ -35,6 +35,7 @@ #include #include #include +#include #include /* Uncommitted CKCH transaction */ diff --git a/src/ssl_sample.c b/src/ssl_sample.c index 55eec78e9..f3fd0b4b0 100644 --- a/src/ssl_sample.c +++ b/src/ssl_sample.c @@ -32,6 +32,7 @@ #include #include #include +#include #include diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 95db8f519..9d6b8b118 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -86,6 +86,7 @@ #include #include #include +#include #include #include #include @@ -5851,48 +5852,6 @@ static void ssl_sock_shutw(struct connection *conn, void *xprt_ctx, int clean) } } -/* fill a buffer with the algorithm and size of a public key */ -int cert_get_pkey_algo(X509 *crt, struct buffer *out) -{ - int bits = 0; - int sig = TLSEXT_signature_anonymous; - int len = -1; - EVP_PKEY *pkey; - - pkey = X509_get_pubkey(crt); - if (pkey) { - bits = EVP_PKEY_bits(pkey); - switch(EVP_PKEY_base_id(pkey)) { - case EVP_PKEY_RSA: - sig = TLSEXT_signature_rsa; - break; - case EVP_PKEY_EC: - sig = TLSEXT_signature_ecdsa; - break; - case EVP_PKEY_DSA: - sig = TLSEXT_signature_dsa; - break; - } - EVP_PKEY_free(pkey); - } - - switch(sig) { - case TLSEXT_signature_rsa: - len = chunk_printf(out, "RSA%d", bits); - break; - case TLSEXT_signature_ecdsa: - len = chunk_printf(out, "EC%d", bits); - break; - case TLSEXT_signature_dsa: - len = chunk_printf(out, "DSA%d", bits); - break; - default: - return 0; - } - if (len < 0) - return 0; - return 1; -} /* used for ppv2 pkey algo (can be used for logging) */ int ssl_sock_get_pkey_algo(struct connection *conn, struct buffer *out) @@ -5967,232 +5926,6 @@ const char *ssl_sock_get_proto_version(struct connection *conn) return SSL_get_version(ctx->ssl); } -/* Extract a serial from a cert, and copy it to a chunk. - * Returns 1 if serial is found and copied, 0 if no serial found and - * -1 if output is not large enough. - */ -int ssl_sock_get_serial(X509 *crt, struct buffer *out) -{ - ASN1_INTEGER *serial; - - serial = X509_get_serialNumber(crt); - if (!serial) - return 0; - - if (out->size < serial->length) - return -1; - - memcpy(out->area, serial->data, serial->length); - out->data = serial->length; - return 1; -} - -/* Extract a cert to der, and copy it to a chunk. - * Returns 1 if the cert is found and copied, 0 on der conversion failure - * and -1 if the output is not large enough. - */ -int ssl_sock_crt2der(X509 *crt, struct buffer *out) -{ - int len; - unsigned char *p = (unsigned char *) out->area;; - - len =i2d_X509(crt, NULL); - if (len <= 0) - return 1; - - if (out->size < len) - return -1; - - i2d_X509(crt,&p); - out->data = len; - return 1; -} - - -/* Copy Date in ASN1_UTCTIME format in struct buffer out. - * Returns 1 if serial is found and copied, 0 if no valid time found - * and -1 if output is not large enough. - */ -int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out) -{ - if (tm->type == V_ASN1_GENERALIZEDTIME) { - ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm; - - if (gentm->length < 12) - return 0; - if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30) - return 0; - if (out->size < gentm->length-2) - return -1; - - memcpy(out->area, gentm->data+2, gentm->length-2); - out->data = gentm->length-2; - return 1; - } - else if (tm->type == V_ASN1_UTCTIME) { - ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm; - - if (utctm->length < 10) - return 0; - if (utctm->data[0] >= 0x35) - return 0; - if (out->size < utctm->length) - return -1; - - memcpy(out->area, utctm->data, utctm->length); - out->data = utctm->length; - return 1; - } - - return 0; -} - -/* Extract an entry from a X509_NAME and copy its value to an output chunk. - * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough. - */ -int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, - struct buffer *out) -{ - X509_NAME_ENTRY *ne; - ASN1_OBJECT *obj; - ASN1_STRING *data; - const unsigned char *data_ptr; - int data_len; - int i, j, n; - int cur = 0; - const char *s; - char tmp[128]; - int name_count; - - name_count = X509_NAME_entry_count(a); - - out->data = 0; - for (i = 0; i < name_count; i++) { - if (pos < 0) - j = (name_count-1) - i; - else - j = i; - - ne = X509_NAME_get_entry(a, j); - obj = X509_NAME_ENTRY_get_object(ne); - data = X509_NAME_ENTRY_get_data(ne); - data_ptr = ASN1_STRING_get0_data(data); - data_len = ASN1_STRING_length(data); - n = OBJ_obj2nid(obj); - if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { - i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); - s = tmp; - } - - if (chunk_strcasecmp(entry, s) != 0) - continue; - - if (pos < 0) - cur--; - else - cur++; - - if (cur != pos) - continue; - - if (data_len > out->size) - return -1; - - memcpy(out->area, data_ptr, data_len); - out->data = data_len; - return 1; - } - - return 0; - -} - -/* - * Extract the DN in the specified format from the X509_NAME and copy result to a chunk. - * Currently supports rfc2253 for returning LDAP V3 DNs. - * Returns 1 if dn entries exist, 0 if no dn entry was found. - */ -int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out) -{ - BIO *bio = NULL; - int ret = 0; - int data_len = 0; - - if (chunk_strcmp(format, "rfc2253") == 0) { - bio = BIO_new(BIO_s_mem()); - if (bio == NULL) - goto out; - - if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0) - goto out; - - if ((data_len = BIO_read(bio, out->area, out->size)) <= 0) - goto out; - - out->data = data_len; - - ret = 1; - } -out: - if (bio) - BIO_free(bio); - return ret; -} - -/* Extract and format full DN from a X509_NAME and copy result into a chunk - * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. - */ -int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out) -{ - X509_NAME_ENTRY *ne; - ASN1_OBJECT *obj; - ASN1_STRING *data; - const unsigned char *data_ptr; - int data_len; - int i, n, ln; - int l = 0; - const char *s; - char *p; - char tmp[128]; - int name_count; - - - name_count = X509_NAME_entry_count(a); - - out->data = 0; - p = out->area; - for (i = 0; i < name_count; i++) { - ne = X509_NAME_get_entry(a, i); - obj = X509_NAME_ENTRY_get_object(ne); - data = X509_NAME_ENTRY_get_data(ne); - data_ptr = ASN1_STRING_get0_data(data); - data_len = ASN1_STRING_length(data); - n = OBJ_obj2nid(obj); - if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { - i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); - s = tmp; - } - ln = strlen(s); - - l += 1 + ln + 1 + data_len; - if (l > out->size) - return -1; - out->data = l; - - *(p++)='/'; - memcpy(p, s, ln); - p += ln; - *(p++)='='; - memcpy(p, data_ptr, data_len); - p += data_len; - } - - if (!out->data) - return 0; - - return 1; -} - void ssl_sock_set_alpn(struct connection *conn, const unsigned char *alpn, int len) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation diff --git a/src/ssl_utils.c b/src/ssl_utils.c new file mode 100644 index 000000000..76c513741 --- /dev/null +++ b/src/ssl_utils.c @@ -0,0 +1,290 @@ +/* + * Utility functions for SSL: + * Mostly generic functions that retrieve information from certificates + * + * Copyright (C) 2012 EXCELIANCE, Emeric Brun + * Copyright (C) 2020 HAProxy Technologies, William Lallemand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + + +#include +#include + +#include + +#include + +/* fill a buffer with the algorithm and size of a public key */ +int cert_get_pkey_algo(X509 *crt, struct buffer *out) +{ + int bits = 0; + int sig = TLSEXT_signature_anonymous; + int len = -1; + EVP_PKEY *pkey; + + pkey = X509_get_pubkey(crt); + if (pkey) { + bits = EVP_PKEY_bits(pkey); + switch(EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + sig = TLSEXT_signature_rsa; + break; + case EVP_PKEY_EC: + sig = TLSEXT_signature_ecdsa; + break; + case EVP_PKEY_DSA: + sig = TLSEXT_signature_dsa; + break; + } + EVP_PKEY_free(pkey); + } + + switch(sig) { + case TLSEXT_signature_rsa: + len = chunk_printf(out, "RSA%d", bits); + break; + case TLSEXT_signature_ecdsa: + len = chunk_printf(out, "EC%d", bits); + break; + case TLSEXT_signature_dsa: + len = chunk_printf(out, "DSA%d", bits); + break; + default: + return 0; + } + if (len < 0) + return 0; + return 1; +} + +/* Extract a serial from a cert, and copy it to a chunk. + * Returns 1 if serial is found and copied, 0 if no serial found and + * -1 if output is not large enough. + */ +int ssl_sock_get_serial(X509 *crt, struct buffer *out) +{ + ASN1_INTEGER *serial; + + serial = X509_get_serialNumber(crt); + if (!serial) + return 0; + + if (out->size < serial->length) + return -1; + + memcpy(out->area, serial->data, serial->length); + out->data = serial->length; + return 1; +} + +/* Extract a cert to der, and copy it to a chunk. + * Returns 1 if the cert is found and copied, 0 on der conversion failure + * and -1 if the output is not large enough. + */ +int ssl_sock_crt2der(X509 *crt, struct buffer *out) +{ + int len; + unsigned char *p = (unsigned char *) out->area;; + + len =i2d_X509(crt, NULL); + if (len <= 0) + return 1; + + if (out->size < len) + return -1; + + i2d_X509(crt,&p); + out->data = len; + return 1; +} + + +/* Copy Date in ASN1_UTCTIME format in struct buffer out. + * Returns 1 if serial is found and copied, 0 if no valid time found + * and -1 if output is not large enough. + */ +int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out) +{ + if (tm->type == V_ASN1_GENERALIZEDTIME) { + ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm; + + if (gentm->length < 12) + return 0; + if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30) + return 0; + if (out->size < gentm->length-2) + return -1; + + memcpy(out->area, gentm->data+2, gentm->length-2); + out->data = gentm->length-2; + return 1; + } + else if (tm->type == V_ASN1_UTCTIME) { + ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm; + + if (utctm->length < 10) + return 0; + if (utctm->data[0] >= 0x35) + return 0; + if (out->size < utctm->length) + return -1; + + memcpy(out->area, utctm->data, utctm->length); + out->data = utctm->length; + return 1; + } + + return 0; +} + +/* Extract an entry from a X509_NAME and copy its value to an output chunk. + * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough. + */ +int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos, + struct buffer *out) +{ + X509_NAME_ENTRY *ne; + ASN1_OBJECT *obj; + ASN1_STRING *data; + const unsigned char *data_ptr; + int data_len; + int i, j, n; + int cur = 0; + const char *s; + char tmp[128]; + int name_count; + + name_count = X509_NAME_entry_count(a); + + out->data = 0; + for (i = 0; i < name_count; i++) { + if (pos < 0) + j = (name_count-1) - i; + else + j = i; + + ne = X509_NAME_get_entry(a, j); + obj = X509_NAME_ENTRY_get_object(ne); + data = X509_NAME_ENTRY_get_data(ne); + data_ptr = ASN1_STRING_get0_data(data); + data_len = ASN1_STRING_length(data); + n = OBJ_obj2nid(obj); + if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { + i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); + s = tmp; + } + + if (chunk_strcasecmp(entry, s) != 0) + continue; + + if (pos < 0) + cur--; + else + cur++; + + if (cur != pos) + continue; + + if (data_len > out->size) + return -1; + + memcpy(out->area, data_ptr, data_len); + out->data = data_len; + return 1; + } + + return 0; + +} + +/* + * Extract the DN in the specified format from the X509_NAME and copy result to a chunk. + * Currently supports rfc2253 for returning LDAP V3 DNs. + * Returns 1 if dn entries exist, 0 if no dn entry was found. + */ +int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out) +{ + BIO *bio = NULL; + int ret = 0; + int data_len = 0; + + if (chunk_strcmp(format, "rfc2253") == 0) { + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto out; + + if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0) + goto out; + + if ((data_len = BIO_read(bio, out->area, out->size)) <= 0) + goto out; + + out->data = data_len; + + ret = 1; + } +out: + if (bio) + BIO_free(bio); + return ret; +} + +/* Extract and format full DN from a X509_NAME and copy result into a chunk + * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough. + */ +int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out) +{ + X509_NAME_ENTRY *ne; + ASN1_OBJECT *obj; + ASN1_STRING *data; + const unsigned char *data_ptr; + int data_len; + int i, n, ln; + int l = 0; + const char *s; + char *p; + char tmp[128]; + int name_count; + + + name_count = X509_NAME_entry_count(a); + + out->data = 0; + p = out->area; + for (i = 0; i < name_count; i++) { + ne = X509_NAME_get_entry(a, i); + obj = X509_NAME_ENTRY_get_object(ne); + data = X509_NAME_ENTRY_get_data(ne); + data_ptr = ASN1_STRING_get0_data(data); + data_len = ASN1_STRING_length(data); + n = OBJ_obj2nid(obj); + if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) { + i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj); + s = tmp; + } + ln = strlen(s); + + l += 1 + ln + 1 + data_len; + if (l > out->size) + return -1; + out->data = l; + + *(p++)='/'; + memcpy(p, s, ln); + p += ln; + *(p++)='='; + memcpy(p, data_ptr, data_len); + p += data_len; + } + + if (!out->data) + return 0; + + return 1; +} +