欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux开发——http加密(https)

程序员文章站 2022-07-09 15:27:57
...

简介

HTTPS (全称:Hypertext Transfer Protocol over Secure Socket Layer) 是HTTP的安全版

HTTPS默认使用TCP端口443,也可以指定其他的TCP端口。

URI格式      https//:URL

加密简介

对称加密:

指的是加密方和解密方使用的是同一个**

优点:加密解密的速度很快

缺点:如果两个从未通信过的用户要进行通信的时候,该如何把解密的**传输给对方呢(**仍然要在网络上传输,所以**还是可能会被“中间人”截获),这是对称加密最大的缺点;

常见的对称加密算法有:

    DES:使用56位的**,2000年的时候被人**了,所以现在基本不再使用

    AES:高级加密标准,可以使用128,129,256三种长度**

其他的还有blowfish,Twofish和RC6,IDEA(商业算法),CAST5等

非对称加密

非对称加密方式解决了对称加密的缺陷,它的加密和解***是不同的,比如对一组数字加密,我们可以用公钥对其加密,然后我们想要将其还原,就必须用私钥进行解密,公钥和私钥是配对使用的,常见的非对称加密算法有:

公钥加密 私钥解密

 RSA:既可以用来加密解密,又可以用来实现用户认证

 DSA:只能用来加密解密,所以使用范围没有RSA广

非对称加密长度通常有512,1024,2048,4096位,最常用的就是2048位,长度固然可以增加安全性但是需要花费很长时间来进行加密/解密,和对称加密相比,加密/解密的时间差不多是对称加密的1000倍,所以我们通常用其作为用户认证,用对称加密来实现数据的加密/解密

单项加密

单向加密就是用来计算一段数据的特征码的,为了防止用户通过“暴力**”的方式解密,所以单向加密一般具有“雪崩效应”就是说:只要被加密内容有一点点的不同,加密所得结果就会有很大的变化。单项加密还有一个特点就是无论被加密的内容多长/短,加密的结果(就是提取特征码)是定长的,用途:用于验证数据的完整性,常用的单项加密算法

MD5:这种加密算法固定长度为128位

SHA1:这种加密算法固定长度是160位

 

ssl简介

SSL协议的功能

1)保证传输数据的保密性

2)保证传输数据的完整性  rc4

3)实现通信双方的互相身份认证-----非对称加密

SSL协议在协议栈的位置

Linux开发——http加密(https)

SSL 协议是一个分层的协议,共有两层组成。

高层协议包括 :

        SSL 握手协议(SSL HandshakeProtocol) 、

        改变加密约定协议(Change Cipher Spec Protocol) 、

        报警协议(AlertProtocol)

处于 SSL 协议的底层的是:  SSL记录层协议(SSL Record Protocol)

https加密详解

Linux开发——http加密(https)

随机数+Key值+公钥---->私钥

解释如下,先说BOB和ALICE通信阶段

黑框A:表示要传输的数据
黑框B:就是单项加密对这段数据提取的特征码,这段特征码同时运用了非对称加密,具体过程是用BOB的私钥加密,传输给ALICE,只要到达后ALICE能解密,表明对方确实是BOB。这一过程同时起到了用户认证和数据完整性的校验。黑框B又称为数字签名
红框A:这一阶段会生成一段很长的随机数(**)然后配合对称加密算法对黑框A和黑框B加密,但是我们如何把加密的**传输给ALICE呢?这就要用到红框B了
红框B:这一阶段是用ALICE的公钥加密这串随机数(对称加密阶段的**),ALICE接受到数据后如果能用自己私钥解密,那就证明接受者确实ALICE


加密过程:
第一步:用单向加密算法提取数据(黑框A)的特征值
第二步:用自己的私钥加密这段特征值形成黑框B
第三步:用对称加密算法,对黑框A和黑框B来加密,得到红框A
第四步:用ALICE的公钥来加密第三步所用的**,得到红框B


解密过程:
第一步:ALICE用自己的私钥解密红框B得到对称加密的**
第二步:用这个**解密红框A内容
第三步:用BOB的公钥解密黑框B,如果能成功,说明发送方确实是BOB,这就完成了身份验证(解密后会得到一串数据的特征值)
第四步:用同样的单项加密算法来对这段数据提取特征值,如果和第三步的特征值一样,说明这段数据是完整的,这就完成了数据完整性的校验

openssl安装命令

apt-get install openssl      

apt-get install libssl-dev    //开发人员需要再安装这个开发库

