Openssl 範例程式:建立SSL連線

說來很神奇,雖然我研究所的研究主題是「網路安全」,但我在研究所卻從來沒撰寫使用 openssl 函式庫的程式。第一次撰寫是在工作上有需求而去使用一些加密的演算法,這次則是工作尚有需求,要去建立 DTLS 的安全連線。DTLS?等一下,標題不是 SSL 嗎?說來話長,要使用 DTLS 的相關 API,按照 openssl 網站的說法,最好是使用 openssl 1.0 以後的版本,問題是客戶所使用的版本是 0.9.8d 的。理論上來說,0.9.8d 的版本也有支援 DTLS,所以我不能拒絕客戶的要求(我會努力繼續嘗試說服客戶的),但還是要做好在 0.9.8d 的開發。問題是兩個版本的寫法不一樣啊(在 1.0.0 以後的版本,有一個 dtlsv1_listen 的函式,簡單來說就是在 UDP 上面實作 listen 的行為。)~雖然已經請同事搞定 1.0 上面的寫法,但 0.9.8 上的寫法還要再研究。所以我就進去開始研究囉。下面就是研究到一半(SSL)的心得。下面的程式碼是從 HP 的網站上改過來的。

ssl_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
 
#define RSA_SERVER_CERT     "serverca.crt"
#define RSA_SERVER_KEY      "serverca.key"

#define RSA_SERVER_CA_CERT  "rootca.crt"
 
#define ON   1
#define OFF  0
 
#define RETURN_NULL(x) if ((x)==NULL) exit(1)
#define RETURN_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }
#define RETURN_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(1); }
 
