第三十一章 SSL实现 / 31.9 编程示例

本示例用多线程实现了一个ssl服务端和一个客户端。

服务端代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <memory.h>

#include <errno.h>

#ifndef    _WIN32

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include <unistd.h>

#else

#include <winsock2.h>

#include <windows.h>

#endif

#include "pthread.h"

#include <openssl/rsa.h>

#include <openssl/crypto.h>

#include <openssl/x509.h>

#include <openssl/pem.h>

#include <openssl/ssl.h>

#include <openssl/err.h>

#define CERTF "certs/sslservercert.pem"

#define KEYF  "certs/sslserverkey.pem"

#define    CAFILE  "certs/cacert.pem"

pthread_mutex_t    mlock=PTHREAD_MUTEX_INITIALIZER;

static pthread_mutex_t *lock_cs;

static long *lock_count;

#define CHK_NULL(x) if ((x)==NULL) { printf("null\n"); }

#define CHK_ERR(err,s) if ((err)==-1) { printf(" -1 \n"); }

#define CHK_SSL(err) if ((err)==-1) {  printf(" -1 \n");}

#define    CAFILE  "certs/cacert.pem"

 

int  verify_callback_server(int ok, X509_STORE_CTX *ctx)

{

              printf("verify_callback_server \n");

        return ok;

}

 

int    SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx,char *filename,char *pass)

{

       EVP_PKEY     *pkey=NULL;

       BIO               *key=NULL;

      

       key=BIO_new(BIO_s_file());

       BIO_read_filename(key,filename);

       pkey=PEM_read_bio_PrivateKey(key,NULL,NULL,pass);

       if(pkey==NULL)

       {

              printf("PEM_read_bio_PrivateKey err");

              return -1;

       }

       if (SSL_CTX_use_PrivateKey(ctx,pkey) <= 0)

       {

              printf("SSL_CTX_use_PrivateKey err\n");

              return -1;

       }

       BIO_free(key);

       return 1;

}

 

static int s_server_verify=SSL_VERIFY_NONE;

void * thread_main(void *arg)

{  

       SOCKET s,AcceptSocket;

       WORD wVersionRequested;

       WSADATA wsaData;

       struct sockaddr_in  service;

       int    err;

      size_t             client_len;                                                                                           SSL_CTX             *ctx;

      SSL        *ssl;

      X509             *client_cert;

      char        *str;

      char    buf[1024];

      SSL_METHOD     *meth;

        

       ssl=(SSL *)arg;

       s=SSL_get_fd(ssl);

       err = SSL_accept (ssl);

      if(err<0)

       {

              printf("ssl accerr\n");

              return ;

       }

      printf ("SSL connection using %s\n", SSL_get_cipher (ssl));

      client_cert = SSL_get_peer_certificate (ssl);

      if (client_cert != NULL)

      {

                   printf ("Client certificate:\n");

                     str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);

                   CHK_NULL(str);

                   printf ("\t subject: %s\n", str);

                   OPENSSL_free (str);

                     str = X509_NAME_oneline (X509_get_issuer_name  (client_cert), 0, 0);

                   CHK_NULL(str);

                   printf ("\t issuer: %s\n", str);

                   OPENSSL_free (str);

                     X509_free (client_cert);

      }

      else

                  printf ("Client does not have certificate.\n");

       memset(buf,0,1024);

       err = SSL_read (ssl, buf, sizeof(buf) - 1);

       if(err<0)

       {

              printf("ssl read err\n");

              closesocket(s);

              return;

       }

       printf("get : %s\n",buf);

#if 0

      buf[err] = '\0';

      err = SSL_write (ssl, "I hear you.", strlen("I hear you."));  CHK_SSL(err);

#endif

      SSL_free (ssl);

       closesocket(s);

}

 

pthread_t pthreads_thread_id(void)

{

       pthread_t ret;

 

       ret=pthread_self();

       return(ret);

}

 

void pthreads_locking_callback(int mode, int type, char *file,

            int line)

{

       if (mode & CRYPTO_LOCK)

              {

              pthread_mutex_lock(&(lock_cs[type]));

              lock_count[type]++;

              }

       else

              {

              pthread_mutex_unlock(&(lock_cs[type]));

              }

}

 

int main ()