私钥公钥,中间服务认证,服务器认证,客户端认证等钥匙制作流程

详见文档:https://download.csdn.net/download/qq_25490573/11944848

ssl加密流程

每个数据包都有不同的对称加密秘钥,随机生成

如何获得对方公钥

证书

      公钥

      数据    --->单项加密

                 ---->对称加密

                 ---->非对称的加密(使用对方的公钥)

每次进行数据传输的时候,每包数据都有一个随机生成的**

制作ca---建立一个server的公钥,建立一个client的公钥

server   私钥,公钥

client     私钥,公钥

自建根证书

Linux开发——http加密(https)

生成服务器证书,并用自建根证书签名

Linux开发——http加密(https)

openssl工具库用法

Linux开发——http加密(https)

ssl全流程握手

Linux开发——http加密(https)

实战例子

server端

#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/opensslv.h>




#define DMSG(msg_fmt, msg_args...) \
    printf("%s(%04u): " msg_fmt "\n", __FILE__, __LINE__, ##msg_args)

#define SSL_FILL_EMSG(msg_buf) ERR_error_string_n(ERR_get_error(), msg_buf, sizeof(msg_buf))
#define SSL_FILL_IO_EMSG(ssl_fd, msg_buf) \
    ERR_error_string_n(SSL_get_error(ssl_fd), msg_buf, sizeof(msg_buf))




int shutdown_process = 0;
char *server_key_passphrase = NULL;
char ssl_ebuf[128];




void signal_handle(
    int signal_value)
{
    switch(signal_value)
    {
        case SIGINT:
        case SIGQUIT:
        case SIGTERM:
            shutdown_process = 1;
            break;
    }

    return;
}

int ssl_init(
    SSL_CTX **ssl_ctx_buf)
{
    const SSL_METHOD *ssl_method;
    SSL_CTX *ssl_ctx;


    // SSL 函式庫初始化.
    SSL_library_init();
    // 載入 SSL 錯誤訊息.
    SSL_load_error_strings();
    // 載入 SSL 演算法.
    OpenSSL_add_all_algorithms();

    // 指定使用 TLSv1 協定.
#if OPENSSL_VERSION_NUMBER >= 0x10002000
    ssl_method = TLSv1_2_server_method();
#else
    ssl_method = TLSv1_server_method();
#endif
    if(ssl_method == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_01;
    }

    // 建立 CTX 物件.
    ssl_ctx = SSL_CTX_new(ssl_method);
    if(ssl_ctx == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_01;
    }

    // 此函式非必要, 設定使用的加密演算法的優先權.
    // 使用方法 https://linux.die.net/man/3/ssl_ctx_set_cipher_list
    if(SSL_CTX_set_cipher_list(ssl_ctx, "DEFAULT") != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_02;
    }

#if OPENSSL_VERSION_NUMBER >= 0x10002000
    // ECDH 相關, OpenSSL 1.0.2 以上才支援.
    if(SSL_CTX_set_ecdh_auto(ssl_ctx, 1) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_set_ecdh_auto() fail [%s]", ssl_ebuf);
        goto FREE_02;
    }
#endif

    *ssl_ctx_buf = ssl_ctx;

    return 0;
FREE_02:
    SSL_CTX_free(ssl_ctx);
FREE_01:
    return -1;
}

int ssl_set_key_passphrase(
    char *buf,
    int size,
    int rwflag,
    void *userdata)
{
    return snprintf(buf, size, "%s", server_key_passphrase);
}

int ssl_load_pem(
    SSL_CTX *ssl_ctx,
    char *server_passphrase,
    char *server_key_path,
    char *server_cert_path,
    char *server_chain_path)
{
    // 設定伺服器的私鑰的密碼.
    // 如果沒有使用 SSL_CTX_set_default_passwd_cb() 設定則會要求手動輸入.
    if(server_passphrase != NULL)
        SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_set_key_passphrase);

    // 載入伺服器的私鑰 (PEM 格式).
    if(SSL_CTX_use_PrivateKey_file(ssl_ctx, server_key_path, SSL_FILETYPE_PEM) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_use_PrivateKey_file(%s) fail [%s]", server_key_path, ssl_ebuf);
        return -1;
    }

    // 載入伺服器的憑證 (PEM 格式).
    if(SSL_CTX_use_certificate_file(ssl_ctx, server_cert_path, SSL_FILETYPE_PEM) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_use_certificate_file(%s) fail [%s]", server_cert_path, ssl_ebuf);
        return -1;
    }

    // 檢查伺服器的私鑰和憑證是否批配.
    if(SSL_CTX_check_private_key(ssl_ctx) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_check_private_key() fail [%s]", ssl_ebuf);
        return -1;
    }

    // 載入伺服器的憑證串鍊 (PEM 格式), 伺服器端要提供憑證串鍊讓客戶端做驗證.
    // 憑證串鍊的內容就是把伺服器憑證和中繼憑證合併成單一憑證,
    // 憑證串鍊內如必須依照 伺服器憑證 -> 中繼憑證-1 -> .. -> 中繼憑證-N 的順序記錄,
    //
    // 以 www.google.com 為例 :
    // www.google.com 的憑證簽發順序是 (上到下),
    // Equifax Secure Certificate Authority,
    //   GeoTrust Global CA,
    //     Google Internet Authority G2,
    //       www.google.com
    // 憑證串鍊內如必須紀錄除了根憑證 (Equifax Secure Certificate Authority) 之外的憑證,
    // 憑證串鍊檔案內容會像 :
    // -----BEGIN CERTIFICATE-----
    // www.google.com 的憑證內容.
    // -----END CERTIFICATE-----
    // -----BEGIN CERTIFICATE-----
    // Google Internet Authority G2 的憑證內容.
    // -----END CERTIFICATE-----
    // -----BEGIN CERTIFICATE-----
    // GeoTrust Global CA 的憑證內容.
    // -----END CERTIFICATE-----
    if(SSL_CTX_use_certificate_chain_file(ssl_ctx, server_chain_path) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_use_certificate_chain_file(%s) fail [%s]",
             server_chain_path, ssl_ebuf);
        return -1;
    }

    return 0;
}

