vislee / leevis.com

Blog
87 stars 13 forks source link

openssl代码分析-extensions #157

Open vislee opened 5 years ago

vislee commented 5 years ago

概述

随着互联网的发展,openssl早期的协议支持不了新的需求。例如:单ip支持https多虚拟主机。 http2还是http1.1的协商等。 因此,需要在原来的协议上再扩展。 现在就来看下扩展的代码的。我们从SNI是如何支持的来看起。

代码

根据上一篇的分析,从read_state_machine函数开始,调用ossl_statem_server_post_process_message函数再调用tls_post_process_client_hello再调用tls_early_post_process_client_hello,再调用tls_parse_all_extensions,该函数又调用tls_parse_extension。在这个函数根据调用了封装在ext_defs数组中的回调。

statem/extensions.c

/* Structure to define a built-in extension */
typedef struct extensions_definition_st {
    /* The defined type for the extension */
    unsigned int type;
    /*
     * The context that this extension applies to, e.g. what messages and
     * protocol versions
     */
    unsigned int context;
    /*
     * Initialise extension before parsing. Always called for relevant contexts
     * even if extension not present
     */
    int (*init)(SSL *s, unsigned int context);
    /* Parse extension sent from client to server */
    int (*parse_ctos)(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
                      size_t chainidx);
    /* Parse extension send from server to client */
    int (*parse_stoc)(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
                      size_t chainidx);
    /* Construct extension sent from server to client */
    EXT_RETURN (*construct_stoc)(SSL *s, WPACKET *pkt, unsigned int context,
                                 X509 *x, size_t chainidx);
    /* Construct extension sent from client to server */
    EXT_RETURN (*construct_ctos)(SSL *s, WPACKET *pkt, unsigned int context,
                                 X509 *x, size_t chainidx);
    /*
     * Finalise extension after parsing. Always called where an extensions was
     * initialised even if the extension was not present. |sent| is set to 1 if
     * the extension was seen, or 0 otherwise.
     */
    int (*final)(SSL *s, unsigned int context, int sent);
} EXTENSION_DEFINITION;

static const EXTENSION_DEFINITION ext_defs[] = {
    {
        TLSEXT_TYPE_renegotiate,
        SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO
        | SSL_EXT_SSL3_ALLOWED | SSL_EXT_TLS1_2_AND_BELOW_ONLY,
        NULL, tls_parse_ctos_renegotiate, tls_parse_stoc_renegotiate,
        tls_construct_stoc_renegotiate, tls_construct_ctos_renegotiate,
        final_renegotiate
    },
    {
        TLSEXT_TYPE_server_name,
        SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO
        | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
        init_server_name,
        tls_parse_ctos_server_name, tls_parse_stoc_server_name,
        tls_construct_stoc_server_name, tls_construct_ctos_server_name,
        final_server_name
    },
   ...
}

./statem/extensions_srvr.c

