From: Stefan Eissing Date: Wed, 30 Nov 2022 13:42:37 +0000 (+0100) Subject: gnutls: use common gnutls init and verify code for ngtcp2 X-Git-Tag: curl-7_87_0~84 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=27ec767ebd2032a98db9d98ec575b0d0301a91d8;p=thirdparty%2Fcurl.git gnutls: use common gnutls init and verify code for ngtcp2 Closes #10007 --- diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c index 33cf474ae8..f16b469946 100644 --- a/lib/vquic/ngtcp2.c +++ b/lib/vquic/ngtcp2.c @@ -27,6 +27,7 @@ #ifdef USE_NGTCP2 #include #include + #ifdef USE_OPENSSL #include #ifdef OPENSSL_IS_BORINGSSL @@ -42,6 +43,7 @@ #include #include "vtls/wolfssl.h" #endif + #include "urldata.h" #include "sendf.h" #include "strdup.h" @@ -321,13 +323,17 @@ static CURLcode quic_set_client_cert(struct Curl_easy *data, /** SSL callbacks ***/ -static int quic_init_ssl(struct quicsocket *qs) +static CURLcode quic_init_ssl(struct quicsocket *qs, + struct Curl_easy *data, + struct connectdata *conn) { const uint8_t *alpn = NULL; size_t alpnlen = 0; /* this will need some attention when HTTPS proxy over QUIC get fixed */ const char * const hostname = qs->conn->host.name; + (void)data; + (void)conn; DEBUGASSERT(!qs->ssl); qs->ssl = SSL_new(qs->sslctx); @@ -342,64 +348,49 @@ static int quic_init_ssl(struct quicsocket *qs) /* set SNI */ SSL_set_tlsext_host_name(qs->ssl, hostname); - return 0; + return CURLE_OK; } #elif defined(USE_GNUTLS) -static int quic_init_ssl(struct quicsocket *qs) +static CURLcode quic_init_ssl(struct quicsocket *qs, + struct Curl_easy *data, + struct connectdata *conn) { + CURLcode result; gnutls_datum_t alpn[2]; /* this will need some attention when HTTPS proxy over QUIC get fixed */ const char * const hostname = qs->conn->host.name; + long * const pverifyresult = &data->set.ssl.certverifyresult; int rc; - DEBUGASSERT(!qs->ssl); + DEBUGASSERT(qs->gtls == NULL); + qs->gtls = calloc(1, sizeof(*(qs->gtls))); + if(!qs->gtls) + return CURLE_OUT_OF_MEMORY; - gnutls_init(&qs->ssl, GNUTLS_CLIENT); - gnutls_session_set_ptr(qs->ssl, &qs->conn_ref); + result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl, + hostname, qs->gtls, pverifyresult); + if(result) + return result; + + gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref); - if(ngtcp2_crypto_gnutls_configure_client_session(qs->ssl) != 0) { + if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) { H3BUGF(fprintf(stderr, "ngtcp2_crypto_gnutls_configure_client_session failed\n")); - return 1; + return CURLE_QUIC_CONNECT_ERROR; } - rc = gnutls_priority_set_direct(qs->ssl, QUIC_PRIORITY, NULL); + rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL); if(rc < 0) { H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n", gnutls_strerror(rc))); - return 1; + return CURLE_QUIC_CONNECT_ERROR; } /* Open the file if a TLS or QUIC backend has not done this before. */ Curl_tls_keylog_open(); if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(qs->ssl, keylog_callback); - } - - if(qs->cred) - gnutls_certificate_free_credentials(qs->cred); - - rc = gnutls_certificate_allocate_credentials(&qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, - "gnutls_certificate_allocate_credentials failed: %s\n", - gnutls_strerror(rc))); - return 1; - } - - rc = gnutls_certificate_set_x509_system_trust(qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, - "gnutls_certificate_set_x509_system_trust failed: %s\n", - gnutls_strerror(rc))); - return 1; - } - - rc = gnutls_credentials_set(qs->ssl, GNUTLS_CRD_CERTIFICATE, qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, "gnutls_credentials_set failed: %s\n", - gnutls_strerror(rc))); - return 1; + gnutls_session_set_keylog_function(qs->gtls->session, keylog_callback); } /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ @@ -408,11 +399,9 @@ static int quic_init_ssl(struct quicsocket *qs) alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; alpn[1].size = sizeof(H3_ALPN_H3) - 2; - gnutls_alpn_set_protocols(qs->ssl, alpn, 2, GNUTLS_ALPN_MANDATORY); + gnutls_alpn_set_protocols(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY); - /* set SNI */ - gnutls_server_name_set(qs->ssl, GNUTLS_NAME_DNS, hostname, strlen(hostname)); - return 0; + return CURLE_OK; } #elif defined(USE_WOLFSSL) @@ -487,13 +476,17 @@ static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data) /** SSL callbacks ***/ -static int quic_init_ssl(struct quicsocket *qs) +static CURLcode quic_init_ssl(struct quicsocket *qs, + struct Curl_easy *data, + struct connectdata *conn) { const uint8_t *alpn = NULL; size_t alpnlen = 0; /* this will need some attention when HTTPS proxy over QUIC get fixed */ const char * const hostname = qs->conn->host.name; + (void)data; + (void)conn; DEBUGASSERT(!qs->ssl); qs->ssl = SSL_new(qs->sslctx); @@ -510,7 +503,7 @@ static int quic_init_ssl(struct quicsocket *qs) wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME, hostname, (unsigned short)strlen(hostname)); - return 0; + return CURLE_OK; } #endif /* defined(USE_WOLFSSL) */ @@ -815,8 +808,9 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, return CURLE_QUIC_CONNECT_ERROR; #endif - if(quic_init_ssl(qs)) - return CURLE_QUIC_CONNECT_ERROR; + result = quic_init_ssl(qs, data, conn); + if(result) + return result; qs->dcid.datalen = NGTCP2_MAX_CIDLEN; result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN); @@ -848,7 +842,11 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, if(rc) return CURLE_QUIC_CONNECT_ERROR; +#ifdef USE_GNUTLS + ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session); +#else ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl); +#endif ngtcp2_connection_close_error_default(&qs->last_error); @@ -935,29 +933,29 @@ static void qs_disconnect(struct quicsocket *qs) close(qs->qlogfd); qs->qlogfd = -1; } - if(qs->ssl) #ifdef USE_OPENSSL + if(qs->ssl) SSL_free(qs->ssl); + qs->ssl = NULL; + SSL_CTX_free(qs->sslctx); #elif defined(USE_GNUTLS) - gnutls_deinit(qs->ssl); + if(qs->gtls) { + if(qs->gtls->cred) + gnutls_certificate_free_credentials(qs->gtls->cred); + if(qs->gtls->session) + gnutls_deinit(qs->gtls->session); + free(qs->gtls); + qs->gtls = NULL; + } #elif defined(USE_WOLFSSL) + if(qs->ssl) wolfSSL_free(qs->ssl); -#endif qs->ssl = NULL; -#ifdef USE_GNUTLS - if(qs->cred) { - gnutls_certificate_free_credentials(qs->cred); - qs->cred = NULL; - } + wolfSSL_CTX_free(qs->sslctx); #endif free(qs->pktbuf); nghttp3_conn_del(qs->h3conn); ngtcp2_conn_del(qs->qconn); -#ifdef USE_OPENSSL - SSL_CTX_free(qs->sslctx); -#elif defined(USE_WOLFSSL) - wolfSSL_CTX_free(qs->sslctx); -#endif } void Curl_quic_disconnect(struct Curl_easy *data, @@ -1675,6 +1673,15 @@ static CURLcode ng_has_connected(struct Curl_easy *data, struct connectdata *conn, int tempindex) { CURLcode result = CURLE_OK; + const char *hostname, *disp_hostname; + int port; + char *snihost; + + Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port); + snihost = Curl_ssl_snihost(data, hostname, NULL); + if(!snihost) + return CURLE_PEER_FAILED_VERIFICATION; + conn->recv[FIRSTSOCKET] = ngh3_stream_recv; conn->send[FIRSTSOCKET] = ngh3_stream_send; conn->handler = &Curl_handler_http3; @@ -1694,22 +1701,18 @@ static CURLcode ng_has_connected(struct Curl_easy *data, X509_free(server_cert); if(result) return result; - infof(data, "Verified certificate just fine"); #elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(conn->cfilter[FIRSTSOCKET], - data, conn->quic->ssl); + result = Curl_gtls_verifyserver(data, conn->quic->gtls->session, + &conn->ssl_config, &data->set.ssl, + hostname, disp_hostname, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; #elif defined(USE_WOLFSSL) - const char *hostname, *disp_hostname; - int port; - char *snihost; - - Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port); - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || - (wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)) + if(wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE) return CURLE_PEER_FAILED_VERIFICATION; - infof(data, "Verified certificate just fine"); #endif + infof(data, "Verified certificate just fine"); } else infof(data, "Skipped certificate verification"); diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h index 6539f5fef3..2265999e22 100644 --- a/lib/vquic/ngtcp2.h +++ b/lib/vquic/ngtcp2.h @@ -36,14 +36,14 @@ #include #ifdef USE_OPENSSL #include -#elif defined(USE_GNUTLS) -#include #elif defined(USE_WOLFSSL) #include #include #include #endif +struct gtls_instance; + struct blocked_pkt { const uint8_t *pkt; size_t pktlen; @@ -64,8 +64,7 @@ struct quicsocket { SSL_CTX *sslctx; SSL *ssl; #elif defined(USE_GNUTLS) - gnutls_certificate_credentials_t cred; - gnutls_session_t ssl; + struct gtls_instance *gtls; #elif defined(USE_WOLFSSL) WOLFSSL_CTX *sslctx; WOLFSSL *ssl; diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index b55b707d11..104dce6093 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -59,14 +59,6 @@ /* The last #include file should be: */ #include "memdebug.h" -#ifdef HAVE_GNUTLS_SRP -/* the function exists */ -#ifdef USE_TLS_SRP -/* the functionality is not disabled */ -#define USE_GNUTLS_SRP -#endif -#endif - /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -85,11 +77,7 @@ static bool gtls_inited = FALSE; # include struct ssl_backend_data { - gnutls_session_t session; - gnutls_certificate_credentials_t cred; -#ifdef USE_GNUTLS_SRP - gnutls_srp_client_credentials_t srp_client_cred; -#endif + struct gtls_instance gtls; }; static ssize_t gtls_push(void *s, const void *buf, size_t blen) @@ -103,7 +91,7 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen) DEBUGASSERT(data); nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); if(nwritten < 0) { - gnutls_transport_set_errno(connssl->backend->session, + gnutls_transport_set_errno(connssl->backend->gtls.session, (CURLE_AGAIN == result)? EAGAIN : EINVAL); nwritten = -1; } @@ -121,7 +109,7 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) DEBUGASSERT(data); nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); if(nread < 0) { - gnutls_transport_set_errno(connssl->backend->session, + gnutls_transport_set_errno(connssl->backend->gtls.session, (CURLE_AGAIN == result)? EAGAIN : EINVAL); nread = -1; } @@ -229,7 +217,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; DEBUGASSERT(backend); - session = backend->session; + session = backend->gtls.session; for(;;) { timediff_t timeout_ms; @@ -334,12 +322,11 @@ static gnutls_x509_crt_fmt_t do_file_type(const char *type) #define GNUTLS_SRP "+SRP" static CURLcode -set_ssl_version_min_max(struct Curl_cfilter *cf, - struct Curl_easy *data, +set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_primary_config *conn_config, const char **prioritylist, const char *tls13support) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); long ssl_version = conn_config->version; long ssl_version_max = conn_config->version_max; @@ -407,20 +394,16 @@ set_ssl_version_min_max(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } -static CURLcode -gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +CURLcode gtls_client_init(struct Curl_easy *data, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + const char *hostname, + struct gtls_instance *gtls, + long *pverifyresult) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); unsigned int init_flags; - gnutls_session_t session; int rc; bool sni = TRUE; /* default is SNI enabled */ - void *transport_ptr = NULL; - gnutls_push_func gnutls_transport_push = NULL; - gnutls_pull_func gnutls_transport_pull = NULL; #ifdef ENABLE_IPV6 struct in6_addr addr; #else @@ -428,54 +411,44 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #endif const char *prioritylist; const char *err = NULL; - const char *hostname = connssl->hostname; - long * const certverifyresult = &ssl_config->certverifyresult; const char *tls13support; CURLcode result; - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; - if(!gtls_inited) gtls_init(); - /* Initialize certverifyresult to OK */ - *certverifyresult = 0; + *pverifyresult = 0; - if(conn_config->version == CURL_SSLVERSION_SSLv2) { + if(config->version == CURL_SSLVERSION_SSLv2) { failf(data, "GnuTLS does not support SSLv2"); return CURLE_SSL_CONNECT_ERROR; } - else if(conn_config->version == CURL_SSLVERSION_SSLv3) + else if(config->version == CURL_SSLVERSION_SSLv3) sni = FALSE; /* SSLv3 has no SNI */ /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(&backend->cred); + rc = gnutls_certificate_allocate_credentials(>ls->cred); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } #ifdef USE_GNUTLS_SRP - if((ssl_config->primary.authtype == CURL_TLSAUTH_SRP) && + if((config->authtype == CURL_TLSAUTH_SRP) && Curl_auth_allowed_to_host(data)) { - infof(data, "Using TLS-SRP username: %s", - ssl_config->primary.username); + infof(data, "Using TLS-SRP username: %s", config->username); - rc = gnutls_srp_allocate_client_credentials(&backend->srp_client_cred); + rc = gnutls_srp_allocate_client_credentials( + >ls->srp_client_cred); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_srp_allocate_client_cred() failed: %s", gnutls_strerror(rc)); return CURLE_OUT_OF_MEMORY; } - rc = gnutls_srp_set_client_credentials(backend->srp_client_cred, - ssl_config->primary.username, - ssl_config->primary.password); + rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, + config->username, + config->password); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_srp_set_client_cred() failed: %s", gnutls_strerror(rc)); @@ -484,67 +457,63 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #endif - if(conn_config->CAfile) { + if(config->CAfile) { /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(backend->cred, + gnutls_certificate_set_verify_flags(gtls->cred, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - rc = gnutls_certificate_set_x509_trust_file(backend->cred, - conn_config->CAfile, + rc = gnutls_certificate_set_x509_trust_file(gtls->cred, + config->CAfile, GNUTLS_X509_FMT_PEM); if(rc < 0) { infof(data, "error reading ca cert file %s (%s)", - conn_config->CAfile, gnutls_strerror(rc)); - if(conn_config->verifypeer) { - *certverifyresult = rc; + config->CAfile, gnutls_strerror(rc)); + if(config->verifypeer) { + *pverifyresult = rc; return CURLE_SSL_CACERT_BADFILE; } } else - infof(data, "found %d certificates in %s", rc, - conn_config->CAfile); + infof(data, "found %d certificates in %s", rc, config->CAfile); } - if(conn_config->CApath) { + if(config->CApath) { /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(backend->cred, - conn_config->CApath, + rc = gnutls_certificate_set_x509_trust_dir(gtls->cred, + config->CApath, GNUTLS_X509_FMT_PEM); if(rc < 0) { infof(data, "error reading ca cert file %s (%s)", - conn_config->CApath, gnutls_strerror(rc)); - if(conn_config->verifypeer) { - *certverifyresult = rc; + config->CApath, gnutls_strerror(rc)); + if(config->verifypeer) { + *pverifyresult = rc; return CURLE_SSL_CACERT_BADFILE; } } else - infof(data, "found %d certificates in %s", - rc, conn_config->CApath); + infof(data, "found %d certificates in %s", rc, config->CApath); } #ifdef CURL_CA_FALLBACK /* use system ca certificate store as fallback */ - if(conn_config->verifypeer && - !(conn_config->CAfile || conn_config->CApath)) { + if(config->verifypeer && !(config->CAfile || config->CApath)) { /* this ignores errors on purpose */ - gnutls_certificate_set_x509_system_trust(backend->cred); + gnutls_certificate_set_x509_system_trust(gtls->cred); } #endif - if(ssl_config->primary.CRLfile) { + if(config->CRLfile) { /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(backend->cred, - ssl_config->primary.CRLfile, + rc = gnutls_certificate_set_x509_crl_file(gtls->cred, + config->CRLfile, GNUTLS_X509_FMT_PEM); if(rc < 0) { failf(data, "error reading crl file %s (%s)", - ssl_config->primary.CRLfile, gnutls_strerror(rc)); + config->CRLfile, gnutls_strerror(rc)); return CURLE_SSL_CRL_BADFILE; } else - infof(data, "found %d CRL in %s", - rc, ssl_config->primary.CRLfile); + infof(data, "found %d CRL in %s", rc, config->CRLfile); } /* Initialize TLS session as a client */ @@ -559,15 +528,12 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) init_flags |= GNUTLS_NO_TICKETS; #endif - rc = gnutls_init(&backend->session, init_flags); + rc = gnutls_init(>ls->session, init_flags); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_init() failed: %d", rc); return CURLE_SSL_CONNECT_ERROR; } - /* convenient assign */ - session = backend->session; - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && #ifdef ENABLE_IPV6 (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && @@ -575,15 +541,15 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) sni) { size_t snilen; char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost || gnutls_server_name_set(session, GNUTLS_NAME_DNS, snihost, - snilen) < 0) { + if(!snihost || gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS, + snihost, snilen) < 0) { failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } } /* Use default priorities */ - rc = gnutls_set_default_priority(session); + rc = gnutls_set_default_priority(gtls->session); if(rc != GNUTLS_E_SUCCESS) return CURLE_SSL_CONNECT_ERROR; @@ -594,13 +560,13 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) * removed if a run-time error indicates that SRP is not supported by this * GnuTLS version */ - if(conn_config->version == CURL_SSLVERSION_SSLv2 || - conn_config->version == CURL_SSLVERSION_SSLv3) { + if(config->version == CURL_SSLVERSION_SSLv2 || + config->version == CURL_SSLVERSION_SSLv3) { failf(data, "GnuTLS does not support SSLv2 or SSLv3"); return CURLE_SSL_CONNECT_ERROR; } - if(conn_config->version == CURL_SSLVERSION_TLSv1_3) { + if(config->version == CURL_SSLVERSION_TLSv1_3) { if(!tls13support) { failf(data, "This GnuTLS installation does not support TLS 1.3"); return CURLE_SSL_CONNECT_ERROR; @@ -608,14 +574,14 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } /* At this point we know we have a supported TLS version, so set it */ - result = set_ssl_version_min_max(cf, data, &prioritylist, tls13support); + result = set_ssl_version_min_max(data, config, &prioritylist, tls13support); if(result) return result; #ifdef USE_GNUTLS_SRP /* Only add SRP to the cipher list if SRP is requested. Otherwise * GnuTLS will disable TLS 1.3 support. */ - if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP) { + if(config->authtype == CURL_TLSAUTH_SRP) { size_t len = strlen(prioritylist); char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); @@ -623,7 +589,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; strcpy(prioritysrp, prioritylist); strcpy(prioritysrp + len, ":" GNUTLS_SRP); - rc = gnutls_priority_set_direct(session, prioritysrp, &err); + rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); free(prioritysrp); if((rc == GNUTLS_E_INVALID_REQUEST) && err) { @@ -633,7 +599,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) else { #endif infof(data, "GnuTLS ciphers: %s", prioritylist); - rc = gnutls_priority_set_direct(session, prioritylist, &err); + rc = gnutls_priority_set_direct(gtls->session, prioritylist, &err); #ifdef USE_GNUTLS_SRP } #endif @@ -644,35 +610,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) return CURLE_SSL_CONNECT_ERROR; } - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - gnutls_datum_t protocols[2]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur].data = (unsigned char *)ALPN_H2; - protocols[cur].size = ALPN_H2_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; - protocols[cur].size = ALPN_HTTP_1_1_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - if(gnutls_alpn_set_protocols(session, protocols, cur, 0)) { - failf(data, "failed setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - if(ssl_config->primary.clientcert) { + if(config->clientcert) { if(ssl_config->key_passwd) { const unsigned int supported_key_encryption_algorithms = GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | @@ -680,10 +618,9 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 | GNUTLS_PKCS_USE_PBES2_AES_256; rc = gnutls_certificate_set_x509_key_file2( - backend->cred, - ssl_config->primary.clientcert, - ssl_config->key ? - ssl_config->key : ssl_config->primary.clientcert, + gtls->cred, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, do_file_type(ssl_config->cert_type), ssl_config->key_passwd, supported_key_encryption_algorithms); @@ -696,10 +633,9 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } else { if(gnutls_certificate_set_x509_key_file( - backend->cred, - ssl_config->primary.clientcert, - ssl_config->key ? - ssl_config->key : ssl_config->primary.clientcert, + gtls->cred, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, do_file_type(ssl_config->cert_type) ) != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 key or certificate file"); @@ -710,9 +646,9 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef USE_GNUTLS_SRP /* put the credentials to the current session */ - if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP) { - rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, - backend->srp_client_cred); + if(config->authtype == CURL_TLSAUTH_SRP) { + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP, + gtls->srp_client_cred); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; @@ -721,44 +657,88 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) else #endif { - rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, - backend->cred); + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->cred); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } } - /* push/pull through filter chain */ - transport_ptr = cf; - gnutls_transport_push = gtls_push; - gnutls_transport_pull = gtls_pull; + if(config->verifystatus) { + rc = gnutls_ocsp_status_request_enable_client(gtls->session, + NULL, 0, NULL); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + } + + return CURLE_OK; +} + +static CURLcode +gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_backend_data *backend = connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + long * const pverifyresult = &ssl_config->certverifyresult; + CURLcode result; + + DEBUGASSERT(backend); - /* set the connection handle */ - gnutls_transport_set_ptr(session, transport_ptr); + if(connssl->state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; - /* register callback functions to send and receive data. */ - gnutls_transport_set_push_function(session, gnutls_transport_push); - gnutls_transport_set_pull_function(session, gnutls_transport_pull); + result = gtls_client_init(data, conn_config, ssl_config, + connssl->hostname, + &backend->gtls, pverifyresult); + if(result) + return result; - if(conn_config->verifystatus) { - rc = gnutls_ocsp_status_request_enable_client(session, NULL, 0, NULL); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc); + if(cf->conn->bits.tls_enable_alpn) { + int cur = 0; + gnutls_datum_t protocols[2]; + +#ifdef USE_HTTP2 + if(data->state.httpwant >= CURL_HTTP_VERSION_2 +#ifndef CURL_DISABLE_PROXY + && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) +#endif + ) { + protocols[cur].data = (unsigned char *)ALPN_H2; + protocols[cur].size = ALPN_H2_LENGTH; + cur++; + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); + } +#endif + + protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; + protocols[cur].size = ALPN_HTTP_1_1_LENGTH; + cur++; + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + + if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) { + failf(data, "failed setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } } /* This might be a reconnect, so we check for a session ID in the cache to speed up things */ - if(ssl_config->primary.sessionid) { + if(conn_config->sessionid) { void *ssl_sessionid; size_t ssl_idsize; Curl_ssl_sessionid_lock(data); if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) { /* we got a session id, use it! */ - gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); + gnutls_session_set_data(backend->gtls.session, + ssl_sessionid, ssl_idsize); /* Informational message */ infof(data, "SSL re-using session ID"); @@ -766,6 +746,11 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) Curl_ssl_sessionid_unlock(data); } + /* register callback functions and handle to send and receive data. */ + gnutls_transport_set_ptr(backend->gtls.session, cf); + gnutls_transport_set_push_function(backend->gtls.session, gtls_push); + gnutls_transport_set_pull_function(backend->gtls.session, gtls_pull); + return CURLE_OK; } @@ -828,13 +813,14 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, } CURLcode -Curl_gtls_verifyserver(struct Curl_cfilter *cf, - struct Curl_easy *data, - gnutls_session_t session) +Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + const char *hostname, + const char *dispname, + const char *pinned_key) { - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); unsigned int cert_list_size; const gnutls_datum_t *chainp; unsigned int verify_status = 0; @@ -846,14 +832,12 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, time_t certclock; const char *ptr; int rc; - gnutls_datum_t proto; CURLcode result = CURLE_OK; #ifndef CURL_DISABLE_VERBOSE_STRINGS unsigned int algo; unsigned int bits; gnutls_protocol_t version = gnutls_protocol_get_version(session); #endif - const char *hostname = connssl->hostname; long * const certverifyresult = &ssl_config->certverifyresult; /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ @@ -872,13 +856,13 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, chainp = gnutls_certificate_get_peers(session, &cert_list_size); if(!chainp) { - if(conn_config->verifypeer || - conn_config->verifyhost || - conn_config->issuercert) { + if(config->verifypeer || + config->verifyhost || + config->issuercert) { #ifdef USE_GNUTLS_SRP if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP && ssl_config->primary.username - && !conn_config->verifypeer + && !config->verifypeer && gnutls_cipher_get(session)) { /* no peer cert, but auth is ok if we have SRP user and cipher and no peer verify */ @@ -912,7 +896,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, } } - if(conn_config->verifypeer) { + if(config->verifypeer) { /* This function will try to verify the peer's certificate and return its status (trusted, invalid etc.). The value of status should be one or more of the gnutls_certificate_status_t enumerated elements bitwise @@ -931,9 +915,9 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, /* verify_status is a bitmask of gnutls_certificate_status bits */ if(verify_status & GNUTLS_CERT_INVALID) { - if(conn_config->verifypeer) { + if(config->verifypeer) { failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", conn_config->CAfile ? conn_config->CAfile: + "CRLfile: %s", config->CAfile ? config->CAfile: "none", ssl_config->primary.CRLfile ? ssl_config->primary.CRLfile : "none"); @@ -948,7 +932,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, else infof(data, " server certificate verification SKIPPED"); - if(conn_config->verifystatus) { + if(config->verifystatus) { if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { gnutls_datum_t status_request; gnutls_ocsp_resp_t ocsp_resp; @@ -1059,21 +1043,21 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, gnutls_x509_crt_t format */ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); - if(conn_config->issuercert) { + if(config->issuercert) { gnutls_x509_crt_init(&x509_issuer); - issuerp = load_file(conn_config->issuercert); + issuerp = load_file(config->issuercert); gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); gnutls_x509_crt_deinit(x509_issuer); unload_file(issuerp); if(rc <= 0) { failf(data, "server certificate issuer check failed (IssuerCert: %s)", - conn_config->issuercert?conn_config->issuercert:"none"); + config->issuercert?config->issuercert:"none"); gnutls_x509_crt_deinit(x509_cert); return CURLE_SSL_ISSUER_ERROR; } infof(data, " server certificate issuer check OK (Issuer Cert: %s)", - conn_config->issuercert?conn_config->issuercert:"none"); + config->issuercert?config->issuercert:"none"); } size = sizeof(certname); @@ -1136,15 +1120,15 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, } #endif if(!rc) { - if(conn_config->verifyhost) { + if(config->verifyhost) { failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certname, connssl->dispname); + "target host name '%s'", certname, dispname); gnutls_x509_crt_deinit(x509_cert); return CURLE_PEER_FAILED_VERIFICATION; } else infof(data, " common name: %s (does not match '%s')", - certname, connssl->dispname); + certname, dispname); } else infof(data, " common name: %s (matched)", certname); @@ -1153,7 +1137,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, certclock = gnutls_x509_crt_get_expiration_time(x509_cert); if(certclock == (time_t)-1) { - if(conn_config->verifypeer) { + if(config->verifypeer) { failf(data, "server cert expiration date verify failed"); *certverifyresult = GNUTLS_CERT_EXPIRED; gnutls_x509_crt_deinit(x509_cert); @@ -1164,7 +1148,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, } else { if(certclock < time(NULL)) { - if(conn_config->verifypeer) { + if(config->verifypeer) { failf(data, "server certificate expiration date has passed."); *certverifyresult = GNUTLS_CERT_EXPIRED; gnutls_x509_crt_deinit(x509_cert); @@ -1180,7 +1164,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, certclock = gnutls_x509_crt_get_activation_time(x509_cert); if(certclock == (time_t)-1) { - if(conn_config->verifypeer) { + if(config->verifypeer) { failf(data, "server cert activation date verify failed"); *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; gnutls_x509_crt_deinit(x509_cert); @@ -1191,7 +1175,7 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, } else { if(certclock > time(NULL)) { - if(conn_config->verifypeer) { + if(config->verifypeer) { failf(data, "server certificate not activated yet."); *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; gnutls_x509_crt_deinit(x509_cert); @@ -1204,11 +1188,8 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, infof(data, " server certificate activation date OK"); } - ptr = Curl_ssl_cf_is_proxy(cf)? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: - data->set.str[STRING_SSL_PINNEDPUBLICKEY]; - if(ptr) { - result = pkp_pin_peer_pubkey(data, x509_cert, ptr); + if(pinned_key) { + result = pkp_pin_peer_pubkey(data, x509_cert, pinned_key); if(result != CURLE_OK) { failf(data, "SSL: public key does not match pinned public key"); gnutls_x509_crt_deinit(x509_cert); @@ -1264,7 +1245,31 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, gnutls_x509_crt_deinit(x509_cert); + return result; +} + +static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const char *pinned_key = Curl_ssl_cf_is_proxy(cf)? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]: + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; + CURLcode result; + + result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, + connssl->hostname, connssl->dispname, + pinned_key); + if(result) + goto out; + if(cf->conn->bits.tls_enable_alpn) { + gnutls_datum_t proto; + int rc; + rc = gnutls_alpn_get_selected_protocol(session, &proto); if(rc == 0) { infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size, @@ -1290,8 +1295,6 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); } - connssl->state = ssl_connection_complete; - if(ssl_config->primary.sessionid) { /* we always unconditionally get the session id here, as even if we already got it from the cache and asked to use it in the connection, it @@ -1334,10 +1337,10 @@ Curl_gtls_verifyserver(struct Curl_cfilter *cf, result = CURLE_OUT_OF_MEMORY; } +out: return result; } - /* * This function is called after the TCP connect has completed. Setup the TLS * layer and do all necessary magic. @@ -1378,12 +1381,13 @@ gtls_connect_common(struct Curl_cfilter *cf, struct ssl_backend_data *backend = connssl->backend; gnutls_session_t session; DEBUGASSERT(backend); - session = backend->session; - rc = Curl_gtls_verifyserver(cf, data, session); + session = backend->gtls.session; + rc = gtls_verifyserver(cf, data, session); if(rc) { result = rc; goto out; } + connssl->state = ssl_connection_complete; } out: @@ -1421,8 +1425,8 @@ static bool gtls_data_pending(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(ctx && ctx->backend); - if(ctx->backend->session && - 0 != gnutls_record_check_pending(ctx->backend->session)) + if(ctx->backend->gtls.session && + 0 != gnutls_record_check_pending(ctx->backend->gtls.session)) return TRUE; return FALSE; } @@ -1439,7 +1443,7 @@ static ssize_t gtls_send(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(backend); - rc = gnutls_record_send(backend->session, mem, len); + rc = gnutls_record_send(backend->gtls.session, mem, len); if(rc < 0) { *curlcode = (rc == GNUTLS_E_AGAIN) @@ -1461,23 +1465,23 @@ static void gtls_close(struct Curl_cfilter *cf, (void) data; DEBUGASSERT(backend); - if(backend->session) { + if(backend->gtls.session) { char buf[32]; /* Maybe the server has already sent a close notify alert. Read it to avoid an RST on the TCP connection. */ - (void)gnutls_record_recv(backend->session, buf, sizeof(buf)); - gnutls_bye(backend->session, GNUTLS_SHUT_WR); - gnutls_deinit(backend->session); - backend->session = NULL; + (void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); + gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); + gnutls_deinit(backend->gtls.session); + backend->gtls.session = NULL; } - if(backend->cred) { - gnutls_certificate_free_credentials(backend->cred); - backend->cred = NULL; + if(backend->gtls.cred) { + gnutls_certificate_free_credentials(backend->gtls.cred); + backend->gtls.cred = NULL; } #ifdef USE_GNUTLS_SRP - if(backend->srp_client_cred) { - gnutls_srp_free_client_credentials(backend->srp_client_cred); - backend->srp_client_cred = NULL; + if(backend->gtls.srp_client_cred) { + gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); + backend->gtls.srp_client_cred = NULL; } #endif } @@ -1503,10 +1507,10 @@ static int gtls_shutdown(struct Curl_cfilter *cf, we do not send one. Let's hope other servers do the same... */ if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - gnutls_bye(backend->session, GNUTLS_SHUT_WR); + gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR); #endif - if(backend->session) { + if(backend->gtls.session) { ssize_t result; bool done = FALSE; char buf[120]; @@ -1517,7 +1521,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf, if(what > 0) { /* Something to read, let's do it and hope that it is the close notify alert from the server */ - result = gnutls_record_recv(backend->session, + result = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); switch(result) { case 0: @@ -1547,18 +1551,18 @@ static int gtls_shutdown(struct Curl_cfilter *cf, done = TRUE; } } - gnutls_deinit(backend->session); + gnutls_deinit(backend->gtls.session); } - gnutls_certificate_free_credentials(backend->cred); + gnutls_certificate_free_credentials(backend->gtls.cred); #ifdef USE_GNUTLS_SRP if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP && ssl_config->primary.username != NULL) - gnutls_srp_free_client_credentials(backend->srp_client_cred); + gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); #endif - backend->cred = NULL; - backend->session = NULL; + backend->gtls.cred = NULL; + backend->gtls.session = NULL; return retval; } @@ -1576,7 +1580,7 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(backend); - ret = gnutls_record_recv(backend->session, buf, buffersize); + ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { *curlcode = CURLE_AGAIN; ret = -1; @@ -1652,7 +1656,7 @@ static void *gtls_get_internals(struct ssl_connect_data *connssl, struct ssl_backend_data *backend = connssl->backend; (void)info; DEBUGASSERT(backend); - return backend->session; + return backend->gtls.session; } const struct Curl_ssl Curl_ssl_gnutls = { diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index af00b45436..49c1c47637 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -25,16 +25,50 @@ ***************************************************************************/ #include "curl_setup.h" +#include #ifdef USE_GNUTLS -#include "urldata.h" -#include "cfilters.h" #include + +#ifdef HAVE_GNUTLS_SRP +/* the function exists */ +#ifdef USE_TLS_SRP +/* the functionality is not disabled */ +#define USE_GNUTLS_SRP +#endif +#endif + +struct Curl_easy; +struct Curl_cfilter; +struct ssl_primary_config; +struct ssl_config_data; + +struct gtls_instance { + gnutls_session_t session; + gnutls_certificate_credentials_t cred; +#ifdef USE_GNUTLS_SRP + gnutls_srp_client_credentials_t srp_client_cred; +#endif +}; + CURLcode -Curl_gtls_verifyserver(struct Curl_cfilter *cf, - struct Curl_easy *data, - gnutls_session_t session); +gtls_client_init(struct Curl_easy *data, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + const char *hostname, + struct gtls_instance *gtls, + long *pverifyresult); + +CURLcode +Curl_gtls_verifyserver(struct Curl_easy *data, + gnutls_session_t session, + struct ssl_primary_config *config, + struct ssl_config_data *ssl_config, + const char *hostname, + const char *dispname, + const char *pinned_key); + extern const struct Curl_ssl Curl_ssl_gnutls; #endif /* USE_GNUTLS */