| /* | |
| * GPAC Multimedia Framework | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2005-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / downloader sub-project | |
| * | |
| * GPAC 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; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "downloader.h" | |
| #ifndef GPAC_DISABLE_NETWORK | |
| #ifdef GPAC_HAS_HTTP2 | |
| #include <nghttp2/nghttp2.h> | |
| #endif | |
| #ifdef GPAC_HAS_NGTCP2 | |
| #include <ngtcp2/ngtcp2_crypto.h> | |
| #include <ngtcp2/ngtcp2_crypto_quictls.h> | |
| #endif | |
| #ifdef GPAC_HAS_SSL | |
| static void init_prng (void) | |
| { | |
| char namebuf[256]; | |
| const char *random_file; | |
| if (RAND_status ()) return; | |
| namebuf[0] = '\0'; | |
| random_file = RAND_file_name (namebuf, sizeof (namebuf)); | |
| if (random_file && *random_file) | |
| RAND_load_file(random_file, 16384); | |
| if (RAND_status ()) return; | |
| } | |
| /* NPN TLS extension client callback. We check that server advertised | |
| the HTTP/2 protocol the nghttp2 library supports. If not, exit | |
| the program. */ | |
| static int ssl_select_next_proto_cb(SSL *ssl , unsigned char **out, | |
| unsigned char *outlen, const unsigned char *in, | |
| unsigned int inlen, void *arg) | |
| { | |
| #ifdef GPAC_HAS_HTTP2 | |
| if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTP/2] Server did not advertise " NGHTTP2_PROTO_VERSION_ID)); | |
| } | |
| #endif | |
| return SSL_TLSEXT_ERR_OK; | |
| } | |
| static Bool _ssl_is_initialized = GF_FALSE; | |
| /*! | |
| * initialize the SSL library once for all download managers | |
| \return GF_FALSE if everyhing is OK, GF_TRUE otherwise | |
| */ | |
| Bool gf_ssl_init_lib() | |
| { | |
| if (_ssl_is_initialized) | |
| return GF_FALSE; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPS] Initializing SSL library...\n")); | |
| init_prng(); | |
| if (RAND_status() != 1) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPS] Error while initializing Random Number generator, failed to init SSL !\n")); | |
| return GF_TRUE; | |
| } | |
| /* per https://www.openssl.org/docs/man1.1.0/ssl/OPENSSL_init_ssl.html | |
| ** As of version 1.1.0 OpenSSL will automatically allocate all resources that it needs so no explicit initialisation is required. | |
| ** Similarly it will also automatically deinitialise as required. | |
| */ | |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L | |
| SSL_library_init(); | |
| SSL_load_error_strings(); | |
| SSLeay_add_all_algorithms(); | |
| SSLeay_add_ssl_algorithms(); | |
| #endif | |
| _ssl_is_initialized = GF_TRUE; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[HTTPS] Initialization of SSL library complete.\n")); | |
| return GF_FALSE; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| static const char *tls_rt_get_name(int type) | |
| { | |
| switch(type) { | |
| #ifdef SSL3_RT_HEADER | |
| case SSL3_RT_HEADER: return "TLS header"; | |
| #endif | |
| case SSL3_RT_CHANGE_CIPHER_SPEC: return "TLS change cipher"; | |
| case SSL3_RT_ALERT: return "TLS alert"; | |
| case SSL3_RT_HANDSHAKE: return "TLS handshake"; | |
| case SSL3_RT_APPLICATION_DATA: return "TLS app data"; | |
| default: return "TLS Unknown"; | |
| } | |
| } | |
| static const char *ssl_msg_get_name(int version, int msg) | |
| { | |
| #ifdef SSL_CTRL_SET_MSG_CALLBACK | |
| #ifdef SSL2_VERSION_MAJOR | |
| if (version == SSL2_VERSION_MAJOR) { | |
| switch(msg) { | |
| case SSL2_MT_ERROR: return "Error"; | |
| case SSL2_MT_CLIENT_HELLO: return "Client hello"; | |
| case SSL2_MT_CLIENT_MASTER_KEY: return "Client key"; | |
| case SSL2_MT_CLIENT_FINISHED: return "Client finished"; | |
| case SSL2_MT_SERVER_HELLO: return "Server hello"; | |
| case SSL2_MT_SERVER_VERIFY: return "Server verify"; | |
| case SSL2_MT_SERVER_FINISHED: return "Server finished"; | |
| case SSL2_MT_REQUEST_CERTIFICATE: return "Request CERT"; | |
| case SSL2_MT_CLIENT_CERTIFICATE: return "Client CERT"; | |
| } | |
| } else | |
| #endif | |
| if (version == SSL3_VERSION_MAJOR) { | |
| switch(msg) { | |
| case SSL3_MT_HELLO_REQUEST: return "Hello request"; | |
| case SSL3_MT_CLIENT_HELLO: return "Client hello"; | |
| case SSL3_MT_SERVER_HELLO: return "Server hello"; | |
| #ifdef SSL3_MT_NEWSESSION_TICKET | |
| case SSL3_MT_NEWSESSION_TICKET: return "Newsession Ticket"; | |
| #endif | |
| case SSL3_MT_CERTIFICATE: return "Certificate"; | |
| case SSL3_MT_SERVER_KEY_EXCHANGE: return "Server key exchange"; | |
| case SSL3_MT_CLIENT_KEY_EXCHANGE: return "Client key exchange"; | |
| case SSL3_MT_CERTIFICATE_REQUEST: return "Request CERT"; | |
| case SSL3_MT_SERVER_DONE: return "Server finished"; | |
| case SSL3_MT_CERTIFICATE_VERIFY: return "CERT verify"; | |
| case SSL3_MT_FINISHED: return "Finished"; | |
| #ifdef SSL3_MT_CERTIFICATE_STATUS | |
| case SSL3_MT_CERTIFICATE_STATUS: return "Certificate Status"; | |
| #endif | |
| #ifdef SSL3_MT_ENCRYPTED_EXTENSIONS | |
| case SSL3_MT_ENCRYPTED_EXTENSIONS: return "Encrypted Extensions"; | |
| #endif | |
| #ifdef SSL3_MT_SUPPLEMENTAL_DATA | |
| case SSL3_MT_SUPPLEMENTAL_DATA: return "Supplemental data"; | |
| #endif | |
| #ifdef SSL3_MT_END_OF_EARLY_DATA | |
| case SSL3_MT_END_OF_EARLY_DATA: return "End of early data"; | |
| #endif | |
| #ifdef SSL3_MT_KEY_UPDATE | |
| case SSL3_MT_KEY_UPDATE: return "Key update"; | |
| #endif | |
| #ifdef SSL3_MT_NEXT_PROTO | |
| case SSL3_MT_NEXT_PROTO: return "Next protocol"; | |
| #endif | |
| #ifdef SSL3_MT_MESSAGE_HASH | |
| case SSL3_MT_MESSAGE_HASH: return "Message hash"; | |
| #endif | |
| } | |
| } | |
| #endif | |
| return "Unknown"; | |
| } | |
| static void ssl_on_log(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *udat) | |
| { | |
| const char *msg_name, *tls_rt_name; | |
| int msg_type; | |
| char szUnknown[32]; | |
| const char *szVersion = NULL; | |
| if (!version) return; | |
| #ifdef SSL3_RT_INNER_CONTENT_TYPE | |
| if (content_type == SSL3_RT_INNER_CONTENT_TYPE) return; | |
| #endif | |
| switch (version) { | |
| #ifdef SSL2_VERSION | |
| case SSL2_VERSION: szVersion = "SSLv2"; break; | |
| #endif | |
| #ifdef SSL3_VERSION | |
| case SSL3_VERSION: szVersion = "SSLv3"; break; | |
| #endif | |
| case TLS1_VERSION: szVersion = "TLSv1.0"; break; | |
| #ifdef TLS1_1_VERSION | |
| case TLS1_1_VERSION: szVersion = "TLSv1.1"; break; | |
| #endif | |
| #ifdef TLS1_2_VERSION | |
| case TLS1_2_VERSION: szVersion = "TLSv1.2"; break; | |
| #endif | |
| #ifdef TLS1_3_VERSION | |
| case TLS1_3_VERSION: szVersion = "TLSv1.3"; break; | |
| #endif | |
| case 0: break; | |
| default: | |
| snprintf(szUnknown, 31, "(%x)", version); | |
| szVersion = szUnknown; | |
| break; | |
| } | |
| version >>= 8; | |
| if ((version == SSL3_VERSION_MAJOR) && content_type) | |
| tls_rt_name = tls_rt_get_name(content_type); | |
| else | |
| tls_rt_name = ""; | |
| if (content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { | |
| msg_type = *(char *)buf; | |
| msg_name = "Change cipher spec"; | |
| } else if (content_type == SSL3_RT_ALERT) { | |
| msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1]; | |
| msg_name = SSL_alert_desc_string_long(msg_type); | |
| } else { | |
| msg_type = *(char *)buf; | |
| msg_name = ssl_msg_get_name(version, msg_type); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_NETWORK, ("[%s] %s %s (%d)\n", szVersion, tls_rt_name, msg_name, msg_type)); | |
| } | |
| #endif //GPAC_DISABLE_LOG | |
| static char ALPN_PROTOS[20]; | |
| void *gf_dm_ssl_init(GF_DownloadManager *dm, Bool no_quic) | |
| { | |
| #if OPENSSL_VERSION_NUMBER > 0x00909000 | |
| const | |
| #endif | |
| SSL_METHOD *meth; | |
| if (!dm) return NULL; | |
| gf_mx_p(dm->cache_mx); | |
| /* The SSL has already been initialized. */ | |
| if (dm->ssl_ctx) { | |
| gf_mx_v(dm->cache_mx); | |
| return dm->ssl_ctx; | |
| } | |
| /* Init the PRNG. If that fails, bail out. */ | |
| if (gf_ssl_init_lib()) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[HTTPS] Failed to properly initialize SSL library\n")); | |
| goto error; | |
| } | |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L | |
| meth = SSLv23_client_method(); | |
| #else | |
| meth = TLS_client_method(); | |
| #endif | |
| dm->ssl_ctx = SSL_CTX_new(meth); | |
| if (!dm->ssl_ctx) goto error; | |
| SSL_CTX_set_options(dm->ssl_ctx, SSL_OP_ALL); | |
| SSL_CTX_set_default_verify_paths(dm->ssl_ctx); | |
| SSL_CTX_load_verify_locations (dm->ssl_ctx, NULL, NULL); | |
| /* SSL_VERIFY_NONE instructs OpenSSL not to abort SSL_connect if the | |
| certificate is invalid. We verify the certificate separately in | |
| ssl_check_certificate, which provides much better diagnostics | |
| than examining the error stack after a failed SSL_connect. */ | |
| SSL_CTX_set_verify(dm->ssl_ctx, SSL_VERIFY_NONE, NULL); | |
| if (!gf_opts_get_bool("core", "broken-cert")) { | |
| const char* ca_bundle = gf_opts_get_key("core", "ca-bundle"); | |
| if (ca_bundle && gf_file_exists(ca_bundle)) { | |
| SSL_CTX_load_verify_locations(dm->ssl_ctx, ca_bundle, NULL); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[SSL] Using CA bundle at %s\n", ca_bundle)); | |
| } | |
| else { | |
| const char* ossl_bundle = X509_get_default_cert_file_env(); | |
| ossl_bundle = getenv(ossl_bundle); | |
| if (!ossl_bundle) | |
| ossl_bundle = X509_get_default_cert_file(); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[SSL] OpenSSL CA bundle location: %s\n", ossl_bundle)); | |
| if (!ossl_bundle || !gf_file_exists(ossl_bundle)) { | |
| const char* ca_bundle_default = gf_opts_get_key("core", "ca-bundle-default"); | |
| if (ca_bundle_default) { | |
| SSL_CTX_load_verify_locations(dm->ssl_ctx, ca_bundle_default, NULL); | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[SSL] Using default CA bundle at %s\n", ca_bundle_default)); | |
| } | |
| } | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { | |
| SSL_CTX_set_msg_callback(dm->ssl_ctx, ssl_on_log); | |
| } | |
| #endif | |
| ALPN_PROTOS[0] = 0; | |
| #ifdef GPAC_HAS_HTTP2 | |
| if (!dm->disable_http2) { | |
| gf_strcat(ALPN_PROTOS, "\x02h2"); | |
| } | |
| #endif | |
| //configure H3 | |
| if (dm->h3_mode && !no_quic) { | |
| #ifdef GPAC_HAS_NGTCP2 | |
| if (ngtcp2_crypto_quictls_configure_client_context(dm->ssl_ctx) != 0) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unable to initialize SSL context for QUIC, disabling HTTP3\n")); | |
| dm->h3_mode = H3_MODE_NO; | |
| } else { | |
| gf_strcat(ALPN_PROTOS, "\x02h3"); | |
| } | |
| #endif | |
| } | |
| if (ALPN_PROTOS[0]) { | |
| SSL_CTX_set_next_proto_select_cb(dm->ssl_ctx, ssl_select_next_proto_cb, NULL); | |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| SSL_CTX_set_alpn_protos(dm->ssl_ctx, ALPN_PROTOS, (u32) strlen(ALPN_PROTOS)); | |
| #endif | |
| } | |
| /* Since fd_write unconditionally assumes partial writes (and handles them correctly), | |
| allow them in OpenSSL. */ | |
| SSL_CTX_set_mode(dm->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); | |
| gf_mx_v(dm->cache_mx); | |
| return dm->ssl_ctx; | |
| error: | |
| if (dm->ssl_ctx) SSL_CTX_free(dm->ssl_ctx); | |
| dm->ssl_ctx = NULL; | |
| gf_mx_v(dm->cache_mx); | |
| return NULL; | |
| } | |
| #ifdef GPAC_HAS_HTTP2 | |
| void h2_initialize_session(GF_DownloadSession *sess); | |
| #endif | |
| #if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_NGTCP2) | |
| static unsigned char next_proto_list[256]; | |
| static size_t next_proto_list_len; | |
| #ifndef OPENSSL_NO_NEXTPROTONEG | |
| static int next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg) | |
| { | |
| if (data && len) { | |
| *data = next_proto_list; | |
| *len = (unsigned int)next_proto_list_len; | |
| } | |
| return SSL_TLSEXT_ERR_OK; | |
| } | |
| #endif //OPENSSL_NO_NEXTPROTONEG | |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) | |
| { | |
| #ifdef GPAC_HAS_NGTCP2 | |
| ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); | |
| if (cref) { | |
| const uint8_t *alpn; | |
| size_t alpnlen; | |
| // This should be the negotiated version, but we have not set the | |
| // negotiated version when this callback is called. | |
| uint32_t version = ngtcp2_conn_get_client_chosen_version( cref->get_conn(cref) ); | |
| switch (version) { | |
| case NGTCP2_PROTO_VER_V1: | |
| case NGTCP2_PROTO_VER_V2: | |
| alpn = "\x2h3"; | |
| alpnlen = 3; | |
| break; | |
| default: | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL]Â Unexpected quic protocol version %X\n", version )); | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; | |
| } | |
| const unsigned char *p, *end; | |
| for (p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { | |
| if (!strncmp(alpn, p, alpnlen)) { | |
| *out = p + 1; | |
| *outlen = *p; | |
| return SSL_TLSEXT_ERR_OK; | |
| } | |
| } | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL]Â Client did not present ALPN %s\n", &alpn[1] )); | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; | |
| } | |
| #endif | |
| #ifdef GPAC_HAS_HTTP2 | |
| int rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); | |
| if (rv == 1) | |
| return SSL_TLSEXT_ERR_OK; | |
| #endif | |
| return SSL_TLSEXT_ERR_NOACK; | |
| } | |
| #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ | |
| #endif //GPAC_HAS_HTTP2 | |
| #if !defined(LIBRESSL_VERSION_NUMBER) && defined(GPAC_HAS_NGTCP2) | |
| int ngq_gen_ticket_cb(SSL *ssl, void *arg) | |
| { | |
| ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); | |
| if (!cref) return 0; | |
| int ver = htonl(ngtcp2_conn_get_negotiated_version(cref->get_conn(cref) ) ); | |
| if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver, sizeof(ver))) { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| SSL_TICKET_RETURN ngq_decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session, const unsigned char *keyname, size_t keynamelen, SSL_TICKET_STATUS status, void *arg) | |
| { | |
| switch (status) { | |
| case SSL_TICKET_EMPTY: | |
| case SSL_TICKET_NO_DECRYPT: | |
| return SSL_TICKET_RETURN_IGNORE_RENEW; | |
| } | |
| uint8_t *pver; | |
| uint32_t ver; | |
| size_t verlen; | |
| if (!SSL_SESSION_get0_ticket_appdata(session, (void **) &pver, &verlen) || (verlen != sizeof(ver))) { | |
| switch (status) { | |
| case SSL_TICKET_SUCCESS: | |
| return SSL_TICKET_RETURN_IGNORE; | |
| case SSL_TICKET_SUCCESS_RENEW: | |
| default: | |
| return SSL_TICKET_RETURN_IGNORE_RENEW; | |
| } | |
| } | |
| memcpy(&ver, pver, sizeof(ver)); | |
| ngtcp2_crypto_conn_ref *cref = (ngtcp2_crypto_conn_ref *) SSL_get_app_data(ssl); | |
| if (!cref) return SSL_TICKET_RETURN_IGNORE_RENEW; | |
| if (ngtcp2_conn_get_client_chosen_version(cref->get_conn(cref) ) != ntohl(ver)) { | |
| switch (status) { | |
| case SSL_TICKET_SUCCESS: | |
| return SSL_TICKET_RETURN_IGNORE; | |
| case SSL_TICKET_SUCCESS_RENEW: | |
| default: | |
| return SSL_TICKET_RETURN_IGNORE_RENEW; | |
| } | |
| } | |
| switch (status) { | |
| case SSL_TICKET_SUCCESS: | |
| return SSL_TICKET_RETURN_USE; | |
| case SSL_TICKET_SUCCESS_RENEW: | |
| default: | |
| return SSL_TICKET_RETURN_USE_RENEW; | |
| } | |
| } | |
| #endif // !definedLIBRESSL_VERSION_NUMBER) && defined(GPAC_HAS_NGTCP2) | |
| void *gf_ssl_server_context_new(const char *cert, const char *key, Bool for_quic) | |
| { | |
| const SSL_METHOD *method; | |
| SSL_CTX *ctx; | |
| method = SSLv23_server_method(); | |
| #ifdef GPAC_HAS_NGTCP2 | |
| if (for_quic) { | |
| method = TLS_server_method(); | |
| } | |
| #else | |
| if (for_quic) return NULL; | |
| #endif | |
| ctx = SSL_CTX_new(method); | |
| if (!ctx) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("Unable to create SSL context\n")); | |
| ERR_print_errors_fp(stderr); | |
| return NULL; | |
| } | |
| #ifndef GPAC_DISABLE_LOG | |
| if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG) ) { | |
| SSL_CTX_set_msg_callback(ctx, ssl_on_log); | |
| } | |
| #endif | |
| //configure H2 | |
| #ifdef GPAC_HAS_HTTP2 | |
| if (!for_quic && !gf_opts_get_bool("core", "no-h2")) { | |
| next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN; | |
| memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN); | |
| next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; | |
| } | |
| #endif | |
| #ifdef GPAC_HAS_NGTCP2 | |
| if (for_quic) { | |
| ngtcp2_crypto_quictls_configure_server_context(ctx); | |
| next_proto_list[next_proto_list_len] = 2; | |
| memcpy(&next_proto_list[next_proto_list_len+1], "h3", 2); | |
| next_proto_list_len = 1 + 2; | |
| SSL_CTX_set_max_early_data(ctx, UINT32_MAX); | |
| u64 ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | | |
| SSL_OP_SINGLE_ECDH_USE | | |
| SSL_OP_CIPHER_SERVER_PREFERENCE | |
| #ifndef LIBRESSL_VERSION_NUMBER | |
| | SSL_OP_NO_ANTI_REPLAY | |
| #endif // !defined(LIBRESSL_VERSION_NUMBER) | |
| ; | |
| SSL_CTX_set_options(ctx, ssl_opts); | |
| SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); | |
| #ifndef LIBRESSL_VERSION_NUMBER | |
| SSL_CTX_set_session_ticket_cb(ctx, ngq_gen_ticket_cb, ngq_decrypt_ticket_cb, NULL); | |
| #endif // !defined(LIBRESSL_VERSION_NUMBER) | |
| SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"); | |
| SSL_CTX_set1_groups_list(ctx, "X25519:P-256:P-384:P-521"); | |
| } | |
| #endif | |
| /* if (!for_quic) | |
| SSL_CTX_set_quic_method(ctx, NULL); | |
| */ | |
| #if defined(GPAC_HAS_HTTP2) || defined(GPAC_HAS_NGTCP2) | |
| if (next_proto_list_len) { | |
| SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL); | |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); | |
| #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| } | |
| #endif | |
| SSL_CTX_set_default_verify_paths(ctx); | |
| SSL_CTX_set_ecdh_auto(ctx, 1); | |
| // if (SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM) <= 0) { | |
| if (SSL_CTX_use_certificate_chain_file(ctx, cert) != 1) { | |
| ERR_print_errors_fp(stderr); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0 ) { | |
| ERR_print_errors_fp(stderr); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| if (SSL_CTX_check_private_key(ctx) != 1) { | |
| ERR_print_errors_fp(stderr); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| return ctx; | |
| } | |
| void gf_ssl_server_context_del(void *ssl_ctx) | |
| { | |
| SSL_CTX_free(ssl_ctx); | |
| } | |
| void *gf_ssl_new(void *ssl_ctx, GF_Socket *client_sock, GF_Err *e) | |
| { | |
| SSL *ssl; | |
| ssl = SSL_new(ssl_ctx); | |
| if (!ssl) { | |
| *e = GF_IO_ERR; | |
| return NULL; | |
| } | |
| SSL_set_fd(ssl, gf_sk_get_handle(client_sock) ); | |
| if (SSL_accept(ssl) <= 0) { | |
| if (gf_log_tool_level_on(GF_LOG_NETWORK, GF_LOG_DEBUG)) { | |
| //this one crashes /exits on windows, only use if debug level | |
| ERR_print_errors_fp(stderr); | |
| } else { | |
| ERR_print_errors_fp(stderr); | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_NETWORK, ("[SSL] Accept failure\n")); | |
| } | |
| SSL_shutdown(ssl); | |
| SSL_free(ssl); | |
| *e = GF_AUTHENTICATION_FAILURE; | |
| return NULL; | |
| } | |
| *e = GF_OK; | |
| return ssl; | |
| } | |
| void gf_ssl_del(void *ssl) | |
| { | |
| if (ssl) SSL_free(ssl); | |
| } | |
| Bool gf_ssl_check_cert(SSL *ssl, const char *server_name) | |
| { | |
| long vresult = SSL_get_verify_result(ssl); | |
| Bool success = (vresult == X509_V_OK); | |
| if (!success) { | |
| int level = GF_LOG_ERROR; | |
| if (gf_opts_get_bool("core", "broken-cert")) { | |
| success = GF_TRUE; | |
| level = GF_LOG_WARNING; | |
| } | |
| GF_LOG(level, GF_LOG_HTTP, ("[SSL] Certificate verification failed for domain %s: %s. %s\n", server_name, ERR_error_string(vresult, NULL), (level == GF_LOG_ERROR ? "Use -broken-cert to bypass." : ""))); | |
| } | |
| return success; | |
| } | |
| GF_Err gf_ssl_write(GF_DownloadSession *sess, const u8 *buffer, u32 size, u32 *written) | |
| { | |
| u32 idx=0; | |
| s32 nb_tls_blocks = size/16000; | |
| *written = 0; | |
| while (nb_tls_blocks>=0) { | |
| u32 len, to_write = 16000; | |
| if (nb_tls_blocks==0) | |
| to_write = size - idx*16000; | |
| len = SSL_write(sess->ssl, buffer + idx*16000, to_write); | |
| nb_tls_blocks--; | |
| idx++; | |
| if (len != to_write) { | |
| if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) { | |
| int err = SSL_get_error(sess->ssl, len); | |
| if ((err==SSL_ERROR_WANT_READ) || (err==SSL_ERROR_WANT_WRITE)) { | |
| return GF_IP_NETWORK_EMPTY; | |
| } | |
| if (err==SSL_ERROR_SSL) { | |
| char msg[1024]; | |
| SSL_load_error_strings(); | |
| ERR_error_string_n(ERR_get_error(), msg, 1023); | |
| msg[1023]=0; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot send, error %s\n", msg)); | |
| } | |
| } | |
| return GF_IP_NETWORK_FAILURE; | |
| } | |
| *written += to_write; | |
| } | |
| return GF_OK; | |
| } | |
| GF_Err gf_ssl_read_data(GF_DownloadSession *sess, char *data, u32 data_size, u32 *out_read) | |
| { | |
| s32 size; | |
| GF_Err e; | |
| //receive on null buffer (select only, check if data available) | |
| e = gf_sk_receive(sess->sock, NULL, 0, NULL); | |
| //empty and no pending bytes in SSL, network empty | |
| if ((e==GF_IP_NETWORK_EMPTY) && | |
| #if 1 | |
| !SSL_pending(sess->ssl) | |
| #else | |
| //no support for SSL_has_pending in old libSSL and same result can be achieved with SSL_pending | |
| !SSL_has_pending(sess->ssl) | |
| #endif | |
| ) { | |
| return GF_NOT_READY; | |
| } | |
| size = SSL_read(sess->ssl, data, data_size); | |
| if (size < 0) { | |
| int err = SSL_get_error(sess->ssl, size); | |
| if (err==SSL_ERROR_SSL) { | |
| char msg[1024]; | |
| SSL_load_error_strings(); | |
| ERR_error_string_n(ERR_get_error(), msg, 1023); | |
| msg[1023]=0; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot read, error %s\n", msg)); | |
| e = GF_IO_ERR; | |
| } else { | |
| e = gf_sk_probe(sess->sock); | |
| } | |
| } else if (!size) | |
| e = GF_IP_NETWORK_EMPTY; | |
| else { | |
| e = GF_OK; | |
| *out_read = size; | |
| } | |
| return e; | |
| } | |
| void gf_dm_sess_server_setup_ssl(GF_DownloadSession *sess) | |
| { | |
| gf_assert(sess->ssl); | |
| if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) | |
| SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); | |
| #ifdef GPAC_HAS_HTTP2 | |
| const unsigned char *alpn = NULL; | |
| unsigned int alpnlen = 0; | |
| SSL_get0_next_proto_negotiated(sess->ssl, &alpn, &alpnlen); | |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| if (alpn == NULL) { | |
| SSL_get0_alpn_selected(sess->ssl, &alpn, &alpnlen); | |
| } | |
| #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| if (alpn && (alpnlen == 2) && !memcmp("h2", alpn, 2)) { | |
| h2_initialize_session(sess); | |
| } | |
| #endif //GPAC_HAS_HTTP2 | |
| } | |
| SSLConnectStatus gf_ssl_try_connect(GF_DownloadSession *sess, const char *proxy) | |
| { | |
| u64 now = gf_sys_clock_high_res(); | |
| if (sess->dm && !sess->dm->ssl_ctx) | |
| gf_dm_ssl_init(sess->dm, (sess->flags & GF_NETIO_SESSION_USE_QUIC) ? GF_FALSE : GF_TRUE); | |
| /*socket is connected, configure SSL layer*/ | |
| if (!sess->dm || !sess->dm->ssl_ctx) return SSL_CONNECT_OK; | |
| int ret; | |
| Bool success; | |
| if (!sess->ssl) { | |
| sess->ssl = SSL_new(sess->dm->ssl_ctx); | |
| SSL_set_fd(sess->ssl, gf_sk_get_handle(sess->sock)); | |
| SSL_ctrl(sess->ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, (void*) proxy); | |
| SSL_set_connect_state(sess->ssl); | |
| if (sess->flags & GF_NETIO_SESSION_NO_BLOCK) | |
| SSL_set_mode(sess->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|SSL_MODE_ENABLE_PARTIAL_WRITE); | |
| #ifdef GPAC_HAS_HTTP2 | |
| if (sess->h2_upgrade_state==3) { | |
| SSL_set_alpn_protos(sess->ssl, NULL, 0); | |
| sess->h2_upgrade_state = 0; | |
| } | |
| //h1 disabled, don't use alpn | |
| else if (sess->h2_upgrade_state==4) { | |
| SSL_set_alpn_protos(sess->ssl, NULL, 0); | |
| } | |
| #endif | |
| #ifdef GPAC_HAS_NGTCP2 | |
| //if not using quic, reset quic methods in ssl | |
| if (!(sess->flags & GF_NETIO_SESSION_USE_QUIC)) | |
| SSL_set_quic_method(sess->ssl, NULL); | |
| #endif | |
| } | |
| if (sess->flags & GF_NETIO_SESSION_USE_QUIC) | |
| return SSL_CONNECT_RETRY; | |
| sess->connect_pending = 0; | |
| ret = SSL_connect(sess->ssl); | |
| if (ret<=0) { | |
| ret = SSL_get_error(sess->ssl, ret); | |
| if (ret==SSL_ERROR_SSL) { | |
| #if (OPENSSL_VERSION_NUMBER >= 0x10002000L) && defined(GPAC_HAS_HTTP2) | |
| //error and we tried with alpn for h2, retry without (kill/reconnect) | |
| if (!sess->h2_upgrade_state && !sess->dm->disable_http2) { | |
| SSL_free(sess->ssl); | |
| sess->ssl = NULL; | |
| dm_sess_sk_del(sess); | |
| sess->status = GF_NETIO_SETUP; | |
| sess->h2_upgrade_state = 3; | |
| return SSL_CONNECT_RETRY; | |
| } | |
| #endif | |
| char msg[1024]; | |
| SSL_load_error_strings(); | |
| ERR_error_string_n(ERR_get_error(), msg, 1023); | |
| msg[1023]=0; | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %s\n", msg)); | |
| if (!sess->dm->disable_http2) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("\tYou may want to retry with HTTP/2 support disabled (-no-h2)\n")); | |
| } | |
| SET_LAST_ERR(GF_SERVICE_ERROR) | |
| } else if ((ret==SSL_ERROR_WANT_READ) || (ret==SSL_ERROR_WANT_WRITE)) { | |
| sess->status = GF_NETIO_SETUP; | |
| sess->connect_pending = 2; | |
| sess->last_error = GF_IP_NETWORK_EMPTY; | |
| return SSL_CONNECT_WAIT; | |
| } else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, ("[SSL] Cannot connect, error %d\n", ret)); | |
| SET_LAST_ERR(GF_REMOTE_SERVICE_ERROR) | |
| } | |
| } else { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[SSL] connected\n")); | |
| #ifdef GPAC_HAS_HTTP2 | |
| if (!sess->dm->disable_http2) { | |
| const u8 *alpn = NULL; | |
| u32 alpnlen = 0; | |
| SSL_get0_next_proto_negotiated(sess->ssl, &alpn, &alpnlen); | |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L | |
| if (alpn == NULL) { | |
| SSL_get0_alpn_selected(sess->ssl, &alpn, &alpnlen); | |
| } | |
| #endif | |
| if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_HTTP, ("[SSL] HTTP/2 is not negotiated\n")); | |
| //disable h2 for the session | |
| sess->h2_upgrade_state = 4; | |
| } else { | |
| h2_initialize_session(sess); | |
| } | |
| } | |
| #endif | |
| } | |
| success = gf_ssl_check_cert(sess->ssl, sess->server_name); | |
| if (!success) { | |
| gf_dm_disconnect(sess, HTTP_RESET_CONN); | |
| sess->status = GF_NETIO_STATE_ERROR; | |
| SET_LAST_ERR(GF_AUTHENTICATION_FAILURE) | |
| gf_dm_sess_notify_state(sess, sess->status, sess->last_error); | |
| } | |
| sess->ssl_setup_time = (u32) (gf_sys_clock_high_res() - now); | |
| return SSL_CONNECT_OK; | |
| } | |
| #endif // GPAC_HAS_SSL | |
| /** | |
| * \brief Saves the digest for authentication of password and username | |
| \param dm The download manager | |
| \param creds The credentials to fill | |
| \param password the password | |
| \param store_info write to cred file or not | |
| \return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. | |
| */ | |
| static GF_Err gf_user_credentials_save_digest( GF_DownloadManager * dm, GF_UserCredentials * creds, const char * password, Bool store_info) { | |
| int size; | |
| char *pass_buf = NULL; | |
| char range_buf[1024]; | |
| if (!dm || !creds || !password) | |
| return GF_BAD_PARAM; | |
| gf_dynstrcat(&pass_buf, creds->username, NULL); | |
| gf_dynstrcat(&pass_buf, password, ":"); | |
| if (!pass_buf) return GF_OUT_OF_MEM; | |
| size = gf_base64_encode(pass_buf, (u32) strlen(pass_buf), range_buf, 1024); | |
| gf_free(pass_buf); | |
| range_buf[size] = 0; | |
| gf_strcpy(creds->digest, range_buf); | |
| creds->valid = GF_TRUE; | |
| #ifndef GPAC_DISABLE_CRYPTO | |
| if (store_info) { | |
| u32 plen = (u32) strlen(password); | |
| char *key = NULL; | |
| char szPATH[GF_MAX_PATH]; | |
| const char *cred = gf_opts_get_key("core", "cred"); | |
| if (!cred) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[Core] No credential key defined, will not store password - use -cred=PATH_TO_KEY to specify it\n")); | |
| return GF_OK; | |
| } | |
| FILE *f = gf_fopen(cred, "r"); | |
| bin128 k; | |
| if (!f) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[Core] Failed to open credential key %s, will not store password\n", cred)); | |
| return GF_OK; | |
| } | |
| u32 ksize = (u32) gf_fread(k, 16, f); | |
| gf_fclose(f); | |
| if (ksize!=16) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("[Core] Invalid credential key %s: size %d but expecting 16 bytes, will not store password\n", cred, ksize)); | |
| return GF_OK; | |
| } | |
| GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); | |
| gf_crypt_init(gfc, k, NULL); | |
| gf_strcpy(szPATH, password); | |
| gf_crypt_encrypt(gfc, szPATH, plen); | |
| gf_crypt_close(gfc); | |
| szPATH[plen] = 0; | |
| gf_dynstrcat(&key, creds->username, NULL); | |
| gf_dynstrcat(&key, szPATH, ":"); | |
| gf_opts_set_key("credentials", creds->site, key); | |
| gf_free(key); | |
| gf_opts_save(); | |
| } | |
| #endif | |
| return GF_OK; | |
| } | |
| /** | |
| * Find a User's credentials for a given site | |
| */ | |
| GF_UserCredentials* gf_user_credentials_find_for_site(GF_DownloadManager *dm, const char *server_name, const char *user_name) | |
| { | |
| GF_UserCredentials * cred; | |
| u32 count, i; | |
| if (!dm || !dm->credentials || !server_name || !strlen(server_name)) | |
| return NULL; | |
| count = gf_list_count( dm->credentials); | |
| for (i = 0 ; i < count; i++) { | |
| cred = (GF_UserCredentials*)gf_list_get(dm->credentials, i ); | |
| if (!cred || strcmp(cred->site, server_name)) | |
| continue; | |
| if (!user_name || !strcmp(user_name, cred->username)) | |
| return cred; | |
| } | |
| #ifndef GPAC_DISABLE_CRYPTO | |
| char *key = (char*) gf_opts_get_key("credentials", server_name); | |
| if (key) { | |
| bin128 k; | |
| const char *credk = gf_opts_get_key("core", "cred"); | |
| if (credk) { | |
| FILE *f = gf_fopen(credk, "r"); | |
| if (f) { | |
| gf_fread(k, 16, f); | |
| gf_fclose(f); | |
| } else { | |
| credk = NULL; | |
| } | |
| } | |
| char *sep = credk ? strrchr(key, ':') : NULL; | |
| if (sep) { | |
| char szP[1024]; | |
| GF_SAFEALLOC(cred, GF_UserCredentials); | |
| if (!cred) return NULL; | |
| cred->dm = dm; | |
| sep[0] = 0; | |
| gf_strcpy(cred->username, key); | |
| gf_strcpy(szP, sep+1); | |
| sep[0] = ':'; | |
| GF_Crypt *gfc = gf_crypt_open(GF_AES_128, GF_CTR); | |
| gf_crypt_init(gfc, k, NULL); | |
| gf_crypt_decrypt(gfc, szP, (u32) strlen(szP)); | |
| gf_crypt_close(gfc); | |
| gf_user_credentials_save_digest(dm, cred, szP, GF_FALSE); | |
| return cred; | |
| } | |
| } | |
| #endif | |
| return NULL; | |
| } | |
| static void on_user_pass(void *udta, const char *user, const char *pass, Bool store_info) | |
| { | |
| GF_UserCredentials *creds = (GF_UserCredentials *)udta; | |
| if (!creds) return; | |
| u32 len = user ? (u32) strlen(user) : 0; | |
| if (len && (user != creds->username)) { | |
| if (len> 49) len = 49; | |
| memcpy(creds->username, user, 49); | |
| creds->username[len]=0; | |
| } | |
| if (user && pass) { | |
| GF_Err e = gf_user_credentials_save_digest((GF_DownloadManager *)creds->dm, creds, pass, store_info); | |
| if (e != GF_OK) { | |
| creds->valid = GF_FALSE; | |
| } | |
| } else { | |
| creds->valid = GF_FALSE; | |
| } | |
| creds->req_state = GF_CREDS_STATE_DONE; | |
| } | |
| /** | |
| * \brief Asks the user for credentials for given site | |
| \param dm The download manager | |
| \param creds The credentials to fill | |
| \param secure is auth required? | |
| \return GF_OK if info has been filled, GF_BAD_PARAM if creds == NULL or dm == NULL, GF_AUTHENTICATION_FAILURE if user did not filled the info. | |
| */ | |
| static GF_Err gf_user_credentials_ask_password( GF_DownloadManager * dm, GF_UserCredentials * creds, Bool secure) | |
| { | |
| char szPASS[50]; | |
| if (!dm || !creds) | |
| return GF_BAD_PARAM; | |
| memset(szPASS, 0, 50); | |
| if (!dm->get_user_password) return GF_AUTHENTICATION_FAILURE; | |
| creds->req_state = GF_CREDS_STATE_PENDING; | |
| if (!dm->get_user_password(dm->usr_cbk, secure, creds->site, creds->username, szPASS, on_user_pass, creds)) { | |
| creds->req_state = GF_CREDS_STATE_NONE; | |
| return GF_AUTHENTICATION_FAILURE; | |
| } | |
| return GF_OK; | |
| } | |
| GF_UserCredentials * gf_user_credentials_register(GF_DownloadManager * dm, Bool secure, const char * server_name, const char * username, const char * password, Bool valid) | |
| { | |
| GF_UserCredentials * creds; | |
| if (!dm) | |
| return NULL; | |
| gf_assert( server_name ); | |
| creds = gf_user_credentials_find_for_site(dm, server_name, username); | |
| /* If none found, we create one */ | |
| if (!creds) { | |
| GF_SAFEALLOC(creds, GF_UserCredentials); | |
| if (!creds) return NULL; | |
| creds->dm = dm; | |
| gf_list_insert(dm->credentials, creds, 0); | |
| } | |
| creds->valid = valid; | |
| if (username) { | |
| gf_strcpy(creds->username, username); | |
| } else { | |
| creds->username[0] = 0; | |
| } | |
| gf_strcpy(creds->site, server_name); | |
| if (username && password && valid) | |
| gf_user_credentials_save_digest(dm, creds, password, GF_FALSE); | |
| else { | |
| if (GF_OK != gf_user_credentials_ask_password(dm, creds, secure)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_HTTP, | |
| ("[Downloader] Failed to get password information.\n")); | |
| gf_list_del_item( dm->credentials, creds); | |
| gf_free( creds ); | |
| creds = NULL; | |
| } | |
| } | |
| return creds; | |
| } | |
| #endif //GPAC_DISABLE_NETWORK |