int tls_parse_ctos_server_name(SSL *s, PACKET *pkt, unsigned int context,
                               X509 *x, size_t chainidx)
{
    unsigned int servname_type;
    PACKET sni, hostname;

    if (!PACKET_as_length_prefixed_2(pkt, &sni)
        /* ServerNameList must be at least 1 byte long. */
        || PACKET_remaining(&sni) == 0) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_SERVER_NAME,
                 SSL_R_BAD_EXTENSION);
        return 0;
    }

    /*
     * Although the intent was for server_name to be extensible, RFC 4366
     * was not clear about it; and so OpenSSL among other implementations,
     * always and only allows a 'host_name' name types.
     * RFC 6066 corrected the mistake but adding new name types
     * is nevertheless no longer feasible, so act as if no other
     * SNI types can exist, to simplify parsing.
     *
     * Also note that the RFC permits only one SNI value per type,
     * i.e., we can only have a single hostname.
     */
    if (!PACKET_get_1(&sni, &servname_type)
        || servname_type != TLSEXT_NAMETYPE_host_name
        || !PACKET_as_length_prefixed_2(&sni, &hostname)) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_SERVER_NAME,
                 SSL_R_BAD_EXTENSION);
        return 0;
    }

    if (!s->hit || SSL_IS_TLS13(s)) {
        if (PACKET_remaining(&hostname) > TLSEXT_MAXLEN_host_name) {
            SSLfatal(s, SSL_AD_UNRECOGNIZED_NAME,
                     SSL_F_TLS_PARSE_CTOS_SERVER_NAME,
                     SSL_R_BAD_EXTENSION);
            return 0;
        }

        if (PACKET_contains_zero_byte(&hostname)) {
            SSLfatal(s, SSL_AD_UNRECOGNIZED_NAME,
                     SSL_F_TLS_PARSE_CTOS_SERVER_NAME,
                     SSL_R_BAD_EXTENSION);
            return 0;
        }

        /*
         * Store the requested SNI in the SSL as temporary storage.
         * If we accept it, it will get stored in the SSL_SESSION as well.
         */
        OPENSSL_free(s->ext.hostname);
        s->ext.hostname = NULL;
        if (!PACKET_strndup(&hostname, &s->ext.hostname)) {
            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_SERVER_NAME,
                     ERR_R_INTERNAL_ERROR);
            return 0;
        }

        s->servername_done = 1;
    }
    if (s->hit) {
        /*
         * TODO(openssl-team): if the SNI doesn't match, we MUST
         * fall back to a full handshake.
         */
        s->servername_done = (s->session->ext.hostname != NULL)
            && PACKET_equal(&hostname, s->session->ext.hostname,
                            strlen(s->session->ext.hostname));

        if (!s->servername_done && s->session->ext.hostname != NULL)
            s->ext.early_data_ok = 0;
    }

    return 1;
}

最后分析一个完整的报文:

nc -l 127.0.0.1 4444 > tls.log
openssl s_client -connect 127.0.0.1:4444 -servername "test.com"

16            --- Content Type: Handshake  --0
03 01         --- Version: TLS 1.0         --1,2
01 4a         --- Length                   --3,4
01            --- Handshake Type: Client Hello --5
0001 46       --- Length                       --6,7,8
03 03         --- Version: TLS 1.2             --9,10
d6 a237 370c a911 7944 f1c0 e47b 17a4 2786 61f9 cf65 17ff c5a5 e98d e32d 6aa1 2a  --- 32字节Random
00            --- Session ID Length:0          --43
0098          --- Cipher Suites Length:152     --44,45
cc14          --- 152字节 Cipher Suites         --46,47
cc13 cc15 c030 c02c c028 c024 c014 c00a
00a3 009f 006b 006a 0039 0038 ff85 00c4
00c3 0088 0087 0081 c032 c02e c02a c026
c00f c005 009d 003d 0035 00c0 0084 c02f
c02b c027 c023 c013 c009 00a2 009e 0067
0040 0033 0032 00be 00bd 0045 0044 c031
c02d c029 c025 c00e c004 009c 003c 002f
00ba 0041 c011 c007 c00c c002 0005 0004
c012 c008 0016 0013 c00d c003 000a 0015
0012 0009 00ff 

01            ---- Compression Methods Length: 1 ---48+152=200
00            ---- Compression Methods           ---201
0085          ---- Extensions Length:133         ---201+1=202

0000          ---- server_name list
000d          ---- server_name list length:13 
000b          ---- length 11
00            --- host_name
0008          --- length
74 6573 742e 636f 6d  ---- test.com

000b          --- ec_point_formats
0004
0300 0102

000a
003a
0038 00 0e00 0d00 1900
1c00 0b00 0c00 1b00 1800 0900 0a00 1a00
1600 1700 0800 0600 0700 1400 1500 0400
0500 1200 1300 0100 0200 0300 0f00 1000
11

0023
0000

000d
0026
002406 0106 0206
03ef ef05 0105 0205 0304 0104 0204 03ee
eeed ed03 0103 0203 0302 0102 0202 03

