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