int ssl_socket_init(
    struct sockaddr_in *local_addr,
    socklen_t addr_len,    
    int *sock_fd_buf)
{
    int sock_fd;


    // 建立 socket.
    sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sock_fd == -1)
    {
        DMSG("call socket() fail [%s]", strerror(errno));
        return -1;
    }

    // 綁定服務位址.
    if(bind(sock_fd, (struct sockaddr *) local_addr, addr_len) == -1)
    {
        DMSG("call bind() fail [%s]", strerror(errno));
        return -1;
    }

    // 監聽客戶端連線.
    if(listen(sock_fd, 4) == -1)
    {
        DMSG("call listen() fail [%s]", strerror(errno));
        return -1;
    }

    *sock_fd_buf = sock_fd;

    return 0;
}

int ssl_socket_accept(
    int sock_fd,
    SSL_CTX *ssl_ctx,
    socklen_t addr_len,
    int *sock_rfd_buf,
    struct sockaddr_in *remote_addr_buf,
    SSL **ssl_session_buf)
{
    fd_set fdset_fd;
    socklen_t alen = addr_len;
    struct sockaddr_in remote_addr;
    int sock_rfd;
    SSL *ssl_session;


    FD_ZERO(&fdset_fd);
    FD_SET(sock_fd, &fdset_fd);

    // 等待客戶端連線.
    if(select(sock_fd + 1, &fdset_fd, NULL, NULL, NULL) == -1)
    {
        DMSG("call select() fail [%s]", strerror(errno));
        goto FREE_01;
    }

    if(FD_ISSET(sock_fd, &fdset_fd) == 0)
    {
        DMSG("call FD_ISSET() return nothing");
        goto FREE_01;
    }

    // 接收客戶端連線.
    sock_rfd = accept(sock_fd, (struct sockaddr *) &remote_addr, &alen);
    if(sock_rfd == -1)
    {
        DMSG("call accept() fail [%s]", strerror(errno));
        goto FREE_01;
    }

    // 基於 ctx 產生新的 SSL.
    ssl_session = SSL_new(ssl_ctx);
    if(ssl_session == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_new() fail [%s]", ssl_ebuf);
        goto FREE_02;
    }

    // 指定 SSL 連線使用的 socket.
    if(SSL_set_fd(ssl_session, sock_rfd) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_set_fd() fail [%s]", ssl_ebuf);
        goto FREE_03;
    }

    // 建立 SSL 連線.
    if(SSL_accept(ssl_session) == -1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_accept() fail [%s]", ssl_ebuf);
        goto FREE_03;
    }

    *sock_rfd_buf = sock_rfd;
    memcpy(remote_addr_buf, &remote_addr, addr_len);
    *ssl_session_buf = ssl_session;

    return 0;
FREE_03:
    SSL_free(ssl_session);
FREE_02:
    close(sock_rfd);
FREE_01:
    return -1;
}