static ngx_uint_t ngx_pkt_peek_net_2(u_char p) { ngx_uint_t x = 0; x = p << 8; x |= *(p+1); return x; }

static ngx_int_t ngx_http_ssl_sni(u_char pkt, size_t len, ngx_str_t sni) { size_t off = 0; ngx_uint_t pkg_len, ext_len; if (len < 4) { return NGX_AGAIN; }

if (pkt[0] != 0x16) {
    return NGX_ERROR;
}

off = 3;
pkg_len = ngx_pkt_peek_net_2(pkt + off);
if (pkg_len > len) {
    return NGX_AGAIN;
}

off += 2;
if (off > len) {
    return NGX_AGAIN;
}

// Handshake Type: Client Hello
if (pkt[off] != 0x01) {
    return NGX_ERROR;
}
off += 1;  // 6

// 3byte:Length + 2byte:tls_version + 32byte Random
off += 3 + 2 + 32;

// session_ID
if (off + 1 > len) {
    return NGX_AGAIN;
}
off += ngx_pkt_peek_net_1(pkt + off) + 1;

// Cipher Suites
if (off + 2 > len) {
    return NGX_AGAIN;
}
off += ngx_pkt_peek_net_2(pkt + off) + 2;

// Compression Methods
if (off + 1 > len) {
    return NGX_AGAIN;
}
off += ngx_pkt_peek_net_1(pkt + off) + 1;

// Extensions Length
if (off + 2 > len) {
    return NGX_AGAIN;
}
ext_len = ngx_pkt_peek_net_2(pkt + off);

if (ext_len + off > len) {
    return NGX_AGAIN;
}

off += 2;
if (off + 2 > len) {
    return NGX_AGAIN;
}

while (off + 4 < len) {
    if (pkt[off] == 0x00 && pkt[off+1] == 0x00) {
        off += 6;
        if (off > len) {
            return NGX_AGAIN;
        }
        if (pkt[off] != 0x00) {
            return NGX_ERROR;
        }
        off += 1;
        if (off + 1 > len) {
            return NGX_AGAIN;
        }
        sni->len = ngx_pkt_peek_net_2(pkt + off);
        off += 2;
        if (sni->len + off > len) {
            return NGX_AGAIN;
        }
        sni->data = pkt + off;

        return NGX_OK;
    }
    off += 2 + ngx_pkt_peek_net_2(pkt + off + 2);
}

return NGX_ERROR;

}

endif


#### 生成带ext的多级证书

* 生成CA0

ca0.cnf

[req] distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no

[req_distinguished_name] C = CN ST = Beijing L = Beijing O = vislee CN = vislee Root CA0

[v3_req] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer basicConstraints = CA:TRUE


```sh
openssl req -newkey rsa:2048 -keyout CA0.key -out CA0.csr -nodes -config ca0.cnf -extensions v3_req
openssl x509 -req -in CA0.csr -out CA0.crt -signkey CA0.key -days 3650 -extensions v3_req -extfile ca0.cnf

ca1.cnf

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = vislee
CN = vislee Root CA1

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:TRUE
openssl req -newkey rsa:2048 -keyout CA1.key -out CA1.csr -nodes -config ca1.cnf -extensions v3_req
openssl x509 -req -in CA1.csr -out CA1.crt -CA CA0.crt -CAkey CA0.key -days 3650 -extensions v3_req -extfile ca1.cnf

server.cnf

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = vislee
CN = vislee.com

[v3_req]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
subjectAltName = @alt_names

[alt_names]
DNS.1 = vislee.com
DNS.2 = *.vislee.com
openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes -config server.cnf -extensions v3_req
openssl x509 -req -in server.csr -out server.crt -CA CA1.crt -CAkey CA1.key -days 1825 -extensions v3_req -extfile server.cnf
openssl verify -CAfile CA0.crt -untrusted CA1.crt server.crt

总结