The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
pairs.c
Go to the documentation of this file.
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: 96d1505c7ed6fa18c2f020a80654ba63bdd60511 $
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 */
25RCSID("$Id: 96d1505c7ed6fa18c2f020a80654ba63bdd60511 $")
26USES_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
36#include "attrs.h"
37#include "bio.h"
38#include "log.h"
39#include "session.h"
40#include "utils.h"
41
42#include <openssl/x509v3.h>
43#include <openssl/ssl.h>
44
46DIAG_OFF(used-but-marked-unused) /* fix spurious warnings for sk macros */
47/** Extract session pairs from the Subject Alternate Name extension
48 *
49 */
50static bool tls_session_pairs_from_san(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509_EXTENSION *ext)
51{
52 GENERAL_NAMES *names = NULL;
53 int i;
55
56 if (!(names = X509V3_EXT_d2i(ext))) return false;
57
58 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
59 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
60
61 switch (name->type) {
62#ifdef GEN_EMAIL
63 case GEN_EMAIL:
67 (char const *)ASN1_STRING_get0_data(name->d.rfc822Name),
68 ASN1_STRING_length(name->d.rfc822Name), true) == 0);
69 break;
70#endif /* GEN_EMAIL */
71#ifdef GEN_DNS
72 case GEN_DNS:
76 (char const *)ASN1_STRING_get0_data(name->d.dNSName),
77 ASN1_STRING_length(name->d.dNSName), true) == 0);
78 break;
79#endif /* GEN_DNS */
80#ifdef GEN_OTHERNAME
81 case GEN_OTHERNAME:
82 /* look for a MS UPN */
83 if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break;
84
85 /* we've got a UPN - Must be ASN1-encoded UTF8 string */
86 if (name->d.otherName->value->type == V_ASN1_UTF8STRING) {
90 (char const *)ASN1_STRING_get0_data(name->d.otherName->value->value.utf8string),
91 ASN1_STRING_length(name->d.otherName->value->value.utf8string),
92 true) == 0);
93 break;
94 }
95 RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)");
96 break;
97#endif /* GEN_OTHERNAME */
98 default:
99 /* XXX TODO handle other SAN types */
100 break;
101 }
102 }
103 if (names != NULL) GENERAL_NAMES_free(names);
104
105 return true;
106}
107
108/** Extract session pairs from the X509v3-CRL-Distribution-Points extension
109 *
110 */
111static bool tls_session_pairs_from_crl(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, UNUSED request_t *request, X509_EXTENSION *ext)
112{
113 ASN1_STRING *s = X509_EXTENSION_get_data(ext);
114 char unsigned const *data = ASN1_STRING_get0_data(s);
115 STACK_OF(DIST_POINT) *dps;
116 DIST_POINT *dp;
117 STACK_OF(GENERAL_NAME) *names;
118 GENERAL_NAME *name;
119 fr_pair_t *vp;
120 int i, j;
121
122 if (!(dps = d2i_CRL_DIST_POINTS(NULL, &data, ASN1_STRING_length(s)))) return false;
123
124 for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
125 dp = sk_DIST_POINT_value(dps, i);
126 names = dp->distpoint->name.fullname;
127
128 /*
129 * We only want CRL distribution points that cover all reasons,
130 * so ignore any where reasons are set.
131 */
132 if (dp->reasons) continue;
133
134 for (j = 0; j < sk_GENERAL_NAME_num(names); j++) {
135 name = sk_GENERAL_NAME_value(names, j);
136
137 if (name->type != GEN_URI) continue;
141 (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier),
142 true) == 0);
143 }
144 }
145
146 CRL_DIST_POINTS_free(dps);
147
148 return true;
149}
150
151/** Extract attributes from an X509 certificate
152 *
153 * @param[out] pair_list to copy attributes to.
154 * @param[in] ctx to allocate attributes in.
155 * @param[in] request the current request.
156 * @param[in] cert to validate.
157 * @return
158 * - 1 already exists.
159 * - 0 on success.
160 * - < 0 on failure.
161 */
162int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509 *cert)
163{
164 int loc;
165 char buff[1024];
166
167 ASN1_TIME const *asn_time;
168 time_t time;
169
170 STACK_OF(X509_EXTENSION) const *ext_list = NULL;
171
172 fr_pair_t *vp = NULL;
173 ssize_t slen;
174 bool san_found = false, crl_found = false;
175
176 /*
177 * Subject
178 */
180 if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
181 X509_get_subject_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
182 fr_tls_bio_dbuff_thread_local_clear();
183 fr_tls_log(request, "Failed retrieving certificate subject");
184 error:
186 return -1;
187 }
188 fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
189
190 RDEBUG3("Creating attributes for \"%pV\":", fr_box_strvalue_buffer(vp->vp_strvalue));
191
192 /*
193 * Common name
194 */
195 slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
196 NID_commonName, NULL, 0);
197 if (slen > 0) {
198 char *cn;
199
201 MEM(fr_pair_value_bstr_alloc(vp, &cn, (size_t)slen, true) == 0); /* Allocs \0 byte in addition to len */
202
203 slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, cn, (size_t)slen + 1);
204 if (slen < 0) {
205 fr_tls_log(request, "Failed retrieving certificate common name");
206 goto error;
207 }
208 }
209
210 /*
211 * Signature
212 */
213 {
214 ASN1_BIT_STRING const *sig;
215 X509_ALGOR const *alg;
216
217 X509_get0_signature(&sig, &alg, cert);
218
221 (uint8_t const *)ASN1_STRING_get0_data(sig),
222 ASN1_STRING_length(sig), true) == 0);
223
224 OBJ_obj2txt(buff, sizeof(buff), alg->algorithm, 0);
227 }
228
229 /*
230 * Issuer
231 */
233 if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
234 X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
235 fr_tls_bio_dbuff_thread_local_clear();
236 fr_tls_log(request, "Failed retrieving certificate issuer");
237 goto error;
238 }
239 fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
240
241 /*
242 * Serial number
243 */
244 {
245 ASN1_INTEGER const *serial = NULL;
246 unsigned char *der;
247 int len;
248
249 serial = X509_get0_serialNumber(cert);
250 if (!serial) {
251 fr_tls_log(request, "Failed retrieving certificate serial");
252 goto error;
253 }
254
255 len = i2d_ASN1_INTEGER(serial, NULL); /* get length */
257 MEM(fr_pair_value_mem_alloc(vp, &der, len, false) == 0);
258 i2d_ASN1_INTEGER(serial, &der);
259 }
260
261 /*
262 * Not valid before
263 */
264 asn_time = X509_get0_notBefore(cert);
265
266 if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
267 RPWDEBUG("Failed parsing certificate not-before");
268 goto error;
269 }
270
272 vp->vp_date = fr_unix_time_from_time(time);
273
274 /*
275 * Not valid after
276 */
277 asn_time = X509_get0_notAfter(cert);
278
279 if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
280 RPWDEBUG("Failed parsing certificate not-after");
281 goto error;
282 }
283
285 vp->vp_date = fr_unix_time_from_time(time);
286
287 /*
288 * Get the RFC822 Subject Alternative Name
289 */
290 loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0);
291 if (loc >= 0) {
292 X509_EXTENSION *ext = X509_get_ext(cert, loc);
293 if (ext) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
294 }
295
296 loc = X509_get_ext_by_NID(cert, NID_crl_distribution_points, 0);
297 if (loc >= 0) {
298 X509_EXTENSION *ext = X509_get_ext(cert, loc);
299 if (ext) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext);
300 }
301
302 /*
303 * Only add extensions for the actual client certificate
304 */
305 ext_list = X509_get0_extensions(cert);
306 if (unlikely(!ext_list)) {
307 RWDEBUG("Failed retrieving extensions");
308 goto done;
309 }
310
311 /*
312 * Grab the X509 extensions, and create attributes out of them.
313 * For laziness, we reuse the OpenSSL names
314 */
315 if (sk_X509_EXTENSION_num(ext_list) > 0) {
316 int i;
317 BIO *bio;
318 fr_tls_bio_dbuff_t *bd;
319 fr_dbuff_t *in, *out;
320
321 bio = fr_tls_bio_dbuff_alloc(&bd, NULL, NULL, 257, 4097, true);
322 in = fr_tls_bio_dbuff_in(bd);
323 out = fr_tls_bio_dbuff_out(bd);
324
325 for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
326 ASN1_OBJECT *obj;
327 X509_EXTENSION *ext;
328 fr_dict_attr_t const *da;
329 char *p;
330
331 ext = sk_X509_EXTENSION_value(ext_list, i);
332
333 obj = X509_EXTENSION_get_object(ext);
334
335 /*
336 * If this is extension is Subject Alternate Name have we already
337 * handled it? If not, do that now.
338 *
339 * Some certificate encodings have been observed where the SAN extension
340 * was not found by X509_get_ext_by_NID() but then seen when the list
341 * of extensions is handled here.
342 */
343 if (OBJ_obj2nid(obj) == NID_subject_alt_name) {
344 if (!san_found) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
345 goto again;
346 }
347
348 if (OBJ_obj2nid(obj) == NID_crl_distribution_points) {
349 if (!crl_found) crl_found = tls_session_pairs_from_crl(pair_list, ctx, request, ext);
350 goto again;
351 }
352
353 if (i2a_ASN1_OBJECT(bio, obj) <= 0) {
354 RPWDEBUG("Skipping X509 Extension (%i) conversion to attribute. "
355 "Conversion from ASN1 failed...", i);
356 again:
357 fr_tls_bio_dbuff_reset(bd);
358 continue;
359 }
360
361 if (fr_dbuff_remaining(out) == 0) goto again; /* Nothing written ? */
362
363 /*
364 * All disallowed chars get mashed to '-'
365 */
366 for (p = (char *)fr_dbuff_current(out);
367 p < (char *)fr_dbuff_end(out);
368 p++) if (!fr_dict_attr_allowed_chars[(uint8_t)*p]) *p = '-';
369
370 /*
371 * Terminate the buffer (after char replacement,
372 * so we do don't replace the \0)
373 */
374 if (unlikely(fr_dbuff_in_bytes(in, (uint8_t)'\0') <= 0)) {
375 RWDEBUG("Attribute name too long");
376 goto again;
377 }
378
380
381 fr_dbuff_set(in, fr_dbuff_current(in) - 1); /* Ensure the \0 isn't counted in remaining */
382
383 if (!da) {
384 RWDEBUG3("Skipping attribute \"%pV\": "
385 "Add a dictionary definition if you want to access it",
388 fr_strerror_clear(); /* Don't leave spurious errors from failed resolution */
389 goto again;
390 }
391
392 fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
393
394 if (X509V3_EXT_print(bio, ext, X509V3_EXT_PARSE_UNKNOWN, 0) != 1) {
395 REDEBUG("Failed extracting data for \"%s\"", da->name);
396 goto again;
397 }
398
399 MEM(vp = fr_pair_afrom_da(ctx, da));
401 NULL, true) < 0) {
402 RPWDEBUG3("Skipping: %s += \"%pV\"",
403 da->name, fr_box_strvalue_len((char *)fr_dbuff_current(out),
406 goto again;
407 }
408 fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
409
411 }
412 talloc_free(bd);
413 }
414
415done:
416 return 0;
417}
418DIAG_ON(used-but-marked-unused)
420#endif
#define USES_APPLE_DEPRECATED_API
Definition build.h:472
#define RCSID(id)
Definition build.h:485
#define DIAG_UNKNOWN_PRAGMAS
Definition build.h:458
#define DIAG_ON(_x)
Definition build.h:460
#define unlikely(_x)
Definition build.h:383
#define UNUSED
Definition build.h:317
#define DIAG_OFF(_x)
Definition build.h:459
#define fr_dbuff_current(_dbuff_or_marker)
Return the 'current' position of a dbuff or marker.
Definition dbuff.h:911
#define fr_dbuff_set(_dst, _src)
Set the 'current' position in a dbuff or marker using another dbuff or marker, a char pointer,...
Definition dbuff.h:1004
#define fr_dbuff_end(_dbuff_or_marker)
Return the current 'end' position of a dbuff or marker.
Definition dbuff.h:938
#define fr_dbuff_remaining(_dbuff_or_marker)
Return the number of bytes remaining between the dbuff or marker and the end of the buffer.
Definition dbuff.h:743
#define fr_dbuff_in_bytes(_dbuff_or_marker,...)
Copy a byte sequence into a dbuff or marker.
Definition dbuff.h:1465
#define MEM(x)
Definition debug.h:36
bool const fr_dict_attr_allowed_chars[UINT8_MAX+1]
Characters that are allowed in dictionary attribute names.
Definition dict_util.c:47
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_attr_err_t *err, fr_dict_attr_t const *parent, char const *attr))
Locate a fr_dict_attr_t by its name.
Definition dict_util.c:3266
static fr_slen_t in
Definition dict.h:840
HIDDEN fr_dict_attr_t const * attr_tls_certificate
Attribute definitions for lib curl.
Definition base.c:36
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RWARN(fmt,...)
Definition log.h:297
#define RPWDEBUG3(fmt,...)
Definition log.h:368
#define RPWDEBUG(fmt,...)
Definition log.h:366
#define RWDEBUG3(fmt,...)
Definition log.h:363
HIDDEN fr_dict_attr_t const * attr_tls_certificate_subject
HIDDEN fr_dict_attr_t const * attr_tls_certificate_serial
HIDDEN fr_dict_attr_t const * attr_tls_certificate_subject_alt_name_dns
HIDDEN fr_dict_attr_t const * attr_tls_certificate_not_after
HIDDEN fr_dict_attr_t const * attr_tls_certificate_not_before
HIDDEN fr_dict_attr_t const * attr_tls_certificate_signature_algorithm
HIDDEN fr_dict_attr_t const * attr_tls_certificate_subject_alt_name_upn
HIDDEN fr_dict_attr_t const * attr_tls_certificate_common_name
HIDDEN fr_dict_attr_t const * attr_tls_certificate_issuer
HIDDEN fr_dict_attr_t const * attr_tls_certificate_x509v3_crl_distribution_points
HIDDEN fr_dict_attr_t const * attr_tls_certificate_signature
HIDDEN fr_dict_attr_t const * attr_tls_certificate_subject_alt_name_email
talloc_free(reap)
long int ssize_t
unsigned char uint8_t
static size_t used
int fr_pair_append_by_da(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, fr_dict_attr_t const *da)
Alloc a new fr_pair_t (and append)
Definition pair.c:1463
int fr_pair_value_memdup(fr_pair_t *vp, uint8_t const *src, size_t len, bool tainted)
Copy data into an "octets" data type.
Definition pair.c:2936
int fr_pair_value_strdup(fr_pair_t *vp, char const *src, bool tainted)
Copy data into an "string" data type.
Definition pair.c:2636
int fr_pair_value_bstrdup_buffer_shallow(fr_pair_t *vp, char const *src, bool tainted)
Assign a string to a "string" type value pair.
Definition pair.c:2857
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, UNUSED bool tainted)
Convert string value to native attribute value.
Definition pair.c:2590
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1342
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:287
int fr_pair_value_bstrndup(fr_pair_t *vp, char const *src, size_t len, bool tainted)
Copy data into a "string" type value pair.
Definition pair.c:2786
int fr_pair_value_bstr_alloc(fr_pair_t *vp, char **out, size_t size, bool tainted)
Pre-allocate a memory buffer for a "string" type value pair.
Definition pair.c:2732
int fr_pair_value_mem_alloc(fr_pair_t *vp, uint8_t **out, size_t size, bool tainted)
Pre-allocate a memory buffer for a "octets" type value pair.
Definition pair.c:2885
static bool done
Definition radclient.c:81
#define REDEBUG(fmt,...)
Definition radclient.h:52
static char const * name
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:41
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
static const char * names[8]
Definition time.c:584
static fr_unix_time_t fr_unix_time_from_time(time_t time)
Convert a time_t into out internal fr_unix_time_t.
Definition time.h:536
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:576
int fr_tls_utils_asn1time_to_epoch(time_t *out, ASN1_TIME const *asn1)
Convert OpenSSL's ASN1_TIME to an epoch time.
Definition utils.c:115
#define fr_box_strvalue_buffer(_val)
Definition value.h:308
static fr_slen_t data
Definition value.h:1288
#define fr_box_strvalue_len(_val, _len)
Definition value.h:305
static size_t char ** out
Definition value.h:1020