int ssl_show_info(
    struct sockaddr_in *remote_addr,
    SSL *ssl_session)
{
    X509 *cert_data;
    char data_buf[256];


    DMSG("");

    // 顯示客戶端的位址.
    DMSG("client :");
    DMSG("%s:%u", inet_ntop(AF_INET, &remote_addr->sin_addr, data_buf, sizeof(data_buf)),
         ntohs(remote_addr->sin_port));

    // 顯示連線的 SSL 版本.
    DMSG("version :");
    DMSG("%s", SSL_get_cipher_version(ssl_session));

    // 顯示使用的加密方式.
    DMSG("cipher :");
    DMSG("%s", SSL_get_cipher_name(ssl_session));

    // 取出對方的憑證.
    cert_data = SSL_get_peer_certificate(ssl_session);
    DMSG("client certificate :");
    if(cert_data != NULL)
    {
        // 取出憑證的主旨.
        X509_NAME_oneline(X509_get_subject_name(cert_data), data_buf, sizeof(data_buf));
        DMSG("subject : %s", data_buf);
        // 取出憑證的簽發者.
        X509_NAME_oneline(X509_get_issuer_name(cert_data), data_buf, sizeof(data_buf));
        DMSG("issuer  : %s", data_buf);
    }
    else
    {
        DMSG("no certificate");
    }

    return 0;
}

int ssl_socket_send(
    SSL *ssl_session,
    void *data_con,
    unsigned int data_len)
{
    int slen;


    slen = SSL_write(ssl_session, data_con, data_len);
    if(slen <= 0)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_write() fail [%s]", ssl_ebuf);
        return -1;
    }

    return slen;
}

int ssl_socket_recv(
    SSL *ssl_session,
    void *data_buf,
    unsigned int buf_size)
{
    int rlen;


    rlen = SSL_read(ssl_session, data_buf, buf_size);
    if(rlen <= 0)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_read() fail [%s]", ssl_ebuf);
        return -1;
    }

    return rlen;
}

int main(
    int argc,
    char **argv)
{
    int fret = -1;
    char opt_ch, *server_key_path = NULL, *server_cert_path = NULL, *server_chain_path = NULL;
    char data_buf[256];
    int sock_fd, sock_rfd;
    SSL_CTX *ssl_ctx;
    SSL *ssl_session;
    struct in_addr naddr;
    struct sockaddr_in local_addr, remote_addr;


    while((opt_ch = getopt(argc , argv, "p:k:c:l:"))!= -1)
        switch(opt_ch)
        {
            case 'p':
                server_key_passphrase = optarg;
                break;
            case 'k':
                server_key_path = optarg;
                break;
            case 'c':
                server_cert_path = optarg;
                break;
            case 'l':
                server_chain_path = optarg;
                break;
            default:
                goto FREE_HELP;
        }

    if(server_key_passphrase == NULL)
        goto FREE_HELP;
    if(server_key_path == NULL)
        goto FREE_HELP;
    if(server_cert_path == NULL)
        goto FREE_HELP;
    if(server_chain_path == NULL)
        goto FREE_HELP;

    signal(SIGINT, signal_handle);
    signal(SIGQUIT, signal_handle);
    signal(SIGTERM, signal_handle);

    // 初始化 SSL 資料.
    if(ssl_init(&ssl_ctx) < 0)
    {
        DMSG("call ssl_init() fail");
        goto FREE_01;
    }

    // 載入伺服器的憑證串鍊, 私鑰, 憑證.
    if(ssl_load_pem(ssl_ctx, server_key_passphrase, server_key_path, server_cert_path,
                    server_chain_path) < 0)
    {
        DMSG("call ssl_load_pem() fail");
        goto FREE_02;
    }

    // 設定伺服器服務的位址.
    if(inet_pton(AF_INET, "127.0.0.1", &naddr) != 1)
    {
        DMSG("call inet_pton() fail [%s]", strerror(errno));
        goto FREE_02;
    }
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = naddr.s_addr;
    local_addr.sin_port = htons(443);

    // socket 初始化並監聽連線.
    if(ssl_socket_init(&local_addr, sizeof(local_addr), &sock_fd) < 0)
    {
        DMSG("call ssl_socket_init() fail");
        goto FREE_02;
    }

    DMSG("listen connect");
    while(shutdown_process == 0)
    {
        // 接收連線.
        fret = ssl_socket_accept(sock_fd, ssl_ctx, sizeof(remote_addr),
                                 &sock_rfd, &remote_addr, &ssl_session);
        if(fret < 0)
        {
            DMSG("call ssl_socket_accept() fail");
            goto FREE_02;
        }

        // 顯示 SSL 的連線和憑證等資訊.
        ssl_show_info(&remote_addr, ssl_session);

        // 接收資料.
        if(ssl_socket_recv(ssl_session, data_buf, sizeof(data_buf)) < 0)
        {
            DMSG("call ssl_socket_recv() fail");
            shutdown_process = 1;
            goto FREE_SESSION;
        }
        DMSG("recv : %s", data_buf);

        // 傳送資料.
        snprintf(data_buf, sizeof(data_buf), "hellow, i am server");
        DMSG("send : %s", data_buf);
        if(ssl_socket_send(ssl_session, data_buf, strlen(data_buf)) < 0)
        {
            DMSG("call ssl_socket_send() fail");
            shutdown_process = 1;
            goto FREE_SESSION;
        }

FREE_SESSION:
        DMSG("close connect");
        SSL_shutdown(ssl_session);
        SSL_free(ssl_session);
        close(sock_rfd);
    }

FREE_02:
    SSL_CTX_free(ssl_ctx);
FREE_01:
    return 0;
FREE_HELP:
    printf("\ntls_one_way_server <-p> <-k> <-c> <-l>\n");
    printf("  -p : server private key pass phrase\n");
    printf("       ex : -p john123\n");
    printf("  -k : server private key path\n");
    printf("       ex : -k ../pem/server/server.key.pem\n");
    printf("  -c : server certificate path\n");
    printf("       ex : -c ../pem/server/server.cert.pem\n");
    printf("  -l : server certificate chain path\n");
    printf("       ex : -l ../pem/server/server_chain.cert.pem\n\n");
    return 0;
}

