/***********************************************\ * The SSL client (sample SSL toolkit) * * (C) 2005 Digithell, Inc. (Pavel Strachota) * * file: sslclient.cc * \***********************************************/ #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="client_cert/client.pem"; static const _string_ KEYFILE="client_cert/client.pem"; // the following file may contain many PEM-formatted certificates static const _string_ TRUSTED_CA_FILE="client_cert/CA_cert.pem"; const _string_ PASSFILE="client_cert/password.txt"; enum SSLProtocolVersion { SSLv2, SSLv3, TLSv1 }; // ------------------------------------------------------- // 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 }, { "protocol-version", required_argument, NULL, 'v' }, { "use-port", required_argument, NULL, 'p' }, { "check-host", no_argument, NULL, 'h' }, { "use-hostname", required_argument, NULL, 'n' }, { "use-port", required_argument, NULL, 'p' }, { "use-ciphers", required_argument, NULL, 'c' }, { 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 = "v:p:c:n:h"; // the shorts for '--version' and '--help' are not supported // ------------------------------------------------------- void PrintVersion() { cout << "SSLClient Version 0.1A, (C) 2005 Digithell, Inc. (Pavel Strachota)" << endl; } void PrintHelp() { PrintVersion(); cout << endl << "usage: sslclient [options] host" << endl << endl << "Options:" << endl << "-p PORT, --use-port=PORT Use the specified port. The default is 6001." << endl << "-v VER, --protocol-version=VER Use the specified protocol version, one of" << endl << " { ssl2, ssl3, tls }. ssl3 is the default." << 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 << "-h, --check-host Verify the host name in the server certificate" << endl << "-n NAME, --use-hostname=NAME Use an alternate host name for verification" << endl << " (has effect together with -h option only)" << endl << "--help Print this help" << endl << "--version Print version information" << endl; } void PrintAdvisory() { cout << "Try 'sslclient --help' for more information." << endl; } const _string_ SSLProtocolName(SSLProtocolVersion pv) { switch(pv) { case SSLv2: return("SSL Version 2"); case SSLv3: return("SSL Version 3"); case TLSv1: return("TLS"); default: return("Unknown"); } } int SendData(SSL * ssl) // sends the data from standard input // return codes: // 0 success // -1 error { int err, nwritten, 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. cin.getline(linebuffer,sizeof(linebuffer)); // get the first sizeof(buffer)-1 characters of the line // (lines longer than the buffer make some problems, however) l=len(linebuffer); if(l==0) { // an empty line - abandon connection cout << "Shutting down the connection..." << flush; break; } if(l0;l-=err) { err=SSL_write(ssl,buffer,l); if(err<=0) goto finish; buffer+=err; } // NOTE: if the server 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) cout << ">> Awaiting echo: " << flush; // read one line of data (possibly different in both length and contents) // The only condition the server must fulfill is that the data must end // with the LF character (the same condition as the client fulfills) 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' && l 0) { int i; for (i = 0; i < extcount; i++) { char *extstr; X509_EXTENSION *ext; ext = X509_get_ext(cert, i); extstr = (char*) OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext))); if (match(extstr, "subjectAltName")) { int j; unsigned char *data; STACK_OF(CONF_VALUE) *val; CONF_VALUE *nval; X509V3_EXT_METHOD *meth; void *ext_str = NULL; if (!(meth = X509V3_EXT_get(ext))) break; data = ext->value->data; #if (OPENSSL_VERSION_NUMBER > 0x00907000L) if (meth->it) ext_str = ASN1_item_d2i(NULL, &data, ext->value->length, ASN1_ITEM_ptr(meth->it)); else ext_str = meth->d2i(NULL, &data, ext->value->length); #else ext_str = meth->d2i(NULL, &data, ext->value->length); #endif val = meth->i2v(meth, ext_str, NULL); for (j = 0; j < sk_CONF_VALUE_num(val); j++) { nval = sk_CONF_VALUE_value(val, j); if (match(nval->name, "DNS") && match(nval->value, host)) { ok = true; break; } } } if (ok) break; } } if (!ok && (subj = X509_get_subject_name(cert)) && X509_NAME_get_text_by_NID(subj, NID_commonName, data, 256) > 0) { data[255] = 0; if (scompare(data, host, NOT SENSITIVE) != 0) { X509_free(cert); return(-3); } } } // if(host != NULL) X509_free(cert); return( (SSL_get_verify_result(ssl) == X509_V_OK) ? 0 : -2 ); } // ======================================================= int main(int argc, char * argv[]) { int opt_index; // its contents will not be used. curr_opt will be used instead int curr_opt; int chk_error; _string_ host; bool alternate_hostname=false; bool check_hostname=false; char hostname[128]; char address[256]; char port[64]=DEFAULT_PORT; char cipher_list[1024]; bool custom_cipher_setup=false; SSLProtocolVersion protocol=SSLv3; // ------------------------------------------------------- // 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 'v': if(match(optarg,"ssl2")) protocol=SSLv2; else if(match(optarg,"ssl3")) protocol=SSLv3; else if(match(optarg,"tls")) protocol=TLSv1; else { ReportError("Invalid protocol specification.",false); PrintAdvisory(); return(1); } break; case 'p': set(port,optarg); break; case 'h': check_hostname=true; break; case 'n': set(hostname,optarg); alternate_hostname=true; break; case 'c': set(cipher_list,optarg); custom_cipher_setup=true; break; default: ReportError("Options processing failure."); break; } // now the variable optind contains the number of processed command line arguments // (i.e. options and their arguments) increased by 1, because its initial value was 1. // Moreover, the non-option arguments were automatically moved further in the argv[] // array. As a result (concerning that argv[1] is the first argument), argv[optind] is // the first non-option argument. // retrieve host address to connect to if(argc<=optind) { ReportError("Host address required.",false); PrintAdvisory(); return(1); } host=argv[optind]; // ------------------------------------------------------------- cout << "Initializing OpenSSL..." << endl; InitSSL(); cout << "Connecting to " << host << " at port " << port << endl; cout << "Selected protocol is: " << SSLProtocolName(protocol) << endl; set(address,host); add(address,":"); add(address,port); // ------------------------------------------------------------- // begin the SSL work SSL * ssl; SSL_CTX * context; BIO * connection; // set up client SSL context // ------------------------- SSL_METHOD * method; // select the communication protocol switch(protocol) { case SSLv2: method=SSLv2_method(); break; case SSLv3: method=SSLv3_method(); break; case TLSv1: method=TLSv1_method(); break; } context = SSL_CTX_new(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; // in case client authentication is required (in this version of sslserver it is): // OpenSSL will need the client's private key and if it's encrypted (this version of sslclient // 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 client certificate from file."); else cout << "Client certificate loaded." << endl; if (SSL_CTX_use_PrivateKey_file(context, KEYFILE, SSL_FILETYPE_PEM) != 1) ReportSSLError("Could not load client private key from file."); else cout << "Client private key loaded." << endl; // set SSL context options, enable all SSL bug workarounds 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) always verify server certificate if anonymous ciphers are not allowed // (which is the default) SSL_CTX_set_verify(context, SSL_VERIFY_PEER, VerifyCert); if(check_hostname) { if(!alternate_hostname) set(hostname,host); cout << "The server host name will be compared to: " << hostname << endl; } // create and connect a connection socket BIO // ------------------------------------------ connection=BIO_new_connect(address); if(!connection) ReportSSLError("Could not create connection BIO."); if (BIO_do_connect(connection) <= 0) ReportSSLError("Can't connect to remote machine."); cout << "Server found. 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); // SSL object is a full duplex I/O interface. // It can generally use two different BIOs for input // and for output if (SSL_connect(ssl) <= 0) ReportError("SSL object connection failed."); // perform the communication // ------------------------- cout << "SSL connection open." << endl; // perform post connection check chk_error = check_hostname ? PostConnectionCheck(ssl,hostname) : PostConnectionCheck(ssl); switch(chk_error) { case 0: if (!SendData(ssl)) SSL_shutdown(ssl); else SSL_clear(ssl); break; case -1: ReportError("Post-connection check: NULL certificate ecnountered.",false); break; case -2: ReportError("Post-connection check: Certificate verification failed.",false); break; case -3: ReportError("Post-connection check: Host name in the certificate does not match the actual host.",false); } cout << endl << "SSL connection closed." << endl; // ------------------------------------------------------------- // clean up SSL_free(ssl); SSL_CTX_free(context); cout << "Done." << endl; return(0); }