pkinit_identity.c revision 7934:6aeeafc994de
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
43static void
44free_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
56static krb5_error_code
57copy_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;
83cleanup:
84    free_list(newlist);
85    return ENOMEM;
86}
87
88char *
89idtype2string(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
102char *
103catype2string(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
114krb5_error_code
115pkinit_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
146krb5_error_code
147pkinit_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;
227cleanup:
228    pkinit_fini_identity_opts(newopts);
229    return retval;
230}
231
232void
233pkinit_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 */
264static krb5_error_code
265parse_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;
331cleanup:
332    free(s);
333    return retval;
334}
335#endif
336
337/* ARGSUSED */
338static krb5_error_code
339parse_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;
365cleanup:
366    if (certname != NULL)
367	free(certname);
368    return retval;
369}
370
371/* ARGSUSED */
372static krb5_error_code
373parse_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;
394cleanup:
395    return retval;
396}
397
398static krb5_error_code
399process_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
487static krb5_error_code
488process_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
523static krb5_error_code
524pkinit_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
568krb5_error_code
569pkinit_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
682errout:
683    return retval;
684}
685
686