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