OpenSSL證書認證過程


游戲服務端這塊,之前是很少用SSL的,畢竟游戲里的數據沒有什么保密的必要,登錄、充值也是傳輸簽名,不涉及密碼什么的。不過這幾年,HTTPS普及得比較快,H5游戲發展迅速。H5游戲是基於web的,和后端通信一般走websocket,加不加SSL其實對於游戲影響不大。但是不少平台都要求加SSL的,一是用戶通過瀏覽器玩游戲時,地址欄里有個鎖頭體驗還是好點,二是喪心病狂的黑產會劫持鏈接加上廣告,想象一下在玩游戲時,右下角彈個廣告是啥體驗。

雖說用上SSL,不過並不一定就要自己實現SSL,比如用Nginx做一層代理,在Nginx處理SSL,在游戲服務器處理邏輯。不過最近有時間,還是研究下SSL。這里不涉及如何用OpenSSL實現一個SSL鏈接,網上的例子已經太多。而是研究下證書的認證,即

瀏覽器地址欄里的這個鎖頭是怎么來的?它怎么判斷當前連接是安全?

這里以OpenSSL為例,簡單說下SSL的的建立過程。普通的socket通過accept/connect來建立連接,然后用SSL_set_fd把socket和OpenSSL關聯起來,接着調用SSL_do_handshake來進行SSL握手,握手成功后,就可以通過SSL_read/SSL_write來進行加密的數據通信。

那證書的認證在哪里處理?證書認證屬於SSL握手(SSL_do_handshake)的一部分,SSL連接分為二種:

  1. 單向認證
    客戶端認證服務端,服務端不認證客戶端,這時服務端需要一個證書,客戶端不需要。網站的HTTPS認證通常屬於這一類,即網站的內容是公開的,它並不在意誰來訪問這些內容,因此無需校驗客戶端。但對客戶端而言,它要保證所訪問的網站是正確的網站,而不是被劫持修改、假冒的釣魚網站,因此需要校驗服務端。

  2. 雙向認證
    客戶端認證服務端,服務端也需要認證客戶端,這時候服務端和客戶端都需要有證書。假如一個員工下班后,需要在家登錄公司的內部管理系統,那這時候,服務端需要確認登錄的用戶屬於自己公司的員工,就需要校驗客戶端。

PS: 測試了下,不存在雙方都不需要證書的SSL連接。

