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 | */ |
---|
38 | static 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 | */ |
---|
59 | static 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 | */ |
---|
87 | int 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 | |
---|
173 | const 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 | } |
---|