client端

// ©.
// https://github.com/sizet/ssl_example

#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/opensslv.h>




#define DMSG(msg_fmt, msg_args...) \
    printf("%s(%04u): " msg_fmt "\n", __FILE__, __LINE__, ##msg_args)

#define SSL_FILL_EMSG(msg_buf) ERR_error_string_n(ERR_get_error(), msg_buf, sizeof(msg_buf))
#define SSL_FILL_IO_EMSG(ssl_session, msg_buf) \
    ERR_error_string_n(SSL_get_error(ssl_session), msg_buf, sizeof(msg_buf))




char ssl_ebuf[128];




void signal_handle(
    int signal_value)
{
    switch(signal_value)
    {
        case SIGINT:
        case SIGQUIT:
        case SIGTERM:
            exit(0);
            break;
    }

    return;
}

int ssl_init(
    SSL_CTX **ssl_ctx_buf)
{
    const SSL_METHOD *ssl_method;
    SSL_CTX *ssl_ctx;


    // SSL 函式庫初始化.
    SSL_library_init();
    // 載入 SSL 錯誤訊息.
    SSL_load_error_strings();
    // 載入 SSL 演算法.
    OpenSSL_add_all_algorithms();

    // 指定使用 TLSv1 協定.
#if OPENSSL_VERSION_NUMBER >= 0x10002000
    ssl_method = TLSv1_2_client_method();
#else
    ssl_method = TLSv1_client_method();
#endif
    if(ssl_method == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_01;
    }

    // 建立 CTX 物件.
    ssl_ctx = SSL_CTX_new(ssl_method);
    if(ssl_ctx == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_01;
    }

    // 此函式非必要, 設定使用的加密演算法的優先權.
    // 使用方法 https://linux.die.net/man/3/ssl_ctx_set_cipher_list
    if(SSL_CTX_set_cipher_list(ssl_ctx, "DEFAULT") != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_new(TLSv1_server_method()) fail [%s]", ssl_ebuf);
        goto FREE_02;
    }

#if OPENSSL_VERSION_NUMBER >= 0x10002000
    // ECDH 相關, OpenSSL 1.0.2 以上才支援.
    if(SSL_CTX_set_ecdh_auto(ssl_ctx, 1) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_CTX_set_ecdh_auto() fail [%s]", ssl_ebuf);
        goto FREE_02;
    }
#endif

    *ssl_ctx_buf = ssl_ctx;

    return 0;
FREE_02:
    SSL_CTX_free(ssl_ctx);
FREE_01:
    return -1;
}

// 驗證伺服器的憑證的函式.
// 憑證的檢查順序是 根憑證 -> 中繼憑證 -> ... -> 伺服器憑證, 每個憑證都會呼叫驗證函式.
// preverify_ok :
//   OpenSSL 在呼叫此函式之前會先對憑證做基本檢查, 這邊會紀錄檢查結果.
//   0 = 憑證錯誤.
//   1 = 憑證正確.
int ssl_verify_cert(
    int preverify_ok,
    X509_STORE_CTX *x509_ctx)
{
    X509 *cert_data;
    int err_code, cert_depth;
    char data_buf[256];
    const char *err_msg;


    // 哪一層的憑證.
    cert_depth = X509_STORE_CTX_get_error_depth(x509_ctx);
    // 錯誤的原因.
    err_code = X509_STORE_CTX_get_error(x509_ctx);

    DMSG("verify server certificate, depth = %d :", cert_depth);

    cert_data = X509_STORE_CTX_get_current_cert(x509_ctx);
    // 取出憑證的主旨.
    X509_NAME_oneline(X509_get_subject_name(cert_data), data_buf, sizeof(data_buf));
    DMSG("subject : %s", data_buf);
    // 取出憑證的簽發者.
    X509_NAME_oneline(X509_get_issuer_name(cert_data), data_buf, sizeof(data_buf));
    DMSG("issuer  : %s", data_buf);

    if(preverify_ok == 0)
    {
        // 憑證錯誤.

        // 以 www.google.com 為例,
        // 在 SSL_CTX_load_verify_locations() 不指定或使用錯誤的根憑證, 會顯示,
        // verify server certificate, depth = 2 :
        // subject : /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
        // issuer  : /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
        // invalid, unable to get local issuer certificate.

        // 錯誤的文字說明.
        err_msg = X509_verify_cert_error_string(err_code);
        DMSG("invalid, %s", err_msg);

        // 設定錯誤原因.
        X509_STORE_CTX_set_error(x509_ctx, err_code);
    }
    else
    {
        // 憑證正確.

        // 以 www.google.com 為例,
        // 在 SSL_CTX_load_verify_locations() 使用正確的根憑證, 會顯示,
        // verify server certificate, depth = 3 :
        // subject : /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
        // issuer  : /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
        // verify server certificate, depth = 2 :
        // subject : /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
        // issuer  : /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
        // verify server certificate, depth = 1 :
        // subject : /C=US/O=Google Inc/CN=Google Internet Authority G2
        // issuer  : /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
        // verify server certificate, depth = 0 :
        // subject : /C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
        // issuer  : /C=US/O=Google Inc/CN=Google Internet Authority G2
        //
        // 可以對憑證作其他額外的檢查,
        // 如果要拒絕此憑證要使用 X509_STORE_CTX_set_error() 設定原因, 並回傳 0 (參考下面說明),
        // 可以設定的值參考 https://linux.die.net/man/3/x509_store_ctx_set_error
    }

    // 回傳值會影響 SSL_connect/SSL_accept 的結果,
    // 0 = 驗證結果錯誤, OpenSSL 會中斷憑證串鍊的驗證並使 SSL_connect/SSL_accept 回傳錯誤.
    // 1 = 驗證結果正確, OpenSSL 會繼續憑證串鍊的驗證.
    return preverify_ok;
}

int ssl_load_pem(
    SSL_CTX *ssl_ctx,
    char *server_root_ca_path)
{
    if(server_root_ca_path != NULL)
    {
        // 客戶端是否驗證伺服器的憑證.
        // 參數 2 表示驗證模式,
        //   SSL_VERIFY_NONE = 不驗證, 預設值.
        //   SSL_VERIFY_PEER = 要驗證.
        // 參數 3 表示額外的驗證函式,
        //   NULL = 使用 OpenSSL 內建的驗證函式.
        //   函式 = 指定的額外驗證函式.
        // 使用 SSL_connect/SSL_accept 取得對方憑證後會呼叫指定的額外驗證函式處理.
        SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, ssl_verify_cert);

        // 在 SSL_CTX_set_verify() 使用 SSL_VERIFY_PEER 的話,
        // 需要指定伺服器使用的憑證的根憑證 (PEM 格式).
        //
        // 以 www.google.com 為例 :
        // 在終端機輸入 echo "Q" | openssl s_client -connect www.google.com:443
        // 顯示的憑證串鍊 :
        // CONNECTED(00000003)
        // depth=3 C = US, O = Equifax, OU = Equifax Secure Certificate Authority
        // verify return:1
        // depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
        // verify return:1
        // depth=1 C = US, O = Google Inc, CN = Google Internet Authority G2
        // verify return:1
        // depth=0 C = US, ST = California, L = Mountain View, O = Google Inc, CN = www.google.com
        // verify return:1
        // 其中 :
        // depth=0, CN = www.google.com,
        //   這邊是 www.google.com 的憑證.
        // depth=1, CN = Google Internet Authority G2,
        //   這邊是 www.google.com 的憑證的簽發者, 屬於中繼憑證.
        // depth=2, CN = GeoTrust Global CA,
        //   這邊是 Google Internet Authority G2 的憑證的簽發者, 屬於中繼憑證.
        // depth=3, CN = Equifax Secure Certificate Authority,
        //   這邊是 GeoTrust Global CA 的憑證的簽發者, 而在他之上沒其他簽發者, 屬於根憑證.
        // www.google.com 的根憑證就是 Equifax Secure Certificate Authority,
        // Equifax Secure Certificate Authority 的憑證可以在網路上下載.
        //
        // 在建立 SSL 連線時, 伺服器必須傳送完整的憑證串鍊給客戶端做驗證,
        // 憑證串鍊一般會紀錄在 Server Hellow 中的 SSL Record Layer 的 Handshake (Certificate),
        // 憑證串鍊會依照 伺服器憑證 -> 中繼憑證-1 -> .. -> 中繼憑證-N 的順序記錄,
        // 以 www.google.com 為例, www.google.com 會傳送的憑證串鍊會是 :
        // [www.google.com 憑證], [Google Internet Authority G2 憑證], [GeoTrust Global CA 憑證],
        // 總共 3 個憑證, 一般情況伺服器傳來的憑證串鍊不會含有根憑證,
        // 客戶端必須自己擁有根憑證來做驗證.
        if(SSL_CTX_load_verify_locations(ssl_ctx, server_root_ca_path, NULL) != 1)
        {
            SSL_FILL_EMSG(ssl_ebuf);
            DMSG("call SSL_CTX_load_verify_locations(%s) fail [%s]",
                 server_root_ca_path, ssl_ebuf);
            return -1;
        }
    }

    return 0;
}

