diff options
author | Kevin Coffman <kwc@citi.umich.edu> | 2007-05-29 22:20:17 +0000 |
---|---|---|
committer | Kevin Coffman <kwc@citi.umich.edu> | 2007-05-29 22:20:17 +0000 |
commit | 0e661a5c3a09f10dd85537d51c9a767ac9f1137e (patch) | |
tree | ec8817cf46d7380a33375ca5e86c758b742765b7 | |
parent | e1904aa474318b5020ca64829ad3d40ea132d871 (diff) | |
download | krb5-0e661a5c3a09f10dd85537d51c9a767ac9f1137e.zip krb5-0e661a5c3a09f10dd85537d51c9a767ac9f1137e.tar.gz krb5-0e661a5c3a09f10dd85537d51c9a767ac9f1137e.tar.bz2 |
Remove some old code from src/lib/krb5/asn.1/asn1_k_decode.c and
src/lib/krb5/krb/preauth2.c
Add new file pkinit_matching.c which implements the ability
for an admin to configure things such that the "right" certificate
from several available ones will be used for pkinit.
Fix a couple of things the Solaris compiler doesn't like.
git-svn-id: svn://anonsvn.mit.edu/krb5/users/coffman/pkinit@19562 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r-- | src/lib/krb5/asn.1/asn1_k_decode.c | 1 | ||||
-rw-r--r-- | src/lib/krb5/krb/preauth2.c | 19 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/Makefile.in | 7 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit.h | 27 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_clnt.c | 38 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_crypto.h | 150 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 729 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_crypto_openssl.h | 42 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_identity.c | 108 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_matching.c | 827 | ||||
-rw-r--r-- | src/plugins/preauth/pkinit/pkinit_srv.c | 130 |
11 files changed, 1812 insertions, 266 deletions
diff --git a/src/lib/krb5/asn.1/asn1_k_decode.c b/src/lib/krb5/asn.1/asn1_k_decode.c index 9117ff3..67c7f30 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.c +++ b/src/lib/krb5/asn.1/asn1_k_decode.c @@ -1248,7 +1248,6 @@ asn1_error_code asn1_decode_trusted_ca(asn1buf *buf, krb5_trusted_ca *val) { setup(); { begin_choice(); - fprintf(stderr, "%s: ********************** The CHOICE is %d **********************\n", __FUNCTION__, tagnum); if (tagnum == choice_trusted_cas_principalName) { val->choice = choice_trusted_cas_principalName; asn1_decode_krb5_principal_name(&subbuf, &(val->u.principalName)); diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index 5cd81d8..18f1727 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -563,30 +563,11 @@ krb5_run_preauth_plugins(krb5_context kcontext, /* Save the new preauth data item. */ if (out_pa_data != NULL) { int i; -#if 0 /* draft9 checksum hack */ - /* - * this is a temporary hack for doing "fixed" draft9 client. - * the client needs to send a pa_type 132 to the win2k kdc - * then the kdc will reply back with a checksum instead - * of the nonce - */ - krb5_pa_data *tmp = NULL; - tmp = malloc(sizeof(krb5_pa_data)); - if (tmp != NULL) { - tmp->pa_type=132; - tmp->length = 0; - tmp->contents = NULL; - } -#endif for (i = 0; out_pa_data[i] != NULL; i++); ret = grow_pa_list(out_pa_list, out_pa_list_size, out_pa_data, i); free(out_pa_data); if (ret != 0) return ret; -#if 0 /* draft9 checksum hack */ - if (tmp != NULL) - grow_pa_list(out_pa_list, out_pa_list_size, &tmp, 1); -#endif } break; } diff --git a/src/plugins/preauth/pkinit/Makefile.in b/src/plugins/preauth/pkinit/Makefile.in index 74dce37..1f7bd2f 100644 --- a/src/plugins/preauth/pkinit/Makefile.in +++ b/src/plugins/preauth/pkinit/Makefile.in @@ -33,6 +33,7 @@ STLIBOBJS= \ pkinit_clnt.o \ pkinit_profile.o \ pkinit_identity.o \ + pkinit_matching.o \ pkinit_crypto_openssl.o SRCS= \ @@ -42,6 +43,7 @@ SRCS= \ $(srcdir)/pkinit_clnt.c \ $(srcdir)/pkinit_profile.c \ $(srcdir)/pkinit_identity.c \ + $(srcdir)/pkinit_matching.c \ $(srcdir)/pkinit_crypto_openssl.c all-unix:: $(LIBBASE)$(SO_EXT) @@ -95,6 +97,11 @@ pkinit_identity.so pkinit_identity.po $(OUTPRE)pkinit_identity.$(OBJEXT): \ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/profile.h \ $(COM_ERR_DEPS) $(SRCTOP)/include/k5-int-pkinit.h $(SRCTOP)/include/krb5/preauth_plugin.h \ pkinit.h pkinit_accessor.h pkinit_crypto.h pkinit_identity.c +pkinit_matching.so pkinit_matching.po $(OUTPRE)pkinit_matching.$(OBJEXT): \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(SRCTOP)/include/k5-int-pkinit.h $(SRCTOP)/include/krb5.h \ + $(SRCTOP)/include/krb5/preauth_plugin.h pkinit.h pkinit_accessor.h \ + pkinit_crypto.h pkinit_matching.c pkinit_crypto_openssl.so pkinit_crypto_openssl.po $(OUTPRE)pkinit_crypto_openssl.$(OBJEXT): \ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/profile.h \ $(COM_ERR_DEPS) $(SRCTOP)/include/k5-int-pkinit.h $(SRCTOP)/include/krb5/preauth_plugin.h \ diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index 352294e..5546de5 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -61,17 +61,11 @@ #define pkiDebug(args...) #endif -extern int longhorn; /* XXX Talking to a Longhorn server? */ - -#define IDTYPE_FILE 1 -#define IDTYPE_DIR 2 -#define IDTYPE_PKCS11 3 -#define IDTYPE_ENVVAR 4 -#define IDTYPE_PKCS12 5 +/* Solaris compiler doesn't grok __FUNCTION__ + * hack for now. Fix all the uses eventually. */ +#define __FUNCTION__ __func__ -#define CATYPE_ANCHORS 1 -#define CATYPE_INTERMEDIATES 2 -#define CATYPE_CRLS 3 +extern int longhorn; /* XXX Talking to a Longhorn server? */ /* Macros to deal with converting between various data types... */ #define PADATA_TO_KRB5DATA(pad, k5d) \ @@ -245,8 +239,19 @@ krb5_error_code pkinit_dup_identity_opts(pkinit_identity_opts *src_opts, krb5_error_code pkinit_identity_initialize (krb5_context context, /* IN */ + pkinit_plg_crypto_context plg_cryptoctx, /* IN */ + pkinit_req_crypto_context req_cryptoctx, /* IN */ pkinit_identity_opts *idopts, /* IN */ - pkinit_identity_crypto_context id_cryptoctx); /* IN/OUT */ + pkinit_identity_crypto_context id_cryptoctx, /* IN/OUT */ + int do_matching, /* IN */ + krb5_principal princ); /* IN (optional) */ + +krb5_error_code pkinit_cert_matching + (krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_principal princ); /* * initialization and free functions diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c index 29c340d..818de0c 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -47,7 +47,7 @@ int longhorn = 0; /* XXX Talking to a Longhorn server? */ krb5_error_code pkinit_client_process (krb5_context context, void *plugin_context, void *request_context, - krb5_get_init_creds_opt *opt, + krb5_get_init_creds_opt *gic_opt, preauth_get_client_data_proc get_data_proc, struct _krb5_preauth_client_rock *rock, krb5_kdc_req * request, krb5_data *encoded_request_body, @@ -59,7 +59,7 @@ krb5_error_code pkinit_client_process krb5_error_code pkinit_client_tryagain (krb5_context context, void *plugin_context, void *request_context, - krb5_get_init_creds_opt *opt, + krb5_get_init_creds_opt *gic_opt, preauth_get_client_data_proc get_data_proc, struct _krb5_preauth_client_rock *rock, krb5_kdc_req * request, krb5_data *encoded_request_body, @@ -81,7 +81,7 @@ krb5_error_code pa_pkinit_gen_req pkinit_req_context reqctx, krb5_kdc_req * request, krb5_pa_data * in_padata, krb5_pa_data *** out_padata, krb5_prompter_fct prompter, void *prompter_data, - krb5_get_init_creds_opt *opt); + krb5_get_init_creds_opt *gic_opt); krb5_error_code pkinit_as_req_create (krb5_context context, pkinit_context plgctx, @@ -120,7 +120,7 @@ pa_pkinit_gen_req(krb5_context context, krb5_pa_data *** out_padata, krb5_prompter_fct prompter, void *prompter_data, - krb5_get_init_creds_opt *opt) + krb5_get_init_creds_opt *gic_opt) { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; @@ -130,7 +130,6 @@ pa_pkinit_gen_req(krb5_context context, krb5_ui_4 nonce = 0; krb5_checksum cksum; krb5_data *der_req = NULL; - char *server_principal = NULL; krb5_pa_data **return_pa_data = NULL; cksum.contents = NULL; @@ -144,16 +143,12 @@ pa_pkinit_gen_req(krb5_context context, return KRB5KDC_ERR_PREAUTH_FAILED; } - /* XXX Does this belong elsewhere ? */ - retval = krb5_unparse_name(context, request->server, &server_principal); - if (retval) - return retval; - - retval = pkinit_get_kdc_cert(context, plgctx->cryptoctx, - reqctx->cryptoctx, reqctx->idctx, server_principal, opt); - free(server_principal); - if (retval) + retval = pkinit_get_kdc_cert(context, plgctx->cryptoctx, reqctx->cryptoctx, + reqctx->idctx, request->server); + if (retval) { + pkiDebug("pkinit_get_kdc_cert returned %d\n", retval); goto cleanup; + } /* checksum of the encoded KDC-REQ-BODY */ retval = encode_krb5_kdc_req_body(request, &der_req); @@ -965,7 +960,7 @@ krb5_error_code pkinit_client_process(krb5_context context, void *plugin_context, void *request_context, - krb5_get_init_creds_opt *opt, + krb5_get_init_creds_opt *gic_opt, preauth_get_client_data_proc get_data_proc, struct _krb5_preauth_client_rock *rock, krb5_kdc_req *request, @@ -1023,8 +1018,9 @@ pkinit_client_process(krb5_context context, if (processing_request) { pkinit_client_profile(context, plgctx, reqctx, request); pkinit_identity_set_prompter(reqctx->idctx, prompter, prompter_data); - retval = pkinit_identity_initialize(context, reqctx->idopts, - reqctx->idctx); + retval = pkinit_identity_initialize(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idopts, + reqctx->idctx, 1, request->client); if (retval) { pkiDebug("pkinit_identity_initialize returned %d (%s)\n", retval, error_message(retval)); @@ -1032,7 +1028,7 @@ pkinit_client_process(krb5_context context, } retval = pa_pkinit_gen_req(context, plgctx, reqctx, request, in_padata, out_padata, prompter, - prompter_data, opt); + prompter_data, gic_opt); } else { /* * Get the enctype of the reply. @@ -1061,7 +1057,7 @@ krb5_error_code pkinit_client_tryagain(krb5_context context, void *plugin_context, void *request_context, - krb5_get_init_creds_opt *opt, + krb5_get_init_creds_opt *gic_opt, preauth_get_client_data_proc get_data_proc, struct _krb5_preauth_client_rock *rock, krb5_kdc_req *request, @@ -1141,7 +1137,7 @@ pkinit_client_tryagain(krb5_context context, if (do_again) { retval = pa_pkinit_gen_req(context, plgctx, reqctx, request, in_padata, - out_padata, prompter, prompter_data, opt); + out_padata, prompter, prompter_data, gic_opt); if (retval) goto cleanup; } @@ -1421,7 +1417,7 @@ handle_gic_opt(krb5_context context, static krb5_error_code pkinit_client_gic_opt(krb5_context context, void *plugin_context, - krb5_get_init_creds_opt *opt, + krb5_get_init_creds_opt *gic_opt, const char *attr, const char *value) { diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h index f984462..dfb31c1 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto.h +++ b/src/plugins/preauth/pkinit/pkinit_crypto.h @@ -52,6 +52,57 @@ enum cms_msg_types { }; /* + * storage types for identity information + */ +#define IDTYPE_FILE 1 +#define IDTYPE_DIR 2 +#define IDTYPE_PKCS11 3 +#define IDTYPE_ENVVAR 4 +#define IDTYPE_PKCS12 5 + +/* + * ca/crl types + */ +#define CATYPE_ANCHORS 1 +#define CATYPE_INTERMEDIATES 2 +#define CATYPE_CRLS 3 + +/* + * The following represent Key Usage values that we + * may care about in a certificate + */ +#define PKINIT_KU_DIGITALSIGNATURE 0x80000000 +#define PKINIT_KU_KEYENCIPHERMENT 0x40000000 + +/* + * The following represent Extended Key Usage oid values + * that we may care about in a certificate + */ +#define PKINIT_EKU_PKINIT 0x80000000 +#define PKINIT_EKU_MSSCLOGIN 0x40000000 +#define PKINIT_EKU_CLIENTAUTH 0x20000000 +#define PKINIT_EKU_EMAILPROTECTION 0x10000000 + + +/* Handle to cert, opaque above crypto interface */ +typedef struct _pkinit_cert_info *pkinit_cert_handle; + +/* Handle to cert iteration information, opaque above crypto interface */ +typedef struct _pkinit_cert_iter_info *pkinit_cert_iter_handle; + +#define PKINIT_ITER_NO_MORE 0x11111111 /* XXX */ + +typedef struct _pkinit_cert_matching_data { + pkinit_cert_handle ch; /* cert handle for this certificate */ + char *subject_dn; /* rfc2253-style subject name string */ + char *issuer_dn; /* rfc2253-style issuer name string */ + unsigned int ku_bits; /* key usage information */ + unsigned int eku_bits; /* extended key usage information */ + krb5_principal *sans; /* Null-terminated array of subject alternative + name info (pkinit and ms-upn) */ +} pkinit_cert_matching_data; + +/* * Functions to initialize and cleanup crypto contexts */ krb5_error_code pkinit_init_plg_crypto(pkinit_plg_crypto_context *); @@ -362,8 +413,90 @@ krb5_error_code crypto_load_certs pkinit_req_crypto_context req_cryptoctx, /* IN */ pkinit_identity_opts *idopts, /* IN */ pkinit_identity_crypto_context id_cryptoctx, /* IN/OUT */ - const char *principal, /* IN */ - krb5_get_init_creds_opt *opt); /* IN */ + krb5_principal princ); /* IN */ + +/* + * Free up information held from crypto_load_certs() + */ +krb5_error_code crypto_free_cert_info + (krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx); + + +/* + * Get number of certificates available after crypto_load_certs() + */ +krb5_error_code crypto_cert_get_count + (krb5_context context, /* IN */ + pkinit_plg_crypto_context plg_cryptoctx, /* IN */ + pkinit_req_crypto_context req_cryptoctx, /* IN */ + pkinit_identity_crypto_context id_cryptoctx, /* IN */ + int *cert_count); /* OUT */ + +/* + * Begin iteration over the certs loaded in crypto_load_certs() + */ +krb5_error_code crypto_cert_iteration_begin + (krb5_context context, /* IN */ + pkinit_plg_crypto_context plg_cryptoctx, /* IN */ + pkinit_req_crypto_context req_cryptoctx, /* IN */ + pkinit_identity_crypto_context id_cryptoctx, /* IN */ + pkinit_cert_iter_handle *iter_handle); /* OUT */ + +/* + * End iteration over the certs loaded in crypto_load_certs() + */ +krb5_error_code crypto_cert_iteration_end + (krb5_context context, /* IN */ + pkinit_cert_iter_handle iter_handle); /* IN */ + +/* + * Get next certificate handle + */ +krb5_error_code crypto_cert_iteration_next + (krb5_context context, /* IN */ + pkinit_cert_iter_handle iter_handle, /* IN */ + pkinit_cert_handle *cert_handle); /* OUT */ + +/* + * Release cert handle + */ +krb5_error_code crypto_cert_release + (krb5_context context, /* IN */ + pkinit_cert_handle cert_handle); /* IN */ + +/* + * Get certificate matching information + */ +krb5_error_code crypto_cert_get_matching_data + (krb5_context context, /* IN */ + pkinit_cert_handle cert_handle, /* IN */ + pkinit_cert_matching_data **ret_data); /* OUT */ + +/* + * Free certificate information + */ +krb5_error_code crypto_cert_free_matching_data + (krb5_context context, /* IN */ + pkinit_cert_matching_data *data); /* IN */ + +/* + * Make the given certificate "the chosen one" + */ +krb5_error_code crypto_cert_select + (krb5_context context, /* IN */ + pkinit_cert_matching_data *data); /* IN */ + +/* + * Select the default certificate as "the chosen one" + */ +krb5_error_code crypto_cert_select_default + (krb5_context context, /* IN */ + pkinit_plg_crypto_context plg_cryptoctx, /* IN */ + pkinit_req_crypto_context req_cryptoctx, /* IN */ + pkinit_identity_crypto_context id_cryptoctx); /* IN */ /* * process the values from idopts and obtain the anchor or @@ -383,11 +516,16 @@ krb5_error_code crypto_load_cas_and_crls char *id); /* IN defines the location (filename, directory name, etc) */ +/* + * on the client, obtain the kdc's certificate to include + * in a request + */ krb5_error_code pkinit_get_kdc_cert - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - const char *principal, krb5_get_init_creds_opt *opt); + (krb5_context context, /* IN */ + pkinit_plg_crypto_context plg_cryptoctx, /* IN */ + pkinit_req_crypto_context req_cryptoctx, /* IN */ + pkinit_identity_crypto_context id_cryptoctx, /* IN/OUT */ + krb5_principal princ); /* IN */ /* * this function creates edata that contains TD-DH-PARAMETERS diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c index 8850454..b45104b 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -164,8 +164,6 @@ unsigned char pkinit_4096_dhprime[4096/8] = { static int pkinit_oids_refs = 0; -#define MAX_CREDS_ALLOWED 20 - krb5_error_code pkinit_init_plg_crypto(pkinit_plg_crypto_context *cryptoctx) { @@ -434,7 +432,10 @@ static krb5_error_code pkinit_init_certs(pkinit_identity_crypto_context ctx) { krb5_error_code retval = ENOMEM; + int i; + for (i = 0; i < MAX_CREDS_ALLOWED; i++) + ctx->creds[i] = NULL; ctx->my_certs = NULL; ctx->cert_index = 0; ctx->my_key = NULL; @@ -3343,8 +3344,7 @@ pkinit_get_kdc_cert(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt) + krb5_principal princ) { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; @@ -3359,9 +3359,7 @@ pkinit_get_certs_pkcs12(krb5_context context, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt, - pkinit_cred_info *creds) + krb5_principal princ) { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; X509 *x = NULL; @@ -3438,14 +3436,16 @@ pkinit_get_certs_pkcs12(krb5_context context, goto cleanup; } } - creds[0] = malloc(sizeof(struct _pkinit_cred_info)); - if (creds[0] == NULL) + id_cryptoctx->creds[0] = malloc(sizeof(struct _pkinit_cred_info)); + if (id_cryptoctx->creds[0] == NULL) goto cleanup; - creds[0]->cert = x; - creds[0]->cert_id = NULL; - creds[0]->cert_id_len = 0; - creds[0]->key = y; - creds[1] = NULL; + id_cryptoctx->creds[0]->cert = x; +#ifndef WITHOUT_PKCS11 + id_cryptoctx->creds[0]->cert_id = NULL; + id_cryptoctx->creds[0]->cert_id_len = 0; +#endif + id_cryptoctx->creds[0]->key = y; + id_cryptoctx->creds[1] = NULL; retval = 0; @@ -3462,18 +3462,60 @@ cleanup: } static krb5_error_code +pkinit_load_fs_cert_and_key(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + char *certname, + char *keyname, + int cindex) +{ + krb5_error_code retval = ENOMEM; + X509 *x = NULL; + EVP_PKEY *y = NULL; + + /* load the certificate */ + x = get_cert(certname); + if (x == NULL) { + pkiDebug("failed to load user's certificate from '%s'\n", certname); + goto cleanup; + } + y = get_key(keyname); + if (y == NULL) { + pkiDebug("failed to load user's private key from '%s'\n", keyname); + goto cleanup; + } + + id_cryptoctx->creds[cindex] = malloc(sizeof(struct _pkinit_cred_info)); + if (id_cryptoctx->creds[cindex] == NULL) + goto cleanup; + id_cryptoctx->creds[cindex]->cert = x; +#ifndef WITHOUT_PKCS11 + id_cryptoctx->creds[cindex]->cert_id = NULL; + id_cryptoctx->creds[cindex]->cert_id_len = 0; +#endif + id_cryptoctx->creds[cindex]->key = y; + id_cryptoctx->creds[cindex+1] = NULL; + + retval = 0; + +cleanup: + if (retval) { + if (x != NULL) + X509_free(x); + if (y != NULL) + EVP_PKEY_free(y); + } + return retval; +} + +static krb5_error_code pkinit_get_certs_fs(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt, - pkinit_cred_info *creds) + krb5_principal princ) { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - X509 *x = NULL; - EVP_PKEY *y = NULL; if (idopts->cert_filename == NULL) { pkiDebug("%s: failed to get user's cert location\n", __FUNCTION__); @@ -3481,43 +3523,101 @@ pkinit_get_certs_fs(krb5_context context, } if (idopts->key_filename == NULL) { - pkiDebug("%s: failed to get user's private key location\n", __FUNCTION__); + pkiDebug("%s: failed to get user's private key location\n", + __FUNCTION__); goto cleanup; } - /* load the certificate */ - if ((x = get_cert(idopts->cert_filename)) == NULL) { - pkiDebug("failed to load user's certificate from '%s'\n", - idopts->cert_filename); - goto cleanup; + retval = pkinit_load_fs_cert_and_key(context, id_cryptoctx, + idopts->cert_filename, + idopts->key_filename, 0); +cleanup: + return retval; +} + +static krb5_error_code +pkinit_get_certs_dir(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_opts *idopts, + pkinit_identity_crypto_context id_cryptoctx, + krb5_principal princ) +{ + krb5_error_code retval = ENOMEM; + DIR *d = NULL; + struct dirent *dentry = NULL; + char certname[1024]; + char keyname[1024]; + int i = 0, len; + char *dirname, *suf; + + if (idopts->cert_filename == NULL) { + pkiDebug("%s: failed to get user's certificate directory location\n", + __FUNCTION__); + return ENOENT; } - else { - /* add the private key */ - if ((y = get_key(idopts->key_filename)) == NULL) { - pkiDebug("failed to load user's private key from '%s'\n", - idopts->key_filename); - goto cleanup; + + dirname = idopts->cert_filename; + d = opendir(dirname); + if (d == NULL) + return errno; + + /* + * We'll assume that certs are named XXX.crt and the corresponding + * key is named XXX.key + */ + while ((i < MAX_CREDS_ALLOWED) && (dentry = readdir(d)) != NULL) { + /* Ignore subdirectories and anything starting with a dot */ +#ifdef DT_DIR + if (dentry->d_type == DT_DIR) + continue; +#endif + if (dentry->d_name[0] == '.') + continue; + len = strlen(dentry->d_name); + if (len < 5) + continue; + suf = dentry->d_name + (len - 4); + if (strncmp(suf, ".crt", 4) != 0) + continue; + + /* Checked length */ + if (strlen(dirname) + strlen(dentry->d_name) + 2 > sizeof(certname)) { + pkiDebug("%s: Path too long -- directory '%s' and file '%s'\n", + __FUNCTION__, dirname, dentry->d_name); + continue; + } + snprintf(certname, sizeof(certname), "%s/%s", dirname, dentry->d_name); + snprintf(keyname, sizeof(keyname), "%s/%s", dirname, dentry->d_name); + len = strlen(keyname); + keyname[len - 3] = 'k'; + keyname[len - 2] = 'e'; + keyname[len - 1] = 'y'; + + retval = pkinit_load_fs_cert_and_key(context, id_cryptoctx, + certname, keyname, i); + if (retval == 0) { + pkiDebug("%s: Successfully loaded cert (and key) for %s\n", + __FUNCTION__, dentry->d_name); + i++; } + else + continue; } - creds[0] = malloc(sizeof(struct _pkinit_cred_info)); - if (creds[0] == NULL) + if (i == 0) { + pkiDebug("%s: No cert/key pairs found in directory '%s'\n", + __FUNCTION__, idopts->cert_filename); + retval = ENOENT; goto cleanup; - creds[0]->cert = x; - creds[0]->cert_id = NULL; - creds[0]->cert_id_len = 0; - creds[0]->key = y; - creds[1] = NULL; + } -retval = 0; + retval = 0; + + cleanup: + if (d) + closedir(d); -cleanup: - if (retval) { - if (x != NULL) - X509_free(x); - if (y != NULL) - EVP_PKEY_free(y); - } return retval; } @@ -3528,9 +3628,7 @@ pkinit_get_certs_pkcs11(krb5_context context, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt, - pkinit_cred_info *creds) + krb5_principal princ) { #ifdef PKINIT_USE_MECH_LIST CK_MECHANISM_TYPE_PTR mechp; @@ -3547,10 +3645,6 @@ pkinit_get_certs_pkcs11(krb5_context context, unsigned int nattrs; X509 *x = NULL; - if (principal == NULL) { - return KRB5_PRINC_NOMATCH; /* XXX ??? */ - } - /* Copy stuff from idopts -> id_cryptoctx */ if (idopts->p11_module_name != NULL) { id_cryptoctx->p11_module_name = strdup(idopts->p11_module_name); @@ -3630,8 +3724,7 @@ pkinit_get_certs_pkcs11(krb5_context context, } free(mechp); - pkiDebug("got %d mechs; reading certs for '%s' from card\n", - (int) count, principal); + pkiDebug("got %d mechs from card\n", (int) count); #endif cls = CKO_CERTIFICATE; @@ -3673,7 +3766,7 @@ pkinit_get_certs_pkcs11(krb5_context context, /* Look for x.509 cert */ if ((r = id_cryptoctx->p11->C_FindObjects(id_cryptoctx->session, &obj, 1, &count)) != CKR_OK || count <= 0) { - creds[i] = NULL; + id_cryptoctx->creds[i] = NULL; break; } @@ -3718,13 +3811,13 @@ pkinit_get_certs_pkcs11(krb5_context context, x = d2i_X509(NULL, &cp, (int) attrs[0].ulValueLen); if (x == NULL) return KRB5KDC_ERR_PREAUTH_FAILED; - creds[i] = malloc(sizeof(struct _pkinit_cred_info)); - if (creds[i] == NULL) + id_cryptoctx->creds[i] = malloc(sizeof(struct _pkinit_cred_info)); + if (id_cryptoctx->creds[i] == NULL) return KRB5KDC_ERR_PREAUTH_FAILED; - creds[i]->cert = x; - creds[i]->key = NULL; - creds[i]->cert_id = cert_id; - creds[i]->cert_id_len = attrs[1].ulValueLen; + id_cryptoctx->creds[i]->cert = x; + id_cryptoctx->creds[i]->key = NULL; + id_cryptoctx->creds[i]->cert_id = cert_id; + id_cryptoctx->creds[i]->cert_id_len = attrs[1].ulValueLen; free(cert); } id_cryptoctx->p11->C_FindObjectsFinal(id_cryptoctx->session); @@ -3734,44 +3827,76 @@ pkinit_get_certs_pkcs11(krb5_context context, } #endif + +static void +free_cred_info(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + struct _pkinit_cred_info *cred) +{ + if (cred != NULL) { + if (cred->cert != NULL) + X509_free(cred->cert); + if (cred->key != NULL) + EVP_PKEY_free(cred->key); +#ifndef WITHOUT_PKCS11 + if (cred->cert_id != NULL) + free(cred->cert_id); +#endif + free(cred); + } +} + +krb5_error_code +crypto_free_cert_info(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx) +{ + int i; + + if (id_cryptoctx == NULL) + return EINVAL; + + for (i = 0; i < MAX_CREDS_ALLOWED; i++) { + if (id_cryptoctx->creds[i] != NULL) { + free_cred_info(context, id_cryptoctx, id_cryptoctx->creds[i]); + } + } + return 0; +} + krb5_error_code crypto_load_certs(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt) + krb5_principal princ) { - pkinit_cred_info *creds = NULL; krb5_error_code retval; - creds = calloc(MAX_CREDS_ALLOWED + 1, sizeof(*creds)); - if (creds == NULL) { - retval = ENOMEM; - goto cleanup; - } - switch(idopts->idtype) { case IDTYPE_FILE: retval = pkinit_get_certs_fs(context, plg_cryptoctx, req_cryptoctx, idopts, - id_cryptoctx, - principal, opt, creds); + id_cryptoctx, princ); + break; + case IDTYPE_DIR: + retval = pkinit_get_certs_dir(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, princ); break; #ifndef WITHOUT_PKCS11 case IDTYPE_PKCS11: retval = pkinit_get_certs_pkcs11(context, plg_cryptoctx, req_cryptoctx, idopts, - id_cryptoctx, - principal, opt, creds); + id_cryptoctx, princ); break; #endif case IDTYPE_PKCS12: retval = pkinit_get_certs_pkcs12(context, plg_cryptoctx, req_cryptoctx, idopts, - id_cryptoctx, - principal, opt, creds); + id_cryptoctx, princ); break; default: retval = EINVAL; @@ -3779,68 +3904,434 @@ crypto_load_certs(krb5_context context, if (retval) goto cleanup; - /* pick the right certificate/key from the retrieved certs */ - retval = pkinit_match_cert(context, plg_cryptoctx, req_cryptoctx, - id_cryptoctx, principal, opt, creds); cleanup: - if (creds) - free(creds); return retval; } -/* XXX This needs to move above "the line"! */ +/* + * Get number of certificates available after crypto_load_certs() + */ +krb5_error_code +crypto_cert_get_count(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int *cert_count) +{ + int count; + + if (id_cryptoctx == NULL || id_cryptoctx->creds[0] == NULL) + return EINVAL; + + for (count = 0; + count <= MAX_CREDS_ALLOWED && id_cryptoctx->creds[count] != NULL; + count++); + *cert_count = count; + return 0; +} + + +/* + * Begin iteration over the certs loaded in crypto_load_certs() + */ +krb5_error_code +crypto_cert_iteration_begin(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + pkinit_cert_iter_handle *ih_ret) +{ + struct _pkinit_cert_iter_data *id; + + if (id_cryptoctx == NULL || ih_ret == NULL) + return EINVAL; + if (id_cryptoctx->creds[0] == NULL) /* No cred info available */ + return ENOENT; + + id = calloc(1, sizeof(*id)); + if (id == NULL) + return ENOMEM; + id->magic = ITER_MAGIC; + id->plgctx = plg_cryptoctx, + id->reqctx = req_cryptoctx, + id->idctx = id_cryptoctx; + id->index = 0; + *ih_ret = (pkinit_cert_iter_handle) id; + return 0; +} + +/* + * End iteration over the certs loaded in crypto_load_certs() + */ +krb5_error_code +crypto_cert_iteration_end(krb5_context context, + pkinit_cert_iter_handle ih) +{ + struct _pkinit_cert_iter_data *id = (struct _pkinit_cert_iter_data *)ih; + + if (id == NULL || id->magic != ITER_MAGIC) + return EINVAL; + free(ih); + return 0; +} + +/* + * Get next certificate handle + */ +krb5_error_code +crypto_cert_iteration_next(krb5_context context, + pkinit_cert_iter_handle ih, + pkinit_cert_handle *ch_ret) +{ + struct _pkinit_cert_iter_data *id = (struct _pkinit_cert_iter_data *)ih; + struct _pkinit_cert_data *cd; + pkinit_identity_crypto_context id_cryptoctx; + + if (id == NULL || id->magic != ITER_MAGIC) + return EINVAL; + + if (ch_ret == NULL) + return EINVAL; + + id_cryptoctx = id->idctx; + if (id_cryptoctx == NULL) + return EINVAL; + + if (id_cryptoctx->creds[id->index] == NULL) + return PKINIT_ITER_NO_MORE; + + cd = calloc(1, sizeof(*cd)); + if (cd == NULL) + return ENOMEM; + + cd->magic = CERT_MAGIC; + cd->plgctx = id->plgctx; + cd->reqctx = id->reqctx; + cd->idctx = id->idctx; + cd->index = id->index; + cd->cred = id_cryptoctx->creds[id->index++]; + *ch_ret = (pkinit_cert_handle)cd; + return 0; +} + +/* + * Release cert handle + */ +krb5_error_code +crypto_cert_release(krb5_context context, + pkinit_cert_handle ch) +{ + struct _pkinit_cert_data *cd = (struct _pkinit_cert_data *)ch; + if (cd == NULL || cd->magic != CERT_MAGIC) + return EINVAL; + free(cd); + return 0; +} + +/* + * Get certificate Key Usage and Extended Key Usage + */ static krb5_error_code -pkinit_match_cert(krb5_context context, - pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - const char *principal, - krb5_get_init_creds_opt *opt, - pkinit_cred_info *creds) +crypto_retieve_X509_key_usage(krb5_context context, + pkinit_plg_crypto_context plgcctx, + pkinit_req_crypto_context reqcctx, + X509 *x, + unsigned int *ret_ku_bits, + unsigned int *ret_eku_bits) { + krb5_error_code retval = 0; + int i; + unsigned int eku_bits = 0, ku_bits = 0; + ASN1_BIT_STRING *usage = NULL; - krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - int i, creds_index = 0; + if (ret_ku_bits == NULL && ret_eku_bits == NULL) + return EINVAL; - /* add code to find the right certificate */ + if (ret_eku_bits) + *ret_eku_bits = 0; + else { + pkiDebug("%s: EKUs not requested, not checking\n", __FUNCTION__); + goto check_kus; + } + + /* Start with Extended Key usage */ + i = X509_get_ext_by_NID(x, NID_ext_key_usage, -1); + if (i >= 0) { + EXTENDED_KEY_USAGE *eku; + + eku = X509_get_ext_d2i(x, NID_ext_key_usage, NULL, NULL); + if (eku) { + for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { + ASN1_OBJECT *certoid; + certoid = sk_ASN1_OBJECT_value(eku, i); + if ((OBJ_cmp(certoid, plgcctx->id_pkinit_KPClientAuth)) == 0) + eku_bits |= PKINIT_EKU_PKINIT; + else if ((OBJ_cmp(certoid, OBJ_nid2obj(NID_ms_smartcard_login))) == 0) + eku_bits |= PKINIT_EKU_MSSCLOGIN; + else if ((OBJ_cmp(certoid, OBJ_nid2obj(NID_client_auth))) == 0) + eku_bits |= PKINIT_EKU_CLIENTAUTH; + else if ((OBJ_cmp(certoid, OBJ_nid2obj(NID_email_protect))) == 0) + eku_bits |= PKINIT_EKU_EMAILPROTECTION; + } + EXTENDED_KEY_USAGE_free(eku); + } + } + pkiDebug("%s: returning eku 0x%08x\n", __FUNCTION__, eku_bits); + *ret_eku_bits = eku_bits; + +check_kus: + /* Now the Key Usage bits */ + if (ret_ku_bits) + *ret_ku_bits = 0; + else { + pkiDebug("%s: KUs not requested, not checking\n", __FUNCTION__); + goto out; + } + + /* Make sure usage exists before checking bits */ + usage = X509_get_ext_d2i(x, NID_key_usage, NULL, NULL); + if (usage) { + if (!ku_reject(x, X509v3_KU_DIGITAL_SIGNATURE)) + ku_bits |= PKINIT_KU_DIGITALSIGNATURE; + if (!ku_reject(x, X509v3_KU_KEY_ENCIPHERMENT)) + ku_bits |= PKINIT_KU_KEYENCIPHERMENT; + ASN1_BIT_STRING_free(usage); + } + pkiDebug("%s: returning ku 0x%08x\n", __FUNCTION__, ku_bits); + *ret_ku_bits = ku_bits; + retval = 0; +out: + return retval; +} + +/* + * Return a string format of an X509_NAME in buf where + * size is an in/out parameter. On input it is the size + * of the buffer, and on output it is the actual length + * of the name. + * If buf is NULL, returns the length req'd to hold name + */ +static char * +X509_NAME_oneline_ex(X509_NAME * a, + char *buf, + unsigned int *size, + unsigned long flag) +{ + BIO *out = NULL; + + out = BIO_new(BIO_s_mem ()); + if (X509_NAME_print_ex(out, a, 0, flag) > 0) { + if (buf != NULL && *size > (int) BIO_number_written(out)) { + memset(buf, 0, *size); + BIO_read(out, buf, (int) BIO_number_written(out)); + } + else { + *size = BIO_number_written(out); + } + } + BIO_free(out); + return (buf); +} + +/* + * Get certificate information + */ +krb5_error_code +crypto_cert_get_matching_data(krb5_context context, + pkinit_cert_handle ch, + pkinit_cert_matching_data **ret_md) +{ + krb5_error_code retval; + pkinit_cert_matching_data *md; + krb5_principal *pkinit_sans =NULL, *upn_sans = NULL; + struct _pkinit_cert_data *cd = (struct _pkinit_cert_data *)ch; + int i, j; + char buf[DN_BUF_LEN]; + unsigned int bufsize = sizeof(buf); + + if (cd == NULL || cd->magic != CERT_MAGIC) + return EINVAL; + if (ret_md == NULL) + return EINVAL; + + md = calloc(1, sizeof(*md)); + if (md == NULL) + return ENOMEM; + + md->ch = ch; + + /* get the subject name (in rfc2253 format) */ + X509_NAME_oneline_ex(X509_get_subject_name(cd->cred->cert), + buf, &bufsize, XN_FLAG_SEP_COMMA_PLUS); + md->subject_dn = strdup(buf); + if (md->subject_dn == NULL) { + retval = ENOMEM; + goto cleanup; + } + + /* get the issuer name (in rfc2253 format) */ + X509_NAME_oneline_ex(X509_get_issuer_name(cd->cred->cert), + buf, &bufsize, XN_FLAG_SEP_COMMA_PLUS); + md->issuer_dn = strdup(buf); + if (md->issuer_dn == NULL) { + retval = ENOMEM; + goto cleanup; + } + + /* get the san data */ + retval = crypto_retrieve_X509_sans(context, cd->plgctx, cd->reqctx, + cd->cred->cert, &pkinit_sans, + &upn_sans, NULL); + if (retval) + goto cleanup; + + j = 0; + if (pkinit_sans != NULL) { + for (i = 0; pkinit_sans[i] != NULL; i++) + j++; + } + if (upn_sans != NULL) { + for (i = 0; upn_sans[i] != NULL; i++) + j++; + } + if (j != 0) { + md->sans = calloc((size_t)j+1, sizeof(*md->sans)); + if (md->sans == NULL) { + retval = ENOMEM; + goto cleanup; + } + j = 0; + if (pkinit_sans != NULL) { + for (i = 0; pkinit_sans[i] != NULL; i++) + md->sans[j++] = pkinit_sans[i]; + free(pkinit_sans); + } + if (upn_sans != NULL) { + for (i = 0; upn_sans[i] != NULL; i++) + md->sans[j++] = upn_sans[i]; + free(upn_sans); + } + md->sans[j] = NULL; + } else + md->sans = NULL; + + /* get the KU and EKU data */ + + retval = crypto_retieve_X509_key_usage(context, cd->plgctx, cd->reqctx, + cd->cred->cert, + &md->ku_bits, &md->eku_bits); + if (retval) + goto cleanup; + + *ret_md = md; + retval = 0; +cleanup: + if (retval) { + if (md) + crypto_cert_free_matching_data(context, md); + } + return retval; +} + +/* + * Free certificate information + */ +krb5_error_code +crypto_cert_free_matching_data(krb5_context context, + pkinit_cert_matching_data *md) +{ + krb5_principal p; + int i; + + if (md == NULL) + return EINVAL; + if (md->subject_dn) + free(md->subject_dn); + if (md->issuer_dn) + free(md->issuer_dn); + if (md->sans) { + for (i = 0, p = md->sans[i]; p != NULL; p = md->sans[++i]) + krb5_free_principal(context, p); + free(md->sans); + } + free(md); + return 0; +} + +/* + * Make this matching certificate "the chosen one" + */ +krb5_error_code +crypto_cert_select(krb5_context context, + pkinit_cert_matching_data *md) +{ + struct _pkinit_cert_data *cd; + if (md == NULL) + return EINVAL; + + cd = (struct _pkinit_cert_data *)md->ch; + if (cd == NULL || cd->magic != CERT_MAGIC) + return EINVAL; + /* copy the selected cert into our id_cryptoctx */ + if (cd->idctx->my_certs != NULL) { + /* XXX free existing cert stack! */ + return 9999; + } + cd->idctx->my_certs = sk_X509_new_null(); + sk_X509_push(cd->idctx->my_certs, cd->cred->cert); + cd->idctx->creds[cd->index]->cert = NULL; /* Don't free it twice */ + cd->idctx->cert_index = 0; + + if (cd->idctx->pkcs11_method != 1) { + cd->idctx->my_key = cd->cred->key; + cd->idctx->creds[cd->index]->key = NULL; /* Don't free it twice */ + } +#ifndef WITHOUT_PKCS11 + else { + cd->idctx->cert_id = cd->cred->cert_id; + cd->idctx->creds[cd->index]->cert_id = NULL; /* Don't free it twice */ + cd->idctx->cert_id_len = cd->cred->cert_id_len; + } +#endif + return 0; +} + +/* + * Choose the default certificate as "the chosen one" + */ +krb5_error_code +crypto_cert_select_default(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx) +{ + /* copy the selected cert into our id_cryptoctx */ + if (id_cryptoctx->my_certs != NULL) { + /* XXX free existing cert stack! */ + return 9999; + } id_cryptoctx->my_certs = sk_X509_new_null(); - sk_X509_push(id_cryptoctx->my_certs, creds[creds_index]->cert); + sk_X509_push(id_cryptoctx->my_certs, id_cryptoctx->creds[0]->cert); + id_cryptoctx->creds[0]->cert = NULL; /* Don't free it twice */ id_cryptoctx->cert_index = 0; if (id_cryptoctx->pkcs11_method != 1) { - id_cryptoctx->my_key = creds[creds_index]->key; + id_cryptoctx->my_key = id_cryptoctx->creds[0]->key; + id_cryptoctx->creds[0]->key = NULL; /* Don't free it twice */ } #ifndef WITHOUT_PKCS11 else { - id_cryptoctx->cert_id = creds[creds_index]->cert_id; - id_cryptoctx->cert_id_len = creds[creds_index]->cert_id_len; + id_cryptoctx->cert_id = id_cryptoctx->creds[0]->cert_id; + id_cryptoctx->creds[0]->cert_id = NULL; /* Don't free it twice */ + id_cryptoctx->cert_id_len = id_cryptoctx->creds[0]->cert_id_len; } #endif + return 0; +} - /* free unused cred_infos XXX cred_fini() instead! */ - for (i = 0; ; i++) { - if (creds[i] == NULL) break; - else if (i == creds_index) { - free (creds[i]); - } else { - if (creds[i]->cert != NULL) - X509_free(creds[i]->cert); - if (creds[i]->key != NULL) - EVP_PKEY_free(creds[i]->key); -#ifndef WITHOUT_PKCS11 - if (creds[i]->cert_id != NULL) - free(creds[i]->cert_id); -#endif - free(creds[i]); - } - } - retval = 0; -cleanup: - return retval; -} static krb5_error_code load_cas_and_crls(krb5_context context, @@ -4028,8 +4519,10 @@ load_cas_and_crls_dir(krb5_context context, goto cleanup; } /* Ignore subdirectories and anything starting with a dot */ +#ifdef DT_DIR if (dentry->d_type == DT_DIR) continue; +#endif if (dentry->d_name[0] == '.') continue; snprintf(filename, sizeof(filename), "%s/%s", dirname, dentry->d_name); diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h index 1bafc31..9976bf3 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h @@ -48,8 +48,20 @@ #include "pkinit.h" #define DN_BUF_LEN 256 +#define MAX_CREDS_ALLOWED 20 + +struct _pkinit_cred_info { + X509 *cert; + EVP_PKEY *key; +#ifndef WITHOUT_PKCS11 + CK_BYTE_PTR cert_id; + int cert_id_len; +#endif +}; +typedef struct _pkinit_cred_info * pkinit_cred_info; struct _pkinit_identity_crypto_context { + pkinit_cred_info creds[MAX_CREDS_ALLOWED+1]; STACK_OF(X509) *my_certs; /* available user certs */ int cert_index; /* cert to use out of available certs*/ EVP_PKEY *my_key; /* available user keys if in filesystem */ @@ -95,13 +107,24 @@ struct _pkinit_req_crypto_context { DH *dh; }; -struct _pkinit_cred_info { - X509 *cert; - EVP_PKEY *key; - CK_BYTE_PTR cert_id; - int cert_id_len; +#define CERT_MAGIC 0x53534c43 +struct _pkinit_cert_data { + unsigned int magic; + pkinit_plg_crypto_context plgctx; + pkinit_req_crypto_context reqctx; + pkinit_identity_crypto_context idctx; + pkinit_cred_info cred; + unsigned int index; /* Index of this cred in the creds[] array */ +}; + +#define ITER_MAGIC 0x53534c49 +struct _pkinit_cert_iter_data { + unsigned int magic; + pkinit_plg_crypto_context plgctx; + pkinit_req_crypto_context reqctx; + pkinit_identity_crypto_context idctx; + unsigned int index; }; -typedef struct _pkinit_cred_info * pkinit_cred_info; static void openssl_init(void); @@ -202,13 +225,6 @@ static krb5_error_code pkinit_decode_data_pkcs11 unsigned char **decoded_data, unsigned int *decoded_data_len); #endif /* WITHOUT_PKCS11 */ -static krb5_error_code pkinit_match_cert - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - const char *principal, krb5_get_init_creds_opt *opt, - pkinit_cred_info *creds); - static krb5_error_code pkinit_sign_data_fs (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, unsigned char *data, unsigned int data_len, diff --git a/src/plugins/preauth/pkinit/pkinit_identity.c b/src/plugins/preauth/pkinit/pkinit_identity.c index 7bae5f7..267e858 100644 --- a/src/plugins/preauth/pkinit/pkinit_identity.c +++ b/src/plugins/preauth/pkinit/pkinit_identity.c @@ -165,6 +165,7 @@ pkinit_dup_identity_opts(pkinit_identity_opts *src_opts, goto cleanup; } +#ifndef WITHOUT_PKCS11 if (src_opts->p11_module_name != NULL) { newopts->p11_module_name = strdup(src_opts->p11_module_name); if (newopts->p11_module_name == NULL) @@ -190,6 +191,7 @@ pkinit_dup_identity_opts(pkinit_identity_opts *src_opts, if (newopts->cert_label == NULL) goto cleanup; } +#endif *dest_opts = newopts; @@ -365,6 +367,8 @@ cleanup: static krb5_error_code process_option_identity(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, const char *value) @@ -412,8 +416,8 @@ process_option_identity(krb5_context context, pkiDebug("%s: idtype is %d\n", __FUNCTION__, idopts->idtype); switch (idtype) { case IDTYPE_ENVVAR: - return process_option_identity(context, idopts, id_cryptoctx, - getenv(residual)); + return process_option_identity(context, plg_cryptoctx, req_cryptoctx, + idopts, id_cryptoctx, getenv(residual)); break; case IDTYPE_FILE: retval = parse_fs_options(context, idopts, residual); @@ -427,8 +431,9 @@ process_option_identity(krb5_context context, break; #endif case IDTYPE_DIR: - pkiDebug("DIR: not supported for user_identity '%s'\n", value); - retval = ENOTSUP; + idopts->cert_filename = strdup(residual); + if (idopts->cert_filename == NULL) + retval = ENOMEM; break; default: krb5_set_error_message(context, KRB5_PREAUTH_FAILED, @@ -436,18 +441,13 @@ process_option_identity(krb5_context context, retval = EINVAL; break; } - retval = crypto_load_certs(context, - NULL, /* XXX plg_cryptoctx */ - NULL, /* XXX req_cryptoctx */ - idopts, - id_cryptoctx, - "<not-supplied>",/* XXX need principal */ - NULL /* XXX need gic opts */); return retval; } static krb5_error_code process_option_ca_crl(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, const char *value, @@ -474,14 +474,16 @@ process_option_ca_crl(krb5_context context, return ENOTSUP; } return crypto_load_cas_and_crls(context, - NULL, /* XXX plg_cryptoctx */ - NULL, /* XXX req_cryptoctx */ + plg_cryptoctx, + req_cryptoctx, idopts, id_cryptoctx, idtype, catype, residual); } static krb5_error_code pkinit_identity_process_option(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, pkinit_identity_crypto_context id_cryptoctx, int attr, @@ -491,19 +493,26 @@ pkinit_identity_process_option(krb5_context context, switch (attr) { case PKINIT_ID_OPT_USER_IDENTITY: - retval = process_option_identity(context, idopts, id_cryptoctx, - value); + retval = process_option_identity(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, value); break; case PKINIT_ID_OPT_ANCHOR_CAS: - retval = process_option_ca_crl(context, idopts, id_cryptoctx, - value, CATYPE_ANCHORS); + retval = process_option_ca_crl(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, value, + CATYPE_ANCHORS); break; case PKINIT_ID_OPT_INTERMEDIATE_CAS: - retval = process_option_ca_crl(context, idopts, id_cryptoctx, + retval = process_option_ca_crl(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, value, CATYPE_INTERMEDIATES); break; case PKINIT_ID_OPT_CRLS: - retval = process_option_ca_crl(context, idopts, id_cryptoctx, + retval = process_option_ca_crl(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, value, CATYPE_CRLS); break; case PKINIT_ID_OPT_OCSP: @@ -518,8 +527,12 @@ pkinit_identity_process_option(krb5_context context, krb5_error_code pkinit_identity_initialize(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, pkinit_identity_opts *idopts, - pkinit_identity_crypto_context id_cryptoctx) + pkinit_identity_crypto_context id_cryptoctx, + int do_matching, + krb5_principal princ) { krb5_error_code retval = EINVAL; int i; @@ -537,12 +550,15 @@ pkinit_identity_initialize(krb5_context context, * in the config file. */ if (idopts->identity != NULL) { - retval = pkinit_identity_process_option(context, idopts, id_cryptoctx, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, PKINIT_ID_OPT_USER_IDENTITY, idopts->identity); } else if (idopts->identity_alt != NULL) { for (i = 0; retval != 0 && idopts->identity_alt[i] != NULL; i++) - retval = pkinit_identity_process_option(context, idopts, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, id_cryptoctx, PKINIT_ID_OPT_USER_IDENTITY, idopts->identity_alt[i]); @@ -553,8 +569,42 @@ pkinit_identity_initialize(krb5_context context, if (retval) goto errout; + retval = crypto_load_certs(context, plg_cryptoctx, req_cryptoctx, + idopts, id_cryptoctx, princ); + if (retval) + goto errout; + + if (do_matching) { + retval = pkinit_cert_matching(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, princ); + if (retval) { + pkiDebug("%s: No matching certificate found\n", __FUNCTION__); + crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx); + goto errout; + } + } else { + /* Tell crypto code to use the "default" */ + retval = crypto_cert_select_default(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx); + if (retval) { + pkiDebug("%s: Failed while selecting default certificate\n", + __FUNCTION__); + crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx); + goto errout; + } + } + + retval = crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx); + if (retval) + goto errout; + for (i = 0; idopts->anchors != NULL && idopts->anchors[i] != NULL; i++) { - retval = pkinit_identity_process_option(context, idopts, id_cryptoctx, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, PKINIT_ID_OPT_ANCHOR_CAS, idopts->anchors[i]); if (retval) @@ -562,21 +612,27 @@ pkinit_identity_initialize(krb5_context context, } for (i = 0; idopts->intermediates != NULL && idopts->intermediates[i] != NULL; i++) { - retval = pkinit_identity_process_option(context, idopts, id_cryptoctx, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, PKINIT_ID_OPT_INTERMEDIATE_CAS, idopts->intermediates[i]); if (retval) goto errout; } for (i = 0; idopts->crls != NULL && idopts->crls[i] != NULL; i++) { - retval = pkinit_identity_process_option(context, idopts, id_cryptoctx, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, PKINIT_ID_OPT_CRLS, idopts->crls[i]); if (retval) goto errout; } if (idopts->ocsp != NULL) { - retval = pkinit_identity_process_option(context, idopts, id_cryptoctx, + retval = pkinit_identity_process_option(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, PKINIT_ID_OPT_OCSP, idopts->ocsp); if (retval) diff --git a/src/plugins/preauth/pkinit/pkinit_matching.c b/src/plugins/preauth/pkinit/pkinit_matching.c new file mode 100644 index 0000000..ebd57f5 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_matching.c @@ -0,0 +1,827 @@ +/* + * COPYRIGHT (C) 2007 + * THE REGENTS OF THE UNIVERSITY OF MICHIGAN + * ALL RIGHTS RESERVED + * + * Permission is granted to use, copy, create derivative works + * and redistribute this software and such derivative works + * for any purpose, so long as the name of The University of + * Michigan is not used in any advertising or publicity + * pertaining to the use of distribution of this software + * without specific, written prior authorization. If the + * above copyright notice or any other identification of the + * University of Michigan is included in any copy of any + * portion of this software, then the disclaimer below must + * also be included. + * + * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION + * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY + * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF + * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING + * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE + * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING + * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN + * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGES. + */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <regex.h> +#include <krb5.h> +#include "pkinit.h" + +typedef struct _pkinit_cert_info pkinit_cert_info; + +typedef enum { + kw_undefined = 0, + kw_subject = 1, + kw_issuer = 2, + kw_san = 3, + kw_eku = 4, + kw_ku = 5, +} keyword_type; + +static char * +keyword2string(unsigned int kw) +{ + switch(kw) { + case kw_undefined: return "NONE"; break; + case kw_subject: return "SUBJECT"; break; + case kw_issuer: return "ISSUER"; break; + case kw_san: return "SAN"; break; + case kw_eku: return "EKU"; break; + case kw_ku: return "KU"; break; + default: return "INVALID"; break; + } +} +typedef enum { + relation_none = 0, + relation_and = 1, + relation_or = 2, +} relation_type; + +static char * +relation2string(unsigned int rel) +{ + switch(rel) { + case relation_none: return "NONE"; break; + case relation_and: return "AND"; break; + case relation_or: return "OR"; break; + default: return "INVALID"; break; + } +} + +typedef enum { + kwvaltype_undefined = 0, + kwvaltype_regexp = 1, + kwvaltype_list = 2, +} kw_value_type; + +static char * +kwval2string(unsigned int kwval) +{ + switch(kwval) { + case kwvaltype_undefined: return "NONE"; break; + case kwvaltype_regexp: return "REGEXP"; break; + case kwvaltype_list: return "LIST"; break; + default: return "INVALID"; break; + } +} + +struct keyword_desc { + const char *value; + size_t length; + keyword_type kwtype; + kw_value_type kwvaltype; +} matching_keywords[] = { + { "<KU>", 4, kw_ku, kwvaltype_list }, + { "<EKU>", 5, kw_eku, kwvaltype_list }, + { "<SAN>", 5, kw_san, kwvaltype_regexp }, + { "<ISSUER>", 8, kw_issuer, kwvaltype_regexp }, + { "<SUBJECT>", 9, kw_subject, kwvaltype_regexp }, + { NULL, 0, kw_undefined, kwvaltype_undefined}, +}; + +struct ku_desc { + const char *value; + size_t length; + unsigned int bitval; +}; + +struct ku_desc ku_keywords[] = { + { "digitalSignature", 16, PKINIT_KU_DIGITALSIGNATURE }, + { "keyEncipherment", 15, PKINIT_KU_KEYENCIPHERMENT }, + { NULL, 0, 0 }, +}; + +struct ku_desc eku_keywords[] = { + { "pkinit", 6, PKINIT_EKU_PKINIT }, + { "msScLogin", 9, PKINIT_EKU_MSSCLOGIN }, + { "clientAuth", 10, PKINIT_EKU_CLIENTAUTH }, + { "emailProtection", 15, PKINIT_EKU_EMAILPROTECTION }, + { NULL, 0, 0 }, +}; + +/* Rule component */ +typedef struct _rule_component { + struct _rule_component *next; + keyword_type kw_type; + kw_value_type kwval_type; + regex_t regexp; /* Compiled regular expression */ + char *regsrc; /* The regular expression source (for debugging) */ + unsigned int ku_bits; + unsigned int eku_bits; +} rule_component; + +/* Set rule components */ +typedef struct _rule_set { + relation_type relation; + int num_crs; + rule_component *crs; +} rule_set; + +static krb5_error_code +free_rule_component(krb5_context context, + rule_component *rc) +{ + if (rc == NULL) + return 0; + + if (rc->kwval_type == kwvaltype_regexp) { + if (rc->regsrc) + free(rc->regsrc); + regfree(&rc->regexp); + } + free(rc); + return 0; +} + +static krb5_error_code +free_rule_set(krb5_context context, + rule_set *rs) +{ + rule_component *rc, *trc; + + if (rs == NULL) + return 0; + for (rc = rs->crs; rc != NULL;) { + trc = rc->next; + free_rule_component(context, rc); + rc = trc; + } + free(rs); + return 0; +} + +static krb5_error_code +parse_list_value(krb5_context context, + keyword_type type, + char *value, + rule_component *rc) +{ + krb5_error_code retval; + char *comma; + struct ku_desc *ku; + int found; + size_t len; + unsigned int *bitptr; + + + if (value == NULL || value[0] == '\0') { + pkiDebug("%s: Missing or empty value for list keyword type %d\n", + __FUNCTION__, type); + retval = EINVAL; + goto out; + } + + if (type == kw_eku) { + bitptr = &rc->eku_bits; + } else if (type == kw_ku) { + bitptr = &rc->ku_bits; + } else { + pkiDebug("%s: Unknown list keyword type %d\n", __FUNCTION__, type); + retval = EINVAL; + goto out; + } + + do { + found = 0; + comma = strchr(value, ','); + if (comma != NULL) + len = comma - value; + else + len = strlen(value); + + if (type == kw_eku) { + ku = eku_keywords; + } else if (type == kw_ku) { + ku = ku_keywords; + } + + for (; ku->value != NULL; ku++) { + if (strncasecmp(value, ku->value, len) == 0) { + *bitptr |= ku->bitval; + found = 1; + pkiDebug("%s: Found value '%s', bitfield is now 0x%x\n", + __FUNCTION__, ku->value, *bitptr); + break; + } + } + if (found) { + value += ku->length; + if (*value == ',') + value += 1; + } else { + pkiDebug("%s: Urecognized value '%s'\n", __FUNCTION__, value); + retval = EINVAL; + goto out; + } + } while (found && *value != '\0'); + + retval = 0; +out: + pkiDebug("%s: returning %d\n", __FUNCTION__, retval); + return retval; +} + +static krb5_error_code +parse_rule_component(krb5_context context, + const char **rule, + int *remaining, + rule_component **ret_rule) +{ + krb5_error_code retval; + rule_component *rc = NULL; + keyword_type kw_type; + kw_value_type kwval_type; + char err_buf[128]; + int ret; + struct keyword_desc *kw, *nextkw; + char *nk; + int found_next_kw = 0; + char *value = NULL; + size_t len; + + for (kw = matching_keywords; kw->value != NULL; kw++) { + if (strncmp(*rule, kw->value, kw->length) == 0) { + kw_type = kw->kwtype; + kwval_type = kw->kwvaltype; + *rule += kw->length; + *remaining -= kw->length; + break; + } + } + if (kw->value == NULL) { + pkiDebug("%s: Missing or invalid keyword in rule '%s'\n", + __FUNCTION__, *rule); + retval = ENOENT; + goto out; + } + + pkiDebug("%s: found keyword '%s'\n", __FUNCTION__, kw->value); + + rc = calloc(1, sizeof(*rc)); + if (rc == NULL) { + retval = ENOMEM; + goto out; + } + rc->next = NULL; + rc->kw_type = kw_type; + rc->kwval_type = kwval_type; + + /* + * Before procesing the value for this keyword, + * (compiling the regular expression or processing the list) + * we need to find the end of it. That means parsing for the + * beginning of the next keyword (or the end of the rule). + */ + nk = strchr(*rule, '<'); + while (nk != NULL) { + /* Possibly another keyword, check it out */ + for (nextkw = matching_keywords; nextkw->value != NULL; nextkw++) { + if (strncmp(nk, nextkw->value, nextkw->length) == 0) { + /* Found a keyword, nk points to the beginning */ + found_next_kw = 1; + break; /* Need to break out of the while! */ + } + } + if (!found_next_kw) + nk = strchr(nk+1, '<'); /* keep looking */ + else + break; + } + + if (nk != NULL && found_next_kw) + len = (nk - *rule); + else + len = (*remaining); + + if (len == 0) { + pkiDebug("%s: Missing value for keyword '%s'\n", + __FUNCTION__, kw->value); + retval = EINVAL; + goto out; + } + + value = calloc(1, len+1); + if (value == NULL) { + retval = ENOMEM; + goto out; + } + memcpy(value, *rule, len); + *remaining -= len; + *rule += len; + pkiDebug("%s: found value '%s'\n", __FUNCTION__, value); + + if (kw->kwvaltype == kwvaltype_regexp) { + ret = regcomp(&rc->regexp, value, REG_EXTENDED); + if (ret) { + regerror(ret, &rc->regexp, err_buf, sizeof(err_buf)); + pkiDebug("%s: Error compiling reg-exp '%s': %s\n", + __FUNCTION__, value, err_buf); + retval = ret; + goto out; + } + rc->regsrc = strdup(value); + if (rc->regsrc == NULL) { + retval = ENOMEM; + goto out; + } + } else if (kw->kwvaltype == kwvaltype_list) { + retval = parse_list_value(context, rc->kw_type, value, rc); + if (retval) { + pkiDebug("%s: Error %d, parsing list values for keyword %s\n", + __FUNCTION__, retval, kw->value); + goto out; + } + } + + *ret_rule = rc; + retval = 0; +out: + if (value != NULL) + free(value); + if (retval && rc != NULL) + free_rule_component(context, rc); + pkiDebug("%s: returning %d\n", __FUNCTION__, retval); + return retval; +} + +static krb5_error_code +parse_rule_set(krb5_context context, + const char *rule_in, + rule_set **out_rs) +{ + const char *rule; + int remaining, totlen; + krb5_error_code ret, retval; + rule_component *rc, *trc; + rule_set *rs; + + + if (rule_in == NULL) + return EINVAL; + rule = rule_in; + totlen = remaining = strlen(rule); + + rs = calloc(1, sizeof(*rs)); + if (rs == NULL) { + retval = ENOMEM; + goto cleanup; + } + + rs->relation = relation_none; + if (remaining > 1) { + if (rule[0] == '&' && rule[1] == '&') { + rs->relation = relation_and; + rule += 2; + remaining -= 2; + } else if (rule_in[0] == '|' && rule_in[1] == '|') { + rs->relation = relation_or; + rule +=2; + remaining -= 2; + } + } + rs->num_crs = 0; + while (remaining > 0) { + if (rs->relation == relation_none && rs->num_crs > 1) { + pkiDebug("%s: Assuming AND relation for multiple components in rule '%s'\n", + __FUNCTION__, rule_in); + rs->relation = relation_and; + } + ret = parse_rule_component(context, &rule, &remaining, &rc); + if (ret) { + retval = ret; + goto cleanup; + } + pkiDebug("%s: After parse_rule_component, remaining %d, rule '%s'\n", + __FUNCTION__, remaining, rule); + rs->num_crs++; + + /* + * Chain the new component on the end (order matters since + * we can short-circuit an OR or an AND relation if an + * earlier check passes + */ + for (trc = rs->crs; trc != NULL && trc->next != NULL; trc = trc->next); + if (trc == NULL) + rs->crs = rc; + else { + trc->next = rc; + } + } + + *out_rs = rs; + + retval = 0; +cleanup: + if (retval && rs != NULL) { + free_rule_set(context, rs); + } + pkiDebug("%s: returning %d\n", __FUNCTION__, retval); + return retval; +} + +static int +regexp_match(krb5_context context, rule_component *rc, char *value) +{ + int code; + + pkiDebug("%s: checking %s rule '%s' with value '%s'\n", + __FUNCTION__, keyword2string(rc->kw_type), rc->regsrc, value); + + code = regexec(&rc->regexp, value, 0, NULL, 0); + + pkiDebug("%s: the result is%s a match\n", __FUNCTION__, + code == REG_NOMATCH ? " NOT" : ""); + + return (code == 0 ? 1: 0); +} + +static int +component_match(krb5_context context, + rule_component *rc, + pkinit_cert_matching_data *md) +{ + int match = 0; + int i; + krb5_principal p; + char *princ_string; + + switch (rc->kwval_type) { + case kwvaltype_regexp: + switch (rc->kw_type) { + case kw_subject: + match = regexp_match(context, rc, md->subject_dn); + break; + case kw_issuer: + match = regexp_match(context, rc, md->issuer_dn); + break; + case kw_san: + for (i = 0, p = md->sans[i]; p != NULL; p = md->sans[++i]) { + krb5_unparse_name(context, p, &princ_string); + match = regexp_match(context, rc, princ_string); + krb5_free_unparsed_name(context, princ_string); + if (match) + break; + } + break; + default: + pkiDebug("%s: keyword %s, keyword value %s mismatch\n", + __FUNCTION__, keyword2string(rc->kw_type), + kwval2string(kwvaltype_regexp)); + break; + } + break; + case kwvaltype_list: + switch(rc->kw_type) { + case kw_eku: + pkiDebug("%s: checking %s: rule 0x%08x, cert 0x%08x\n", + __FUNCTION__, keyword2string(rc->kw_type), + rc->eku_bits, md->eku_bits); + if ((rc->eku_bits & md->eku_bits) == rc->eku_bits) + match = 1; + break; + case kw_ku: + pkiDebug("%s: checking %s: rule 0x%08x, cert 0x%08x\n", + __FUNCTION__, keyword2string(rc->kw_type), + rc->ku_bits, md->ku_bits); + if ((rc->ku_bits & md->ku_bits) == rc->ku_bits) + match = 1; + break; + default: + pkiDebug("%s: keyword %s, keyword value %s mismatch\n", + __FUNCTION__, keyword2string(rc->kw_type), + kwval2string(kwvaltype_regexp)); + break; + } + break; + default: + pkiDebug("%s: unknown keyword value type %d\n", + __FUNCTION__, rc->kwval_type); + break; + } + pkiDebug("%s: returning match = %d\n", __FUNCTION__, match); + return match; +} +/* + * Returns match_found == 1 only if exactly one certificate matches + * the given rule + */ +static krb5_error_code +check_all_certs(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_principal princ, + rule_set *rs, /* rule to check */ + pkinit_cert_matching_data **matchdata, + int *match_found, + pkinit_cert_matching_data **matching_cert) +{ + krb5_error_code retval; + pkinit_cert_matching_data *md; + int i; + int comp_match = 0; + int total_cert_matches = 0; + rule_component *rc; + int certs_checked = 0; + pkinit_cert_matching_data *save_match = NULL; + + if (match_found == NULL || matching_cert == NULL) + return EINVAL; + + *matching_cert = NULL; + *match_found = 0; + + pkiDebug("%s: matching rule relation is %s with %d components\n", + __FUNCTION__, relation2string(rs->relation), rs->num_crs); + + /* + * Loop through all the certs available and count + * how many match the rule + */ + for (i = 0, md = matchdata[i]; md != NULL; md = matchdata[++i]) { + pkiDebug("%s: subject: '%s'\n", __FUNCTION__, md->subject_dn); +#if 0 + pkiDebug("%s: issuer: '%s'\n", __FUNCTION__, md->subject_dn); + for (j = 0, p = md->sans[j]; p != NULL; p = md->sans[++j]) { + char *san_string; + krb5_unparse_name(context, p, &san_string); + pkiDebug("%s: san: '%s'\n", __FUNCTION__, san_string); + krb5_free_unparsed_name(context, san_string); + } +#endif + certs_checked++; + for (rc = rs->crs; rc != NULL; rc = rc->next) { + comp_match = component_match(context, rc, md); + if (comp_match) { + pkiDebug("%s: match for keyword type %s\n", + __FUNCTION__, keyword2string(rc->kw_type)); + } + if (comp_match && rs->relation == relation_or) { + pkiDebug("%s: cert matches rule (OR relation)\n", + __FUNCTION__); + total_cert_matches++; + save_match = md; + goto nextcert; + } + if (!comp_match && rs->relation == relation_and) { + pkiDebug("%s: cert does not match rule (AND relation)\n", + __FUNCTION__); + goto nextcert; + } + } + if (rc == NULL && comp_match) { + pkiDebug("%s: cert matches rule (AND relation)\n", __FUNCTION__); + total_cert_matches++; + save_match = md; + } +nextcert: + continue; + } + pkiDebug("%s: After checking %d certs, we found %d matches\n", + __FUNCTION__, certs_checked, total_cert_matches); + if (total_cert_matches == 1) { + *match_found = 1; + *matching_cert = save_match; + } + + retval = 0; + + pkiDebug("%s: returning %d, match_found %d\n", + __FUNCTION__, retval, *match_found); + return retval; +} + +static krb5_error_code +free_all_cert_matching_data(krb5_context context, + pkinit_cert_matching_data **matchdata) +{ + krb5_error_code retval; + pkinit_cert_matching_data *md; + int i; + + if (matchdata == NULL) + return EINVAL; + + for (i = 0, md = matchdata[i]; md != NULL; md = matchdata[++i]) { + pkinit_cert_handle ch = md->ch; + retval = crypto_cert_free_matching_data(context, md); + if (retval) { + pkiDebug("%s: crypto_cert_free_matching_data error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + retval = crypto_cert_release(context, ch); + if (retval) { + pkiDebug("%s: crypto_cert_release error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + } + free(matchdata); + retval = 0; + +cleanup: + return retval; +} + +static krb5_error_code +obtain_all_cert_matching_data(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + pkinit_cert_matching_data ***all_matching_data) +{ + krb5_error_code retval; + int i, cert_count; + pkinit_cert_iter_handle ih = NULL; + pkinit_cert_handle ch; + pkinit_cert_matching_data **matchdata; + + retval = crypto_cert_get_count(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, &cert_count); + if (retval) { + pkiDebug("%s: crypto_cert_get_count error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + + pkiDebug("%s: crypto_cert_get_count says there are %d certs\n", + __FUNCTION__, cert_count); + + matchdata = calloc((size_t)cert_count + 1, sizeof(*matchdata)); + if (matchdata == NULL) + return ENOMEM; + + retval = crypto_cert_iteration_begin(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, &ih); + if (retval) { + pkiDebug("%s: crypto_cert_iteration_begin returned %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + + for (i = 0; i < cert_count; i++) { + retval = crypto_cert_iteration_next(context, ih, &ch); + if (retval) { + if (retval == PKINIT_ITER_NO_MORE) + pkiDebug("%s: We thought there were %d certs, but " + "crypto_cert_iteration_next stopped after %d?\n", + __FUNCTION__, cert_count, i); + else + pkiDebug("%s: crypto_cert_iteration_next error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + + retval = crypto_cert_get_matching_data(context, ch, &matchdata[i]); + if (retval) { + pkiDebug("%s: crypto_cert_get_matching_data error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + + } + + *all_matching_data = matchdata; + retval = 0; +cleanup: + if (ih != NULL) + crypto_cert_iteration_end(context, ih); + if (retval) { + if (matchdata != NULL) + free_all_cert_matching_data(context, matchdata); + } + pkiDebug("%s: returning %d, certinfo %p\n", + __FUNCTION__, retval, *all_matching_data); + return retval; +} + +krb5_error_code +pkinit_cert_matching(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_principal princ) +{ + + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + int x; + char **rules = NULL; + rule_set *rs = NULL; + int match_found = 0; + pkinit_cert_matching_data **matchdata = NULL; + pkinit_cert_matching_data *the_matching_cert; + + /* If no matching rules, select the default cert and we're done */ + pkinit_libdefault_strings(context, krb5_princ_realm(context, princ), + "pkinit_cert_match", &rules); + if (rules == NULL) { + pkiDebug("%s: no matching rules found in config file\n", __FUNCTION__); + retval = crypto_cert_select_default(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx); + goto cleanup; + } + + /* parse each rule line one at a time and check all the certs against it */ + for (x = 0; rules[x] != NULL; x++) { + pkiDebug("%s: Processing rule '%s'\n", __FUNCTION__, rules[x]); + + /* Free rules from previous time through... */ + if (rs != NULL) { + free_rule_set(context, rs); + rs = NULL; + } + retval = parse_rule_set(context, rules[x], &rs); + if (retval) { + if (retval == EINVAL) { + fprintf(stderr, "Ignoring invalid rule pkinit_cert_match = '%s'\n", rules[x]); + continue; + } + goto cleanup; + } + + /* + * Optimize so that we do not get cert info unless we have + * valid rules to check. Once obtained, keep it around + * until we are done. + */ + if (matchdata == NULL) { + retval = obtain_all_cert_matching_data(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, + &matchdata); + if (retval || matchdata == NULL) { + pkiDebug("%s: Error %d obtaining certificate information\n", + __FUNCTION__, retval); + retval = ENOENT; + goto cleanup; + } + } + + retval = check_all_certs(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, princ, rs, matchdata, + &match_found, &the_matching_cert); + if (retval) { + pkiDebug("%s: Error %d, checking certs against rule '%s'\n", + __FUNCTION__, retval, rules[x]); + goto cleanup; + } + if (match_found) { + pkiDebug("%s: We have an exact match with rule '%s'\n", + __FUNCTION__, rules[x]); + break; + } + } + + if (match_found && the_matching_cert != NULL) { + pkiDebug("%s: Selecting the matching cert!\n", __FUNCTION__); + retval = crypto_cert_select(context, the_matching_cert); + if (retval) { + pkiDebug("%s: crypto_cert_select error %d, %s\n", + __FUNCTION__, retval, error_message(retval)); + goto cleanup; + } + } else { + retval = ENOENT; /* XXX */ + goto cleanup; + } + + retval = 0; +cleanup: + if (rules != NULL) + profile_free_list(rules); + if (rs != NULL) + free_rule_set(context, rs); + if (matchdata != NULL) + free_all_cert_matching_data(context, matchdata); + return retval; +} diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c index 2554b73..63f47a6 100644 --- a/src/plugins/preauth/pkinit/pkinit_srv.c +++ b/src/plugins/preauth/pkinit/pkinit_srv.c @@ -657,6 +657,8 @@ pkinit_server_return_padata(krb5_context context, pkinit_kdc_context plgctx; pkinit_kdc_req_context reqctx; + int fixed_keypack = 0; + *send_pa = NULL; if (padata == NULL || padata->length <= 0 || padata->contents == NULL) return 0; @@ -814,49 +816,66 @@ pkinit_server_return_padata(krb5_context context, goto cleanup; } - switch ((int)padata->pa_type) { - case KRB5_PADATA_PK_AS_REQ: - init_krb5_reply_key_pack(&key_pack); - if (key_pack == NULL) { - retval = ENOMEM; - goto cleanup; - } - /* retrieve checksums for a given enctype of the reply key */ - retval = krb5_c_keyed_checksum_types(context, - encrypting_key->enctype, &num_types, &cksum_types); - if (retval) - goto cleanup; + /* check if PA_TYPE of 132 is present which means the client is + * requesting that a checksum is send back instead of the nonce + */ + for (i = 0; request->padata[i] != NULL; i++) { + pkiDebug("%s: Checking pa_type 0x%08x\n", + __FUNCTION__, request->padata[i]->pa_type); + if (request->padata[i]->pa_type == 132 + /* XXX The asn1 encoding from Apple seems to be incorrect? */ + || request->padata[i]->pa_type == 0xffffff84) + fixed_keypack = 1; + } + pkiDebug("%s: return checksum instead of nonce = %d\n", + __FUNCTION__, fixed_keypack); + + /* if this is an RFC reply or draft9 client requested a checksum + * in the reply instead of the nonce, create an RFC-style keypack + */ + if ((int)padata->pa_type == KRB5_PADATA_PK_AS_REQ || fixed_keypack) { + init_krb5_reply_key_pack(&key_pack); + if (key_pack == NULL) { + retval = ENOMEM; + goto cleanup; + } + /* retrieve checksums for a given enctype of the reply key */ + retval = krb5_c_keyed_checksum_types(context, + encrypting_key->enctype, &num_types, &cksum_types); + if (retval) + goto cleanup; - /* pick the first of acceptable enctypes for the checksum */ - retval = krb5_c_make_checksum(context, cksum_types[0], + /* pick the first of acceptable enctypes for the checksum */ + retval = krb5_c_make_checksum(context, cksum_types[0], encrypting_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, req_pkt, &key_pack->asChecksum); - if (retval) { - pkiDebug("unable to calculate AS REQ checksum\n"); - goto cleanup; - } + if (retval) { + pkiDebug("unable to calculate AS REQ checksum\n"); + goto cleanup; + } #ifdef DEBUG_CKSUM - pkiDebug("calculating checksum on buf size = %d\n", - req_pkt->length); - print_buffer(req_pkt->data, req_pkt->length); - pkiDebug("checksum size = %d\n", - key_pack->asChecksum.length); - print_buffer(key_pack->asChecksum.contents, - key_pack->asChecksum.length); - pkiDebug("encrypting key (%d)\n", encrypting_key->length); - print_buffer(encrypting_key->contents, encrypting_key->length); + pkiDebug("calculating checksum on buf size = %d\n", req_pkt->length); + print_buffer(req_pkt->data, req_pkt->length); + pkiDebug("checksum size = %d\n", key_pack->asChecksum.length); + print_buffer(key_pack->asChecksum.contents, + key_pack->asChecksum.length); + pkiDebug("encrypting key (%d)\n", encrypting_key->length); + print_buffer(encrypting_key->contents, encrypting_key->length); #endif - krb5_copy_keyblock_contents(context, encrypting_key, - &key_pack->replyKey); + krb5_copy_keyblock_contents(context, encrypting_key, + &key_pack->replyKey); - retval = k5int_encode_krb5_reply_key_pack(key_pack, - &encoded_key_pack); - if (retval) { - pkiDebug("failed to encode reply_key_pack\n"); - goto cleanup; - } + retval = k5int_encode_krb5_reply_key_pack(key_pack, + &encoded_key_pack); + if (retval) { + pkiDebug("failed to encode reply_key_pack\n"); + goto cleanup; + } + } + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: rep->choice = choice_pa_pk_as_rep_encKeyPack; retval = cms_envelopeddata_create(context, plgctx->cryptoctx, reqctx->cryptoctx, plgctx->idctx, padata->pa_type, 1, @@ -866,21 +885,26 @@ pkinit_server_return_padata(krb5_context context, break; case KRB5_PADATA_PK_AS_REP_OLD: case KRB5_PADATA_PK_AS_REQ_OLD: - init_krb5_reply_key_pack_draft9(&key_pack9); - if (key_pack9 == NULL) { - retval = ENOMEM; - goto cleanup; - } - key_pack9->nonce = reqctx->rcv_auth_pack9->pkAuthenticator.nonce; - krb5_copy_keyblock_contents(context, encrypting_key, - &key_pack9->replyKey); - - retval = k5int_encode_krb5_reply_key_pack_draft9(key_pack9, + /* if the request is from the broken draft9 client that + * expects back a nonce, create it now + */ + if (!fixed_keypack) { + init_krb5_reply_key_pack_draft9(&key_pack9); + if (key_pack9 == NULL) { + retval = ENOMEM; + goto cleanup; + } + key_pack9->nonce = reqctx->rcv_auth_pack9->pkAuthenticator.nonce; + krb5_copy_keyblock_contents(context, encrypting_key, + &key_pack9->replyKey); + + retval = k5int_encode_krb5_reply_key_pack_draft9(key_pack9, &encoded_key_pack); - if (retval) { - pkiDebug("failed to encode reply_key_pack\n"); - goto cleanup; - } + if (retval) { + pkiDebug("failed to encode reply_key_pack\n"); + goto cleanup; + } + } rep9->choice = choice_pa_pk_as_rep_draft9_encKeyPack; retval = cms_envelopeddata_create(context, plgctx->cryptoctx, @@ -982,7 +1006,10 @@ pkinit_server_return_padata(krb5_context context, case KRB5_PADATA_PK_AS_REQ_OLD: free_krb5_pa_pk_as_req_draft9(&reqp9); free_krb5_pa_pk_as_rep_draft9(&rep9); - free_krb5_reply_key_pack_draft9(&key_pack9); + if (!fixed_keypack) + free_krb5_reply_key_pack_draft9(&key_pack9); + else + free_krb5_reply_key_pack(&key_pack); break; } @@ -1162,7 +1189,8 @@ pkinit_server_plugin_init_realm(krb5_context context, const char *realmname, if (retval) goto errout; - retval = pkinit_identity_initialize(context, plgctx->idopts, plgctx->idctx); + retval = pkinit_identity_initialize(context, plgctx->cryptoctx, NULL, + plgctx->idopts, plgctx->idctx, 0, NULL); if (retval) goto errout; |