現在使用的證書一般是x509標准證書。可以用openssl s_client -servername cnblogs.com -connect cnblogs.com:443 </dev/null 2>/dev/null | openssl x509 -text來查看證書的內容,一般包括以下內容:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0d:2e:94:94:ec:65:e6:e6:4a:a7:a9:4d:1b:bf:8d:e4
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = RapidSSL RSA CA 2018
        Validity
            Not Before: Mar  6 00:00:00 2020 GMT
            Not After : Mar  6 12:00:00 2021 GMT
        Subject: CN = *.cnblogs.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:bd:39:42:6b:62:fc:e7:24:3d:23:f9:1a:db:c4:
                    d1:4e:bc:8c:d4:b6:71:54:11:b1:24:d4:0f:a1:fe:
                    1e:8d:a8:cb:81:fb:72:e7:fe:2c:c1:40:1d:1b:4c:
                    96:f3:28:3c:cf:ba:20:3c:d7:6d:d6:18:bf:7f:a9:
                    f8:3e:6a:a6:50:46:b1:1a:36:b7:81:6f:b8:81:f0:
                    6a:64:77:20:05:6e:7c:5a:17:6b:4f:f5:0d:f5:59:
                    3c:93:7c:50:22:95:a0:4a:3c:43:63:f2:28:81:a2:
                    4e:e1:41:7e:6c:c9:c7:9b:56:72:1a:ce:6c:b5:78:
                    f9:0f:62:14:9e:38:e4:f4:4e:e7:40:dc:de:fa:4f:
                    21:6e:9f:88:7e:d5:0b:58:f3:36:a4:2a:92:63:fb:
                    91:e8:93:86:3e:21:e5:df:8c:79:5e:03:e1:05:57:
                    f3:13:df:e7:8b:6f:a8:80:86:82:85:30:2b:21:f5:
                    e8:bb:25:ae:8a:26:17:46:d7:28:11:b5:e0:26:a4:
                    90:b8:2a:bb:44:27:59:4a:f3:40:ec:1e:78:58:ef:
                    83:c9:df:0a:55:bb:f4:de:25:2c:89:00:30:45:81:
                    db:f6:fc:46:b2:03:e9:e9:47:97:c8:0e:a8:a0:55:
                    81:c5:21:c6:e0:e7:8b:77:c9:e5:28:c2:8e:09:12:
                    c6:f9
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier: 
                keyid:53:CA:17:59:FC:6B:C0:03:21:2F:1A:AE:E4:AA:A8:1C:82:56:DA:75

            X509v3 Subject Key Identifier: 
                4A:82:6C:3C:60:F8:58:F5:FB:18:0B:82:65:8D:9F:3E:ED:18:13:F7
            X509v3 Subject Alternative Name: 
                DNS:*.cnblogs.com, DNS:cnblogs.com
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 CRL Distribution Points: 

                Full Name:
                  URI:http://cdp.rapidssl.com/RapidSSLRSACA2018.crl

            X509v3 Certificate Policies: 
                Policy: 2.16.840.1.114412.1.2
                  CPS: https://www.digicert.com/CPS
                Policy: 2.23.140.1.2.1

            Authority Information Access: 
                OCSP - URI:http://status.rapidssl.com
                CA Issuers - URI:http://cacerts.rapidssl.com/RapidSSLRSACA2018.crt

            X509v3 Basic Constraints: 
                CA:FALSE
            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : F6:5C:94:2F:D1:77:30:22:14:54:18:08:30:94:56:8E:
                                E3:4D:13:19:33:BF:DF:0C:2F:20:0B:CC:4E:F1:64:E3
                    Timestamp : Mar  6 08:06:35.594 2020 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:46:02:21:00:DA:3B:5E:67:CF:E7:2E:83:66:5F:12:
                                52:F9:36:C1:51:00:04:2F:D6:69:A6:F2:0E:90:43:47:
                                F6:28:09:C9:1C:02:21:00:8D:9A:5C:F3:42:87:47:61:
                                0C:0A:63:80:5C:DB:F9:EC:E7:DC:EF:93:04:46:9F:3F:
                                B0:49:29:33:FA:84:D6:FC
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 44:94:65:2E:B0:EE:CE:AF:C4:40:07:D8:A8:FE:28:C0:
                                DA:E6:82:BE:D8:CB:31:B5:3F:D3:33:96:B5:B6:81:A8
                    Timestamp : Mar  6 08:06:35.535 2020 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:0E:4B:F7:24:22:D2:01:7A:9D:0E:30:76:
                                21:37:0A:42:2C:7B:0C:A2:62:0C:7A:74:07:9B:7E:CB:
                                23:E1:EF:AD:02:20:14:6D:B0:C9:2E:C2:97:42:78:F6:
                                0B:DE:A6:40:FE:20:09:7E:A0:A6:12:0A:48:F3:1B:65:
                                7E:05:A4:A9:2F:90
    Signature Algorithm: sha256WithRSAEncryption
         97:19:57:2b:1a:48:7a:47:f3:52:f6:cb:9f:cc:8c:b2:ab:68:
         61:cd:6d:c4:0a:f4:a9:4c:6a:cc:1d:e7:f4:b6:da:47:4e:e6:
         d5:11:ef:a5:80:73:e4:25:c0:42:71:77:53:2e:11:04:d8:4a:
         9f:39:43:54:8a:f0:71:e3:18:49:79:25:73:ef:26:ff:d9:d2:
         09:bb:e5:2d:8f:d1:60:d2:4c:55:6f:c4:3d:76:be:d1:49:ac:
         89:7c:fe:63:71:ec:32:d9:c5:00:f6:5e:b1:7f:f4:5b:05:40:
         a3:e0:95:6b:6d:7e:49:d5:0f:ee:45:9d:e1:4b:9d:55:c1:60:
         2a:2b:23:5f:4f:78:cd:e2:dc:f7:af:bb:df:43:4e:b4:f2:72:
         a4:1b:4b:15:28:e3:8f:67:e6:32:73:93:81:d9:be:bb:bd:e8:
         8f:fe:e6:35:d0:ec:92:09:50:34:14:28:61:65:04:94:9a:3c:
         c4:56:09:e4:bf:48:4e:93:82:bd:40:35:e8:0a:7b:32:46:73:
         74:8d:0d:3b:5a:02:ff:17:be:e5:aa:65:92:3c:76:e2:f1:f6:
         82:32:7b:d7:db:ed:2e:38:36:e3:63:5f:0e:d1:f3:c8:44:0a:
         0a:5b:a1:bb:02:2e:de:e9:13:6b:68:5b:12:a8:60:a5:c8:c0:
         40:4a:15:2d

我們選取幾個重要的點來說:

  • 時間
        Validity
            Not Before: Mar  6 00:00:00 2020 GMT
            Not After : Mar  6 12:00:00 2021 GMT