{

       int                  err;                 

       int                  i;

       SOCKET        s,AcceptSocket;

       WORD           wVersionRequested;

       WSADATA            wsaData;

       struct sockaddr_in  service;

       pthread_tpid;

      size_t             client_len;

      SSL_CTX             *ctx;

      SSL               *ssl;

      X509             *client_cert;

       char        *str;

      char    buf[1024];

      SSL_METHOD     *meth;

 

      SSL_load_error_strings();

      SSLeay_add_ssl_algorithms();

      meth = SSLv3_server_method();

      ctx = SSL_CTX_new (meth);

      if (!ctx)

      {

                  ERR_print_errors_fp(stderr);

                  exit(2);

      }

       if ((!SSL_CTX_load_verify_locations(ctx,CAFILE,NULL)) ||

                (!SSL_CTX_set_default_verify_paths(ctx)))

    {

              printf("err\n");

              exit(1);

    }

      if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0)

      {

           ERR_print_errors_fp(stderr);

           exit(3);

      }

      if (SSL_CTX_use_PrivateKey_file_pass(ctx, KEYF, "123456") <= 0)

      {

                  ERR_print_errors_fp(stderr);

                  exit(4);

      }

       if (!SSL_CTX_check_private_key(ctx))

       {

                  fprintf(stderr,"Private key does not match the certificate public key\n");

                  exit(5);

      }

       s_server_verify=SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|

                                SSL_VERIFY_CLIENT_ONCE;

       SSL_CTX_set_verify(ctx,s_server_verify,verify_callback_server);

       SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file(CAFILE));

       wVersionRequested = MAKEWORD( 2, 2 );

       err = WSAStartup( wVersionRequested, &wsaData );

       if ( err != 0 )

       {

              printf("err\n");      

              return -1;

       }

       s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

       if(s<0) return -1;

       service.sin_family = AF_INET;

       service.sin_addr.s_addr = inet_addr("127.0.0.1");

       service.sin_port = htons(1111);

       if (bind( s, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR)

       {

              printf("bind() failed.\n");

              closesocket(s);

              return -1;

       }

    if (listen( s, 1 ) == SOCKET_ERROR)

              printf("Error listening on socket.\n");

 

       printf("recv .....\n");

       lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));

       lock_count=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));

       for (i=0; i<CRYPTO_num_locks(); i++)

       {

              lock_count[i]=0;

              pthread_mutex_init(&(lock_cs[i]),NULL);

       }

       CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);

       CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);

       while(1)

       {

              struct timeval tv;

              fd_set fdset;

              tv.tv_sec = 1;

              tv.tv_usec = 0;

              FD_ZERO(&fdset);

              FD_SET(s, &fdset);

           select(s+1, &fdset, NULL, NULL, (struct timeval *)&tv);

           if(FD_ISSET(s, &fdset))

              {

                     AcceptSocket=accept(s, NULL,NULL);

                     ssl = SSL_new (ctx);      

                    CHK_NULL(ssl);

                     err=SSL_set_fd (ssl, AcceptSocket);

                     if(err>0)

                     {

                            err=pthread_create(&pid,NULL,&thread_main,(void *)ssl);

                            pthread_detach(pid);

                     }

                     else

                            continue;

              }

       }

      SSL_CTX_free (ctx);

      return 0;

}

客户端代码如下:

#include <stdio.h>

#include <memory.h>

#include <errno.h>

#ifndef    _WIN32

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include <unistd.h>

#else

#include <windows.h>

#endif

#include "pthread.h"

#include <openssl/crypto.h>

#include <openssl/x509.h>

#include <openssl/pem.h>

#include <openssl/ssl.h>

#include <openssl/err.h>

#define    MAX_T 1000

#define    CLIENTCERT       "certs/sslclientcert.pem"

#define    CLIENTKEY  "certs/sslclientkey.pem"

#define    CAFILE         "certs/cacert.pem"

static pthread_mutex_t *lock_cs;

static long *lock_count;

 

pthread_t pthreads_thread_id(void)

{

       pthread_t ret;

 

       ret=pthread_self();

       return(ret);

}

 

void pthreads_locking_callback(int mode, int type, char *file,

            int line)

{

       if (mode & CRYPTO_LOCK)

              {

              pthread_mutex_lock(&(lock_cs[type]));

              lock_count[type]++;

              }

       else

              {

              pthread_mutex_unlock(&(lock_cs[type]));

              }

}

 

int    verify_callback(int ok, X509_STORE_CTX *ctx)

{

       printf("verify_callback\n");

       return ok;

}

 

int    SSL_CTX_use_PrivateKey_file_pass(SSL_CTX *ctx,char *filename,char *pass)

{

       EVP_PKEY     *pkey=NULL;

       BIO               *key=NULL;

      

       key=BIO_new(BIO_s_file());

       BIO_read_filename(key,filename);

       pkey=PEM_read_bio_PrivateKey(key,NULL,NULL,pass);

       if(pkey==NULL)

       {

              printf("PEM_read_bio_PrivateKey err");

              return -1;

       }

       if (SSL_CTX_use_PrivateKey(ctx,pkey) <= 0)

       {

              printf("SSL_CTX_use_PrivateKey err\n");

              return -1;

       }

       BIO_free(key);

       return 1;

}

 

