source: mod_gnutls/src/gnutls_sni.c @ c0fc11e

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

Separate functions for default SNI and loading virtual host credentials

The default SNI method using gnutls_server_name_get() won't be
necessary with early SNI parsing, but needs to remain available as a
fallback for old GnuTLS versions.

Loading virtual host credentials should happen in a separate function
so it can easily happen in pre or post client hello hooks alike.

  • Property mode set to 100644
File size: 6.6 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}
140
141
142
143/**
144 * Default buffer size for SNI data, including the terminating NULL
145 * byte. The size matches what gnutls-cli uses initially.
146 */
147#define DEFAULT_SNI_HOST_LEN 256
148
149const char* mgs_server_name_get(mgs_handle_t *ctxt)
150{
151    char *sni_name = apr_palloc(ctxt->c->pool, DEFAULT_SNI_HOST_LEN);
152    size_t sni_len = DEFAULT_SNI_HOST_LEN;
153    unsigned int sni_type;
154
155    /* Search for a DNS SNI element. Note that RFC 6066 prohibits more
156     * than one server name per type. */
157    int sni_index = -1;
158    int rv = 0;
159    do {
160        /* The sni_index is incremented before each use, so if the
161         * loop terminates with a type match we will have the right
162         * one stored. */
163        rv = gnutls_server_name_get(ctxt->session, sni_name,
164                                    &sni_len, &sni_type, ++sni_index);
165        if (rv == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
166        {
167            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_EGENERAL, ctxt->c,
168                          "%s: no DNS SNI found (last index: %d).",
169                          __func__, sni_index);
170            return NULL;
171        }
172    } while (sni_type != GNUTLS_NAME_DNS);
173    /* The (rv == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) path inside
174     * the loop above returns, so if we reach this point we have a DNS
175     * SNI at the current index. */
176
177    if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER)
178    {
179        /* Allocate a new buffer of the right size and retry */
180        sni_name = apr_palloc(ctxt->c->pool, sni_len);
181        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
182                      "%s: reallocated SNI data buffer for %" APR_SIZE_T_FMT
183                      " bytes.", __func__, sni_len);
184        rv = gnutls_server_name_get(ctxt->session, sni_name,
185                                    &sni_len, &sni_type, sni_index);
186    }
187
188    /* Unless there's a bug in the GnuTLS API only GNUTLS_E_IDNA_ERROR
189     * can occur here, but a catch all is safer and no more
190     * complicated. */
191    if (rv != GNUTLS_E_SUCCESS)
192    {
193        ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_EGENERAL, ctxt->c,
194                      "%s: error while getting SNI DNS data: '%s' (%d).",
195                      __func__, gnutls_strerror(rv), rv);
196        return NULL;
197    }
198
199    return sni_name;
200}
Note: See TracBrowser for help on using the repository browser.