時間的校驗比較簡單,檢測時間在Validity的[Not Before, Not After]區間內即可,具體的實現可以看OpenSSL源碼int x509_check_cert_time(X509_STORE_CTX *ctx, X509 *x, int depth)

  • 域名
            X509v3 Subject Alternative Name: 
                DNS:*.cnblogs.com, DNS:cnblogs.com

證書里包含域名。連接建立的時候,肯定有對方的IP,可以查看對方的IP是否在域名列表里,在就合法,不在那就是非法,具體可以看OpenSSl源碼X509_check_host的實現

  • 認證鏈
        Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = RapidSSL RSA CA 2018
            Authority Information Access: 
                OCSP - URI:http://status.rapidssl.com
                CA Issuers - URI:http://cacerts.rapidssl.com/RapidSSLRSACA2018.crt
    Signature Algorithm: sha256WithRSAEncryption
         97:19:57:2b:1a:48:7a:47:f3:52:f6:cb:9f:cc:8c:b2:ab:68:
         61:cd:6d:c4:0a:f4:a9:4c:6a:cc:1d:e7:f4:b6:da:47:4e:e6:
         d5:11:ef:a5:80:73:e4:25:c0:42:71:77:53:2e:11:04:d8:4a:
         9f:39:43:54:8a:f0:71:e3:18:49:79:25:73:ef:26:ff:d9:d2:
         09:bb:e5:2d:8f:d1:60:d2:4c:55:6f:c4:3d:76:be:d1:49:ac:
         89:7c:fe:63:71:ec:32:d9:c5:00:f6:5e:b1:7f:f4:5b:05:40:
         a3:e0:95:6b:6d:7e:49:d5:0f:ee:45:9d:e1:4b:9d:55:c1:60:
         2a:2b:23:5f:4f:78:cd:e2:dc:f7:af:bb:df:43:4e:b4:f2:72:
         a4:1b:4b:15:28:e3:8f:67:e6:32:73:93:81:d9:be:bb:bd:e8:
         8f:fe:e6:35:d0:ec:92:09:50:34:14:28:61:65:04:94:9a:3c:
         c4:56:09:e4:bf:48:4e:93:82:bd:40:35:e8:0a:7b:32:46:73:
         74:8d:0d:3b:5a:02:ff:17:be:e5:aa:65:92:3c:76:e2:f1:f6:
         82:32:7b:d7:db:ed:2e:38:36:e3:63:5f:0e:d1:f3:c8:44:0a:
         0a:5b:a1:bb:02:2e:de:e9:13:6b:68:5b:12:a8:60:a5:c8:c0:
         40:4a:15:2d

證書最核心的功能,就是用於識別對方的身份。那客戶端是怎么校驗這個證書是不是被修改過呢?畢竟數據是通過網絡下發的,別人可以攔截並替換成其他證書。

首先,申請證書的時候,需要提交一系列的材料,包括有效日期、公司名字等等之類的東西,然后頒發者(Issuer)會把這些東西通過算法(比如sha256)得到一份摘要(就是類似md5的一長串字符串),然后用自己的私鑰把這份摘要加密,得到簽名(就是上面Signature Algorithm之后那個長長的字符串)。具體的算法可以看RFC 5280

客戶端要校驗這個證書,就是把這個過程逆向。收到證書后,根據證書里頒發者提供的地址CA Issuers - URI:http://cacerts.rapidssl.com/RapidSSLRSACA2018.crt下載頒發者的證書,然后取出頒發者證書里的公鑰(Subject Public Key Info,注意是頒發者的不是當前證書的),用這個公鑰對當前證書的簽名進行解密,就會得到頒發時的那份摘要。

接着客戶端用頒發時一樣的算法,對當前證書的有效日期、公司名字...等數據計算一份摘要,和上面解密得到的摘要進行對比,如果一致,說明這個證書是經過頒發者認證的。

但是證書是經過頒發者認證的並不能說明這個證書就是合法的,別人劫持數據包修改證書的時候,一樣可以修改頒發者的證書URL,這時候就需要繼續對頒發者的證書進行校驗。由於頒發者的證書是使用同樣的標准,重復上面的驗證過程即可。直到最后一個頒發者的時候,已經沒有更高級的頒發者了,他的證書稱為根證書。這時沒有下載證書的地址了,就需要根據頒發者的名字搜索根證書目錄,如果發現對應的證書,則取出證書的公鑰來校驗。