int ssl_socket_init(
    int *sock_fd_buf)
{
    int sock_fd;


    // 建立 socket.
    sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sock_fd == -1)
    {
        DMSG("call socket() fail [%s]", strerror(errno));
        return -1;
    }

    *sock_fd_buf = sock_fd;

    return 0;
}

int ssl_socket_connect(
    int sock_fd,
    SSL_CTX *ssl_ctx,
    struct sockaddr_in *remote_addr,
    socklen_t addr_len,
    SSL **ssl_session_buf)
{
    int cret;
    SSL *ssl_session;


    // 連線.
    if(connect(sock_fd, (struct sockaddr *) remote_addr, addr_len) == -1)
    {
        DMSG("call connect() fail [%s]", strerror(errno));
        goto FREE_01;
    }

    // 基於 CTX 產生新的 SSL 連線.
    ssl_session = SSL_new(ssl_ctx);
    if(ssl_session == NULL)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_new() fail [%s]", ssl_ebuf);
        goto FREE_01;
    }

    // 指定 SSL 連線使用的 socket.
    if(SSL_set_fd(ssl_session, sock_fd) != 1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_set_fd() fail [%s]", ssl_ebuf);
        goto FREE_02;
    }

    // 建立 SSL 連線.
    if(SSL_connect(ssl_session) == -1)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_connect() fail [%s]", ssl_ebuf);
        cret = SSL_get_verify_result(ssl_session);
        if(cret != X509_V_OK)
        {
            DMSG("invalid certificate, %s", X509_verify_cert_error_string(cret));
        }
        goto FREE_02;
    }

    *ssl_session_buf = ssl_session;

    return 0;