void*thread_main(void *arg)

{

       int          err,buflen,read;

      int          sd;

       SSL_CTX             *ctx=(SSL_CTX *)arg;

       struct            sockaddr_in dest_sin;

       SOCKET        sock;

       PHOSTENT   phe;

       WORD           wVersionRequested;

       WSADATA            wsaData;

      SSL               *ssl;

      X509             *server_cert;

      char     *str;

      char        buf [1024];

      SSL_METHOD     *meth;

       FILE              *fp;

 

       wVersionRequested = MAKEWORD( 2, 2 );

       err = WSAStartup( wVersionRequested, &wsaData );

       if ( err != 0 )

       {

              printf("WSAStartup err\n");      

              return -1;

       }

       sock = socket(AF_INET, SOCK_STREAM, 0);

       dest_sin.sin_family = AF_INET;

       dest_sin.sin_addr.s_addr = inet_addr( "127.0.0.1" );

       dest_sin.sin_port = htons( 1111 );

 

again:

       err=connect( sock,(PSOCKADDR) &dest_sin, sizeof( dest_sin));

       if(err<0)

       {

              Sleep(1);

              goto again;

       }

    ssl = SSL_new (ctx);                        

       if(ssl==NULL)

       {

              printf("ss new err\n");

              return ;

       }

       SSL_set_fd(ssl,sock);

      err = SSL_connect (ssl);                    

      if(err<0)

       {

              printf("SSL_connect err\n");

              return;

       }

      printf ("SSL connection using %s\n", SSL_get_cipher (ssl));

      server_cert = SSL_get_peer_certificate (ssl);      

      printf ("Server certificate:\n");

      str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);

      printf ("\t subject: %s\n", str);

      OPENSSL_free (str);

      str = X509_NAME_oneline (X509_get_issuer_name  (server_cert),0,0);

      printf ("\t issuer: %s\n", str);

      OPENSSL_free (str);  

      X509_free (server_cert);

       err = SSL_write (ssl, "Hello World!", strlen("Hello World!"));

       if(err<0)

       {

              printf("ssl write err\n");

              return ;

       }

#if 0

       memset(buf,0,ONE_BUF_SIZE);

      err = SSL_read (ssl, buf, sizeof(buf) - 1);                  

       if(err<0)

       {

              printf("ssl read err\n");

              return ;

       }

      buf[err] = '\0';

      printf ("Got %d chars:'%s'\n", err, buf);

#endif

      SSL_shutdown (ssl);  /* send SSL/TLS close_notify */

      SSL_free (ssl);

       closesocket(sock);

}

 

int    main ()

{

       int          err,buflen,read;

      int          sd;

 

       struct            sockaddr_in dest_sin;

       SOCKETsock;

       PHOSTENT phe;

       WORD wVersionRequested;

       WSADATA wsaData;

      SSL_CTX             *ctx;

      SSL        *ssl;

      X509             *server_cert;

      char     *str;

      char        buf [1024];

      SSL_METHOD     *meth;

       int           i;

       pthread_tpid[MAX_T];

     

      SSLeay_add_ssl_algorithms();

      meth = SSLv3_client_method();

      SSL_load_error_strings();

      ctx = SSL_CTX_new (meth);                      

       if(ctx==NULL)

       {

              printf("ssl ctx new eer\n");

              return -1;

       }

 

       if (SSL_CTX_use_certificate_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM) <= 0)

    {

        ERR_print_errors_fp(stderr);

        exit(3);

    }

    if (SSL_CTX_use_PrivateKey_file_pass(ctx, CLIENTKEY, "123456") <= 0)

    {

         ERR_print_errors_fp(stderr);

         exit(4);

     }

       lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));

       lock_count=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));

       for (i=0; i<CRYPTO_num_locks(); i++)

       {

              lock_count[i]=0;

              pthread_mutex_init(&(lock_cs[i]),NULL);

       }

       CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);

       CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);

       for(i=0;i<MAX_T;i++)

       {           

              err=pthread_create(&(pid[i]),NULL,&thread_main,(void *)ctx);

              if(err!=0)

              {

                     printf("pthread_create err\n");

                     continue;

              }

       }

       for (i=0; i<MAX_T; i++)

       {

              pthread_join(pid[i],NULL);

       }

      SSL_CTX_free (ctx);

      printf("test ok\n");

       return 0;

}

上述程序在windows下运行成功,采用了windows下的开源pthread库。

需要注意的是,如果多线程用openssl,需要设置两个回调函数

CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);

CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);