那這個根證書目錄是從哪來呢?一個是系統自帶的,在安裝系統時就固定配置了一些證書,比如Debian下就是放在/etc/ssl/certs/ca-certificates.crt,一些瀏覽器可能也自帶了一些根證書,在安裝瀏覽器的時候固定配置了證書。這些目錄一般是不更新的,因為公認頒發根證書機構就那幾個,基本不會變的。如果是需要添加一些自簽的證書,則可以手動添加。

  • CRL & OCSP
            X509v3 CRL Distribution Points: 

                Full Name:
                  URI:http://cdp.rapidssl.com/RapidSSLRSACA2018.crl

            Authority Information Access: 
                OCSP - URI:http://status.rapidssl.com

在證書里一般還會包含這兩個地址,CRL(Certificate Revocation List)提供一個接口查詢哪些證書已經被注銷,一般是發錯了證書,但證書這個東西又沒法收回,只能通過這種方式注銷。OCSP(Online Certificate Status Protocol)可以實時查詢該證書是否還有效,證書是否被注銷也可以通過這個接口查詢。

上面是一般情況下證書的認證過程,但OpenSSL這個庫默認情況下不會做證書認證。在調用OpenSSl實現SSL連接時,我們可以通過SSL_CTX_set_verify來指定是否校驗對方的證書

    // 一個ctx可以給多個連接使用,因此一個證書就創建一個ctx就可以了
    SSL_CTX *ctx = SSL_CTX_new(method);
    if (!ctx)
    {
        ssl_error("new_ssl_ctx:can NOT create ssl content");
        return -1;
    }

    // 指定了根ca證書路徑,說明需要校驗對方證書的正確性
    if (ca)
    {
        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);

        /*加載CA FILE*/
        if (SSL_CTX_load_verify_locations(ctx, ca, nullptr) != 1)
        {
            SSL_CTX_free(ctx);
            ssl_error("load verify fail");
            return -1;
        }
    }

當不調用SSL_CTX_set_verify或者參數為SSL_VERIFY_NONE時,表示不校驗對方的證書,即連接依然是SSL(數據還是經過加密),但對方的證書可能是無效的(自簽的、過期的),這還是有一些應用場景的,比如說H5游戲用自簽證書就可以防止別人劫持連接加廣告。

當參數為SSL_VERIFY_PEER,在SSL握手時,將會校驗對方的證書。校驗過程是在SSL握手時進行的,如果校驗不通過,SSL握手將失敗(SSL_do_handshake返回失敗)例如無法驗證根證書時

[T1CE10-28 10:24:28]    error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
[T1CE10-28 10:24:28]    error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca

那啟用校驗時,OpenSSL是不是就會自動完成上面所說的那些校驗呢?肯定不是的。OpenSSL作為一個庫它提供了一些接口,但並不會主動去做這些東西。例如它不會自動加載/etc/ssl/certs/ca-certificates.crt下的根證書,需要調用SSL_CTX_load_verify_locations去加載。如果用的是自簽證書,則是加載自簽根證書而不是系統根證書。它也不會去做CRLOCSP的檢測,但你可以用X509_STORE_add_crl來加載自己已下載的CRL列表,以及OCSP的實現

另外,對於SSL_CTX_set_verify這個接口,我一直以為verify_callback這個參數是自定義證書校驗函數。

 typedef int (*SSL_verify_cb)(int preverify_ok, X509_STORE_CTX *x509_ctx);

 void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb verify_callback);

If no special callback was set before, the default callback for the underlying ctx is used

然而在看原碼的時候,所謂的default callback是這樣的

// x509_vfy.c
static int null_callback(int ok, X509_STORE_CTX *e)
{
    return ok;
}

這個callback的作用是每次校驗證書的時候,觸發一次回調,用於做一些自定義的操作,但是這個回調並不影響校驗結果的。真正的默認校驗函數在

// x59_vfy.c

/* verify the issuer signatures and cert times of ctx->chain */
static int internal_verify(X509_STORE_CTX *ctx)
{
      // ...
}

可以用X509_STORE_CTX_set_verify_cb來自定義校驗函數。

PS: 還有一個問題沒搞明白,那就是做證書鏈認證的時候,OpenSSL到底有沒有自動下載Issuer的證書
open verify實現
在程序里,通過設置ca文件為/etc/ssl/certs/ca-certificates.crt后,postman-echo.com可以測試通過。但當我在瀏覽器里點擊https://postman-echo.com/get的鎖頭-證書-詳細信息-導出后,用openssl verify -verbose -CAfile /etc/ssl/certs/ca-certificates.crt postman-echo.com 沒有校驗通過,后續再看看是什么問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM