source: mod_gnutls/src/gnutls_sni.c

mod_gnutls/0.10.0
Last change on this file was 0da10eb, checked in by Fiona Klute <fiona.klute@…>, 17 months ago

Document Early SNI related functions and rename the post client hello hook

  • Property mode set to 100644
File size: 7.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
34/**
35 * Read a 16 bit unsigned int in network byte order from the data,
36 * and return the value in host byte order.
37 */
38static inline uint16_t read_uint16(const unsigned char *data)
39{
40    uint16_t u;
41    memcpy(&u, data, sizeof(uint16_t));
42#if APR_IS_BIGENDIAN == 0
43    u = bswap_16(u);
44#endif
45    return u;
46}
47
48/**
49 * Check if the string contains only alphanumeric characters, `-`, and
50 * `.`. APR port of GnuTLS' _gnutls_dnsname_is_valid() (from
51 * lib/str.h).
52 *
53 * @param str the string to check
54 * @param size length of the input string (must not include any
55 * terminating null byte)
56 *
57 * @return `1` if the string is a valid DNS name, `0` otherwise
58 */
59static inline int is_valid_dnsname(const unsigned char *str, unsigned int size)
60{
61    for (unsigned int i = 0; i < size; i++)
62    {
63        if (!(apr_isalnum(str[i]) || str[i] == '-' || str[i] == '.'))
64            return 0;
65    }
66    return 1;
67}
68
69/**
70 * Callback for gnutls_ext_raw_parse(), checks if the extension is a
71 * Server Name Indication, and tries to parse it if so. In case of
72 * success the requested hostname is stored in the mod_gnutls session
73 * context.
74 *
75 * See [RFC 6066 Sec. 3](https://tools.ietf.org/html/rfc6066#section-3)
76 * for the definition of the SNI data structure. The function
77 * signature is defined by the GnuTLS API.
78 *
79 * @param ctx must be the `gnutls_session_t` for the current
80 * connection
81 * @param tls_id TLS extension ID
82 * @param data the extension data
83 * @param size length of the extension data (bytes)
84 *
85 * @return `GNUTLS_E_SUCCESS` or a GnuTLS error code
86 */
87int mgs_sni_ext_hook(void *ctx, unsigned tls_id,
88                     const unsigned char *data, unsigned size)
89{
90    const char *name = NULL;
91
92    gnutls_session_t session = (gnutls_session_t) ctx;
93    mgs_handle_t *ctxt = (mgs_handle_t *) gnutls_session_get_ptr(session);
94
95    if (tls_id == EXT_ID_SERVER_NAME)
96    {
97        /*
98         * This is SNI extension data. GnuTLS does the following (see
99         * _gnutls_server_name_recv_params() in lib/ext/server_name.c):
100         *
101         * Verify that total length lines up with received data size
102         *
103         * Iterate over type/size pairs, if type == 0 it's a DNS
104         * name. Ignore any other type.
105         *
106         * Verify a DNS name using _gnutls_dnsname_is_valid() (from
107         * lib/str.h)
108         *
109         * In case of any issue with sizes:
110         * return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
111         *
112         * In case of invalid data:
113         * return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
114         */
115
116        /* Read position for parsing */
117        unsigned int pos = 0;
118
119        /* Size of the ServerNameList (2 bytes) */
120        if (size < sizeof(uint16_t))
121            return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
122        uint16_t list_len = read_uint16(data);
123        pos += sizeof(uint16_t);
124
125        if (pos + list_len != size)
126            return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
127
128        while (pos + SERVER_NAME_HDR_SIZE <= size)
129        {
130            /* NameType (one byte) */
131            uint8_t type = *(data + pos);
132            pos += sizeof(uint8_t);
133            /* Size of the ServerName (2 bytes) */
134            uint16_t name_len = read_uint16(data + pos);
135            pos += sizeof(uint16_t);
136
137            if (pos + name_len > size)
138                return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
139
140            if (type == SERVER_NAME_TYPE_DNS)
141            {
142                if (!is_valid_dnsname(data + pos, name_len))
143                    return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER;
144                /* Without APR pools this would require a target
145                 * buffer or malloc/free */
146                name = apr_pstrndup(ctxt->c->pool,
147                                    (const char *) data + pos,
148                                    name_len);
149                /* We don't handle any other ServerName types, ignore
150                 * whatever follows */
151                break;
152            }
153            pos += name_len;
154        }
155    }
156
157    if (name != NULL)
158    {
159        /* Assign to session context */
160        ctxt->sni_name = name;
161    }
162    return GNUTLS_E_SUCCESS;
163}
164
165
166
167/**
168 * Default buffer size for SNI data, including the terminating NULL
169 * byte. The size matches what gnutls-cli uses initially.
170 */
171#define DEFAULT_SNI_HOST_LEN 256
172
173const char* mgs_server_name_get(mgs_handle_t *ctxt)
174{
175    char *sni_name = apr_palloc(ctxt->c->pool, DEFAULT_SNI_HOST_LEN);
176    size_t sni_len = DEFAULT_SNI_HOST_LEN;
177    unsigned int sni_type;
178
179    /* Search for a DNS SNI element. Note that RFC 6066 prohibits more
180     * than one server name per type. */
181    int sni_index = -1;
182    int rv = 0;
183    do {
184        /* The sni_index is incremented before each use, so if the
185         * loop terminates with a type match we will have the right
186         * one stored. */
187        rv = gnutls_server_name_get(ctxt->session, sni_name,
188                                    &sni_len, &sni_type, ++sni_index);
189        if (rv == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
190        {
191            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_EGENERAL, ctxt->c,
192                          "%s: no DNS SNI found (last index: %d).",
193                          __func__, sni_index);
194            return NULL;
195        }
196    } while (sni_type != GNUTLS_NAME_DNS);
197    /* The (rv == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) path inside
198     * the loop above returns, so if we reach this point we have a DNS
199     * SNI at the current index. */
200
201    if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER)
202    {
203        /* Allocate a new buffer of the right size and retry */
204        sni_name = apr_palloc(ctxt->c->pool, sni_len);
205        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
206                      "%s: reallocated SNI data buffer for %" APR_SIZE_T_FMT
207                      " bytes.", __func__, sni_len);
208        rv = gnutls_server_name_get(ctxt->session, sni_name,
209                                    &sni_len, &sni_type, sni_index);
210    }
211
212    /* Unless there's a bug in the GnuTLS API only GNUTLS_E_IDNA_ERROR
213     * can occur here, but a catch all is safer and no more
214     * complicated. */
215    if (rv != GNUTLS_E_SUCCESS)
216    {
217        ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_EGENERAL, ctxt->c,
218                      "%s: error while getting SNI DNS data: '%s' (%d).",
219                      __func__, gnutls_strerror(rv), rv);
220        return NULL;
221    }
222
223    return sni_name;
224}
Note: See TracBrowser for help on using the repository browser.