FREE_02:
    SSL_free(ssl_session);
FREE_01:
    return -1;
}

int ssl_show_info(
    struct sockaddr_in *remote_addr,
    SSL *ssl_session)
{
    X509 *cert_data;
    char data_buf[256];


    DMSG("");

    // 顯示伺服器的位址.
    DMSG("server :");
    DMSG("%s:%u", inet_ntop(AF_INET, &remote_addr->sin_addr, data_buf, sizeof(data_buf)),
         ntohs(remote_addr->sin_port));

    // 顯示連線的 SSL 版本.
    DMSG("version :");
    DMSG("%s", SSL_get_cipher_version(ssl_session));

    // 顯示使用的加密方式.
    DMSG("cipher :");
    DMSG("%s", SSL_get_cipher_name(ssl_session));

    // 取出對方的憑證.
    cert_data = SSL_get_peer_certificate(ssl_session);
    DMSG("server certificate :");
    if(cert_data != NULL)
    {
        // 取出憑證的主旨.
        X509_NAME_oneline(X509_get_subject_name(cert_data), data_buf, sizeof(data_buf));
        DMSG("subject : %s", data_buf);
        // 取出憑證的簽發者.
        X509_NAME_oneline(X509_get_issuer_name(cert_data), data_buf, sizeof(data_buf));
        DMSG("issuer  : %s", data_buf);
    }
    else
    {
        DMSG("no certificate");
    }

    return 0;
}

