/*------------------------------------------------------------------ * Wrapper for the OpenSSL module. *------------------------------------------------------------------ */ #include "driver.h" #include "machine.h" #if defined(USE_TLS) && defined(HAS_OPENSSL) #include <stdio.h> #include <openssl/ssl.h> #include <openssl/rand.h> #include <openssl/err.h> #include <openssl/x509.h> #include <openssl/x509v3.h> #include <sys/utsname.h> #include <openssl/opensslconf.h> #include <openssl/sha.h> #include <openssl/md5.h> #include <openssl/ripemd.h> #include <openssl/hmac.h> #include <openssl/evp.h> #include "pkg-tls.h" #include "actions.h" #include "array.h" #include "comm.h" #include "interpret.h" #include "main.h" #include "mstrings.h" #include "object.h" #include "sha1.h" #include "svalue.h" #include "xalloc.h" #include "../mudlib/sys/tls.h" /*-------------------------------------------------------------------------*/ /* Variables */ static Bool tls_is_available = MY_FALSE; /* Set to TRUE when the TLS layer has been initialised successfully. */ static SSL_CTX * context = NULL; /* The SSL program context. */ static DH *dhe1024 = NULL; /* The Diffie-Hellmann parameters. */ /*-------------------------------------------------------------------------*/ static int no_passphrase_callback (char * buf, int num, int w, void *arg) /* OpenSSL: Empty method to hinder OpenSSL from asking for passphrases. */ { return -1; } /* no_passphrase_callback() */ /*-------------------------------------------------------------------------*/ static Bool set_dhe1024 (void) /* Set the Diffie-Hellmann parameters. * Return MY_TRUE on success, and MY_FALSE on error. */ { int i; DSA *dsaparams; DH *dhparams; const char *seed[] = { ";-) :-( :-) :-( ", ";-) :-( :-) :-( ", "Random String no. 12", ";-) :-( :-) :-( ", "hackers have even mo", /* from jargon file */ }; unsigned char seedbuf[20]; if (dhe1024 != NULL) return MY_TRUE; RAND_bytes((unsigned char *) &i, sizeof i); /* Make sure that i is non-negative - pick one of the provided seeds */ if (i < 0) i = -1; if (i < 0) /* happens if i == MININT */ i = 0; i %= sizeof seed / sizeof seed[0]; memcpy(seedbuf, seed[i], 20); dsaparams = DSA_generate_parameters(1024, seedbuf, 20, NULL, NULL, 0, NULL); if (dsaparams == NULL) return MY_FALSE; dhparams = DSA_dup_DH(dsaparams); DSA_free(dsaparams); if (dhparams == NULL) return MY_FALSE; dhe1024 = dhparams; return MY_TRUE; } /* set_dhe1024() */ /*-------------------------------------------------------------------------*/ static int tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* This function will be called if the client did present a certificate * always returns MY_TRUE so that the handshake will succeed * and the verification status can later be checked on mudlib level * see also: SSL_set_verify(3) */ { if (d_flag) { char buf[512]; printf("%s tls_verify_callback(%d, ...)\n", time_stamp(), preverify_ok); X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, sizeof buf); printf("depth %d: %s\n", X509_STORE_CTX_get_error_depth(ctx), buf); } return MY_TRUE; } /* tls_verify_callback() */ /*-------------------------------------------------------------------------*/ void tls_verify_init (void) /* initialize or reinitialize tls certificate storage and revocation lists */ { STACK_OF(X509_NAME) *stack = NULL; if (tls_trustfile != NULL && tls_trustdirectory != NULL) { printf("%s TLS: (OpenSSL) trusted x509 certificates from '%s' and directory '%s'.\n" , time_stamp(), tls_trustfile, tls_trustdirectory); debug_message("%s TLS: (OpenSSL) trusted x509 certificates from '%s' and directory '%s'.\n" , time_stamp(), tls_trustfile, tls_trustdirectory); } else if (tls_trustfile != NULL) { printf("%s TLS: (OpenSSL) trusted x509 certificates from '%s'.\n" , time_stamp(), tls_trustfile); debug_message("%s TLS: (OpenSSL) trusted x509 certificates from '%s'.\n" , time_stamp(), tls_trustfile); } else if (tls_trustdirectory != NULL) { printf("%s TLS: (OpenSSL) trusted x509 certificates from directory '%s'.\n" , time_stamp(), tls_trustdirectory); debug_message("%s TLS: (OpenSSL) trusted x509 certificates from directory '%s'.\n" , time_stamp(), tls_trustdirectory); } else { printf("%s TLS: (OpenSSL) Trusted x509 certificates locations not specified.\n" , time_stamp()); debug_message("%s TLS: (OpenSSL) trusted x509 certificates locations not specified.\n" , time_stamp()); } if (tls_crlfile != NULL || tls_crldirectory != NULL) { X509_STORE *store = X509_STORE_new(); if (store != NULL) { if (tls_crlfile != NULL) { X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); if (lookup != NULL) X509_LOOKUP_load_file(lookup, tls_crlfile, X509_FILETYPE_PEM); } if (tls_crldirectory != NULL) { X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); if (lookup != NULL) X509_LOOKUP_add_dir(lookup, tls_crldirectory, X509_FILETYPE_PEM); } #if OPENSSL_VERSION_NUMBER >= 0x00907000L X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); SSL_CTX_set_cert_store(context, store); if (tls_crlfile != NULL && tls_crldirectory != NULL) { printf("%s TLS: (OpenSSL) CRLs from '%s' and '%s'.\n" , time_stamp(), tls_crlfile, tls_crldirectory); debug_message("%s TLS: (OpenSSL) CRLs from '%s' and '%s'.\n" , time_stamp(), tls_crlfile, tls_crldirectory); } else if (tls_crlfile != NULL) { printf("%s TLS: (OpenSSL) CRLs from '%s'.\n" , time_stamp(), tls_crlfile); debug_message("%s TLS: (OpenSSL) CRLs from '%s'.\n" , time_stamp(), tls_crlfile); } else if (tls_crldirectory != NULL) { printf("%s TLS: (OpenSSL) CRLs from '%s'.\n" , time_stamp(), tls_crldirectory); debug_message("%s TLS: (OpenSSL) CRLs from '%s'.\n" , time_stamp(), tls_crldirectory); } else { printf("%s TLS: (OpenSSL) CRL checking disabled.\n" , time_stamp()); debug_message("%s TLS: (OpenSSL) CRL checking disabled.\n" , time_stamp()); } #else printf("%s TLS: Warning: Your OpenSSL version does not support " "Certificate revocation list checking\n" , time_stamp()); debug_message("%s TLS: Warning: Your OpenSSL version does not " "support Certificate revocation list checking\n" , time_stamp()); #endif } else { printf("%s TLS: Warning: There was a problem getting the " "storage context from OpenSSL. Certificate revocation " "list checking is not enabled.\n" , time_stamp()); debug_message("%s TLS: Warning: There was a problem getting the " "storage context from OpenSSL. Certificate revocation " "list checking is not enabled.\n" , time_stamp()); } } if (!SSL_CTX_load_verify_locations(context, tls_trustfile, tls_trustdirectory)) { printf("%s TLS: Error preparing x509 verification certificates\n", time_stamp()); debug_message("%s TLS: Error preparing x509 verification certificates\n", time_stamp()); } if (tls_trustfile != NULL) { stack = SSL_load_client_CA_file(tls_trustfile); SSL_CTX_set_client_CA_list(context, stack); } else { stack = SSL_CTX_get_client_CA_list(context); if (stack == NULL) { stack = sk_X509_NAME_new_null(); SSL_CTX_set_client_CA_list(context, stack); } } if (tls_trustdirectory != NULL && stack != NULL) { SSL_add_dir_cert_subjects_to_stack(stack, tls_trustdirectory); } } /*-------------------------------------------------------------------------*/ void tls_global_init (void) /* Initialise the TLS package; to be called once at program startup. */ { if (tls_keyfile == NULL) { printf("%s TLS deactivated.\n", time_stamp()); return; } printf("%s TLS: (OpenSSL) x509 keyfile '%s', certfile '%s'\n" , time_stamp(), tls_keyfile, tls_certfile); debug_message("%s TLS: (OpenSSL) Keyfile '%s', Certfile '%s'\n" , time_stamp(), tls_keyfile, tls_certfile); SSL_load_error_strings(); ERR_load_BIO_strings(); if (!SSL_library_init()) { printf("%s TLS: Initialising the SSL library failed.\n" , time_stamp()); debug_message("%s TLS: Initialising the SSL library failed.\n" , time_stamp()); return; } /* SSL uses the rand(3) generator from libcrypto(), which needs * to be seeded. */ { struct { struct utsname uname; int uname_1; int uname_2; uid_t uid; uid_t euid; gid_t gid; gid_t egid; } data1; struct { pid_t pid; time_t time; void *stack; } data2; data1.uname_1 = uname(&data1.uname); data1.uname_2 = errno; /* Let's hope that uname fails randomly :-) */ data1.uid = getuid(); data1.euid = geteuid(); data1.gid = getgid(); data1.egid = getegid(); RAND_seed((const void *)&data1, sizeof data1); data2.pid = getpid(); data2.time = time(NULL); data2.stack = (void *)&data2; RAND_seed((const void *)&data2, sizeof data2); } context = SSL_CTX_new (SSLv23_method()); if (!context) { printf("%s TLS: Can't get SSL context:\n" , time_stamp()); debug_message("%s TLS: Can't get SSL context:\n" , time_stamp()); goto ssl_init_err; } SSL_CTX_set_default_passwd_cb(context, no_passphrase_callback); SSL_CTX_set_mode(context, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_session_id_context(context, (unsigned char*) "ldmud", 5); if (!SSL_CTX_use_PrivateKey_file(context, tls_keyfile, SSL_FILETYPE_PEM)) { printf("%s TLS: Error setting x509 keyfile:\n" , time_stamp()); debug_message("%s TLS: Error setting x509 keyfile:\n" , time_stamp()); goto ssl_init_err; } if (!SSL_CTX_use_certificate_file(context, tls_certfile, SSL_FILETYPE_PEM)) { printf("%s TLS: Error setting x509 certfile:\n" , time_stamp()); debug_message("%s TLS: Error setting x509 certfile:\n" , time_stamp()); goto ssl_init_err; } tls_verify_init(); if (!set_dhe1024() || !SSL_CTX_set_tmp_dh(context, dhe1024) ) { printf("%s TLS: Error setting Diffie-Hellmann parameters:\n" , time_stamp()); debug_message("%s TLS: Error setting Diffie-Hellmann parameters:\n" , time_stamp()); goto ssl_init_err; } /* Avoid small subgroup attacks */ SSL_CTX_set_options(context, SSL_OP_SINGLE_DH_USE); /* OpenSSL successfully initialised */ tls_is_available = MY_TRUE; return; /* General error handling for the initialisation */ ssl_init_err: { unsigned long err; while (0 != (err = ERR_get_error())) { char * errstring = ERR_error_string(err, NULL); printf("%s TLS: SSL %s.\n" , time_stamp(), errstring); debug_message("%s TLS: SSL %s.\n" , time_stamp(), errstring); } if (dhe1024 != NULL) { DH_free(dhe1024); dhe1024 = NULL; } if (context != NULL) { SSL_CTX_free(context); context = NULL; } return; } } /* tls_global_init() */ /*-------------------------------------------------------------------------*/ void tls_global_deinit (void) /* Clean up the TLS package on program termination. */ { if (dhe1024 != NULL) { DH_free(dhe1024); dhe1024 = NULL; } if (context != NULL) { SSL_CTX_free(context); context = NULL; } tls_is_available = MY_FALSE; } /* tls_global_deinit() */ /*-------------------------------------------------------------------------*/ int tls_read (interactive_t *ip, char *buffer, int length) /* Read up to <length> bytes data for the TLS connection of <ip> * and store it in <buffer>. * Return then number of bytes read, or a negative number if an error * occured. */ { int ret = -11; int err; do { ret = SSL_read(ip->tls_session, buffer, length); } while (ret < 0 && (err = SSL_get_error(ip->tls_session, ret)) && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); if (ret == 0) { tls_deinit_connection(ip); } else if (ret < 0) { ret = SSL_get_error(ip->tls_session, ret); debug_message("%s TLS: Received corrupted data (%d). " "Closing the connection.\n" , time_stamp(), ret); tls_deinit_connection(ip); } return (ret < 0 ? -1 : ret); } /* tls_read() */ /*-------------------------------------------------------------------------*/ int tls_write (interactive_t *ip, char *buffer, int length) /* Write <length> bytes from <buffer> to the TLS connection of <ip> * Return the number of bytes written, or a negative number if an error * occured. */ { int ret = -1; int err; do { ret = SSL_write(ip->tls_session, buffer, length); } while (ret < 0 && (err = SSL_get_error(ip->tls_session, ret)) && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); if (ret == 0) { tls_deinit_connection(ip); } else if (ret < 0) { ret = SSL_get_error(ip->tls_session, ret); debug_message("%s TLS: Sending data failed (%d). " "Closing the connection.\n" , time_stamp(), ret); tls_deinit_connection(ip); } return (ret<0 ? -1 : ret); } /* tls_write() */ /*-------------------------------------------------------------------------*/ int tls_do_handshake (interactive_t *ip) /* Continue the TLS initialisation handshake for interactive <ip>. * Return a negative error code if the connection can not be set up. * Return 0 if the connection is still begin set up. * Return 1 if the connection is now active (or if no secure connection * had been requested). * * This function does only the package specific part of * tls_continue_handshake. */ { int ret, n; if ((n = SSL_do_handshake(ip->tls_session)) < 0) ret = SSL_get_error(ip->tls_session, n); else ret = 0; if (ret != SSL_ERROR_WANT_READ && ret != SSL_ERROR_WANT_WRITE) { if (ret != 0) { /* Setup failed */ SSL_free(ip->tls_session); ip->tls_session = NULL; ret = - ret; } else { /* Setup finished */ /* TODO: Check SSL_in_init() at this place? */ ret = 1; } } else ret = 0; return ret; } /* tls_do_handshake() */ /*-------------------------------------------------------------------------*/ int tls_init_connection (interactive_t *ip) /* Starts a TLS secured connection to the interactive <ip>. * Returns a negative error code or 0 for success. * * After a successful start tls_do_handshake will be called. */ { SSL * session = SSL_new(context); if (session == NULL) { return -ERR_get_error(); } if (!SSL_set_fd(session, ip->socket)) { SSL_free(session); return -ERR_get_error(); } if (ip->outgoing_conn) SSL_set_connect_state(session); else { SSL_set_accept_state(session); /* request a client certificate */ SSL_set_verify( session, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE , tls_verify_callback); } ip->tls_session = session; return 0; } /* tls_init_connection() */ /*-------------------------------------------------------------------------*/ vector_t * tls_check_certificate (interactive_t *ip, Bool more) /* Checks the certificate of the secured connection and returns * an array with the information about it for the efun * tls_check_certificate(). */ { vector_t *v = NULL; X509 *peer; X509_NAME *subject; peer = SSL_get_peer_certificate(ip->tls_session); if (peer != NULL) { int i, j, len; char buf[256]; vector_t *extra = NULL; v = allocate_array(more ? 3 : 2); /* the result of SSL verification, the most important thing here * see verify(1) for more details */ put_number(&(v->item[0]), SSL_get_verify_result(ip->tls_session)); subject = X509_get_subject_name(peer); j = X509_NAME_entry_count(subject); extra = allocate_array(3 * j); /* iterate all objects in the certificate */ for (i = 0; i < j; i++) { X509_NAME_ENTRY *entry; ASN1_OBJECT *ob; entry = X509_NAME_get_entry(subject, i); ob = X509_NAME_ENTRY_get_object(entry); len = OBJ_obj2txt(buf, sizeof buf, ob, 1); put_c_n_string(&(extra->item[3 * i]), buf, len); len = OBJ_obj2txt(buf, sizeof buf, ob, 0); put_c_n_string(&(extra->item[3 * i + 1]), buf, len); put_c_string(&(extra->item[3 * i + 2]) , (char *)ASN1_STRING_data(X509_NAME_ENTRY_get_data(entry))); } put_array(&(v->item[1]), extra); /* also get all information from extensions like subjectAltName */ if (more) { vector_t *extensions = NULL; vector_t *extension = NULL; j = X509_get_ext_count(peer); extensions = allocate_array(3 * j); for (i = X509_get_ext_by_NID(peer, NID_subject_alt_name, -1) ; i != -1 ; i = X509_get_ext_by_NID(peer, NID_subject_alt_name, i)) { int iter, count; X509_EXTENSION *ext = NULL; STACK_OF(GENERAL_NAME) *ext_vals = NULL; ext = X509_get_ext(peer, i); if (ext == NULL) { break; } /* extension name */ len = OBJ_obj2txt(buf, sizeof buf, ext->object, 1), put_c_n_string(&(extensions->item[3 * i]), (char *)buf, len); len = OBJ_obj2txt(buf, sizeof buf, ext->object, 0), put_c_n_string(&(extensions->item[3 * i + 1]), (char *)buf, len); /* extension values */ ext_vals = X509V3_EXT_d2i(ext); if (ext_vals == NULL) { break; } count = sk_GENERAL_NAME_num(ext_vals); extension = allocate_array(3 * count); put_array(&(extensions->item[3 * i + 2]), extension); for (iter = 0; iter < count; iter++) { GENERAL_NAME *ext_val = NULL; ASN1_STRING *value = NULL; ext_val = sk_GENERAL_NAME_value(ext_vals, iter); switch(ext_val->type) { case GEN_OTHERNAME: value = ext_val->d.otherName->value->value.asn1_string; len = OBJ_obj2txt(buf, sizeof buf, ext_val->d.otherName->type_id, 1), put_c_n_string(&(extension->item[3 * iter]), buf, len); len = OBJ_obj2txt(buf, sizeof buf, ext_val->d.otherName->type_id, 0), put_c_n_string(&(extension->item[3 * iter + 1]), buf, len); put_c_string(&(extension->item[3 * iter + 2]) , (char*)ASN1_STRING_data(value)); break; case GEN_DNS: value = ext_val->d.dNSName; put_c_n_string(&(extension->item[3 * iter]), "dNSName", 7); put_c_n_string(&(extension->item[3 * iter + 1]), "dNSName", 7); put_c_string(&(extension->item[3 * iter + 2]) , (char*)ASN1_STRING_data(value)); break; case GEN_EMAIL: value = ext_val->d.rfc822Name; put_c_n_string(&(extension->item[3 * iter]), "rfc822Name", 10); put_c_n_string(&(extension->item[3 * iter + 1]), "rfc822Name", 10); put_c_string(&(extension->item[3 * iter + 2]) , (char*)ASN1_STRING_data(value)); break; case GEN_URI: value = ext_val->d.uniformResourceIdentifier; put_c_n_string(&(extension->item[3 * iter]), "uniformResourceIdentifier", 25); put_c_n_string(&(extension->item[3 * iter + 1]), "uniformResourceIdentifier", 25); put_c_string(&(extension->item[3 * iter + 2]) , (char*)ASN1_STRING_data(value)); break; /* TODO: the following are unimplemented * and the structure is getting ugly */ case GEN_X400: case GEN_DIRNAME: case GEN_EDIPARTY: case GEN_IPADD: case GEN_RID: default: break; } } } put_array(&(v->item[2]), extensions); } X509_free(peer); } return v; } /* tls_check_certificate() */ /*-------------------------------------------------------------------------*/ void tls_deinit_connection (interactive_t *ip) /* Close the TLS connection for the interactive <ip> if there is one. */ { if (ip->tls_status != TLS_INACTIVE) { SSL_shutdown(ip->tls_session); SSL_free(ip->tls_session); ip->tls_session = NULL; } if (ip->tls_cb != NULL) { free_callback(ip->tls_cb); xfree(ip->tls_cb); ip->tls_cb = NULL; } ip->tls_status = TLS_INACTIVE; } /* tls_deinit_connection() */ /*-------------------------------------------------------------------------*/ const char * tls_error (int err) /* Returns a string describing the error behind the * error number <err>, which is always negative. */ { return ERR_error_string(-err, NULL); } /* tls_error() */ /*-------------------------------------------------------------------------*/ vector_t * tls_query_connection_info (interactive_t *ip) /* Returns the connection info array for the efun * tls_query_connection_info(). <ip> is guaranteed * to have a TLS secured connection. */ { vector_t * rc; rc = allocate_array(TLS_INFO_MAX); put_c_string(&(rc->item[TLS_CIPHER]) , SSL_get_cipher(ip->tls_session)); put_number(&(rc->item[TLS_COMP]), 0); put_number(&(rc->item[TLS_KX]), 0); put_number(&(rc->item[TLS_MAC]), 0); put_c_string(&(rc->item[TLS_PROT]) , SSL_get_version(ip->tls_session)); return rc; } /* tls_query_connection_info() */ /*-------------------------------------------------------------------------*/ Bool tls_available () /* If the global TLS Initialisation could not been set up, tls_available() * returns MY_FALSE, otherwise MY_TRUE. */ { return tls_is_available; } /* tls_available() */ /*------------------------------------------------------------------ * Interface to the openssl cryptography api *------------------------------------------------------------------ */ Bool get_digest (int num, digest_t * md, size_t *len) /* Determine the proper digest descriptor <*md> and length <*len> * from the designator <num>, which is one of the TLS_HASH_ constants. * * Return MY_FALSE if the desired digest isn't available. */ { switch(num) { #ifndef OPENSSL_NO_SHA1 # ifdef SHA_DIGEST_LENGTH case TLS_HASH_SHA1: (*len) = SHA_DIGEST_LENGTH; (*md) = EVP_sha1(); break; # endif #endif #ifndef OPENSSL_NO_SHA256 # ifdef SHA224_DIGEST_LENGTH case TLS_HASH_SHA224: (*len) = SHA224_DIGEST_LENGTH; (*md) = EVP_sha224(); break; # endif # ifdef SHA256_DIGEST_LENGTH case TLS_HASH_SHA256: (*len) = SHA256_DIGEST_LENGTH; (*md) = EVP_sha256(); break; # endif #endif #ifndef OPENSSL_NO_SHA512 # ifdef SHA384_DIGEST_LENGTH case TLS_HASH_SHA384: (*len) = SHA384_DIGEST_LENGTH; (*md) = EVP_sha384(); break; # endif # ifdef SHA512_DIGEST_LENGTH case TLS_HASH_SHA512: (*len) = SHA512_DIGEST_LENGTH; (*md) = EVP_sha512(); break; # endif #endif #ifndef OPENSSL_NO_MD5 # ifdef MD5_DIGEST_LENGTH case TLS_HASH_MD5: (*len) = MD5_DIGEST_LENGTH; (*md) = EVP_md5(); break; # endif #endif #ifndef OPENSSL_NO_RIPEMD # ifdef RIPEMD160_DIGEST_LENGTH case TLS_HASH_RIPEMD160: (*len) = RIPEMD160_DIGEST_LENGTH; (*md) = EVP_ripemd160(); break; # endif #endif default: (*md) = NULL; return MY_FALSE; } return MY_TRUE; } /* get_digest() */ /*-------------------------------------------------------------------------*/ void calc_digest (digest_t md, void *dest, size_t destlen, void *msg, size_t msglen, void *key, size_t keylen) /* Calculates the hash or the HMAC if <key> != NULL from <msg> as determined * by method <md> as it was returned by get_digest(). */ { if(key) { #if defined(OPENSSL_NO_HMAC) errorf("OpenSSL wasn't configured to provide the hmac() method.\n"); /* NOTREACHED */ #else HMAC_CTX ctx; HMAC_Init(&ctx, key, keylen, md); HMAC_Update(&ctx, msg, msglen); HMAC_Final(&ctx, dest, NULL); #endif } else { EVP_MD_CTX ctx; EVP_DigestInit(&ctx, md); EVP_DigestUpdate(&ctx, msg, msglen); EVP_DigestFinal(&ctx, dest, NULL); } } /* calc_digest() */ /***************************************************************************/ #endif /* USE_TLS */