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