/***********************************************\ * The SSL server (sample SSL toolkit) * * (C) 2005 Digithell, Inc. (Pavel Strachota) * * file: sslserver.cc * \***********************************************/ #include #include // use the GNU standard argument parser (getopt_long) #include #include #include "include/strings.h" #include "service.h" using namespace std; // the same file can be used because more PEM objects can be aggregated in a single file static const _string_ CERTFILE="server_cert/server.pem"; static const _string_ KEYFILE="server_cert/server.pem"; // the following file may contain many PEM-formatted certificates static const _string_ TRUSTED_CA_FILE="server_cert/CA_cert.pem"; const _string_ PASSFILE="server_cert/server_password.txt"; bool persistent=true; // don't shut down server after connection shutdown // ------------------------------------------------------- // options for getopt_long struct option options [] = { // The following line describes the simpler format of the option structure // name needs_arg? *** return value of getopt_long { "version", no_argument, NULL, 1 }, { "help", no_argument, NULL, 2 }, { "refuse-ssl2", no_argument, NULL, 3 }, { "use-ciphers", required_argument, NULL, 'c' }, { "no-persist", no_argument, NULL, 'n' }, { "use-port", required_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } // this is a termination item for getopt_long() so that it knows where // it should stop scanning the structure array. }; _string_ short_options = "p:c:n"; // the shorts for '--version', '--help' and '--refuse-ssl2' are not supported // ------------------------------------------------------- void PrintVersion() { cout << "SSLServer Version 0.1A, (C) 2005 Digithell, Inc. (Pavel Strachota)" << endl; } void PrintHelp() { PrintVersion(); cout << endl << "usage: sslserver [options]" << endl << endl << "Options:" << endl << "-p PORT, --use-port=PORT Use the specified port. The default is 6001." << endl << "-c CLST, --use-ciphers=CLST Negotiate using the selected cipher list CLST." << endl << " For instructions on how to format a CLST string," << endl << " refer to the 'ciphers' manpage. Not all ciphers" << endl << " are available for use with all certificates" << endl << "--refuse-ssl2 Do not allow clients to connect using SSLv2" << endl << " (SSL version 2 is known to have security holes)" << endl << "-n, --no-persist Shut down server after the connection is closed" << endl << "--help Print this help" << endl << "--version Print version information" << endl; } void PrintAdvisory() { cout << "Try 'sslserver --help' for more information." << endl; } int Communicate(SSL * ssl) // perform the communication with the client // return codes: // 0 success // -1 error { int err,l; char linebuffer[1024]; _string_ buffer; for(;;) { // The following code will run well because the communication // between the client and the server is SYNCHRONIZED. The client // send the whole line, the server reads it and after it's done, // the server sends the whole line back. The client can not send // any more data until it receives the whole line back. // NOTE: if the client shuts down the connection, we will recognize it // upon the next call to SSL_read(). (SSL_write() would not recognize it // immediately - this is just a note that the shutdown recognition is solved // at both sides) // read the whole line: the received data can NOT have LF anywhere // else than at the end l=0; buffer=linebuffer; do { err=SSL_read(ssl,buffer,sizeof(linebuffer)-l); if(err<=0) goto finish; // we must leave both for cycles on error l+=err; buffer+=err; } while(buffer[-1] != '\n' && l0;l-=err) { err=SSL_write(ssl,buffer,l); if(err<=0) goto finish; buffer+=err; } } finish: // check if data read error occurred due to communication shutdown // (in that case it is actually not an error) return ( (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) ? 0 : -1 ); } void AcceptSSLConnection(SSL * ssl) // accepts an SSL connection prepared in an SSL object. This function with slight // parameter modifications could be executed as a separate thread. { if (SSL_accept(ssl) <= 0) { ReportError("SSL object connection failed.",false); SSL_clear(ssl); return; } // perform the communication cout << "SSL connection open." << endl; // NOTE: the server does not do a post-connection check since: // 1) the certificate is always required from the client and thus the client // can not submit a NULL certificate (which would be checked) // 2) the address of the client can vary, so it's useless to check it if (!Communicate(ssl)) SSL_shutdown(ssl); else SSL_clear(ssl); cout << endl << "SSL connection closed." << endl; // clean up SSL_free(ssl); } // ======================================================= int main(int argc, char * argv[]) { int opt_index; // its contents will not be used. curr_opt will be used instead int curr_opt; char port[64]=DEFAULT_PORT; bool refuse_ssl2=false; char cipher_list[1024]; bool custom_cipher_setup=false; // ------------------------------------------------------- // handle command line arguments // opterr=0; // disable error messages issued by getopt_long while((curr_opt=getopt_long(argc,argv,short_options,options,&opt_index))!=-1) switch(curr_opt) { case '?': // invalid argument PrintAdvisory(); return(0); case 1: PrintVersion(); return(0); case 2: PrintHelp(); return(0); case 3: refuse_ssl2=true; break; case 'n': persistent=false; break; case 'p': set(port,optarg); break; case 'c': set(cipher_list,optarg); custom_cipher_setup=true; break; default: ReportError("Options processing failure."); break; } // ------------------------------------------------------------- cout << "Initializing OpenSSL..." << endl; InitSSL(); cout << "Starting the server at port " << port << endl; // ------------------------------------------------------------- // begin the SSL work SSL * ssl; SSL_CTX * context; BIO * acceptor; BIO * connection; // set up server SSL context // ------------------------- context = SSL_CTX_new(SSLv23_method()); // use custom cipher list if(custom_cipher_setup) if( SSL_CTX_set_cipher_list(context, cipher_list) != 1) ReportSSLError("Could not use the specified cipher list."); else cout << "Using the custom cipher list." << endl; // OpenSSL will need the server's private key and if it's encrypted (this version of sslserver // uses encrypted keys), the pass phrase for it is needed. In order not to ask the user, the // passphrase is stored in the specified file and the user callback function reads it from that file. SSL_CTX_set_default_passwd_cb(context,LoadPassphrase); if (SSL_CTX_use_certificate_chain_file(context, CERTFILE) != 1) ReportSSLError("Could not load server certificate from file."); else cout << "Server certificate loaded." << endl; if (SSL_CTX_use_PrivateKey_file(context, KEYFILE, SSL_FILETYPE_PEM) != 1) ReportSSLError("Could not load server private key from file."); else cout << "Server private key loaded." << endl; // set SSL context options, enable all SSL bug workarounds if(refuse_ssl2) { cout << "Insecure SSLv2 protocol will be refused." << endl; SSL_CTX_set_options(context, SSL_OP_ALL|SSL_OP_NO_SSLv2); } else SSL_CTX_set_options(context, SSL_OP_ALL); // VERIFICATION SETUP: // 1) load trusted certificates from FILE (not directory => that's why the 3rd argument is NULL) // The file contents are loaded immediately. if (SSL_CTX_load_verify_locations(context,TRUSTED_CA_FILE,NULL) != 1) ReportSSLError("Could not load trusted CA repository."); else cout << "Trusted CA repository loaded." << endl; // 2) set the depth of certificate verification in the chain SSL_CTX_set_verify_depth(context,VERIFY_DEPTH); // 3) set the verification callback filter and the verification options // the flags mean: 1) verify client certificate if supplied and 2) always require client certificate // => both mean: always require and verify client certificate SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, VerifyCert); // create and connect a listening socket BIO // ------------------------------------------ acceptor=BIO_new_accept(port); if(!acceptor) ReportSSLError("Could not create server listening socket BIO."); // put the socket BIO to listening mode if (BIO_do_accept(acceptor) <= 0) ReportSSLError("Could not enter listening mode."); do { cout << "Waiting for the connection (Press Ctrl-C to terminate) ..." << endl; /* SERIAL CONNECTION PROCESSING: The following code up to ********* is placed in an "infinite" loop. Because a new connected socket bio is obtained by detaching it from the acceptor bio, then, theoretically, many connections may be created this way at the same time and they might be handled by multiple threads. However, multithreading is not involved here: Since (it seems that) new BIOs are appended to the acceptor BIO upon each new connection, they stay ready for detaching even if there is already one connection open and thus they get detached and processed AFTER this connection shuts down, in the same order as they had connected. This happens in the mentioned loop. This means that the server can handle only one connection at a time and the other clients wait until this connection is closed. CONJECTURE: BIO_pop removes the SPECIFIED BIO from the chain and returns the next BIO if there is one. In order to explain the behavior described above, we must believe that the BIO chain may have more branches and more "2nd-level" BIOs may be attached to a single node of the chain (here it's the 'acceptor' bio - and it represents the head of the chain). If there is only one BIO attached to the acceptor, BIO_pop detaches the acceptor from it and that new BIO is returned. If there are several BIOs attached all to the acceptor, BIO_pop detaches acceptor only from one of them and that one is returned. For a BIO that is not the head of a chain, this would get more complicated... However, this conjecture is encouraged by an example, where a chain of four BIOs is created. (b1->b2->b3->b4) Instead of using BIO_push(b1,b2); BIO_push(b1,b3); BIO_push(b1,b4); they use BIO_push(b1,b2); BIO_push(b2,b3); BIO_push(b3,b4); ---------------------------- */ if(BIO_do_accept(acceptor) <=0) ReportSSLError("Could not accept incoming connection."); // detach the connected bio from the chain starting at 'acceptor' connection=BIO_pop(acceptor); cout << "Socket connection open. Starting SSL handshake." << endl; // create an SSL session object if (!(ssl = SSL_new(context))) ReportError("SSL context creation failed"); SSL_set_bio(ssl, connection, connection); // see sslclient.cc for notes on this line // the following function would be executed as a separate thread AcceptSSLConnection(ssl); // ********* } while(persistent); // ------------------------------------------------------------- // clean up SSL_CTX_free(context); BIO_free(acceptor); cout << "Server shut down." << endl; return(0); }