diff options
author | Michael Forney <mforney@mforney.org> | 2019-11-07 20:17:18 -0800 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2019-11-26 08:32:23 +0100 |
commit | 9b879160df01e7ddbb4770904391d3b74114302b (patch) | |
tree | 459aa36410942ab4dfc69d424d759d87ec8d5259 /lib | |
parent | 95e94c64fb0290b6c0e66e78de7d7dfd109c4080 (diff) |
TLS: add BearSSL vtls implementation
Closes #4597
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.inc | 5 | ||||
-rw-r--r-- | lib/curl_config.h.cmake | 3 | ||||
-rw-r--r-- | lib/curl_setup.h | 3 | ||||
-rw-r--r-- | lib/vtls/bearssl.c | 874 | ||||
-rw-r--r-- | lib/vtls/bearssl.h | 32 | ||||
-rw-r--r-- | lib/vtls/vtls.c | 4 | ||||
-rw-r--r-- | lib/vtls/vtls.h | 1 |
7 files changed, 918 insertions, 4 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 3a561d4d1..6c90c2675 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -30,12 +30,13 @@ LIB_VAUTH_HFILES = vauth/vauth.h vauth/digest.h vauth/ntlm.h LIB_VTLS_CFILES = vtls/openssl.c vtls/gtls.c vtls/vtls.c vtls/nss.c \ vtls/polarssl.c vtls/polarssl_threadlock.c \ vtls/wolfssl.c vtls/schannel.c vtls/schannel_verify.c \ - vtls/sectransp.c vtls/gskit.c vtls/mbedtls.c vtls/mesalink.c + vtls/sectransp.c vtls/gskit.c vtls/mbedtls.c vtls/mesalink.c \ + vtls/bearssl.c LIB_VTLS_HFILES = vtls/openssl.h vtls/vtls.h vtls/gtls.h \ vtls/nssg.h vtls/polarssl.h vtls/polarssl_threadlock.h \ vtls/wolfssl.h vtls/schannel.h vtls/sectransp.h vtls/gskit.h \ - vtls/mbedtls.h vtls/mesalink.h + vtls/mbedtls.h vtls/mesalink.h vtls/bearssl.h LIB_VQUIC_CFILES = vquic/ngtcp2.c vquic/quiche.c diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake index e0793a7ee..60837d4f4 100644 --- a/lib/curl_config.h.cmake +++ b/lib/curl_config.h.cmake @@ -948,6 +948,9 @@ ${SIZEOF_TIME_T_CODE} /* if mbedTLS is enabled */ #cmakedefine USE_MBEDTLS 1 +/* if BearSSL is enabled */ +#cmakedefine USE_BEARSSL 1 + /* if libSSH2 is in use */ #cmakedefine USE_LIBSSH2 1 diff --git a/lib/curl_setup.h b/lib/curl_setup.h index 13af8cdec..b4ba92931 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -644,7 +644,8 @@ int netware_init(void); #if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_NSS) || \ defined(USE_MBEDTLS) || \ defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \ - defined(USE_SECTRANSP) || defined(USE_GSKIT) || defined(USE_MESALINK) + defined(USE_SECTRANSP) || defined(USE_GSKIT) || defined(USE_MESALINK) || \ + defined(USE_BEARSSL) #define USE_SSL /* SSL support has been enabled */ #endif diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c new file mode 100644 index 000000000..51694c48d --- /dev/null +++ b/lib/vtls/bearssl.c @@ -0,0 +1,874 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2019, Michael Forney, <mforney@mforney.org> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_BEARSSL + +#include <bearssl.h> + +#include "bearssl.h" +#include "urldata.h" +#include "sendf.h" +#include "inet_pton.h" +#include "vtls.h" +#include "connect.h" +#include "select.h" +#include "multiif.h" +#include "curl_printf.h" +#include "curl_memory.h" + +struct x509_context { + const br_x509_class *vtable; + br_x509_minimal_context minimal; + bool verifyhost; + bool verifypeer; +}; + +struct ssl_backend_data { + br_ssl_client_context ctx; + struct x509_context x509; + unsigned char buf[BR_SSL_BUFSIZE_BIDI]; + br_x509_trust_anchor *anchors; + size_t anchors_len; + const char *protocols[2]; + /* SSL client context is active */ + bool active; +}; + +#define BACKEND connssl->backend + +struct cafile_parser { + CURLcode err; + bool in_cert; + br_x509_decoder_context xc; + /* array of trust anchors loaded from CAfile */ + br_x509_trust_anchor *anchors; + size_t anchors_len; + /* buffer for DN data */ + unsigned char dn[1024]; + size_t dn_len; +}; + +static void append_dn(void *ctx, const void *buf, size_t len) +{ + struct cafile_parser *ca = ctx; + + if(ca->err != CURLE_OK || !ca->in_cert) + return; + if(sizeof(ca->dn) - ca->dn_len < len) { + ca->err = CURLE_FAILED_INIT; + return; + } + memcpy(ca->dn + ca->dn_len, buf, len); + ca->dn_len += len; +} + +static void x509_push(void *ctx, const void *buf, size_t len) +{ + struct cafile_parser *ca = ctx; + + if(ca->in_cert) + br_x509_decoder_push(&ca->xc, buf, len); +} + +static CURLcode load_cafile(const char *path, br_x509_trust_anchor **anchors, + size_t *anchors_len) +{ + struct cafile_parser ca; + br_pem_decoder_context pc; + br_x509_trust_anchor *ta; + size_t ta_size; + br_x509_trust_anchor *new_anchors; + size_t new_anchors_len; + br_x509_pkey *pkey; + FILE *fp; + unsigned char buf[BUFSIZ], *p; + const char *name; + size_t n, i, pushed; + + fp = fopen(path, "rb"); + if(!fp) + return CURLE_SSL_CACERT_BADFILE; + + ca.err = CURLE_OK; + ca.in_cert = FALSE; + ca.anchors = NULL; + ca.anchors_len = 0; + br_pem_decoder_init(&pc); + br_pem_decoder_setdest(&pc, x509_push, &ca); + for(;;) { + n = fread(buf, 1, sizeof(buf), fp); + if(n == 0) + break; + p = buf; + while(n) { + pushed = br_pem_decoder_push(&pc, p, n); + if(ca.err) + goto fail; + p += pushed; + n -= pushed; + + switch(br_pem_decoder_event(&pc)) { + case 0: + break; + case BR_PEM_BEGIN_OBJ: + name = br_pem_decoder_name(&pc); + if(strcmp(name, "CERTIFICATE") && strcmp(name, "X509 CERTIFICATE")) + break; + br_x509_decoder_init(&ca.xc, append_dn, &ca); + if(ca.anchors_len == SIZE_MAX / sizeof(ca.anchors[0])) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + new_anchors_len = ca.anchors_len + 1; + new_anchors = realloc(ca.anchors, + new_anchors_len * sizeof(ca.anchors[0])); + if(!new_anchors) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + ca.anchors = new_anchors; + ca.anchors_len = new_anchors_len; + ca.in_cert = TRUE; + ca.dn_len = 0; + ta = &ca.anchors[ca.anchors_len - 1]; + ta->dn.data = NULL; + break; + case BR_PEM_END_OBJ: + if(!ca.in_cert) + break; + ca.in_cert = FALSE; + if(br_x509_decoder_last_error(&ca.xc)) { + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + ta->flags = 0; + if(br_x509_decoder_isCA(&ca.xc)) + ta->flags |= BR_X509_TA_CA; + pkey = br_x509_decoder_get_pkey(&ca.xc); + if(!pkey) { + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + ta->pkey = *pkey; + + /* calculate space needed for trust anchor data */ + ta_size = ca.dn_len; + switch(pkey->key_type) { + case BR_KEYTYPE_RSA: + ta_size += pkey->key.rsa.nlen + pkey->key.rsa.elen; + break; + case BR_KEYTYPE_EC: + ta_size += pkey->key.ec.qlen; + break; + default: + ca.err = CURLE_FAILED_INIT; + goto fail; + } + + /* fill in trust anchor DN and public key data */ + ta->dn.data = malloc(ta_size); + if(!ta->dn.data) { + ca.err = CURLE_OUT_OF_MEMORY; + goto fail; + } + memcpy(ta->dn.data, ca.dn, ca.dn_len); + ta->dn.len = ca.dn_len; + switch(pkey->key_type) { + case BR_KEYTYPE_RSA: + ta->pkey.key.rsa.n = ta->dn.data + ta->dn.len; + memcpy(ta->pkey.key.rsa.n, pkey->key.rsa.n, pkey->key.rsa.nlen); + ta->pkey.key.rsa.e = ta->pkey.key.rsa.n + ta->pkey.key.rsa.nlen; + memcpy(ta->pkey.key.rsa.e, pkey->key.rsa.e, pkey->key.rsa.elen); + break; + case BR_KEYTYPE_EC: + ta->pkey.key.ec.q = ta->dn.data + ta->dn.len; + memcpy(ta->pkey.key.ec.q, pkey->key.ec.q, pkey->key.ec.qlen); + break; + } + break; + default: + ca.err = CURLE_SSL_CACERT_BADFILE; + goto fail; + } + } + } + if(ferror(fp)) + ca.err = CURLE_READ_ERROR; + +fail: + fclose(fp); + if(ca.err == CURLE_OK) { + *anchors = ca.anchors; + *anchors_len = ca.anchors_len; + } + else { + for(i = 0; i < ca.anchors_len; ++i) + free(ca.anchors[i].dn.data); + free(ca.anchors); + } + + return ca.err; +} + +static void x509_start_chain(const br_x509_class **ctx, + const char *server_name) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + if(!x509->verifyhost) + server_name = NULL; + x509->minimal.vtable->start_chain(&x509->minimal.vtable, server_name); +} + +static void x509_start_cert(const br_x509_class **ctx, uint32_t length) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + x509->minimal.vtable->start_cert(&x509->minimal.vtable, length); +} + +static void x509_append(const br_x509_class **ctx, const unsigned char *buf, + size_t len) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + x509->minimal.vtable->append(&x509->minimal.vtable, buf, len); +} + +static void x509_end_cert(const br_x509_class **ctx) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + x509->minimal.vtable->end_cert(&x509->minimal.vtable); +} + +static unsigned x509_end_chain(const br_x509_class **ctx) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + unsigned err; + + err = x509->minimal.vtable->end_chain(&x509->minimal.vtable); + if(err && !x509->verifypeer) { + /* ignore any X.509 errors */ + err = BR_ERR_OK; + } + + return err; +} + +static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx, + unsigned *usages) +{ + struct x509_context *x509 = (struct x509_context *)ctx; + + return x509->minimal.vtable->get_pkey(&x509->minimal.vtable, usages); +} + +static const br_x509_class x509_vtable = { + sizeof(struct x509_context), + x509_start_chain, + x509_start_cert, + x509_append, + x509_end_cert, + x509_end_chain, + x509_get_pkey +}; + +static CURLcode bearssl_connect_step1(struct connectdata *conn, int sockindex) +{ + struct Curl_easy *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); + const char *hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : + conn->host.name; + const bool verifypeer = SSL_CONN_CONFIG(verifypeer); + const bool verifyhost = SSL_CONN_CONFIG(verifyhost); + CURLcode ret; + unsigned version_min, version_max; +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + + switch(SSL_CONN_CONFIG(version)) { + case CURL_SSLVERSION_SSLv2: + failf(data, "BearSSL does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_SSLv3: + failf(data, "BearSSL does not support SSLv3"); + return CURLE_SSL_CONNECT_ERROR; + case CURL_SSLVERSION_TLSv1_0: + version_min = BR_TLS10; + version_max = BR_TLS10; + break; + case CURL_SSLVERSION_TLSv1_1: + version_min = BR_TLS11; + version_max = BR_TLS11; + break; + case CURL_SSLVERSION_TLSv1_2: + version_min = BR_TLS12; + version_max = BR_TLS12; + break; + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + version_min = BR_TLS10; + version_max = BR_TLS12; + break; + default: + failf(data, "BearSSL: unknown CURLOPT_SSLVERSION"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(ssl_cafile) { + ret = load_cafile(ssl_cafile, &BACKEND->anchors, &BACKEND->anchors_len); + if(ret != CURLE_OK) { + if(verifypeer) { + failf(data, "error setting certificate verify locations:\n" + " CAfile: %s\n", ssl_cafile); + return ret; + } + infof(data, "error setting certificate verify locations," + " continuing anyway:\n"); + } + } + + /* initialize SSL context */ + br_ssl_client_init_full(&BACKEND->ctx, &BACKEND->x509.minimal, + BACKEND->anchors, BACKEND->anchors_len); + br_ssl_engine_set_versions(&BACKEND->ctx.eng, version_min, version_max); + br_ssl_engine_set_buffer(&BACKEND->ctx.eng, BACKEND->buf, + sizeof(BACKEND->buf), 1); + + /* initialize X.509 context */ + BACKEND->x509.vtable = &x509_vtable; + BACKEND->x509.verifypeer = verifypeer; + BACKEND->x509.verifyhost = verifyhost; + br_ssl_engine_set_x509(&BACKEND->ctx.eng, &BACKEND->x509.vtable); + + if(SSL_SET_OPTION(primary.sessionid)) { + void *session; + + Curl_ssl_sessionid_lock(conn); + if(!Curl_ssl_getsessionid(conn, &session, NULL, sockindex)) { + br_ssl_engine_set_session_parameters(&BACKEND->ctx.eng, session); + infof(data, "BearSSL: re-using session ID\n"); + } + Curl_ssl_sessionid_unlock(conn); + } + + if(conn->bits.tls_enable_alpn) { + int cur = 0; + + /* NOTE: when adding more protocols here, increase the size of the + * protocols array in `struct ssl_backend_data`. + */ + +#ifdef USE_NGHTTP2 + if(data->set.httpversion >= CURL_HTTP_VERSION_2 && + (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy)) { + BACKEND->protocols[cur++] = NGHTTP2_PROTO_VERSION_ID; + infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); + } +#endif + + BACKEND->protocols[cur++] = ALPN_HTTP_1_1; + infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); + + br_ssl_engine_set_protocol_names(&BACKEND->ctx.eng, + BACKEND->protocols, cur); + } + + if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) +#ifdef ENABLE_IPV6 + || (1 == Curl_inet_pton(AF_INET6, hostname, &addr)) +#endif + ) { + if(verifyhost) { + failf(data, "BearSSL: " + "host verification of IP address is not supported"); + return CURLE_PEER_FAILED_VERIFICATION; + } + hostname = NULL; + } + + if(!br_ssl_client_reset(&BACKEND->ctx, hostname, 0)) + return CURLE_FAILED_INIT; + BACKEND->active = TRUE; + + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode bearssl_connect_step2(struct connectdata *conn, int sockindex) +{ + struct Curl_easy *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + unsigned state; + unsigned char *buf; + size_t len; + ssize_t ret; + int err; + + for(;;) { + state = br_ssl_engine_current_state(&BACKEND->ctx.eng); + if(state & BR_SSL_CLOSED) { + err = br_ssl_engine_last_error(&BACKEND->ctx.eng); + switch(err) { + case BR_ERR_X509_EXPIRED: + failf(data, "SSL: X.509 verification: " + "certificate is expired or not yet valid"); + return CURLE_PEER_FAILED_VERIFICATION; + case BR_ERR_X509_BAD_SERVER_NAME: + failf(data, "SSL: X.509 verification: " + "expected server name was not found in the chain"); + return CURLE_PEER_FAILED_VERIFICATION; + case BR_ERR_X509_NOT_TRUSTED: + failf(data, "SSL: X.509 verification: " + "chain could not be linked to a trust anchor"); + return CURLE_PEER_FAILED_VERIFICATION; + } + /* X.509 errors are documented to have the range 32..63 */ + if(err >= 32 && err < 64) + return CURLE_PEER_FAILED_VERIFICATION; + return CURLE_SSL_CONNECT_ERROR; + } + if(state & (BR_SSL_SENDAPP | BR_SSL_RECVAPP)) { + connssl->connecting_state = ssl_connect_3; + return CURLE_OK; + } + if(state & BR_SSL_SENDREC) { + buf = br_ssl_engine_sendrec_buf(&BACKEND->ctx.eng, &len); + ret = swrite(sockfd, buf, len); + if(ret == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + return CURLE_SEND_ERROR; + } + br_ssl_engine_sendrec_ack(&BACKEND->ctx.eng, ret); + } + else if(state & BR_SSL_RECVREC) { + buf = br_ssl_engine_recvrec_buf(&BACKEND->ctx.eng, &len); + ret = sread(sockfd, buf, len); + if(ret == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + return CURLE_READ_ERROR; + } + if(ret == 0) + return CURLE_SSL_CONNECT_ERROR; + br_ssl_engine_recvrec_ack(&BACKEND->ctx.eng, ret); + } + } +} + +static CURLcode bearssl_connect_step3(struct connectdata *conn, int sockindex) +{ + struct Curl_easy *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + CURLcode ret; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + + if(conn->bits.tls_enable_alpn) { + const char *protocol; + + protocol = br_ssl_engine_get_selected_protocol(&BACKEND->ctx.eng); + if(protocol) { + infof(data, "ALPN, server accepted to use %s\n", protocol); + +#ifdef USE_NGHTTP2 + if(!strcmp(protocol, NGHTTP2_PROTO_VERSION_ID)) + conn->negnpn = CURL_HTTP_VERSION_2; + else +#endif + if(!strcmp(protocol, ALPN_HTTP_1_1)) + conn->negnpn = CURL_HTTP_VERSION_1_1; + else + infof(data, "ALPN, unrecognized protocol %s\n", protocol); + Curl_multiuse_state(conn, conn->negnpn == CURL_HTTP_VERSION_2 ? + BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + } + else + infof(data, "ALPN, server did not agree to a protocol\n"); + } + + if(SSL_SET_OPTION(primary.sessionid)) { + bool incache; + void *oldsession; + br_ssl_session_parameters *session; + + session = malloc(sizeof(*session)); + if(!session) + return CURLE_OUT_OF_MEMORY; + br_ssl_engine_get_session_parameters(&BACKEND->ctx.eng, session); + Curl_ssl_sessionid_lock(conn); + incache = !(Curl_ssl_getsessionid(conn, &oldsession, NULL, sockindex)); + if(incache) + Curl_ssl_delsessionid(conn, oldsession); + ret = Curl_ssl_addsessionid(conn, session, 0, sockindex); + Curl_ssl_sessionid_unlock(conn); + if(ret) { + free(session); + return CURLE_OUT_OF_MEMORY; + } + } + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static ssize_t bearssl_send(struct connectdata *conn, int sockindex, + const void *buf, size_t len, CURLcode *err) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + unsigned state; + unsigned char *rec, *app; + size_t reclen, applen; + ssize_t ret; + + applen = 0; + for(;;) { + state = br_ssl_engine_current_state(&BACKEND->ctx.eng); + if(state & BR_SSL_SENDREC) { + rec = br_ssl_engine_sendrec_buf(&BACKEND->ctx.eng, &reclen); + ret = swrite(conn->sock[sockindex], rec, reclen); + if(ret == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) + *err = CURLE_AGAIN; + else + *err = CURLE_SEND_ERROR; + return -1; + } + br_ssl_engine_sendrec_ack(&BACKEND->ctx.eng, ret); + } + else if(state & BR_SSL_SENDAPP && applen == 0) { + app = br_ssl_engine_sendapp_buf(&BACKEND->ctx.eng, &applen); + if(applen > len) + applen = len; + memcpy(app, buf, applen); + br_ssl_engine_sendapp_ack(&BACKEND->ctx.eng, applen); + br_ssl_engine_flush(&BACKEND->ctx.eng, 0); + } + else if(state & BR_SSL_CLOSED || applen == 0) { + *err = CURLE_SEND_ERROR; + return -1; + } + else + break; + } + + return applen; +} + +static ssize_t bearssl_recv(struct connectdata *conn, int sockindex, + char *buf, size_t len, CURLcode *err) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + unsigned state; + unsigned char *rec, *app; + size_t reclen, applen; + ssize_t ret; + + for(;;) { + state = br_ssl_engine_current_state(&BACKEND->ctx.eng); + if(state & BR_SSL_RECVREC) { + rec = br_ssl_engine_recvrec_buf(&BACKEND->ctx.eng, &reclen); + ret = sread(conn->sock[sockindex], rec, reclen); + if(ret == -1 && (SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK)) { + *err = CURLE_AGAIN; + return -1; + } + if(ret <= 0) { + *err = CURLE_RECV_ERROR; + return -1; + } + br_ssl_engine_recvrec_ack(&BACKEND->ctx.eng, ret); + } + else if(state & BR_SSL_RECVAPP) { + app = br_ssl_engine_recvapp_buf(&BACKEND->ctx.eng, &applen); + if(applen > len) + applen = len; + memcpy(buf, app, applen); + br_ssl_engine_recvapp_ack(&BACKEND->ctx.eng, applen); + break; + } + else { + *err = CURLE_RECV_ERROR; + return -1; + } + } + + return applen; +} + +static CURLcode bearssl_connect_common(struct connectdata *conn, + int sockindex, + bool nonblocking, + bool *done) +{ + CURLcode ret; + struct Curl_easy *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + time_t timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + ret = bearssl_connect_step1(conn, sockindex); + if(ret) + return ret; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + curl_socket_t writefd = ssl_connect_2_writing == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading == + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, + nonblocking?0:timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if this + * connection is done nonblocking and this loop would execute again. This + * permits the owner of a multi handle to abort a connection attempt + * before step2 has completed while ensuring that a client using select() + * or epoll() will always have a valid fdset to wait on. + */ + ret = bearssl_connect_step2(conn, sockindex); + if(ret || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return ret; + } + + if(ssl_connect_3 == connssl->connecting_state) { + ret = bearssl_connect_step3(conn, sockindex); + if(ret) + return ret; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = bearssl_recv; + conn->send[sockindex] = bearssl_send; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +static size_t Curl_bearssl_version(char *buffer, size_t size) +{ + return msnprintf(buffer, size, "BearSSL"); +} + +static bool Curl_bearssl_data_pending(const struct connectdata *conn, + int connindex) +{ + const struct ssl_connect_data *connssl = &conn->ssl[connindex]; + + return br_ssl_engine_current_state(&BACKEND->ctx.eng) & BR_SSL_RECVAPP; +} + +static CURLcode Curl_bearssl_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy, size_t length) +{ + static br_hmac_drbg_context ctx; + static bool seeded = FALSE; + + if(!seeded) { + br_prng_seeder seeder; + + br_hmac_drbg_init(&ctx, &br_sha256_vtable, NULL, 0); + seeder = br_prng_seeder_system(NULL); + if(!seeder || !seeder(&ctx.vtable)) + return CURLE_FAILED_INIT; + seeded = TRUE; + } + br_hmac_drbg_generate(&ctx, entropy, length); + + return CURLE_OK; +} + +static CURLcode Curl_bearssl_connect(struct connectdata *conn, int sockindex) +{ + CURLcode ret; + bool done = FALSE; + + ret = bearssl_connect_common(conn, sockindex, FALSE, &done); + if(ret) + return ret; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static CURLcode Curl_bearssl_connect_nonblocking(struct connectdata *conn, + int sockindex, bool *done) +{ + return bearssl_connect_common(conn, sockindex, TRUE, done); +} + +static void *Curl_bearssl_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) +{ + return &BACKEND->ctx; +} + +static void Curl_bearssl_close(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + unsigned char *buf; + size_t len, i; + ssize_t ret; + + if(BACKEND->active) { + br_ssl_engine_close(&BACKEND->ctx.eng); + while(br_ssl_engine_current_state(&BACKEND->ctx.eng) & BR_SSL_SENDREC) { + buf = br_ssl_engine_sendrec_buf(&BACKEND->ctx.eng, &len); + ret = swrite(conn->sock[sockindex], buf, len); + if(ret < 0) + break; + br_ssl_engine_sendrec_ack(&BACKEND->ctx.eng, ret); + } + } + for(i = 0; i < BACKEND->anchors_len; ++i) + free(BACKEND->anchors[i].dn.data); + free(BACKEND->anchors); +} + +static void Curl_bearssl_session_free(void *ptr) +{ + free(ptr); +} + +static CURLcode Curl_bearssl_md5sum(unsigned char *input, + size_t inputlen, + unsigned char *md5sum, + size_t md5len UNUSED_PARAM) +{ + br_md5_context ctx; + + br_md5_init(&ctx); + br_md5_update(&ctx, input, inputlen); + br_md5_out(&ctx, md5sum); + return CURLE_OK; +} + +static CURLcode Curl_bearssl_sha256sum(const unsigned char *input, + size_t inputlen, + unsigned char *sha256sum, + size_t sha256len UNUSED_PARAM) +{ + br_sha256_context ctx; + + br_sha256_init(&ctx); + br_sha256_update(&ctx, input, inputlen); + br_sha256_out(&ctx, sha256sum); + return CURLE_OK; +} + +const struct Curl_ssl Curl_ssl_bearssl = { + { CURLSSLBACKEND_BEARSSL, "bearssl" }, + + 0, + + sizeof(struct ssl_backend_data), + + Curl_none_init, + Curl_none_cleanup, + Curl_bearssl_version, + Curl_none_check_cxn, + Curl_none_shutdown, + Curl_bearssl_data_pending, + Curl_bearssl_random, + Curl_none_cert_status_request, + Curl_bearssl_connect, + Curl_bearssl_connect_nonblocking, + Curl_bearssl_get_internals, + Curl_bearssl_close, + Curl_none_close_all, + Curl_bearssl_session_free, + Curl_none_set_engine, + Curl_none_set_engine_default, + Curl_none_engines_list, + Curl_none_false_start, + Curl_bearssl_md5sum, + Curl_bearssl_sha256sum +}; + +#endif /* USE_BEARSSL */ diff --git a/lib/vtls/bearssl.h b/lib/vtls/bearssl.h new file mode 100644 index 000000000..5f94922b9 --- /dev/null +++ b/lib/vtls/bearssl.h @@ -0,0 +1,32 @@ +#ifndef HEADER_CURL_BEARSSL_H +#define HEADER_CURL_BEARSSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2019, Michael Forney, <mforney@mforney.org> + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_BEARSSL + +extern const struct Curl_ssl Curl_ssl_bearssl; + +#endif /* USE_BEARSSL */ +#endif /* HEADER_CURL_BEARSSL_H */ diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index e6d756225..894fd8a43 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -517,7 +517,7 @@ void Curl_ssl_close_all(struct Curl_easy *data) #if defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \ defined(USE_SECTRANSP) || defined(USE_POLARSSL) || defined(USE_NSS) || \ - defined(USE_MBEDTLS) || defined(USE_WOLFSSL) + defined(USE_MBEDTLS) || defined(USE_WOLFSSL) || defined(USE_BEARSSL) int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks) { struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; @@ -1189,6 +1189,8 @@ const struct Curl_ssl *Curl_ssl = &Curl_ssl_schannel; #elif defined(USE_MESALINK) &Curl_ssl_mesalink; +#elif defined(USE_BEARSSL) + &Curl_ssl_bearssl; #else #error "Missing struct Curl_ssl for selected SSL backend" #endif diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 61d8416c2..976cc4360 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -108,6 +108,7 @@ CURLcode Curl_none_md5sum(unsigned char *input, size_t inputlen, #include "sectransp.h" /* SecureTransport (Darwin) version */ #include "mbedtls.h" /* mbedTLS versions */ #include "mesalink.h" /* MesaLink versions */ +#include "bearssl.h" /* BearSSL versions */ #ifndef MAX_PINNED_PUBKEY_SIZE #define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */ |