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 | static 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 | */ |
---|
47 | static 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 | */ |
---|
63 | int 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 | |
---|
149 | const 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 | } |
---|