#include #include #include #include extern struct uwsgi_server uwsgi; /* ssl additional datas are retrieved via indexes. You can create an index with SSL_CTX_get_ex_new_index and set data in it with SSL_CTX_set_ex_data */ void uwsgi_ssl_init(void) { OPENSSL_config(NULL); SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); uwsgi.ssl_initialized = 1; } void uwsgi_ssl_info_cb(SSL const *ssl, int where, int ret) { if (where & SSL_CB_HANDSHAKE_DONE) { if (ssl->s3) { ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; } } } int uwsgi_ssl_verify_callback(int ok, X509_STORE_CTX * x509_store) { if (!ok && uwsgi.ssl_verbose) { char buf[256]; X509 *err_cert; int depth; int err; depth = X509_STORE_CTX_get_error_depth(x509_store); err_cert = X509_STORE_CTX_get_current_cert(x509_store); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); err = X509_STORE_CTX_get_error(x509_store); uwsgi_log("[uwsgi-ssl] client certificate verify error: num=%d:%s:depth=%d:%s\n", err, X509_verify_cert_error_string(err), depth, buf); } return ok; } int uwsgi_ssl_session_new_cb(SSL *ssl, SSL_SESSION *sess) { char session_blob[4096]; int len = i2d_SSL_SESSION(sess, NULL); if (len > 4096) { if (uwsgi.ssl_verbose) { uwsgi_log("[uwsgi-ssl] unable to store session of size %d\n", len); } return 0; } unsigned char *p = (unsigned char *) session_blob; i2d_SSL_SESSION(sess, &p); // ok let's write the value to the cache uwsgi_wlock(uwsgi.ssl_sessions_cache->lock); if (uwsgi_cache_set2(uwsgi.ssl_sessions_cache, (char *) sess->session_id, sess->session_id_length, session_blob, len, uwsgi.ssl_sessions_timeout, 0)) { if (uwsgi.ssl_verbose) { uwsgi_log("[uwsgi-ssl] unable to store session of size %d in the cache\n", len); } } uwsgi_rwunlock(uwsgi.ssl_sessions_cache->lock); return 0; } SSL_SESSION *uwsgi_ssl_session_get_cb(SSL *ssl, unsigned char *key, int keylen, int *copy) { uint64_t valsize = 0; *copy = 0; uwsgi_rlock(uwsgi.ssl_sessions_cache->lock); char *value = uwsgi_cache_get2(uwsgi.ssl_sessions_cache, (char *)key, keylen, &valsize); if (!value) { uwsgi_rwunlock(uwsgi.ssl_sessions_cache->lock); if (uwsgi.ssl_verbose) { uwsgi_log("[uwsgi-ssl] cache miss\n"); } return NULL; } #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) SSL_SESSION *sess = d2i_SSL_SESSION(NULL, (const unsigned char **)&value, valsize); #else SSL_SESSION *sess = d2i_SSL_SESSION(NULL, (unsigned char **)&value, valsize); #endif uwsgi_rwunlock(uwsgi.ssl_sessions_cache->lock); return sess; } void uwsgi_ssl_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) { uwsgi_wlock(uwsgi.ssl_sessions_cache->lock); if (uwsgi_cache_del2(uwsgi.ssl_sessions_cache, (char *) sess->session_id, sess->session_id_length, 0, 0)) { if (uwsgi.ssl_verbose) { uwsgi_log("[uwsgi-ssl] error removing cache item\n"); } } uwsgi_rwunlock(uwsgi.ssl_sessions_cache->lock); } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME static int uwsgi_sni_cb(SSL *ssl, int *ad, void *arg) { const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!servername) return SSL_TLSEXT_ERR_NOACK; size_t servername_len = strlen(servername); // reduce DOS attempts int count = 5; while(count > 0) { struct uwsgi_string_list *usl = uwsgi.sni; while(usl) { if (!uwsgi_strncmp(usl->value, usl->len, (char *)servername, servername_len)) { SSL_set_SSL_CTX(ssl, (SSL_CTX *) usl->custom_ptr); // the following steps are taken from nginx SSL_set_verify(ssl, SSL_CTX_get_verify_mode((SSL_CTX *)usl->custom_ptr), SSL_CTX_get_verify_callback((SSL_CTX *)usl->custom_ptr)); SSL_set_verify_depth(ssl, SSL_CTX_get_verify_depth((SSL_CTX *)usl->custom_ptr)); #ifdef SSL_CTRL_CLEAR_OPTIONS SSL_clear_options(ssl, SSL_get_options(ssl) & ~SSL_CTX_get_options((SSL_CTX *)usl->custom_ptr)); #endif SSL_set_options(ssl, SSL_CTX_get_options((SSL_CTX *)usl->custom_ptr)); return SSL_TLSEXT_ERR_OK; } usl = usl->next; } if (!uwsgi.subscription_dotsplit) break; char *next = memchr(servername+1, '.', servername_len-1); if (next) { servername_len -= next - servername; servername = next; count--; continue; } break; } if (uwsgi.subscription_dotsplit) goto end; #ifdef UWSGI_PCRE struct uwsgi_regexp_list *url = uwsgi.sni_regexp; while(url) { if (uwsgi_regexp_match(url->pattern, url->pattern_extra, (char *)servername, servername_len) >= 0) { SSL_set_SSL_CTX(ssl, url->custom_ptr); return SSL_TLSEXT_ERR_OK; } url = url->next; } #endif if (uwsgi.sni_dir) { size_t sni_dir_len = strlen(uwsgi.sni_dir); char *sni_dir_cert = uwsgi_concat4n(uwsgi.sni_dir, sni_dir_len, "/", 1, (char *) servername, servername_len, ".crt", 4); char *sni_dir_key = uwsgi_concat4n(uwsgi.sni_dir, sni_dir_len, "/", 1, (char *) servername, servername_len, ".key", 4); char *sni_dir_client_ca = uwsgi_concat4n(uwsgi.sni_dir, sni_dir_len, "/", 1, (char *) servername, servername_len, ".ca", 3); if (uwsgi_file_exists(sni_dir_cert) && uwsgi_file_exists(sni_dir_key)) { char *client_ca = NULL; if (uwsgi_file_exists(sni_dir_client_ca)) { client_ca = sni_dir_client_ca; } struct uwsgi_string_list *usl = uwsgi_ssl_add_sni_item(uwsgi_str((char *)servername), sni_dir_cert, sni_dir_key, uwsgi.sni_dir_ciphers, client_ca); if (!usl) goto done; free(sni_dir_cert); free(sni_dir_key); free(sni_dir_client_ca); SSL_set_SSL_CTX(ssl, usl->custom_ptr); return SSL_TLSEXT_ERR_OK; } done: free(sni_dir_cert); free(sni_dir_key); free(sni_dir_client_ca); } end: return SSL_TLSEXT_ERR_NOACK; } #endif char *uwsgi_write_pem_to_file(char *name, char *buf, size_t len, char *ext) { if (!uwsgi.ssl_tmp_dir) return NULL; char *filename = uwsgi_concat4(uwsgi.ssl_tmp_dir, "/", name, ext); int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR); if (fd < 0) { uwsgi_error_open(filename); free(filename); return NULL; } if (write(fd, buf, len) != (ssize_t) len) { uwsgi_log("unable to write pem data in file %s\n", filename); uwsgi_error("uwsgi_write_pem_to_file()/write()"); free(filename); close(fd); return NULL; } close(fd); return filename; } SSL_CTX *uwsgi_ssl_new_server_context(char *name, char *crt, char *key, char *ciphers, char *client_ca) { int crt_need_free = 0; int key_need_free = 0; int client_ca_need_free = 0; SSL_CTX *ctx = SSL_CTX_new(SSLv23_server_method()); if (!ctx) { uwsgi_log("[uwsgi-ssl] unable to initialize context \"%s\"\n", name); return NULL; } // this part is taken from nginx and stud, removing unneeded functionality // stud (for me) has made the best choice on choosing DH approach long ssloptions = SSL_OP_NO_SSLv2 | SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; // disable compression (if possibile) #ifdef SSL_OP_NO_COMPRESSION ssloptions |= SSL_OP_NO_COMPRESSION; #endif if (!uwsgi.sslv3) { ssloptions |= SSL_OP_NO_SSLv3; } // release/reuse buffers as soon as possibile #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); #endif /* we need to support both file-based certs and stream based as dealing with openssl memory bio for keys is really overkill, we store them in a tmp directory */ if (uwsgi.ssl_tmp_dir && !uwsgi_starts_with(crt, strlen(crt), "-----BEGIN ", 11)) { crt = uwsgi_write_pem_to_file(name, crt, strlen(crt), ".crt"); if (!crt) { SSL_CTX_free(ctx); return NULL; } crt_need_free = 1; } if (SSL_CTX_use_certificate_chain_file(ctx, crt) <= 0) { uwsgi_log("[uwsgi-ssl] unable to assign certificate %s for context \"%s\"\n", crt, name); SSL_CTX_free(ctx); if (crt_need_free) free(crt); return NULL; } // this part is based from stud BIO *bio = BIO_new_file(crt, "r"); if (bio) { DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if (dh) { SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); SSL_CTX_set_tmp_dh(ctx, dh); DH_free(dh); } } #if OPENSSL_VERSION_NUMBER >= 0x0090800fL #ifndef OPENSSL_NO_ECDH #ifdef NID_X9_62_prime256v1 EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if (ecdh) { SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); SSL_CTX_set_tmp_ecdh(ctx, ecdh); EC_KEY_free(ecdh); } #endif #endif #endif if (crt_need_free) free(crt); if (uwsgi.ssl_tmp_dir && !uwsgi_starts_with(key, strlen(key), "-----BEGIN ", 11)) { key = uwsgi_write_pem_to_file(name, key, strlen(key), ".key"); if (!key) { SSL_CTX_free(ctx); return NULL; } key_need_free = 1; } if (SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) <= 0) { uwsgi_log("[uwsgi-ssl] unable to assign key %s for context \"%s\"\n", key, name); SSL_CTX_free(ctx); if (key_need_free) free(key); return NULL; } if (key_need_free) free(key); // if ciphers are specified, prefer server ciphers if (ciphers && strlen(ciphers) > 0) { if (SSL_CTX_set_cipher_list(ctx, ciphers) == 0) { uwsgi_log("[uwsgi-ssl] unable to set requested ciphers (%s) for context \"%s\"\n", ciphers, name); SSL_CTX_free(ctx); return NULL; } ssloptions |= SSL_OP_CIPHER_SERVER_PREFERENCE; } // set session context (if possibile), this is required for client certificate authentication if (name) { SSL_CTX_set_session_id_context(ctx, (unsigned char *) name, strlen(name)); } if (client_ca) { if (client_ca[0] == '!') { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, uwsgi_ssl_verify_callback); client_ca++; } else { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, uwsgi_ssl_verify_callback); } // in the future we should allow to set the verify depth SSL_CTX_set_verify_depth(ctx, 1); if (uwsgi.ssl_tmp_dir && !uwsgi_starts_with(client_ca, strlen(client_ca), "-----BEGIN ", 11)) { if (!name) { SSL_CTX_free(ctx); return NULL; } client_ca = uwsgi_write_pem_to_file(name, client_ca, strlen(client_ca), ".ca"); if (!client_ca) { SSL_CTX_free(ctx); return NULL; } client_ca_need_free = 1; } if (SSL_CTX_load_verify_locations(ctx, client_ca, NULL) == 0) { uwsgi_log("[uwsgi-ssl] unable to set ssl verify locations (%s) for context \"%s\"\n", client_ca, name); SSL_CTX_free(ctx); if (client_ca_need_free) free(client_ca); return NULL; } STACK_OF(X509_NAME) * list = SSL_load_client_CA_file(client_ca); if (!list) { uwsgi_log("unable to load client CA certificate (%s) for context \"%s\"\n", client_ca, name); SSL_CTX_free(ctx); if (client_ca_need_free) free(client_ca); return NULL; } SSL_CTX_set_client_CA_list(ctx, list); if (client_ca_need_free) free(client_ca); } SSL_CTX_set_info_callback(ctx, uwsgi_ssl_info_cb); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME SSL_CTX_set_tlsext_servername_callback(ctx, uwsgi_sni_cb); #endif // disable session caching by default SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); if (uwsgi.ssl_sessions_use_cache) { // we need to early initialize locking and caching uwsgi_setup_locking(); uwsgi_cache_create_all(); uwsgi.ssl_sessions_cache = uwsgi_cache_by_name(uwsgi.ssl_sessions_use_cache); if (!uwsgi.ssl_sessions_cache) { // check for default cache if (!strcmp(uwsgi.ssl_sessions_use_cache, "true") && uwsgi.caches) { uwsgi.ssl_sessions_cache = uwsgi.caches; } else { uwsgi_log("unable to find cache \"%s\"\n", uwsgi.ssl_sessions_use_cache ? uwsgi.ssl_sessions_use_cache : "default"); exit(1); } } if (!uwsgi.ssl_sessions_cache->max_items) { uwsgi_log("you have to enable uWSGI cache to use it as SSL session store !!!\n"); exit(1); } if (uwsgi.ssl_sessions_cache->blocksize < 4096) { uwsgi_log("cache blocksize for SSL session store must be at least 4096 bytes\n"); exit(1); } SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER| SSL_SESS_CACHE_NO_INTERNAL| SSL_SESS_CACHE_NO_AUTO_CLEAR); #ifdef SSL_OP_NO_TICKET ssloptions |= SSL_OP_NO_TICKET; #endif // just for fun SSL_CTX_sess_set_cache_size(ctx, 0); // set the callback for ssl sessions SSL_CTX_sess_set_new_cb(ctx, uwsgi_ssl_session_new_cb); SSL_CTX_sess_set_get_cb(ctx, uwsgi_ssl_session_get_cb); SSL_CTX_sess_set_remove_cb(ctx, uwsgi_ssl_session_remove_cb); } SSL_CTX_set_timeout(ctx, uwsgi.ssl_sessions_timeout); struct uwsgi_string_list *usl = NULL; uwsgi_foreach(usl, uwsgi.ssl_options) { ssloptions |= atoi(usl->value); } SSL_CTX_set_options(ctx, ssloptions); return ctx; } char *uwsgi_rsa_sign(char *algo_key, char *message, size_t message_len, unsigned int *s_len) { // openssl could not be initialized if (!uwsgi.ssl_initialized) { uwsgi_ssl_init(); } *s_len = 0; EVP_PKEY *pk = NULL; char *algo = uwsgi_str(algo_key); char *colon = strchr(algo, ':'); if (!colon) { uwsgi_log("invalid RSA signature syntax, must be: :\n"); free(algo); return NULL; } *colon = 0; char *keyfile = colon + 1; char *signature = NULL; FILE *kf = fopen(keyfile, "r"); if (!kf) { uwsgi_error_open(keyfile); free(algo); return NULL; } if (PEM_read_PrivateKey(kf, &pk, NULL, NULL) == 0) { uwsgi_log("unable to load private key: %s\n", keyfile); free(algo); fclose(kf); return NULL; } fclose(kf); EVP_MD_CTX *ctx = EVP_MD_CTX_create(); if (!ctx) { free(algo); EVP_PKEY_free(pk); return NULL; } const EVP_MD *md = EVP_get_digestbyname(algo); if (!md) { uwsgi_log("unknown digest algo: %s\n", algo); free(algo); EVP_PKEY_free(pk); EVP_MD_CTX_destroy(ctx); return NULL; } *s_len = EVP_PKEY_size(pk); signature = uwsgi_malloc(*s_len); if (EVP_SignInit_ex(ctx, md, NULL) == 0) { ERR_print_errors_fp(stderr); free(signature); signature = NULL; *s_len = 0; goto clear; } if (EVP_SignUpdate(ctx, message, message_len) == 0) { ERR_print_errors_fp(stderr); free(signature); signature = NULL; *s_len = 0; goto clear; } if (EVP_SignFinal(ctx, (unsigned char *) signature, s_len, pk) == 0) { ERR_print_errors_fp(stderr); free(signature); signature = NULL; *s_len = 0; goto clear; } clear: free(algo); EVP_PKEY_free(pk); EVP_MD_CTX_destroy(ctx); return signature; } char *uwsgi_sanitize_cert_filename(char *base, char *key, uint16_t keylen) { // stop at the first slash if mountpoints are involved if (uwsgi.subscription_mountpoints) { char *slash = memchr(key, '/', keylen); if (slash) { keylen = slash - key; } } uint16_t i; char *filename = uwsgi_concat4n(base, strlen(base), "/", 1, key, keylen, ".pem\0", 5); for (i = strlen(base) + 1; i < (strlen(base) + 1) + keylen; i++) { if (filename[i] >= '0' && filename[i] <= '9') continue; if (filename[i] >= 'A' && filename[i] <= 'Z') continue; if (filename[i] >= 'a' && filename[i] <= 'z') continue; if (filename[i] == '.') continue; if (filename[i] == '-') continue; if (filename[i] == '_') continue; filename[i] = '_'; } return filename; } char *uwsgi_ssl_rand(size_t len) { unsigned char *buf = uwsgi_calloc(len); if (RAND_bytes(buf, len) <= 0) { free(buf); return NULL; } return (char *) buf; } char *uwsgi_sha1(char *src, size_t len, char *dst) { SHA_CTX sha; SHA1_Init(&sha); SHA1_Update(&sha, src, len); SHA1_Final((unsigned char *)dst, &sha); return dst; } char *uwsgi_md5(char *src, size_t len, char *dst) { MD5_CTX md5; MD5_Init(&md5); MD5_Update(&md5, src, len); MD5_Final((unsigned char *)dst, &md5); return dst; } char *uwsgi_sha1_2n(char *s1, size_t len1, char *s2, size_t len2, char *dst) { SHA_CTX sha; SHA1_Init(&sha); SHA1_Update(&sha, s1, len1); SHA1_Update(&sha, s2, len2); SHA1_Final((unsigned char *)dst, &sha); return dst; } void uwsgi_opt_sni(char *opt, char *value, void *foobar) { char *client_ca = NULL; char *v = uwsgi_str(value); char *space = strchr(v, ' '); if (!space) { uwsgi_log("invalid %s syntax, must be sni_keycrt,key[,ciphers,client_ca]\n", opt); exit(1); } *space = 0; char *crt = space+1; char *key = strchr(crt, ','); if (!key) { uwsgi_log("invalid %s syntax, must be sni_keycrt,key[,ciphers,client_ca]\n", opt); exit(1); } *key = '\0'; key++; char *ciphers = strchr(key, ','); if (ciphers) { *ciphers = '\0'; ciphers++; client_ca = strchr(ciphers, ','); if (client_ca) { *client_ca = '\0'; client_ca++; } } if (!uwsgi.ssl_initialized) { uwsgi_ssl_init(); } SSL_CTX *ctx = uwsgi_ssl_new_server_context(v, crt, key, ciphers, client_ca); if (!ctx) { uwsgi_log("[uwsgi-ssl] DANGER unable to initialize context for \"%s\"\n", v); free(v); return; } #ifdef UWSGI_PCRE if (!strcmp(opt, "sni-regexp")) { struct uwsgi_regexp_list *url = uwsgi_regexp_new_list(&uwsgi.sni_regexp, v); url->custom_ptr = ctx; } else { #endif struct uwsgi_string_list *usl = uwsgi_string_new_list(&uwsgi.sni, v); usl->custom_ptr = ctx; #ifdef UWSGI_PCRE } #endif } struct uwsgi_string_list *uwsgi_ssl_add_sni_item(char *name, char *crt, char *key, char *ciphers, char *client_ca) { if (!uwsgi.ssl_initialized) { uwsgi_ssl_init(); } SSL_CTX *ctx = uwsgi_ssl_new_server_context(name, crt, key, ciphers, client_ca); if (!ctx) { uwsgi_log("[uwsgi-ssl] DANGER unable to initialize context for \"%s\"\n", name); free(name); return NULL; } struct uwsgi_string_list *usl = uwsgi_string_new_list(&uwsgi.sni, name); usl->custom_ptr = ctx; // mark it as dynamic usl->custom = 1; uwsgi_log_verbose("[uwsgi-sni for pid %d] added SSL context for %s\n", (int) getpid(), name); return usl; } void uwsgi_ssl_del_sni_item(char *name, uint16_t name_len) { struct uwsgi_string_list *usl = NULL, *last_sni = NULL, *sni_item = NULL; uwsgi_foreach(usl, uwsgi.sni) { if (!uwsgi_strncmp(usl->value, usl->len, name, name_len) && usl->custom) { sni_item = usl; break; } last_sni = usl; } if (!sni_item) return; if (last_sni) { last_sni->next = sni_item->next; } else { uwsgi.sni = sni_item->next; } // we are free to destroy it as no more clients are using it SSL_CTX_free((SSL_CTX *) sni_item->custom_ptr); free(sni_item->value); free(sni_item); uwsgi_log_verbose("[uwsgi-sni for pid %d] destroyed SSL context for %s\n",(int) getpid(), name); }