/src/freeradius-server/src/lib/tls/pairs.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 4fa71237698596e98c0b2849f86be943ac2defea $ |
19 | | * |
20 | | * @file tls/pairs.c |
21 | | * @brief Functions to convert certificate OIDs to attribute pairs |
22 | | * |
23 | | * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | */ |
25 | | RCSID("$Id: 4fa71237698596e98c0b2849f86be943ac2defea $") |
26 | | USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ |
27 | | |
28 | | #ifdef WITH_TLS |
29 | | #define LOG_PREFIX "tls" |
30 | | |
31 | | #include <freeradius-devel/tls/openssl_user_macros.h> |
32 | | #include <freeradius-devel/util/pair.h> |
33 | | #include <freeradius-devel/server/request.h> |
34 | | #include <freeradius-devel/server/pair.h> |
35 | | #include <freeradius-devel/der/der.h> |
36 | | |
37 | | #include "attrs.h" |
38 | | #include "bio.h" |
39 | | #include "log.h" |
40 | | #include "session.h" |
41 | | #include "utils.h" |
42 | | |
43 | | #include <openssl/x509v3.h> |
44 | | #include <openssl/ssl.h> |
45 | | |
46 | | DIAG_OFF(DIAG_UNKNOWN_PRAGMAS) |
47 | | DIAG_OFF(used-but-marked-unused) /* fix spurious warnings for sk macros */ |
48 | | /** Extract session pairs from the Subject Alternate Name extension |
49 | | * |
50 | | */ |
51 | | static bool tls_session_pairs_from_san(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509_EXTENSION *ext) |
52 | 0 | { |
53 | 0 | GENERAL_NAMES *names = NULL; |
54 | 0 | int i; |
55 | 0 | fr_pair_t *vp; |
56 | |
|
57 | 0 | if (!(names = X509V3_EXT_d2i(ext))) return false; |
58 | | |
59 | 0 | for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { |
60 | 0 | GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); |
61 | |
|
62 | 0 | switch (name->type) { |
63 | 0 | #ifdef GEN_EMAIL |
64 | 0 | case GEN_EMAIL: |
65 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, |
66 | 0 | attr_tls_certificate_subject_alt_name_email) == 0); |
67 | 0 | MEM(fr_pair_value_bstrndup(vp, |
68 | 0 | (char const *)ASN1_STRING_get0_data(name->d.rfc822Name), |
69 | 0 | ASN1_STRING_length(name->d.rfc822Name), true) == 0); |
70 | 0 | break; |
71 | 0 | #endif /* GEN_EMAIL */ |
72 | 0 | #ifdef GEN_DNS |
73 | 0 | case GEN_DNS: |
74 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, |
75 | 0 | attr_tls_certificate_subject_alt_name_dns) == 0); |
76 | 0 | MEM(fr_pair_value_bstrndup(vp, |
77 | 0 | (char const *)ASN1_STRING_get0_data(name->d.dNSName), |
78 | 0 | ASN1_STRING_length(name->d.dNSName), true) == 0); |
79 | 0 | break; |
80 | 0 | #endif /* GEN_DNS */ |
81 | 0 | #ifdef GEN_OTHERNAME |
82 | 0 | case GEN_OTHERNAME: |
83 | | /* look for a MS UPN */ |
84 | 0 | if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break; |
85 | | |
86 | | /* we've got a UPN - Must be ASN1-encoded UTF8 string */ |
87 | 0 | if (name->d.otherName->value->type == V_ASN1_UTF8STRING) { |
88 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, |
89 | 0 | attr_tls_certificate_subject_alt_name_upn) == 0); |
90 | 0 | MEM(fr_pair_value_bstrndup(vp, |
91 | 0 | (char const *)ASN1_STRING_get0_data(name->d.otherName->value->value.utf8string), |
92 | 0 | ASN1_STRING_length(name->d.otherName->value->value.utf8string), |
93 | 0 | true) == 0); |
94 | 0 | break; |
95 | 0 | } |
96 | 0 | RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)"); |
97 | 0 | break; |
98 | 0 | #endif /* GEN_OTHERNAME */ |
99 | 0 | default: |
100 | | /* XXX TODO handle other SAN types */ |
101 | 0 | break; |
102 | 0 | } |
103 | 0 | } |
104 | 0 | if (names != NULL) GENERAL_NAMES_free(names); |
105 | |
|
106 | 0 | return true; |
107 | 0 | } |
108 | | |
109 | | /** Extract session pairs from the X509v3-CRL-Distribution-Points extension |
110 | | * |
111 | | */ |
112 | | static bool tls_session_pairs_from_crl(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, UNUSED request_t *request, X509_EXTENSION *ext) |
113 | | { |
114 | | ASN1_STRING *s = X509_EXTENSION_get_data(ext); |
115 | | char unsigned const *data = ASN1_STRING_get0_data(s); |
116 | | STACK_OF(DIST_POINT) *dps; |
117 | | DIST_POINT *dp; |
118 | | STACK_OF(GENERAL_NAME) *names; |
119 | | GENERAL_NAME *name; |
120 | | fr_pair_t *vp; |
121 | | int i, j; |
122 | | |
123 | | if (!(dps = d2i_CRL_DIST_POINTS(NULL, &data, ASN1_STRING_length(s)))) return false; |
124 | | |
125 | | for (i = 0; i < sk_DIST_POINT_num(dps); i++) { |
126 | | dp = sk_DIST_POINT_value(dps, i); |
127 | | |
128 | | /* |
129 | | * RFC 5280 Section 4.2.1.13 says that the distpoint is optional. |
130 | | */ |
131 | | if (!dp->distpoint) { |
132 | | CRL_DIST_POINTS_free(dps); |
133 | | return false; |
134 | | } |
135 | | |
136 | | names = dp->distpoint->name.fullname; |
137 | | |
138 | | /* |
139 | | * We only want CRL distribution points that cover all reasons, |
140 | | * so ignore any where reasons are set. |
141 | | */ |
142 | | if (dp->reasons) continue; |
143 | | |
144 | | for (j = 0; j < sk_GENERAL_NAME_num(names); j++) { |
145 | | name = sk_GENERAL_NAME_value(names, j); |
146 | | |
147 | | if (name->type != GEN_URI) continue; |
148 | | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, |
149 | | attr_tls_certificate_x509v3_crl_distribution_points) == 0); |
150 | | MEM(fr_pair_value_strdup(vp, |
151 | | (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier), |
152 | | true) == 0); |
153 | | } |
154 | | } |
155 | | |
156 | | CRL_DIST_POINTS_free(dps); |
157 | | |
158 | | return true; |
159 | | } |
160 | | |
161 | | /** Extract attributes from an X509 certificate |
162 | | * |
163 | | * @param[out] pair_list to copy attributes to. |
164 | | * @param[in] ctx to allocate attributes in. |
165 | | * @param[in] request the current request. |
166 | | * @param[in] cert to validate. |
167 | | * @param[in] der_decode should the certificate be parsed with the DER decoder. |
168 | | * @return |
169 | | * - 1 already exists. |
170 | | * - 0 on success. |
171 | | * - < 0 on failure. |
172 | | */ |
173 | | int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509 *cert, |
174 | | #if OPENSSL_VERSION_NUMBER >= 0x30400000L |
175 | | bool der_decode |
176 | | #else |
177 | | UNUSED bool der_decode |
178 | | #endif |
179 | | ) |
180 | 0 | { |
181 | 0 | int loc; |
182 | 0 | char buff[1024]; |
183 | |
|
184 | 0 | ASN1_TIME const *asn_time; |
185 | 0 | time_t time; |
186 | |
|
187 | 0 | STACK_OF(X509_EXTENSION) const *ext_list = NULL; |
188 | |
|
189 | 0 | fr_pair_t *vp = NULL; |
190 | 0 | ssize_t slen; |
191 | 0 | bool san_found = false, crl_found = false; |
192 | | |
193 | | /* |
194 | | * We require OpenSSL >= 3.4 to call the DER decoder due to the stack size |
195 | | * needed to handle the recursive calls involved in certificate decoding. |
196 | | */ |
197 | | #if OPENSSL_VERSION_NUMBER >= 0x30400000L |
198 | | if (der_decode) { |
199 | | uint8_t *cert_der; |
200 | | uint8_t *cd; |
201 | | int der_len; |
202 | | fr_der_decode_ctx_t der_ctx; |
203 | | fr_pair_list_t tmp_list; |
204 | | |
205 | | der_len = i2d_X509(cert, NULL); |
206 | | if (der_len < 0) { |
207 | | fr_tls_log(request, "Failed retrieving certificate"); |
208 | | return -1; |
209 | | } |
210 | | der_ctx = (fr_der_decode_ctx_t) { |
211 | | .tmp_ctx = talloc_new(ctx), |
212 | | .root = attr_der_certificate, |
213 | | }; |
214 | | cert_der = cd = talloc_array(der_ctx.tmp_ctx, uint8_t, der_len); |
215 | | i2d_X509(cert, &cd); |
216 | | fr_pair_list_init(&tmp_list); |
217 | | slen = fr_der_decode_pair_dbuff(request->session_state_ctx, &tmp_list, |
218 | | attr_der_certificate, &FR_DBUFF_TMP(cert_der, (size_t)der_len), &der_ctx); |
219 | | talloc_free(der_ctx.tmp_ctx); |
220 | | if (slen < 0) { |
221 | | fr_tls_log(request, "Failed decoding certificate"); |
222 | | fr_pair_list_free(&tmp_list); |
223 | | return -1; |
224 | | } |
225 | | /* |
226 | | * Certificates are decoded in CA .. intermediate .. client sequence |
227 | | * so, prepend each decoded one to get the client cert first. |
228 | | */ |
229 | | fr_pair_list_prepend(&request->session_state_pairs, &tmp_list); |
230 | | } |
231 | | #endif |
232 | | |
233 | | /* |
234 | | * Subject |
235 | | */ |
236 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_subject) == 0); |
237 | 0 | if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0), |
238 | 0 | X509_get_subject_name(cert), 0, XN_FLAG_ONELINE) < 0)) { |
239 | 0 | fr_tls_bio_dbuff_thread_local_clear(); |
240 | 0 | fr_tls_log(request, "Failed retrieving certificate subject"); |
241 | 0 | error: |
242 | 0 | fr_pair_list_free(pair_list); |
243 | 0 | return -1; |
244 | 0 | } |
245 | 0 | fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true); |
246 | |
|
247 | 0 | RDEBUG3("Creating attributes for \"%pV\":", fr_box_strvalue_buffer(vp->vp_strvalue)); |
248 | | |
249 | | /* |
250 | | * Common name |
251 | | */ |
252 | 0 | slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert), |
253 | 0 | NID_commonName, NULL, 0); |
254 | 0 | if (slen > 0) { |
255 | 0 | char *cn; |
256 | |
|
257 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_common_name) == 0); |
258 | 0 | MEM(fr_pair_value_bstr_alloc(vp, &cn, (size_t)slen, true) == 0); /* Allocs \0 byte in addition to len */ |
259 | |
|
260 | 0 | slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, cn, (size_t)slen + 1); |
261 | 0 | if (slen < 0) { |
262 | 0 | fr_tls_log(request, "Failed retrieving certificate common name"); |
263 | 0 | goto error; |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | | /* |
268 | | * Signature |
269 | | */ |
270 | 0 | { |
271 | 0 | ASN1_BIT_STRING const *sig; |
272 | 0 | X509_ALGOR const *alg; |
273 | |
|
274 | 0 | X509_get0_signature(&sig, &alg, cert); |
275 | |
|
276 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_signature) == 0); |
277 | 0 | MEM(fr_pair_value_memdup(vp, |
278 | 0 | (uint8_t const *)ASN1_STRING_get0_data(sig), |
279 | 0 | ASN1_STRING_length(sig), true) == 0); |
280 | |
|
281 | 0 | OBJ_obj2txt(buff, sizeof(buff), alg->algorithm, 0); |
282 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_signature_algorithm) == 0); |
283 | 0 | fr_pair_value_strdup(vp, buff, false); |
284 | 0 | } |
285 | | |
286 | | /* |
287 | | * Issuer |
288 | | */ |
289 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_issuer) == 0); |
290 | 0 | if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0), |
291 | 0 | X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE) < 0)) { |
292 | 0 | fr_tls_bio_dbuff_thread_local_clear(); |
293 | 0 | fr_tls_log(request, "Failed retrieving certificate issuer"); |
294 | 0 | goto error; |
295 | 0 | } |
296 | 0 | fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true); |
297 | | |
298 | | /* |
299 | | * Serial number |
300 | | */ |
301 | 0 | { |
302 | 0 | ASN1_INTEGER const *serial = NULL; |
303 | 0 | unsigned char *der; |
304 | 0 | int len; |
305 | |
|
306 | 0 | serial = X509_get0_serialNumber(cert); |
307 | 0 | if (!serial) { |
308 | 0 | fr_tls_log(request, "Failed retrieving certificate serial"); |
309 | 0 | goto error; |
310 | 0 | } |
311 | | |
312 | 0 | len = i2d_ASN1_INTEGER(serial, NULL); /* get length */ |
313 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_serial) == 0); |
314 | 0 | MEM(fr_pair_value_mem_alloc(vp, &der, len, false) == 0); |
315 | 0 | i2d_ASN1_INTEGER(serial, &der); |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * Not valid before |
320 | | */ |
321 | 0 | asn_time = X509_get0_notBefore(cert); |
322 | |
|
323 | 0 | if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) { |
324 | 0 | RPWDEBUG("Failed parsing certificate not-before"); |
325 | 0 | goto error; |
326 | 0 | } |
327 | | |
328 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_not_before) == 0); |
329 | 0 | vp->vp_date = fr_unix_time_from_time(time); |
330 | | |
331 | | /* |
332 | | * Not valid after |
333 | | */ |
334 | 0 | asn_time = X509_get0_notAfter(cert); |
335 | |
|
336 | 0 | if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) { |
337 | 0 | RPWDEBUG("Failed parsing certificate not-after"); |
338 | 0 | goto error; |
339 | 0 | } |
340 | | |
341 | 0 | MEM(fr_pair_append_by_da(ctx, &vp, pair_list, attr_tls_certificate_not_after) == 0); |
342 | 0 | vp->vp_date = fr_unix_time_from_time(time); |
343 | | |
344 | | /* |
345 | | * Get the RFC822 Subject Alternative Name |
346 | | */ |
347 | 0 | loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0); |
348 | 0 | if (loc >= 0) { |
349 | 0 | X509_EXTENSION *ext = X509_get_ext(cert, loc); |
350 | 0 | if (ext) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext); |
351 | 0 | } |
352 | |
|
353 | 0 | loc = X509_get_ext_by_NID(cert, NID_crl_distribution_points, 0); |
354 | 0 | if (loc >= 0) { |
355 | 0 | X509_EXTENSION *ext = X509_get_ext(cert, loc); |
356 | 0 | if (ext) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext); |
357 | 0 | } |
358 | | |
359 | | /* |
360 | | * Only add extensions for the actual client certificate |
361 | | */ |
362 | 0 | ext_list = X509_get0_extensions(cert); |
363 | 0 | if (unlikely(!ext_list)) { |
364 | 0 | RWDEBUG("Failed retrieving extensions"); |
365 | 0 | goto done; |
366 | 0 | } |
367 | | |
368 | | /* |
369 | | * Grab the X509 extensions, and create attributes out of them. |
370 | | * For laziness, we reuse the OpenSSL names |
371 | | */ |
372 | 0 | if (sk_X509_EXTENSION_num(ext_list) > 0) { |
373 | 0 | int i; |
374 | 0 | BIO *bio; |
375 | 0 | fr_tls_bio_dbuff_t *bd; |
376 | 0 | fr_dbuff_t *in, *out; |
377 | |
|
378 | 0 | bio = fr_tls_bio_dbuff_alloc(&bd, NULL, NULL, 257, 4097, true); |
379 | 0 | in = fr_tls_bio_dbuff_in(bd); |
380 | 0 | out = fr_tls_bio_dbuff_out(bd); |
381 | |
|
382 | 0 | for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) { |
383 | 0 | ASN1_OBJECT *obj; |
384 | 0 | X509_EXTENSION *ext; |
385 | 0 | fr_dict_attr_t const *da; |
386 | 0 | char *p; |
387 | |
|
388 | 0 | ext = sk_X509_EXTENSION_value(ext_list, i); |
389 | |
|
390 | 0 | obj = X509_EXTENSION_get_object(ext); |
391 | | |
392 | | /* |
393 | | * If this is extension is Subject Alternate Name have we already |
394 | | * handled it? If not, do that now. |
395 | | * |
396 | | * Some certificate encodings have been observed where the SAN extension |
397 | | * was not found by X509_get_ext_by_NID() but then seen when the list |
398 | | * of extensions is handled here. |
399 | | */ |
400 | 0 | if (OBJ_obj2nid(obj) == NID_subject_alt_name) { |
401 | 0 | if (!san_found) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext); |
402 | 0 | goto again; |
403 | 0 | } |
404 | | |
405 | 0 | if (OBJ_obj2nid(obj) == NID_crl_distribution_points) { |
406 | 0 | if (!crl_found) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext); |
407 | 0 | goto again; |
408 | 0 | } |
409 | | |
410 | 0 | if (i2a_ASN1_OBJECT(bio, obj) <= 0) { |
411 | 0 | RPWDEBUG("Skipping X509 Extension (%i) conversion to attribute. " |
412 | 0 | "Conversion from ASN1 failed...", i); |
413 | 0 | again: |
414 | 0 | fr_tls_bio_dbuff_reset(bd); |
415 | 0 | continue; |
416 | 0 | } |
417 | | |
418 | 0 | if (fr_dbuff_remaining(out) == 0) goto again; /* Nothing written ? */ |
419 | | |
420 | | /* |
421 | | * All disallowed chars get mashed to '-' |
422 | | */ |
423 | 0 | for (p = (char *)fr_dbuff_current(out); |
424 | 0 | p < (char *)fr_dbuff_end(out); |
425 | 0 | p++) if (!fr_dict_attr_allowed_chars[(uint8_t)*p]) *p = '-'; |
426 | | |
427 | | /* |
428 | | * Terminate the buffer (after char replacement, |
429 | | * so we do don't replace the \0) |
430 | | */ |
431 | 0 | if (unlikely(fr_dbuff_in_bytes(in, (uint8_t)'\0') <= 0)) { |
432 | 0 | RWDEBUG("Attribute name too long"); |
433 | 0 | goto again; |
434 | 0 | } |
435 | | |
436 | 0 | da = fr_dict_attr_by_name(NULL, attr_tls_certificate, (char *)fr_dbuff_current(out)); |
437 | |
|
438 | 0 | fr_dbuff_set(in, fr_dbuff_current(in) - 1); /* Ensure the \0 isn't counted in remaining */ |
439 | |
|
440 | 0 | if (!da) { |
441 | 0 | RWDEBUG3("Skipping attribute \"%pV\": " |
442 | 0 | "Add a dictionary definition if you want to access it", |
443 | 0 | fr_box_strvalue_len((char *)fr_dbuff_current(out), |
444 | 0 | fr_dbuff_remaining(out))); |
445 | 0 | fr_strerror_clear(); /* Don't leave spurious errors from failed resolution */ |
446 | 0 | goto again; |
447 | 0 | } |
448 | | |
449 | 0 | fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */ |
450 | |
|
451 | 0 | if (X509V3_EXT_print(bio, ext, X509V3_EXT_PARSE_UNKNOWN, 0) != 1) { |
452 | 0 | REDEBUG("Failed extracting data for \"%s\"", da->name); |
453 | 0 | goto again; |
454 | 0 | } |
455 | | |
456 | 0 | MEM(vp = fr_pair_afrom_da(ctx, da)); |
457 | 0 | if (fr_pair_value_from_str(vp, (char *)fr_dbuff_current(out), fr_dbuff_remaining(out), |
458 | 0 | NULL, true) < 0) { |
459 | 0 | RPWDEBUG3("Skipping: %s += \"%pV\"", |
460 | 0 | da->name, fr_box_strvalue_len((char *)fr_dbuff_current(out), |
461 | 0 | fr_dbuff_remaining(out))); |
462 | 0 | talloc_free(vp); |
463 | 0 | goto again; |
464 | 0 | } |
465 | 0 | fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */ |
466 | |
|
467 | 0 | fr_pair_append(pair_list, vp); |
468 | 0 | } |
469 | 0 | talloc_free(bd); |
470 | 0 | } |
471 | | |
472 | 0 | done: |
473 | 0 | return 0; |
474 | 0 | } |
475 | | |
476 | | static int fr_tls_extension_decode(request_t *request, fr_pair_t *container, uint8_t const *extension, |
477 | | size_t hlen, size_t dlen, size_t total_len, fr_dict_attr_t const *da) |
478 | 0 | { |
479 | 0 | size_t i,extension_len; |
480 | 0 | uint8_t const *data; |
481 | |
|
482 | 0 | fr_assert((hlen == 1) || (hlen == 2)); |
483 | 0 | fr_assert((dlen == 1) || (dlen == 2)); |
484 | |
|
485 | 0 | if (total_len < hlen) { |
486 | 0 | REDEBUG("Missing length in %s extension", da->name); |
487 | 0 | return -1; |
488 | 0 | } |
489 | | |
490 | 0 | if (hlen == 1) { |
491 | 0 | fr_assert(da->type == FR_TYPE_UINT8); |
492 | 0 | extension_len = extension[0]; |
493 | 0 | } else { |
494 | 0 | fr_assert(da->type == FR_TYPE_UINT16); |
495 | 0 | extension_len = (extension[0] << 8) + extension[1]; |
496 | 0 | } |
497 | |
|
498 | 0 | if (extension_len * dlen + hlen > total_len) { |
499 | 0 | REDEBUG("Invalid length in %s extension", da->name); |
500 | 0 | return -1; |
501 | 0 | } |
502 | | |
503 | 0 | data = extension + hlen; |
504 | |
|
505 | 0 | for (i = 0; i < extension_len; i += dlen) { |
506 | 0 | fr_pair_t *vp; |
507 | |
|
508 | 0 | MEM(fr_pair_append_by_da(container, &vp, &container->vp_group, da) >= 0); |
509 | |
|
510 | 0 | switch (dlen) { |
511 | 0 | case 1: |
512 | 0 | vp->vp_uint8= data[0]; |
513 | 0 | break; |
514 | | |
515 | 0 | case 2: |
516 | 0 | vp->vp_uint16 = (data[0] << 8) + data[1]; |
517 | 0 | break; |
518 | 0 | } |
519 | | |
520 | 0 | data += dlen; |
521 | 0 | } |
522 | | |
523 | 0 | return 0; |
524 | 0 | } |
525 | | |
526 | | /** Callback to extract pairs from a Client Hello |
527 | | * |
528 | | */ |
529 | | int fr_tls_session_client_hello_cb(SSL *ssl, UNUSED int *al, UNUSED void *arg) |
530 | 0 | { |
531 | 0 | request_t *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); |
532 | 0 | request_t *parent = request->parent; |
533 | 0 | uint8_t const *ciphers, *extension; |
534 | 0 | int i, *extensions = NULL; |
535 | 0 | size_t data_size, j; |
536 | 0 | STACK_OF(SSL_CIPHER) *sk; |
537 | 0 | SSL_CIPHER const *cipher; |
538 | 0 | STACK_OF(SSL_CIPHER) *scsvs; |
539 | 0 | fr_pair_t *container, *vp; |
540 | |
|
541 | 0 | fr_assert(parent); |
542 | |
|
543 | 0 | container = fr_pair_find_by_da(&parent->session_state_pairs, NULL, attr_tls_client_hello); |
544 | 0 | if (container) return SSL_CLIENT_HELLO_SUCCESS; |
545 | | |
546 | 0 | MEM(container = fr_pair_afrom_da(parent->session_state_ctx, attr_tls_client_hello)); |
547 | |
|
548 | 0 | data_size = SSL_client_hello_get0_ciphers(ssl, &ciphers); |
549 | 0 | if (SSL_bytes_to_cipher_list(ssl, ciphers, data_size, SSL_client_hello_isv2(ssl), &sk, &scsvs) == 0) { |
550 | 0 | RPEDEBUG("Failed to decode cipher list"); |
551 | 0 | fail: |
552 | 0 | if (extensions) OPENSSL_free(extensions); |
553 | 0 | talloc_free(container); |
554 | 0 | return SSL_CLIENT_HELLO_ERROR; |
555 | 0 | } |
556 | | |
557 | 0 | for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) { |
558 | 0 | fr_pair_append_by_da(container, &vp, &container->vp_group, attr_tls_client_hello_cipher); |
559 | 0 | cipher = sk_SSL_CIPHER_value(sk, i); |
560 | 0 | fr_value_box_strdup(vp, &vp->data, NULL, SSL_CIPHER_get_name(cipher), false); |
561 | 0 | } |
562 | |
|
563 | 0 | sk_SSL_CIPHER_free(sk); |
564 | 0 | sk_SSL_CIPHER_free(scsvs); |
565 | | |
566 | | /* |
567 | | * Fetch the extensions and decode the ones we know about |
568 | | * These are the ones which are returned as simple lists |
569 | | * of values of known length which map to enum attributes. |
570 | | */ |
571 | 0 | if (SSL_client_hello_get1_extensions_present(ssl, &extensions, &data_size) == 0) { |
572 | 0 | RPEDEBUG("Failed to fetch client hello extensions"); |
573 | 0 | goto fail; |
574 | 0 | } |
575 | | |
576 | 0 | for (j = 0; j < data_size; j++) { |
577 | 0 | size_t total_len; |
578 | |
|
579 | 0 | if (SSL_client_hello_get0_ext(ssl, extensions[j], &extension, &total_len) == 0) { |
580 | 0 | RPDEBUG("Failed getting client hello extension %d", extensions[j]); |
581 | 0 | goto fail; |
582 | 0 | } |
583 | | |
584 | 0 | switch (extensions[j]) { |
585 | 0 | case TLSEXT_TYPE_supported_groups: /* length[2] + 2*data */ |
586 | 0 | if (fr_tls_extension_decode(request, container, extension, 2, 2, total_len, |
587 | 0 | attr_tls_client_hello_supported_group) < 0) goto fail; |
588 | 0 | break; |
589 | | |
590 | 0 | case TLSEXT_TYPE_ec_point_formats: /* length[1] + data */ |
591 | 0 | if (fr_tls_extension_decode(request, container, extension, 1, 1, total_len, |
592 | 0 | attr_tls_client_hello_ec_point_format) < 0) goto fail; |
593 | 0 | break; |
594 | | |
595 | 0 | case TLSEXT_TYPE_signature_algorithms: /* length[2] + 2*data */ |
596 | 0 | if (fr_tls_extension_decode(request, container, extension, 2, 2, total_len, |
597 | 0 | attr_tls_client_hello_sig_algo) < 0) goto fail; |
598 | 0 | break; |
599 | | |
600 | 0 | case TLSEXT_TYPE_supported_versions: /* length[1] + 2*data */ |
601 | 0 | if (fr_tls_extension_decode(request, container, extension, 1, 2, total_len, |
602 | 0 | attr_tls_client_hello_tls_version) < 0) goto fail; |
603 | 0 | break; |
604 | | |
605 | 0 | case TLSEXT_TYPE_psk_kex_modes: /* length[1] + data */ |
606 | 0 | if (fr_tls_extension_decode(request, container, extension, 1, 1, total_len, |
607 | 0 | attr_tls_client_hello_psk_key_mode) < 0) goto fail; |
608 | 0 | break; |
609 | 0 | } |
610 | 0 | } |
611 | | |
612 | 0 | if (extensions) OPENSSL_free(extensions); |
613 | | |
614 | | /* |
615 | | * This is the version being negotiated by the client, but tops at 1.2. |
616 | | * After that the supported_versions extension is where the real value is. |
617 | | */ |
618 | 0 | fr_pair_append_by_da(container, &vp, &container->vp_group, attr_tls_client_hello_tls_version); |
619 | 0 | vp->vp_uint16 = SSL_client_hello_get0_legacy_version(ssl); |
620 | |
|
621 | 0 | fr_pair_append(&parent->session_state_pairs, container); |
622 | |
|
623 | | return SSL_CLIENT_HELLO_SUCCESS; |
624 | 0 | } |
625 | | DIAG_ON(used-but-marked-unused) |
626 | | DIAG_ON(DIAG_UNKNOWN_PRAGMAS) |
627 | | #endif |