int ssl_socket_send(
    SSL *ssl_session,
    void *data_con,
    unsigned int data_len)
{
    int slen;


    slen = SSL_write(ssl_session, data_con, data_len);
    if(slen <= 0)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_write() fail [%s]", ssl_ebuf);
        return -1;
    }

    return slen;
}

int ssl_socket_recv(
    SSL *ssl_session,
    void *data_buf,
    unsigned int buf_size)
{
    int rlen;


    rlen = SSL_read(ssl_session, data_buf, buf_size);
    if(rlen <= 0)
    {
        SSL_FILL_EMSG(ssl_ebuf);
        DMSG("call SSL_read() fail [%s]", ssl_ebuf);
        return -1;
    }

    return rlen;
}

int main(
    int argc,
    char **argv)
{
    int sock_fd;
    char opt_ch, *server_root_ca_path = NULL, data_buf[256];
    SSL_CTX *ssl_ctx;
    SSL *ssl_session;
    struct in_addr naddr;
    struct sockaddr_in remote_addr;


    while((opt_ch = getopt(argc , argv, "r:"))!= -1)
        switch(opt_ch)
        {
            case 'r':
                server_root_ca_path = optarg;
                break;
            default:
                goto FREE_HELP;
        }

    if(server_root_ca_path == NULL)
        goto FREE_HELP;

    signal(SIGINT, signal_handle);
    signal(SIGQUIT, signal_handle);
    signal(SIGTERM, signal_handle);

    // 初始化 SSL 資料.
    if(ssl_init(&ssl_ctx) < 0)
    {
        DMSG("call ssl_init() fail");
        goto FREE_01;
    }

    // 載入伺服器的根憑證, 需要驗證伺服器的憑證才需要.
    if(ssl_load_pem(ssl_ctx, server_root_ca_path) < 0)
    {
        DMSG("call ssl_load_pem() fail");
        goto FREE_02;
    }

    // socket 初始化.
    if(ssl_socket_init(&sock_fd) < 0)
    {
        DMSG("call ssl_socket_init() fail");
        goto FREE_02;
    }

    // 設定伺服器的位址.
    if(inet_pton(AF_INET, "127.0.0.1", &naddr) != 1)
    {
        DMSG("call inet_pton() fail [%s]", strerror(errno));
        goto FREE_02;
    }
    memset(&remote_addr, 0, sizeof(remote_addr));
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_addr.s_addr = naddr.s_addr;
    remote_addr.sin_port = htons(443);

    // 連線.
    if(ssl_socket_connect(sock_fd, ssl_ctx, &remote_addr, sizeof(remote_addr), &ssl_session) < 0)
    {
        DMSG("call ssl_socket_connect() fail");
        goto FREE_03;
    }

    // 顯示 SSL 的連線和憑證等資訊.
    ssl_show_info(&remote_addr, ssl_session);

    // 傳送資料.
    snprintf(data_buf, sizeof(data_buf), "hellow, i am client");
    DMSG("send : %s", data_buf);
    if(ssl_socket_send(ssl_session, data_buf, strlen(data_buf)) < 0)
    {
        DMSG("call ssl_socket_send() fail");
        goto FREE_03;
    }

    // 接收資料.
    if(ssl_socket_recv(ssl_session, data_buf, sizeof(data_buf)) < 0)
    {
        DMSG("call ssl_socket_recv() fail");
        goto FREE_03;
    }
    DMSG("recv : %s", data_buf);

    // 關閉 SSL 連線.
    SSL_shutdown(ssl_session);

    SSL_free(ssl_session);
FREE_03:
    close(sock_fd);
FREE_02:
    SSL_CTX_free(ssl_ctx);
FREE_01:
    return 0;
FREE_HELP:
    printf("\ntls_one_way_client <-r>\n");
    printf("  -r : server root CA certificate path\n");
    printf("       ex : -r ../pem/server/root_ca.cert.pem\n");
    return 0;
}