/* * ExampleSSLServer.c * * This program is an OpenSSL server that sits and listens for an incoming * SSL connection. Once a connection is made, the server reads a "message" * (defined as a stream terminated by ) from the client, and sends * back a modified response to the client before closing the connection. * 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: * servercert - assumed to point at a .PEM file containing a certificate * chain to be used by this server to authenticate itself * privatekey - assumed to point at a .PEM file containing a private * key for the server certificate * serverpwd - the password for the private key * * 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 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"; int isTracing = 0; /* 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); } } /* * 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 the server certificate if (getenv("servercert") == NULL) { trace(1, "\nThe environment variable 'servercert' is not defined, which means that"); trace(1, "this program has no certificate chain to send to a client when using a"); trace(1, "cipher suite that requires authentication. Set the environment variable"); trace(1, "'servercert' to point to a suitable file if you need to use one.\n"); } // Is there an environment variable which we can use for the password? if (getenv("serverpwd") == NULL) { trace(1, "\nThe environment variable 'serverpwd' is not defined, which means"); trace(1, "that a password be required for a certificate's private key, the SSL"); trace(1, "library will prompt you for it at the terminal. Set the environment"); trace(1, "variable 'serverpwd' to a password to override this behaviour.\n"); } // Is there an environment variable which we can use for the private key? if (getenv("privatekey") == NULL) { trace(1, "\nThe environment variable 'privatekey' is not defined, which means"); trace(1, "that it may not be possible to use server certificates for cipher suites"); trace(1, "that require server authentication. Set the environment variable"); trace(1, "'privatekey' to specify a PEM format private key file.\n"); } } void usage() { trace(1,"\nUsage: ExampleSSLServer [trace]"); 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 callback hands back the password to be used during decryption, * and returns a value derived from the environment variable "serverpwd". * * buf : the function will write the password into this buffer * size : the size of "buf" * rwflag : indicates whether the callback is being used for reading/ * decryption (0) or writing/encryption (1) * userdata : pointer to area which can be used by the application */ static int passwd_cb(char *buf,int size, int rwflag,void *userdata) { char *password; int password_length; trace(isTracing,"password_cb has been called"); password = getenv("serverpwd"); password_length = strlen(password); if ((password_length + 1) > size) { trace(1,"Password specified by environment variable is too big"); return 0; } strcpy(buf,password); return password_length; } /* * The following function was generated using the openssl utility, using * the command : "openssl dhparam -dsaparam -C 512" */ DH *get_dh512() { static unsigned char dh512_p[]={ 0x9B,0x9A,0x2B,0x34,0xDA,0x9A,0x55,0x53,0x47,0xDB,0xCF,0xB4, 0x26,0xAA,0x4D,0xFD,0x01,0x91,0x4A,0x19,0xE0,0x90,0xFA,0x6B, 0x99,0xD6,0xE2,0x78,0xF3,0x31,0xD3,0x93,0x9B,0x7B,0xE1,0x65, 0x57,0xFD,0x4D,0x2C,0x4E,0x17,0xE1,0xAC,0x30,0xB7,0xD0,0xA6, 0x80,0x13,0xEE,0x37,0xD1,0x83,0xCD,0x5F,0x88,0x38,0x79,0x9C, 0xFD,0xCE,0x85,0xED, }; static unsigned char dh512_g[]={ 0x8B,0x17,0x22,0x46,0x30,0xAD,0xE5,0x06,0x42,0x60,0x15,0x79, 0xA2,0x2F,0xD9,0xAA,0x7B,0xD7,0x8A,0x6F,0x39,0xEB,0x13,0x38, 0x54,0xA6,0xBE,0xAD,0xC6,0x6A,0x17,0x95,0xBE,0x8B,0x29,0xE0, 0x60,0x14,0x72,0xC9,0x5C,0x84,0x5D,0xD6,0x8B,0x57,0xD9,0x9D, 0x08,0x60,0x73,0x78,0x3F,0xDD,0x26,0x2C,0x40,0x63,0xCF,0xE0, 0xDC,0x58,0x7A,0x9C, }; DH *dh; if ((dh=DH_new()) == NULL) return(NULL); dh->p=BN_bin2bn(dh512_p,sizeof(dh512_p),NULL); dh->g=BN_bin2bn(dh512_g,sizeof(dh512_g),NULL); if ((dh->p == NULL) || (dh->g == NULL)) { DH_free(dh); return(NULL); } dh->length = 160; return(dh); } /* * 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); } /* * Set up a socket to listen on a specific port, and then wait for the * first incoming connection. Once a connection appears, the function * returns the socket id for the new connection. */ int getNewConnection(int portNum) { int ListenSock, newConnection; int stat; int val = 1; struct sockaddr_in SA; ListenSock = socket(AF_INET,SOCK_STREAM,0); if (ListenSock < 0) { error_exit("Call to create socket failed"); } memset(&SA,0,sizeof(SA)); // Fill in the name & address for the listen socket SA.sin_family = AF_INET; SA.sin_port = htons(portNum); SA.sin_addr.s_addr = INADDR_ANY; setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val)); stat = bind(ListenSock, (struct sockaddr *)&SA, sizeof(SA)); if (stat == -1) { error_exit("Call to bind failed"); } stat = listen(ListenSock,5); // 5 is the "backlog" if (stat == -1) { error_exit("Call to listen failed"); } newConnection = accept(ListenSock,0,0); return newConnection; } void main(int argc, char *argv[]) { // parse command line args int portNumber; int enableAnonSuites = 0; int enableCertSuites = 0; int enableNullSuites = 0; int stat; char tracebuf[512]; char buff[512]; char ciphers[512]; char clientMessage[512]; int clientSocket; int totalBytes; int messageComplete; int i; SSL_METHOD *meth = NULL; SSL_CTX *ctx; SSL *ssl; BIO *sbio; if (argc < 4) { usage(); return; } portNumber = atoi(argv[1]); enableAnonSuites = (strchr(argv[2],'a') != NULL); enableCertSuites = (strchr(argv[2],'c') != NULL); enableNullSuites = (strchr(argv[2],'n') != NULL); switch (atoi(argv[3])) { 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 > 4) { 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 if you're lucky, SSL will complain "PRNG not seeded". Alternatively // things may just fail. 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 don't want to request client certificates for // verification trace(isTracing,"SSL_CTX_set_verify()..."); SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); // Now tell SSL where its server certificate chain is (assuming the // user has told us) if (getenv("servercert") != NULL) { trace(isTracing,"Calling SSL_CTX_use_certificate_chain_file()..."); stat = SSL_CTX_use_certificate_chain_file(ctx,getenv("servercert")); if (stat != 1) { error_exit("SSL_CTX_use_certificate_chain_file failed"); } } // If the user has specified a password, tell SSL the address of // a callback routine which will return it if (getenv("serverpwd") != NULL) { trace(isTracing,"Calling SSL_CTX_set_default_passwd_cb()..."); SSL_CTX_set_default_passwd_cb(ctx,passwd_cb); } if (getenv("privatekey") != NULL) { trace(isTracing,"Calling SSL_CTX_use_PrivateKey_file()..."); stat = SSL_CTX_use_PrivateKey_file(ctx, getenv("privatekey"), SSL_FILETYPE_PEM); } // 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"); } // Provide DH key information (if you don't do this, then ciphers that // require DH key exchange won't be used, even if they are in the list of // ciphers for the ctx) trace(isTracing,"Calling SSL_CTX_set_tmp_dh()..."); stat = SSL_CTX_set_tmp_dh(ctx,get_dh512()); ssl = SSL_new(ctx); if (isTracing) { trace(1,"Enabled cipher suites are :"); enumerate_ciphers(ssl); } // Wait for an incoming connection trace(1,"\n...Now waiting for a client to connect"); clientSocket = getNewConnection(portNumber); trace(1,"Connection received"); // Generate a BIO wrapper for the TCP socket trace(isTracing,"Calling BIO_new_socket()..."); sbio = BIO_new_socket(clientSocket,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); trace(isTracing,"Calling SSL_accept()..."); stat = SSL_accept(ssl); if (stat <= 0) { error_exit("SSL_accept failed"); } // Now read stuff from the client, assuming that it will be a // message 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); sprintf(clientMessage,"OpenSSL server says thank-you for saying : %s",buff); 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"); } 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(clientSocket); trace(1,"\nProgam terminating"); }