int main()
{
    int     err;
    int     verify_client = ON; /* To verify a client certificate, set ON */
 
    int             listen_sock;
    int             sock;
    struct          sockaddr_in sa_serv;
    struct          sockaddr_in sa_cli;
    unsigned int    client_len;
    
    char    *str;
    char    buf[4096];
    char    client_ip_str[INET_ADDRSTRLEN];
 
    SSL_CTX         *ctx;
    SSL             *ssl;
    SSL_METHOD      *meth;

    X509            *client_cert = NULL;
 
    short int       s_port = 5555;

/*----------------------------------------------------------------*/

    /* Load encryption & hashing algorithms for the SSL program */
    SSL_library_init();
 
    /* Load the error strings for SSL & CRYPTO APIs */
    SSL_load_error_strings();
 
    /* Create a SSL_METHOD structure (choose a SSL/TLS protocol version) */
    meth = SSLv3_method();
 
    /* Create a SSL_CTX structure */
    ctx = SSL_CTX_new(meth);
 
    if (!ctx) 
    {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
 
    /* Load the server certificate into the SSL_CTX structure */
    if ( SSL_CTX_use_certificate_file(ctx, RSA_SERVER_CERT, SSL_FILETYPE_PEM) <= 0 ) 
    {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
 
    /* Load the private-key corresponding to the server certificate */
    if ( SSL_CTX_use_PrivateKey_file(ctx, RSA_SERVER_KEY, SSL_FILETYPE_PEM) <= 0 ) 
    {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
 
    /* Check if the server certificate and private-key matches */
    if ( !SSL_CTX_check_private_key(ctx) ) 
    {
        fprintf(stderr,"Private key does not match the certificate public key\n");
        exit(1);
    }
 
    if( verify_client == ON ) 
    {
        /* Load the RSA CA certificate into the SSL_CTX structure */
        if ( !SSL_CTX_load_verify_locations( ctx, RSA_SERVER_CA_CERT, NULL ) ) 
        {
            ERR_print_errors_fp(stderr);
            exit(1);
        }
 
        /* Set to require peer (client) certificate verification */
        SSL_CTX_set_verify( ctx, SSL_VERIFY_PEER, NULL );
 
        /* Set the verification depth to 1 */
        SSL_CTX_set_verify_depth(ctx,1);
 
    }

/* ----------------------------------------------- */
       
    /* Set up a TCP socket */
 
    listen_sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );   
 
    RETURN_ERR(listen_sock, "socket");
    memset (&sa_serv, '\0', sizeof(sa_serv));
    sa_serv.sin_family      = AF_INET;
    sa_serv.sin_addr.s_addr = INADDR_ANY;
    sa_serv.sin_port        = htons (s_port);          /* Server Port number */
    err = bind(listen_sock, (struct sockaddr*)&sa_serv,sizeof(sa_serv));
 
    RETURN_ERR( err, "bind" );
     
    /* Wait for an incoming TCP connection. */
    err = listen( listen_sock, 5 );                    
 
    RETURN_ERR(err, "listen");
    client_len = sizeof( sa_cli );
 
    /* Socket for a TCP/IP connection is created */
    sock = accept( listen_sock, (struct sockaddr*)&sa_cli, &client_len );
 
    RETURN_ERR(sock, "accept");
    close (listen_sock);
 
    inet_ntop( AF_INET, &( sa_cli.sin_addr.s_addr ), client_ip_str, INET_ADDRSTRLEN );
    printf ("Connection from %s, port %d\n", client_ip_str, sa_cli.sin_port);
 
/* ----------------------------------------------- */

    /* TCP connection is ready. */
    /* A SSL structure is created */
    ssl = SSL_new(ctx);
 
    RETURN_NULL(ssl);
 
    /* Assign the socket into the SSL structure (SSL and socket without BIO) */
    SSL_set_fd(ssl, sock);
 
    /* Perform SSL Handshake on the SSL server */
    err = SSL_accept(ssl);
 
    RETURN_SSL(err);
 
    /* Informational output (optional) */
    printf("SSL connection using %s\n", SSL_get_cipher (ssl));
 
    if (verify_client == ON)
    {
        /* Get the client's certificate (optional) */
        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);
            RETURN_NULL(str);
            
            printf ("\t subject: %s\n", str);
            free (str);
            
            str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
            RETURN_NULL(str);
            
            printf ("\t issuer: %s\n", str);
            free (str);
            X509_free(client_cert);
        }  
        else
        {
            printf("The SSL client does not have certificate.\n");
        }
    }
 
    /*------- DATA EXCHANGE - Receive message and send reply. -------*/
    /* Receive data from the SSL client */
    
    err = SSL_read(ssl, buf, sizeof(buf) - 1);
 
    RETURN_SSL(err);
 
    buf[err] = '\0';
 
    printf ("Received %d chars:'%s'\n", err, buf);
 
    /* Send data to the SSL client */
    err = SSL_write( ssl, "This message is from the SSL server", strlen("This message is from the SSL server"));
 
    RETURN_SSL(err);
 
    /*--------------- SSL closure ---------------*/
    /* Shutdown this side (server) of the connection. */
    
    err = SSL_shutdown(ssl);
 
    RETURN_SSL(err);
 
    /* Terminate communication on a socket */
    err = close(sock);
 
    RETURN_ERR(err, "close");
 
    /* Free the SSL structure */
    SSL_free(ssl);
 
    /* Free the SSL_CTX structure */
    SSL_CTX_free(ctx);
    
    return 0;
}

ssl_client.c


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
 
#define RETURN_NULL(x) if ((x)==NULL) exit (1)
#define RETURN_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }
#define RETURN_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(1); }
 
#define RSA_CLIENT_CERT     "clientca.crt"
#define RSA_CLIENT_KEY      "clientca.key"
 
#define RSA_CLIENT_CA_CERT  "rootca.crt"
 
#define ON      1
#define OFF     0
 
