xref: /illumos-gate/usr/src/lib/krb5/plugins/preauth/pkinit/pkinit_identity.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
1 /*
2  * COPYRIGHT (C) 2007
3  * THE REGENTS OF THE UNIVERSITY OF MICHIGAN
4  * ALL RIGHTS RESERVED
5  *
6  * Permission is granted to use, copy, create derivative works
7  * and redistribute this software and such derivative works
8  * for any purpose, so long as the name of The University of
9  * Michigan is not used in any advertising or publicity
10  * pertaining to the use of distribution of this software
11  * without specific, written prior authorization.  If the
12  * above copyright notice or any other identification of the
13  * University of Michigan is included in any copy of any
14  * portion of this software, then the disclaimer below must
15  * also be included.
16  *
17  * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
18  * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
19  * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
20  * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
21  * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
22  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
23  * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
24  * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
25  * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
26  * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
27  * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGES.
29  */
30 
31 #include <errno.h>
32 #include <string.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <dlfcn.h>
36 #include <unistd.h>
37 #include <dirent.h>
38 
39 #include <libintl.h>
40 
41 #include "pkinit.h"
42 
43 static void
44 free_list(char **list)
45 {
46     int i;
47 
48     if (list == NULL)
49 	return;
50 
51     for (i = 0; list[i] != NULL; i++)
52 	free(list[i]);
53      free(list);
54 }
55 
56 static krb5_error_code
57 copy_list(char ***dst, char **src)
58 {
59     int i;
60     char **newlist;
61 
62     if (dst == NULL)
63 	return EINVAL;
64     *dst = NULL;
65 
66     if (src == NULL)
67 	return 0;
68 
69     for (i = 0; src[i] != NULL; i++);
70 
71     newlist = calloc(1, (i + 1) * sizeof(*newlist));
72     if (newlist == NULL)
73 	return ENOMEM;
74 
75     for (i = 0; src[i] != NULL; i++) {
76 	newlist[i] = strdup(src[i]);
77 	if (newlist[i] == NULL)
78 	    goto cleanup;
79     }
80     newlist[i] = NULL;
81     *dst = newlist;
82     return 0;
83 cleanup:
84     free_list(newlist);
85     return ENOMEM;
86 }
87 
88 char *
89 idtype2string(int idtype)
90 {
91 /* Solaris Kerberos: Removed "break"s (lint) */
92     switch(idtype) {
93     case IDTYPE_FILE: return "FILE";
94     case IDTYPE_DIR: return "DIR";
95     case IDTYPE_PKCS11: return "PKCS11";
96     case IDTYPE_PKCS12: return "PKCS12";
97     case IDTYPE_ENVVAR: return "ENV";
98     default: return "INVALID";
99     }
100 }
101 
102 char *
103 catype2string(int catype)
104 {
105 /* Solaris Kerberos: Removed "break"s (lint) */
106     switch(catype) {
107     case CATYPE_ANCHORS: return "ANCHORS";
108     case CATYPE_INTERMEDIATES: return "INTERMEDIATES";
109     case CATYPE_CRLS: return "CRLS";
110     default: return "INVALID";
111     }
112 }
113 
114 krb5_error_code
115 pkinit_init_identity_opts(pkinit_identity_opts **idopts)
116 {
117     pkinit_identity_opts *opts = NULL;
118 
119     *idopts = NULL;
120     opts = (pkinit_identity_opts *) calloc(1, sizeof(pkinit_identity_opts));
121     if (opts == NULL)
122 	return ENOMEM;
123 
124     opts->identity = NULL;
125     opts->anchors = NULL;
126     opts->intermediates = NULL;
127     opts->crls = NULL;
128     opts->ocsp = NULL;
129     opts->dn_mapping_file = NULL;
130 
131     opts->cert_filename = NULL;
132     opts->key_filename = NULL;
133 #ifndef WITHOUT_PKCS11
134     opts->p11_module_name = NULL;
135     opts->slotid = PK_NOSLOT;
136     opts->token_label = NULL;
137     opts->cert_id_string = NULL;
138     opts->cert_label = NULL;
139 #endif
140 
141     *idopts = opts;
142 
143     return 0;
144 }
145 
146 krb5_error_code
147 pkinit_dup_identity_opts(pkinit_identity_opts *src_opts,
148 			 pkinit_identity_opts **dest_opts)
149 {
150     pkinit_identity_opts *newopts;
151     krb5_error_code retval;
152 
153     *dest_opts = NULL;
154     retval = pkinit_init_identity_opts(&newopts);
155     if (retval)
156 	return retval;
157 
158     retval = ENOMEM;
159 
160     if (src_opts->identity != NULL) {
161 	newopts->identity = strdup(src_opts->identity);
162 	if (newopts->identity == NULL)
163 	    goto cleanup;
164     }
165 
166     retval = copy_list(&newopts->anchors, src_opts->anchors);
167     if (retval)
168 	goto cleanup;
169 
170     retval = copy_list(&newopts->intermediates,src_opts->intermediates);
171     if (retval)
172 	goto cleanup;
173 
174     retval = copy_list(&newopts->crls, src_opts->crls);
175     if (retval)
176 	goto cleanup;
177 
178     if (src_opts->ocsp != NULL) {
179 	newopts->ocsp = strdup(src_opts->ocsp);
180 	if (newopts->ocsp == NULL)
181 	    goto cleanup;
182     }
183 
184     if (src_opts->cert_filename != NULL) {
185 	newopts->cert_filename = strdup(src_opts->cert_filename);
186 	if (newopts->cert_filename == NULL)
187 	    goto cleanup;
188     }
189 
190     if (src_opts->key_filename != NULL) {
191 	newopts->key_filename = strdup(src_opts->key_filename);
192 	if (newopts->key_filename == NULL)
193 	    goto cleanup;
194     }
195 
196 #ifndef WITHOUT_PKCS11
197     if (src_opts->p11_module_name != NULL) {
198 	newopts->p11_module_name = strdup(src_opts->p11_module_name);
199 	if (newopts->p11_module_name == NULL)
200 	    goto cleanup;
201     }
202 
203     newopts->slotid = src_opts->slotid;
204 
205     if (src_opts->token_label != NULL) {
206 	newopts->token_label = strdup(src_opts->token_label);
207 	if (newopts->token_label == NULL)
208 	    goto cleanup;
209     }
210 
211     if (src_opts->cert_id_string != NULL) {
212 	newopts->cert_id_string = strdup(src_opts->cert_id_string);
213 	if (newopts->cert_id_string == NULL)
214 	    goto cleanup;
215     }
216 
217     if (src_opts->cert_label != NULL) {
218 	newopts->cert_label = strdup(src_opts->cert_label);
219 	if (newopts->cert_label == NULL)
220 	    goto cleanup;
221     }
222 #endif
223 
224 
225     *dest_opts = newopts;
226     return 0;
227 cleanup:
228     pkinit_fini_identity_opts(newopts);
229     return retval;
230 }
231 
232 void
233 pkinit_fini_identity_opts(pkinit_identity_opts *idopts)
234 {
235     if (idopts == NULL)
236 	return;
237 
238     if (idopts->identity != NULL)
239 	free(idopts->identity);
240     free_list(idopts->anchors);
241     free_list(idopts->intermediates);
242     free_list(idopts->crls);
243     free_list(idopts->identity_alt);
244 
245     if (idopts->cert_filename != NULL)
246 	free(idopts->cert_filename);
247     if (idopts->key_filename != NULL)
248 	free(idopts->key_filename);
249 #ifndef WITHOUT_PKCS11
250     if (idopts->p11_module_name != NULL)
251 	free(idopts->p11_module_name);
252     if (idopts->token_label != NULL)
253 	free(idopts->token_label);
254     if (idopts->cert_id_string != NULL)
255 	free(idopts->cert_id_string);
256     if (idopts->cert_label != NULL)
257 	free(idopts->cert_label);
258 #endif
259     free(idopts);
260 }
261 
262 #ifndef WITHOUT_PKCS11
263 /* ARGSUSED */
264 static krb5_error_code
265 parse_pkcs11_options(krb5_context context,
266 		     pkinit_identity_opts *idopts,
267 		     const char *residual)
268 {
269     char *s, *cp, *vp;
270     krb5_error_code retval = ENOMEM;
271 
272     if (residual == NULL || residual[0] == '\0')
273 	return 0;
274 
275     /* Split string into attr=value substrings */
276     s = strdup(residual);
277     if (s == NULL)
278 	return retval;
279 
280     for ((cp = strtok(s, ":")); cp; (cp = strtok(NULL, ":"))) {
281 	vp = strchr(cp, '=');
282 
283 	/* If there is no "=", this is a pkcs11 module name */
284 	if (vp == NULL) {
285 	    if (idopts->p11_module_name != NULL)
286 		free(idopts->p11_module_name);
287 	    idopts->p11_module_name = strdup(cp);
288 	    if (idopts->p11_module_name == NULL)
289 		goto cleanup;
290 	    continue;
291 	}
292 	*vp++ = '\0';
293 	if (!strcmp(cp, "module_name")) {
294 	    if (idopts->p11_module_name != NULL)
295 		free(idopts->p11_module_name);
296 	    idopts->p11_module_name = strdup(vp);
297 	    if (idopts->p11_module_name == NULL)
298 		goto cleanup;
299 	} else if (!strcmp(cp, "slotid")) {
300 	    long slotid = strtol(vp, NULL, 10);
301 	    if ((slotid == LONG_MIN || slotid == LONG_MAX) && errno != 0) {
302 		retval = EINVAL;
303 		goto cleanup;
304 	    }
305 	    if ((long) (int) slotid != slotid) {
306 		retval = EINVAL;
307 		goto cleanup;
308 	    }
309 	    idopts->slotid = slotid;
310 	} else if (!strcmp(cp, "token")) {
311 	    if (idopts->token_label != NULL)
312 		free(idopts->token_label);
313 	    idopts->token_label = strdup(vp);
314 	    if (idopts->token_label == NULL)
315 		goto cleanup;
316 	} else if (!strcmp(cp, "certid")) {
317 	    if (idopts->cert_id_string != NULL)
318 		free(idopts->cert_id_string);
319 	    idopts->cert_id_string = strdup(vp);
320 	    if (idopts->cert_id_string == NULL)
321 		goto cleanup;
322 	} else if (!strcmp(cp, "certlabel")) {
323 	    if (idopts->cert_label != NULL)
324 		free(idopts->cert_label);
325 	    idopts->cert_label = strdup(vp);
326 	    if (idopts->cert_label == NULL)
327 		goto cleanup;
328 	}
329     }
330     retval = 0;
331 cleanup:
332     free(s);
333     return retval;
334 }
335 #endif
336 
337 /* ARGSUSED */
338 static krb5_error_code
339 parse_fs_options(krb5_context context,
340 		 pkinit_identity_opts *idopts,
341 		 const char *residual)
342 {
343     char *certname, *keyname;
344     krb5_error_code retval = ENOMEM;
345 
346     if (residual == NULL || residual[0] == '\0')
347 	return 0;
348 
349     certname = strdup(residual);
350     if (certname == NULL)
351 	goto cleanup;
352 
353     certname = strtok(certname, ",");
354     keyname = strtok(NULL, ",");
355 
356     idopts->cert_filename = strdup(certname);
357     if (idopts->cert_filename == NULL)
358 	goto cleanup;
359 
360     idopts->key_filename = strdup(keyname ? keyname : certname);
361     if (idopts->key_filename == NULL)
362 	goto cleanup;
363 
364     retval = 0;
365 cleanup:
366     if (certname != NULL)
367 	free(certname);
368     return retval;
369 }
370 
371 /* ARGSUSED */
372 static krb5_error_code
373 parse_pkcs12_options(krb5_context context,
374 		     pkinit_identity_opts *idopts,
375 		     const char *residual)
376 {
377     krb5_error_code retval = ENOMEM;
378 
379     if (residual == NULL || residual[0] == '\0')
380 	return 0;
381 
382     idopts->cert_filename = strdup(residual);
383     if (idopts->cert_filename == NULL)
384 	goto cleanup;
385 
386     idopts->key_filename = strdup(residual);
387     if (idopts->key_filename == NULL)
388 	goto cleanup;
389 
390     pkiDebug("%s: cert_filename '%s' key_filename '%s'\n",
391 	     __FUNCTION__, idopts->cert_filename,
392 	     idopts->key_filename);
393     retval = 0;
394 cleanup:
395     return retval;
396 }
397 
398 static krb5_error_code
399 process_option_identity(krb5_context context,
400 			pkinit_plg_crypto_context plg_cryptoctx,
401 			pkinit_req_crypto_context req_cryptoctx,
402 			pkinit_identity_opts *idopts,
403 			pkinit_identity_crypto_context id_cryptoctx,
404 			const char *value)
405 {
406     const char *residual;
407     int idtype;
408     krb5_error_code retval = 0;
409 
410     pkiDebug("%s: processing value '%s'\n",
411 	     __FUNCTION__, value ? value : "NULL");
412     if (value == NULL)
413 	return EINVAL;
414 
415     residual = strchr(value, ':');
416     if (residual != NULL) {
417 	unsigned int typelen;
418 	residual++; /* skip past colon */
419 	typelen = residual - value;
420 	if (strncmp(value, "FILE:", typelen) == 0) {
421 	    idtype = IDTYPE_FILE;
422 #ifndef WITHOUT_PKCS11
423 	} else if (strncmp(value, "PKCS11:", typelen) == 0) {
424 	    idtype = IDTYPE_PKCS11;
425 #endif
426 	} else if (strncmp(value, "PKCS12:", typelen) == 0) {
427 	    idtype = IDTYPE_PKCS12;
428 	} else if (strncmp(value, "DIR:", typelen) == 0) {
429 	    idtype = IDTYPE_DIR;
430 	} else if (strncmp(value, "ENV:", typelen) == 0) {
431 	    idtype = IDTYPE_ENVVAR;
432 	} else {
433 	    pkiDebug("%s: Unsupported type while processing '%s'\n",
434 		     __FUNCTION__, value);
435 	    krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
436 				   "Unsupported type while processing '%s'\n",
437 				   value);
438 	    return KRB5_PREAUTH_FAILED;
439 	}
440     } else {
441 	idtype = IDTYPE_FILE;
442 	residual = value;
443     }
444 
445     idopts->idtype = idtype;
446     pkiDebug("%s: idtype is %s\n", __FUNCTION__, idtype2string(idopts->idtype));
447     switch (idtype) {
448     case IDTYPE_ENVVAR: {
449 	    /* Solaris Kerberos: Improved error messages */
450 	    char *envvar = getenv(residual);
451 	    if (envvar == NULL) {
452 		    krb5_set_error_message(context, EINVAL,
453 		        gettext("failed to find environmental variable \'%s\'"),
454 		        residual);
455 		    return EINVAL;
456 	    }
457 	    return process_option_identity(context, plg_cryptoctx,
458 				       req_cryptoctx, idopts, id_cryptoctx,
459 				       envvar);
460 	    /* Solaris Kerberos: not reached */
461 	}
462     case IDTYPE_FILE:
463 	retval = parse_fs_options(context, idopts, residual);
464 	break;
465     case IDTYPE_PKCS12:
466 	retval = parse_pkcs12_options(context, idopts, residual);
467 	break;
468 #ifndef WITHOUT_PKCS11
469     case IDTYPE_PKCS11:
470 	retval = parse_pkcs11_options(context, idopts, residual);
471 	break;
472 #endif
473     case IDTYPE_DIR:
474 	idopts->cert_filename = strdup(residual);
475 	if (idopts->cert_filename == NULL)
476 	    retval = ENOMEM;
477 	break;
478     default:
479 	krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
480 			       "Internal error parsing X509_user_identity\n");
481 	retval = EINVAL;
482 	break;
483     }
484     return retval;
485 }
486 
487 static krb5_error_code
488 process_option_ca_crl(krb5_context context,
489 		      pkinit_plg_crypto_context plg_cryptoctx,
490 		      pkinit_req_crypto_context req_cryptoctx,
491 		      pkinit_identity_opts *idopts,
492 		      pkinit_identity_crypto_context id_cryptoctx,
493 		      const char *value,
494 		      int catype)
495 {
496     char *residual;
497     unsigned int typelen;
498     int idtype;
499 
500     pkiDebug("%s: processing catype %s, value '%s'\n",
501 	     __FUNCTION__, catype2string(catype), value);
502     residual = strchr(value, ':');
503     if (residual == NULL) {
504 	pkiDebug("No type given for '%s'\n", value);
505 	return EINVAL;
506     }
507     residual++; /* skip past colon */
508     typelen = residual - value;
509     if (strncmp(value, "FILE:", typelen) == 0) {
510 	idtype = IDTYPE_FILE;
511     } else if (strncmp(value, "DIR:", typelen) == 0) {
512 	idtype = IDTYPE_DIR;
513     } else {
514 	return ENOTSUP;
515     }
516     return crypto_load_cas_and_crls(context,
517 				    plg_cryptoctx,
518 				    req_cryptoctx,
519 				    idopts, id_cryptoctx,
520 				    idtype, catype, residual);
521 }
522 
523 static krb5_error_code
524 pkinit_identity_process_option(krb5_context context,
525 			       pkinit_plg_crypto_context plg_cryptoctx,
526 			       pkinit_req_crypto_context req_cryptoctx,
527 			       pkinit_identity_opts *idopts,
528 			       pkinit_identity_crypto_context id_cryptoctx,
529 			       int attr,
530 			       const char *value)
531 {
532     krb5_error_code retval = 0;
533 
534     switch (attr) {
535 	case PKINIT_ID_OPT_USER_IDENTITY:
536 	    retval = process_option_identity(context, plg_cryptoctx,
537 					     req_cryptoctx, idopts,
538 					     id_cryptoctx, value);
539 	    break;
540 	case PKINIT_ID_OPT_ANCHOR_CAS:
541 	    retval = process_option_ca_crl(context, plg_cryptoctx,
542 					   req_cryptoctx, idopts,
543 					   id_cryptoctx, value,
544 					   CATYPE_ANCHORS);
545 	    break;
546 	case PKINIT_ID_OPT_INTERMEDIATE_CAS:
547 	    retval = process_option_ca_crl(context, plg_cryptoctx,
548 					   req_cryptoctx, idopts,
549 					   id_cryptoctx,
550 					   value, CATYPE_INTERMEDIATES);
551 	    break;
552 	case PKINIT_ID_OPT_CRLS:
553 	    retval = process_option_ca_crl(context, plg_cryptoctx,
554 					   req_cryptoctx, idopts,
555 					   id_cryptoctx,
556 					   value, CATYPE_CRLS);
557 	    break;
558 	case PKINIT_ID_OPT_OCSP:
559 	    retval = ENOTSUP;
560 	    break;
561 	default:
562 	    retval = EINVAL;
563 	    break;
564     }
565     return retval;
566 }
567 
568 krb5_error_code
569 pkinit_identity_initialize(krb5_context context,
570 			   pkinit_plg_crypto_context plg_cryptoctx,
571 			   pkinit_req_crypto_context req_cryptoctx,
572 			   pkinit_identity_opts *idopts,
573 			   pkinit_identity_crypto_context id_cryptoctx,
574 			   int do_matching,
575 			   krb5_principal princ)
576 {
577     krb5_error_code retval = EINVAL;
578     int i;
579 
580     pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx);
581     if (idopts == NULL || id_cryptoctx == NULL)
582 	goto errout;
583 
584     /*
585      * If identity was specified, use that.  (For the kdc, this
586      * is specified as pkinit_identity in the kdc.conf.  For users,
587      * this is specified on the command line via X509_user_identity.)
588      * If a user did not specify identity on the command line,
589      * then we will try alternatives which may have been specified
590      * in the config file.
591      */
592     if (idopts->identity != NULL) {
593 	retval = pkinit_identity_process_option(context, plg_cryptoctx,
594 						req_cryptoctx, idopts,
595 						id_cryptoctx,
596 						PKINIT_ID_OPT_USER_IDENTITY,
597 						idopts->identity);
598     } else if (idopts->identity_alt != NULL) {
599 	for (i = 0; retval != 0 && idopts->identity_alt[i] != NULL; i++)
600 		retval = pkinit_identity_process_option(context, plg_cryptoctx,
601 						    req_cryptoctx, idopts,
602 						    id_cryptoctx,
603 						    PKINIT_ID_OPT_USER_IDENTITY,
604 						    idopts->identity_alt[i]);
605     } else {
606 	pkiDebug("%s: no user identity options specified\n", __FUNCTION__);
607 	goto errout;
608     }
609     if (retval)
610 	goto errout;
611 
612     retval = crypto_load_certs(context, plg_cryptoctx, req_cryptoctx,
613 			       idopts, id_cryptoctx, princ);
614     if (retval)
615 	goto errout;
616 
617     if (do_matching) {
618 	retval = pkinit_cert_matching(context, plg_cryptoctx, req_cryptoctx,
619 				      id_cryptoctx, princ);
620 	if (retval) {
621 	    pkiDebug("%s: No matching certificate found\n", __FUNCTION__);
622 	    (void) crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
623 				  id_cryptoctx);
624 	    goto errout;
625 	}
626     } else {
627 	/* Tell crypto code to use the "default" */
628 	retval = crypto_cert_select_default(context, plg_cryptoctx,
629 					    req_cryptoctx, id_cryptoctx);
630 	if (retval) {
631 	    pkiDebug("%s: Failed while selecting default certificate\n",
632 		     __FUNCTION__);
633 	    (void) crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
634 				  id_cryptoctx);
635 	    goto errout;
636 	}
637     }
638 
639     retval = crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
640 				   id_cryptoctx);
641     if (retval)
642 	    goto errout;
643 
644     for (i = 0; idopts->anchors != NULL && idopts->anchors[i] != NULL; i++) {
645 	retval = pkinit_identity_process_option(context, plg_cryptoctx,
646 						req_cryptoctx, idopts,
647 						id_cryptoctx,
648 						PKINIT_ID_OPT_ANCHOR_CAS,
649 						idopts->anchors[i]);
650 	if (retval)
651 	    goto errout;
652     }
653     for (i = 0; idopts->intermediates != NULL
654 		&& idopts->intermediates[i] != NULL; i++) {
655 	retval = pkinit_identity_process_option(context, plg_cryptoctx,
656 						req_cryptoctx, idopts,
657 						id_cryptoctx,
658 						PKINIT_ID_OPT_INTERMEDIATE_CAS,
659 						idopts->intermediates[i]);
660 	if (retval)
661 	    goto errout;
662     }
663     for (i = 0; idopts->crls != NULL && idopts->crls[i] != NULL; i++) {
664 	retval = pkinit_identity_process_option(context, plg_cryptoctx,
665 						req_cryptoctx, idopts,
666 						id_cryptoctx,
667 						PKINIT_ID_OPT_CRLS,
668 						idopts->crls[i]);
669 	if (retval)
670 	    goto errout;
671     }
672     if (idopts->ocsp != NULL) {
673 	retval = pkinit_identity_process_option(context, plg_cryptoctx,
674 						req_cryptoctx, idopts,
675 						id_cryptoctx,
676 						PKINIT_ID_OPT_OCSP,
677 						idopts->ocsp);
678 	if (retval)
679 	    goto errout;
680     }
681 
682 errout:
683     return retval;
684 }
685 
686