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: c9934c35e871811f8484f17e0d5bee3d3d525562 $
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: c9934c35e871811f8484f17e0d5bee3d3d525562 $")
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 "base.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
47DIAG_OFF(used-but-marked-unused) /* fix spurious warnings for sk macros */
48/** Extract session pairs from the Subject Alternate Name extension
49 *
50 */
51static bool tls_session_pairs_from_san(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509_EXTENSION *ext)
52{
53 GENERAL_NAMES *names = NULL;
54 int i;
56
57 if (!(names = X509V3_EXT_d2i(ext))) return false;
58
59 for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
60 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
61
62 switch (name->type) {
63#ifdef GEN_EMAIL
64 case GEN_EMAIL:
68 (char const *)ASN1_STRING_get0_data(name->d.rfc822Name),
69 ASN1_STRING_length(name->d.rfc822Name), true) == 0);
70 break;
71#endif /* GEN_EMAIL */
72#ifdef GEN_DNS
73 case GEN_DNS:
77 (char const *)ASN1_STRING_get0_data(name->d.dNSName),
78 ASN1_STRING_length(name->d.dNSName), true) == 0);
79 break;
80#endif /* GEN_DNS */
81#ifdef GEN_OTHERNAME
82 case GEN_OTHERNAME:
83 /* look for a MS UPN */
84 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 if (name->d.otherName->value->type == V_ASN1_UTF8STRING) {
91 (char const *)ASN1_STRING_get0_data(name->d.otherName->value->value.utf8string),
92 ASN1_STRING_length(name->d.otherName->value->value.utf8string),
93 true) == 0);
94 break;
95 }
96 RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)");
97 break;
98#endif /* GEN_OTHERNAME */
99 default:
100 /* XXX TODO handle other SAN types */
101 break;
102 }
103 }
104 if (names != NULL) GENERAL_NAMES_free(names);
105
106 return true;
107}
108
109/** Extract attributes from an X509 certificate
110 *
111 * @param[out] pair_list to copy attributes to.
112 * @param[in] ctx to allocate attributes in.
113 * @param[in] request the current request.
114 * @param[in] cert to validate.
115 * @return
116 * - 1 already exists.
117 * - 0 on success.
118 * - < 0 on failure.
119 */
120int fr_tls_session_pairs_from_x509_cert(fr_pair_list_t *pair_list, TALLOC_CTX *ctx, request_t *request, X509 *cert)
121{
122 int loc;
123 char buff[1024];
124
125 ASN1_TIME const *asn_time;
126 time_t time;
127
128 STACK_OF(X509_EXTENSION) const *ext_list = NULL;
129
130 fr_pair_t *vp = NULL;
131 ssize_t slen;
132 bool san_found = false;
133
134 /*
135 * Subject
136 */
138 if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
139 X509_get_subject_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
140 fr_tls_bio_dbuff_thread_local_clear();
141 fr_tls_log(request, "Failed retrieving certificate subject");
142 error:
144 return -1;
145 }
146 fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
147
148 RDEBUG3("Creating attributes for \"%pV\":", fr_box_strvalue_buffer(vp->vp_strvalue));
149
150 /*
151 * Common name
152 */
153 slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
154 NID_commonName, NULL, 0);
155 if (slen > 0) {
156 char *cn;
157
159 MEM(fr_pair_value_bstr_alloc(vp, &cn, (size_t)slen, true) == 0); /* Allocs \0 byte in addition to len */
160
161 slen = X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, cn, (size_t)slen + 1);
162 if (slen < 0) {
163 fr_tls_log(request, "Failed retrieving certificate common name");
164 goto error;
165 }
166 }
167
168 /*
169 * Signature
170 */
171 {
172 ASN1_BIT_STRING const *sig;
173 X509_ALGOR const *alg;
174
175 X509_get0_signature(&sig, &alg, cert);
176
179 (uint8_t const *)ASN1_STRING_get0_data(sig),
180 ASN1_STRING_length(sig), true) == 0);
181
182 OBJ_obj2txt(buff, sizeof(buff), alg->algorithm, 0);
185 }
186
187 /*
188 * Issuer
189 */
191 if (unlikely(X509_NAME_print_ex(fr_tls_bio_dbuff_thread_local(vp, 256, 0),
192 X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE) < 0)) {
193 fr_tls_bio_dbuff_thread_local_clear();
194 fr_tls_log(request, "Failed retrieving certificate issuer");
195 goto error;
196 }
197 fr_pair_value_bstrdup_buffer_shallow(vp, fr_tls_bio_dbuff_thread_local_finalise_bstr(), true);
198
199 /*
200 * Serial number
201 */
202 {
203 ASN1_INTEGER const *serial = NULL;
204
205 serial = X509_get0_serialNumber(cert);
206 if (!serial) {
207 fr_tls_log(request, "Failed retrieving certificate serial");
208 goto error;
209 }
210
212 MEM(fr_pair_value_memdup(vp, serial->data, serial->length, true) == 0);
213 }
214
215 /*
216 * Not valid before
217 */
218 asn_time = X509_get0_notBefore(cert);
219
220 if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
221 RPWDEBUG("Failed parsing certificate not-before");
222 goto error;
223 }
224
226 vp->vp_date = fr_unix_time_from_time(time);
227
228 /*
229 * Not valid after
230 */
231 asn_time = X509_get0_notAfter(cert);
232
233 if (fr_tls_utils_asn1time_to_epoch(&time, asn_time) < 0) {
234 RPWDEBUG("Failed parsing certificate not-after");
235 goto error;
236 }
237
239 vp->vp_date = fr_unix_time_from_time(time);
240
241 /*
242 * Get the RFC822 Subject Alternative Name
243 */
244 loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0);
245 if (loc >= 0) {
246 X509_EXTENSION *ext = X509_get_ext(cert, loc);
247 if (ext) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
248 }
249
250 /*
251 * Only add extensions for the actual client certificate
252 */
253 ext_list = X509_get0_extensions(cert);
254 if (unlikely(!ext_list)) {
255 RWDEBUG("Failed retrieving extensions");
256 goto done;
257 }
258
259 /*
260 * Grab the X509 extensions, and create attributes out of them.
261 * For laziness, we reuse the OpenSSL names
262 */
263 if (sk_X509_EXTENSION_num(ext_list) > 0) {
264 int i;
265 BIO *bio;
266 fr_tls_bio_dbuff_t *bd;
267 fr_dbuff_t *in, *out;
268
269 bio = fr_tls_bio_dbuff_alloc(&bd, NULL, NULL, 257, 4097, true);
270 in = fr_tls_bio_dbuff_in(bd);
271 out = fr_tls_bio_dbuff_out(bd);
272
273 for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) {
274 ASN1_OBJECT *obj;
275 X509_EXTENSION *ext;
276 fr_dict_attr_t const *da;
277 char *p;
278
279 ext = sk_X509_EXTENSION_value(ext_list, i);
280
281 obj = X509_EXTENSION_get_object(ext);
282
283 /*
284 * If this is extension is Subject Alternate Name have we already
285 * handled it? If not, do that now.
286 *
287 * Some certificate encodings have been observed where the SAN extension
288 * was not found by X509_get_ext_by_NID() but then seen when the list
289 * of extensions is handled here.
290 */
291 if (OBJ_obj2nid(obj) == NID_subject_alt_name) {
292 if (!san_found) san_found = tls_session_pairs_from_san(pair_list, ctx, request, ext);
293 goto again;
294 }
295
296 if (i2a_ASN1_OBJECT(bio, obj) <= 0) {
297 RPWDEBUG("Skipping X509 Extension (%i) conversion to attribute. "
298 "Conversion from ASN1 failed...", i);
299 again:
300 fr_tls_bio_dbuff_reset(bd);
301 continue;
302 }
303
304 if (fr_dbuff_remaining(out) == 0) goto again; /* Nothing written ? */
305
306 /*
307 * All disallowed chars get mashed to '-'
308 */
309 for (p = (char *)fr_dbuff_current(out);
310 p < (char *)fr_dbuff_end(out);
311 p++) if (!fr_dict_attr_allowed_chars[(uint8_t)*p]) *p = '-';
312
313 /*
314 * Terminate the buffer (after char replacement,
315 * so we do don't replace the \0)
316 */
317 if (unlikely(fr_dbuff_in_bytes(in, (uint8_t)'\0') <= 0)) {
318 RWDEBUG("Attribute name too long");
319 goto again;
320 }
321
323
324 fr_dbuff_set(in, fr_dbuff_current(in) - 1); /* Ensure the \0 isn't counted in remaining */
325
326 if (!da) {
327 RWDEBUG3("Skipping attribute \"%pV\": "
328 "Add a dictionary definition if you want to access it",
331 fr_strerror_clear(); /* Don't leave spurious errors from failed resolution */
332 goto again;
333 }
334
335 fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
336
337 if (X509V3_EXT_print(bio, ext, X509V3_EXT_PARSE_UNKNOWN, 0) != 1) {
338 REDEBUG("Failed extracting data for \"%s\"", da->name);
339 goto again;
340 }
341
342 MEM(vp = fr_pair_afrom_da(ctx, da));
344 NULL, true) < 0) {
345 RPWDEBUG3("Skipping: %s += \"%pV\"",
346 da->name, fr_box_strvalue_len((char *)fr_dbuff_current(out),
349 goto again;
350 }
351 fr_tls_bio_dbuff_reset(bd); /* 'free' any data used */
352
354 }
355 talloc_free(bd);
356 }
357
358done:
359 return 0;
360}
361DIAG_ON(used-but-marked-unused)
363#endif
#define USES_APPLE_DEPRECATED_API
Definition build.h:470
#define RCSID(id)
Definition build.h:483
#define DIAG_UNKNOWN_PRAGMAS
Definition build.h:456
#define DIAG_ON(_x)
Definition build.h:458
#define unlikely(_x)
Definition build.h:381
#define DIAG_OFF(_x)
Definition build.h:457
#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:51
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:3263
static fr_slen_t in
Definition dict.h:824
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_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:1466
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:2981
int fr_pair_value_strdup(fr_pair_t *vp, char const *src, bool tainted)
Copy data into an "string" data type.
Definition pair.c:2634
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:2855
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:1345
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:283
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:2784
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:2730
int fr_pair_value_from_str(fr_pair_t *vp, char const *value, size_t inlen, fr_sbuff_unescape_rules_t const *uerules, bool tainted)
Convert string value to native attribute value.
Definition pair.c:2589
static bool done
Definition radclient.c:80
#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:621
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:577
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:289
#define fr_box_strvalue_len(_val, _len)
Definition value.h:286
static size_t char ** out
Definition value.h:997