int main()
{
    int     err;

    int     verify_client = ON; /* To verify a client certificate, set ON */
    int     sock;
    struct  sockaddr_in server_addr;
    char    *str;
    char    buf [4096];
    char    hello[80];

    SSL_CTX         *ctx;
    SSL             *ssl;
    SSL_METHOD      *meth;
    X509            *server_cert;

    short int       s_port = 5555;
    const char      *s_ipaddr = "127.0.0.1";
 
/*----------------------------------------------------------*/
    
    printf ("Message to be sent to the SSL server: ");
    fgets ( hello, 80, stdin );
 
    /* Load encryption & hashing algorithms for the SSL program */
    SSL_library_init();
 
    /* Load the error strings for SSL & CRYPTO APIs */
    SSL_load_error_strings();
 
    /* Create an SSL_METHOD structure (choose an SSL/TLS protocol version) */
    meth = SSLv3_method();
 
    /* Create an SSL_CTX structure */
    ctx = SSL_CTX_new(meth);                        
 
    RETURN_NULL(ctx);

/*----------------------------------------------------------*/
      
    if( verify_client == ON )
    { 
        /* Load the client certificate into the SSL_CTX structure */
        if ( SSL_CTX_use_certificate_file(ctx, RSA_CLIENT_CERT,  SSL_FILETYPE_PEM) <= 0 ) 
        {
            ERR_print_errors_fp( stderr );
            exit(1);
        }
 
        /* Load the private-key corresponding to the client certificate */
        if ( SSL_CTX_use_PrivateKey_file( ctx, RSA_CLIENT_KEY, SSL_FILETYPE_PEM ) <= 0 ) 
        {
            ERR_print_errors_fp(stderr);
            exit(1);
        }
 
        /* Check if the client certificate and private-key matches */
        if ( !SSL_CTX_check_private_key(ctx)) 
        {
            fprintf(stderr,"Private key does not match the certificate public key\n " );
            exit(1);
        }
    }
 
    /* Load the RSA CA certificate into the SSL_CTX structure */
    /* This will allow this client to verify the server's     */
    /* certificate.                                           */

    if ( !SSL_CTX_load_verify_locations( ctx, RSA_CLIENT_CA_CERT, NULL ) ) 
    {
        ERR_print_errors_fp(stderr);
        exit(1);
    }
 
    /* Set flag in context to require peer (server) certificate verification */
 
    SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL);
 
    SSL_CTX_set_verify_depth(ctx,1);

/* ------------------------------------------------------------- */

    /* Set up a TCP socket */
 
    sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);       
 
    RETURN_ERR(sock, "socket");
 
    memset (&server_addr, '\0', sizeof(server_addr));
    server_addr.sin_family      = AF_INET; 
    server_addr.sin_port        = htons(s_port);       /* Server Port number */ 
    server_addr.sin_addr.s_addr = inet_addr(s_ipaddr); /* Server IP */
 
    /* Establish a TCP/IP connection to the SSL client */
 
    err = connect(sock, (struct sockaddr*) &server_addr, sizeof(server_addr)); 
 
    RETURN_ERR(err, "connect");

/* ----------------------------------------------- */

    /* An SSL structure is created */
    ssl = SSL_new (ctx);
 
    RETURN_NULL(ssl);
 
    /* Assign the socket into the SSL structure (SSL and socket without BIO) */
    SSL_set_fd(ssl, sock);
 
    /* Perform SSL Handshake on the SSL client */
    err = SSL_connect(ssl);
 
    RETURN_SSL(err);
 
    /* Informational output (optional) */
    printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
 
    /* Get the server's certificate (optional) */
    server_cert = SSL_get_peer_certificate (ssl);    
 
    if ( server_cert != NULL )
    {
        printf ("Server certificate:\n");

        str = X509_NAME_oneline(X509_get_subject_name(server_cert),0,0);
        RETURN_NULL(str);
        
        printf ("\t subject: %s\n", str);
        free (str);
 
        str = X509_NAME_oneline(X509_get_issuer_name(server_cert),0,0);
        RETURN_NULL(str);
        printf ("\t issuer: %s\n", str);
        free(str);
 
        X509_free (server_cert);

    }
    else
    {
        printf("The SSL server does not have certificate.\n");
    }
 
    /*-------- DATA EXCHANGE - send message and receive reply. -------*/
    /* Send data to the SSL server */
    err = SSL_write(ssl, hello, strlen(hello));  
 
    RETURN_SSL(err);
 
    /* Receive data from the SSL server */
    err = SSL_read(ssl, buf, sizeof(buf)-1);                     
 
    RETURN_SSL(err);
    buf[err] = '\0';
    printf ("Received %d chars:'%s'\n", err, buf);
 
    /*--------------- SSL closure ---------------*/
    /* Shutdown the client side of the SSL connection */
 
    err = SSL_shutdown(ssl);
    RETURN_SSL(err);
 
    /* Terminate communication on a socket */
    err = close(sock);
 
    RETURN_ERR(err, "close");
 
    /* Free the SSL structure */
    SSL_free(ssl);
 
    /* Free the SSL_CTX structure */
    SSL_CTX_free(ctx);
    
    return 0;
}

下面是 Makefile:



all:
    gcc -Wall ssl_client.c -o ssl_client -lssl -lcrypto
    gcc -Wall ssl_server.c -o ssl_server -lssl -lcrypto
clean:
    rm ssl_client ssl_server

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP