/* * ExampleSSLClient.c * * This program is an OpenSSL client that talks to a server over an SSL * connection. Once a connection is made, the client sends a "message" * (defined as a stream terminated by ) to the server, and reads * back the response which is a message sent back by the server. * The code to perform the SSL stuff is (as much as possible) all contained * in "main" - the use of other functions is limited to just displaying * information, so that you should only need to read the "main" code to * see what order things are done. * * Environment variables: * truststore - assumed to point at .PEM file containing certificates * that are trusted by this client * * Tested on * Tru64 UNIX V5.1, OpenSSL 0.9.6c * OpenVMS V7.2-1, OpenSSL 0.9.5a with "CC/POINTER=64" * * Nick Hudson, May 2002 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int isTracing = 0; char *randbuf = "this isn't a very good way to seed the PRNG but will suffice" "this isn't a very good way to seed the PRNG but will suffice" "this isn't a very good way to seed the PRNG but will suffice" "this isn't a very good way to seed the PRNG but will suffice" "this isn't a very good way to seed the PRNG but will suffice"; /* Print SSL errors and exit*/ void error_exit(char *string) { printf("%s : \n",string); ERR_print_errors_fp(stdout); exit(0); } /* * If "doTrace" is non-zero, then send the message to the stdout */ void trace(int doTrace, const char *message) { if (doTrace) { printf("%s\n",message); } } void usage() { trace(1,"\nUsage: ExampleSSLClient " " [trace]"); trace(1," server : the server to talk to"); trace(1," port : port number to use"); trace(1," ciphers : types of cipher suites to propose :-"); trace(1," 'c' : suites that use server-cert"); trace(1," 'a' : anonymous (no server-cert)"); trace(1," 'n' : suites with no encryption"); trace(1," protocol : '1' '2' or '3' or '4' where :-"); trace(1," '1' means TLSv1"); trace(1," '2' means SSLv2"); trace(1," '3' means SSLv3"); trace(1," '4' means SSLv23"); trace(1," trace : (optional) tracing to enable :-"); trace(1," 't' : turn on program tracing"); } /* * This method looks at the run-time environment in an attempt to check * if things are set up correctly for the program to run. It reports * areas of concern but does not abort */ void checkEnvironment() { // Check whether they've specified a location for trusted certificates if (getenv("truststore") == NULL) { trace(1, "\nThe environment variable 'truststore' is not defined, which means that"); trace(1, "this program has no way to verify certificates sent by a server during"); trace(1, "cipher negotiation. Set the environment variable 'truststore' to point"); trace(1, "at a suitable file if you need to use one.\n"); } } /* * Display all the ciphers available for a specific SSL structure */ void enumerate_ciphers(SSL *ssl) { char tracebuf[512]; int index = 0; const char *next = NULL; do { next = SSL_get_cipher_list(ssl,index); if (next != NULL) { sprintf(tracebuf," %s",next); trace(1,tracebuf); index++; } } while (next != NULL); } /* * Create a socket connected to specified host:port * Returns the id of a connected socket. If any operation in * the function fails, then error_exit is called and the program * will terminate. */ int tcpConnect(char *host, int port) { struct hostent *he; struct sockaddr_in SA; int stat; int sock; sock = socket(AF_INET,SOCK_STREAM,0); if (sock == -1) { error_exit("Call to create socket failed"); } he = gethostbyname(host); if (he == NULL) { error_exit("Server name supplied was unrecognized"); } memset(&SA,0,sizeof(SA)); SA.sin_addr=*(struct in_addr*)he->h_addr_list[0]; SA.sin_family=AF_INET; SA.sin_port=htons(port); stat = connect(sock, (struct sockaddr *)&SA, sizeof(SA)); if (stat == -1) { error_exit("Couldn't connect socket"); } return sock; } void main(int argc, char *argv[]) { char *serverName = NULL; int portNumber; int enableAnonSuites = 0; int enableCertSuites = 0; int enableNullSuites = 0; char tracebuf[512]; char ciphers[512]; char *clientMessage = "Message from OpenSSL client\n"; char buff[512]; char *serverResponse; int stat; int socket; int totalBytes; int messageComplete; int i; SSL_METHOD *meth = NULL; SSL_CTX *ctx; SSL *ssl; BIO *sbio; // parse command line args if (argc < 5) { usage(); return; } serverName = argv[1]; portNumber = atoi(argv[2]); enableAnonSuites = (strchr(argv[3],'a') != NULL); enableCertSuites = (strchr(argv[3],'c') != NULL); enableNullSuites = (strchr(argv[3],'n') != NULL); switch (atoi(argv[4])) { case 1 : meth = TLSv1_method(); break; case 2 : meth = SSLv2_method(); break; case 3 : meth = SSLv3_method(); break; case 4 : meth = SSLv23_method(); break; default : usage(); return; } // trace argument is optional, but there's only one kind of // tracing we support if (argc > 5) { isTracing = 1; } checkEnvironment(); trace(1,"Configuring SSL connection..."); // Register all the libssl error strings trace(isTracing,"Calling SSL_load_error_strings()..."); SSL_load_error_strings(); // Register all available ciphers and digests. The documentation // says you can disregard the return status trace(isTracing,"Calling SSL_library_init()..."); SSL_library_init(); // Seed the pseudo random number generator (PRNG). If you don't do this // then certain SSL functions will complain "PRNG not seeded" trace(isTracing,"Calling RAND_seed()..."); RAND_seed(randbuf,strlen(randbuf)); // Now create the SSL context from which we will be able to create // SSL sessions trace(isTracing,"Calling SSL_CTX_new()..."); ctx = SSL_CTX_new(meth); if (ctx == NULL) { error_exit("call to SSL_CTX_new() returned NULL"); } // Tell SSL that we want verification of all server certificates. Unless // this call is made, then any certificate presented by a server will // be acceptable. trace(isTracing,"SSL_CTX_set_verify()..."); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); // If the user has defined 'truststore', then tell SSL to use this file // as a source of CA certificates. if (getenv("truststore") != NULL) { trace(isTracing,"Calling SSL_CTX_load_verify_locations()..."); stat = SSL_CTX_load_verify_locations(ctx, getenv("truststore"), // CAfile NULL // CApath ); if (stat != 1) { error_exit("SSL_CTX_load_verify_locations() failed"); } } // Load the ciphers that they've asked for. // It could be inferred from the documentation for ciphers(1) that you // can call SSL_CTX_set_cipher_list multiple times to build up a list // of ciphers. But that isn't how it works; rather you build up a list // of ciphers in a string, and then pass that in a single call: // First of all disable any ciphers we've got by default ciphers[0] = 0; strcat(ciphers,"-ALL"); // If they want cipher suites that require certificates, add them in if (enableCertSuites) { strcat(ciphers,":ALL:-aNULL"); } // If they want the null ones, add them in if (enableNullSuites) { strcat(ciphers,":NULL"); } // If they want the anonymous ones, add them in if (enableAnonSuites) { strcat(ciphers,":aNULL"); } sprintf(tracebuf,"Calling SSL_CTX_set_cipher_list(\"%s\")...",ciphers); trace(isTracing,tracebuf); stat = SSL_CTX_set_cipher_list(ctx,ciphers); if (stat == 0) { error_exit("SSL_CTX_set_cipher_list() failed"); } ssl = SSL_new(ctx); if (isTracing) { trace(1,"Enabled cipher suites are :"); enumerate_ciphers(ssl); } trace(isTracing,"Connecting to server"); socket = tcpConnect(serverName,portNumber); // Generate a BIO wrapper for the TCP socket trace(isTracing,"Calling BIO_new_socket()..."); sbio = BIO_new_socket(socket,BIO_NOCLOSE); if (sbio == NULL) { error_exit("BIO_new_socket() failed"); } // Connect up our SSL session with the socket trace(isTracing,"Calling SSL_set_bio()..."); SSL_set_bio(ssl,sbio,sbio); // Initiate the SSL handshake trace(isTracing,"Calling SSL_connect()..."); stat = SSL_connect(ssl); if (stat <= 0) { error_exit("SSL_connect failed"); } // We are now ready to talk with the server trace(1,"\n...Now connected to server"); trace(isTracing,"Calling SSL_write()..."); stat = SSL_write(ssl,clientMessage,strlen(clientMessage)); if (stat <= 0) { error_exit("SSL_write failed"); } if (stat != strlen(clientMessage)) { error_exit("Incomplete SSL write"); } // Now read the response from the server, assuming it will be // terminated by EOL totalBytes = 0; messageComplete = 0; do { trace(isTracing,"Calling SSL_read()..."); stat = SSL_read(ssl, &buff[totalBytes], (sizeof(buff) - totalBytes)); if (stat <= 0) { error_exit("SSL_read failed"); } // Look for an EOL for (i=0; i%s<--", buff); trace(1,tracebuf); trace(isTracing,"Shutting down connection..."); stat = SSL_shutdown(ssl); if (stat < 0) { error_exit("SSL_shutdown failed"); } if (stat == 0) { // According to docs at the OpenSSL website, this means that the shutdown // has not yet finished, and we must call SSL_shutdown again.. stat = SSL_shutdown(ssl); if (stat <= 0) { error_exit("SSL_shutdown (2nd time) failed"); } } SSL_free(ssl); SSL_CTX_free(ctx); close(socket); trace(1,"\nProgam terminating"); }