source: mod_gnutls/src/gnutls_sni.c @ 7ff6c6c

debian/master
Last change on this file since 7ff6c6c was 7ff6c6c, checked in by Fiona Klute <fiona.klute@…>, 15 months ago

Add proof-of-concept SNI parser in a pre client hello hook

The SNI parser is complete, but right now the hook only retrieves the
SNI data and logs it. The goal is to select the right virtual host and
load ALPN parameters (and possibly others) before GnuTLS processes the
ClientHello? message. That should make different "Protocols" directives
between virtual hosts work as expected.

  • Property mode set to 100644
File size: 4.4 KB
Line 
1/*
2 *  Copyright 2018 Fiona Klute
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17#include "mod_gnutls.h"
18
19#include <apr_lib.h>
20#include <apr_strings.h>
21#include <byteswap.h>
22#include <gnutls/gnutls.h>
23#include <inttypes.h>
24
25/** Defined in https://tools.ietf.org/html/rfc6066#section-1.1 */
26#define EXT_ID_SERVER_NAME 0
27/** "host_name" type as defined in
28 * https://tools.ietf.org/html/rfc6066#section-3 */
29#define SERVER_NAME_TYPE_DNS 0
30/** size of type and length field for each ServerName as defined in
31 * https://tools.ietf.org/html/rfc6066#section-3 */
32#define SERVER_NAME_HDR_SIZE (sizeof(uint16_t) + sizeof(uint8_t))
33
34static inline uint16_t read_uint16(const unsigned char *data)
35{
36    uint16_t u;
37    memcpy(&u, data, sizeof(uint16_t));
38#if APR_IS_BIGENDIAN == 0
39    u = bswap_16(u);
40#endif
41    return u;
42}
43
44/**
45 * APR port of GnuTLS' _gnutls_dnsname_is_valid() (from lib/str.h)
46 */
47static inline int is_valid_dnsname(const unsigned char *str, unsigned int size)
48{
49    for (unsigned int i = 0; i < size; i++)
50    {
51        if (!(apr_isalnum(str[i]) || str[i] == '-' || str[i] == '.'))
52            return 0;
53    }
54    return 1;
55}
56
57/**
58 * Callback for gnutls_ext_raw_parse(), called for each
59 * extension. Check if the extension is a Server Name Indication,
60 * parse if so. The SNI data structure is defined in [RFC 6066
61 * Sec. 3](https://tools.ietf.org/html/rfc6066#section-3)
62 */
63int mgs_sni_ext_hook(void *ctx, unsigned tls_id,
64                     const unsigned char *data, unsigned size)
65{
66    const char *name = NULL;
67
68    gnutls_session_t session = (gnutls_session_t) ctx;
69    mgs_handle_t *ctxt = (mgs_handle_t *) gnutls_session_get_ptr(session);
70
71    if (tls_id == EXT_ID_SERVER_NAME)
72    {
73        /*
74         * This is SNI extension data. GnuTLS does the following (see
75         * _gnutls_server_name_recv_params() in lib/ext/server_name.c):
76         *
77         * Verify that total length lines up with received data size
78         *
79         * Iterate over type/size pairs, if type == 0 it's a DNS
80         * name. Ignore any other type.
81         *
82         * Verify a DNS name using _gnutls_dnsname_is_valid() (from
83         * lib/str.h)
84         *
85         * In case of any issue with sizes:
86         * return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
87         *
88         * In case of invalid data:
89         * return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
90         */
91
92        /* Read position for parsing */
93        unsigned int pos = 0;
94
95        /* Size of the ServerNameList (2 bytes) */
96        if (size < sizeof(uint16_t))
97            return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
98        uint16_t list_len = read_uint16(data);
99        pos += sizeof(uint16_t);
100
101        if (pos + list_len != size)
102            return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
103
104        while (pos + SERVER_NAME_HDR_SIZE <= size)
105        {
106            /* NameType (one byte) */
107            uint8_t type = *(data + pos);
108            pos += sizeof(uint8_t);
109            /* Size of the ServerName (2 bytes) */
110            uint16_t name_len = read_uint16(data + pos);
111            pos += sizeof(uint16_t);
112
113            if (pos + name_len > size)
114                return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
115
116            if (type == SERVER_NAME_TYPE_DNS)
117            {
118                if (!is_valid_dnsname(data + pos, name_len))
119                    return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
120                /* Without APR pools this would require a target
121                 * buffer or malloc/free */
122                name = apr_pstrndup(ctxt->c->pool,
123                                    (const char *) data + pos,
124                                    name_len);
125                /* We don't handle any other ServerName types, ignore
126                 * whatever follows */
127                break;
128            }
129            pos += name_len;
130        }
131    }
132
133    if (name != NULL)
134    {
135        /* Assign to session context */
136        ctxt->sni_name = name;
137    }
138    return 0;
139}
Note: See TracBrowser for help on using the repository browser.