zoukankan      html  css  js  c++  java
  • 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 没有校验通过,后续再看看是什么问题。

  • 相关阅读:
    父子进程 signal 出现 Interrupted system call 问题
    一个测试文章
    《淘宝客户端 for Android》项目实战 html webkit android css3
    Django 中的 ForeignKey ContentType GenericForeignKey 对应的数据库结构
    coreseek 出现段错误和Unigram dictionary load Error 新情况(Gentoo)
    一个 PAM dbus 例子
    漫画统计学 T分数
    解决 paramiko 安装问题 Unable to find vcvarsall.bat
    20141202
    js
  • 原文地址:https://www.cnblogs.com/coding-my-life/p/13910569.html
Copyright © 2011-2022 走看看