diff options
author | Kevin Coffman <kwc@citi.umich.edu> | 2007-08-01 22:09:13 +0000 |
---|---|---|
committer | Kevin Coffman <kwc@citi.umich.edu> | 2007-08-01 22:09:13 +0000 |
commit | 0ef0646069c1d1376aa632a4791ea7e429f5ae9b (patch) | |
tree | 5b9f842dc45a9a14d5698a6f3ff321cea612d7c5 /src/plugins | |
parent | 101446c6f40a13917fd0ba020bc276e82590058d (diff) | |
download | krb5-0ef0646069c1d1376aa632a4791ea7e429f5ae9b.zip krb5-0ef0646069c1d1376aa632a4791ea7e429f5ae9b.tar.gz krb5-0ef0646069c1d1376aa632a4791ea7e429f5ae9b.tar.bz2 |
Add PKINIT support
Pull up PKINIT support onto the trunk.
Changes from the version in branch users/coffman/pkinit are:
- Update the preauth plugin interface version to avoid
conflict with any existing plugins.
- Add a pkcs11.h locally to the pkinit code rather than
depending on opensc being installed.
ticket: new
Target_Version: 1.6.3
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@19745 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/plugins')
20 files changed, 14264 insertions, 28 deletions
diff --git a/src/plugins/preauth/cksum_body/cksum_body_main.c b/src/plugins/preauth/cksum_body/cksum_body_main.c index 8efda23..66b1620 100644 --- a/src/plugins/preauth/cksum_body/cksum_body_main.c +++ b/src/plugins/preauth/cksum_body/cksum_body_main.c @@ -41,7 +41,7 @@ * The AS-REP carries no preauthentication data for this scheme. */ -#ident "$Id$" +#ident "$Id: cksum_body_main.c,v 1.4 2007/01/02 22:33:50 kwc Exp $" #include "autoconf.h" @@ -66,6 +66,11 @@ struct server_stats{ int successes, failures; }; +typedef struct _test_svr_req_ctx { + int value1; + int value2; +} test_svr_req_ctx; + static int client_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) { @@ -89,9 +94,9 @@ client_process(krb5_context kcontext, void *gak_data, krb5_data *salt, krb5_data *s2kparams, krb5_keyblock *as_key, - krb5_pa_data **out_pa_data) + krb5_pa_data ***out_pa_data) { - krb5_pa_data *send_pa; + krb5_pa_data **send_pa; krb5_checksum checksum; krb5_enctype enctype; krb5_cksumtype *cksumtypes; @@ -188,23 +193,31 @@ client_process(krb5_context kcontext, } /* Allocate the preauth data structure. */ - send_pa = malloc(sizeof(krb5_pa_data)); + send_pa = malloc(2 * sizeof(krb5_pa_data *)); if (send_pa == NULL) { krb5_free_checksum_contents(kcontext, &checksum); return ENOMEM; } - send_pa->pa_type = KRB5_PADATA_CKSUM_BODY_REQ; - send_pa->length = 4 + checksum.length; - send_pa->contents = malloc(4 + checksum.length); - if (send_pa->contents == NULL) { + send_pa[1] = NULL; /* Terminate list */ + send_pa[0] = malloc(sizeof(krb5_pa_data)); + if (send_pa[0] == NULL) { + krb5_free_checksum_contents(kcontext, &checksum); + free(send_pa); + return ENOMEM; + } + send_pa[0]->pa_type = KRB5_PADATA_CKSUM_BODY_REQ; + send_pa[0]->length = 4 + checksum.length; + send_pa[0]->contents = malloc(4 + checksum.length); + if (send_pa[0]->contents == NULL) { krb5_free_checksum_contents(kcontext, &checksum); + free(send_pa[0]); free(send_pa); return ENOMEM; } /* Store the checksum. */ - memcpy(send_pa->contents, &cksumtype, 4); - memcpy(send_pa->contents + 4, checksum.contents, checksum.length); + memcpy(send_pa[0]->contents, &cksumtype, 4); + memcpy(send_pa[0]->contents + 4, checksum.contents, checksum.length); *out_pa_data = send_pa; /* Clean up. */ @@ -229,7 +242,7 @@ client_gic_opt(krb5_context kcontext, /* Initialize and tear down the server-side module, and do stat tracking. */ static krb5_error_code -server_init(krb5_context kcontext, void **module_context) +server_init(krb5_context kcontext, void **module_context, const char **realmnames) { struct server_stats *stats; stats = malloc(sizeof(struct server_stats)); @@ -324,7 +337,8 @@ server_verify(krb5_context kcontext, preauth_get_entry_data_proc server_get_entry_data, void *pa_module_context, void **pa_request_context, - krb5_data **e_data) + krb5_data **e_data, + krb5_authdata ***authz_data) { krb5_int32 cksumtype; krb5_checksum checksum; @@ -338,9 +352,14 @@ server_verify(krb5_context kcontext, krb5_error_code status; struct server_stats *stats; krb5_data *test_edata; + test_svr_req_ctx *svr_req_ctx; + krb5_authdata **my_authz_data = NULL; stats = pa_module_context; +#ifdef DEBUG + fprintf(stderr, "cksum_body: server_verify\n"); +#endif /* Verify the preauth data. Start with the checksum type. */ if (data->length < 4) { stats->failures++; @@ -477,6 +496,54 @@ server_verify(krb5_context kcontext, return KRB5KDC_ERR_PREAUTH_FAILED; } + /* + * Return some junk authorization data just to exercise the + * code path handling the returned authorization data. + * + * NOTE that this is NOT VALID authorization data! + */ +#ifdef DEBUG + fprintf(stderr, "cksum_body: doing authorization data!\n"); +#endif +#if 1 /* USE_5000_AD */ +#define AD_ALLOC_SIZE 5000 + /* ad_header consists of a sequence tag (0x30) and length (0x82 0x1384) + * followed by octet string tag (0x04) and length (0x82 0x1380) */ + krb5_octet ad_header[] = {0x30, 0x82, 0x13, 0x84, 0x04, 0x82, 0x13, 0x80}; +#else +#define AD_ALLOC_SIZE 100 + /* ad_header consists of a sequence tag (0x30) and length (0x62) + * followed by octet string tag (0x04) and length (0x60) */ + krb5_octet ad_header[] = {0x30, 0x62, 0x04, 0x60}; +#endif + my_authz_data = malloc(2 * sizeof(*my_authz_data)); + if (my_authz_data != NULL) { + my_authz_data[1] = NULL; + my_authz_data[0] = malloc(sizeof(krb5_authdata)); + if (my_authz_data[0] == NULL) { + free(my_authz_data); + return ENOMEM; + } + my_authz_data[0]->contents = malloc(AD_ALLOC_SIZE); + if (my_authz_data[0]->contents == NULL) { + free(my_authz_data[0]); + free(my_authz_data); + return ENOMEM; + } + memset(my_authz_data[0]->contents, '\0', AD_ALLOC_SIZE); + my_authz_data[0]->magic = KV5M_AUTHDATA; + my_authz_data[0]->ad_type = 1; + my_authz_data[0]->length = AD_ALLOC_SIZE; + memcpy(my_authz_data[0]->contents, ad_header, sizeof(ad_header)); + sprintf(my_authz_data[0]->contents + sizeof(ad_header), + "cksum authorization data: %d bytes worth!\n", AD_ALLOC_SIZE); + *authz_data = my_authz_data; +#ifdef DEBUG + fprintf(stderr, "Returning %d bytes of authorization data\n", + AD_ALLOC_SIZE); +#endif + } + /* Return edata to exercise code that handles edata... */ test_edata = malloc(sizeof(*test_edata)); if (test_edata != NULL) { @@ -490,6 +557,18 @@ server_verify(krb5_context kcontext, } } + /* Return a request context to exercise code that handles it */ + svr_req_ctx = malloc(sizeof(*svr_req_ctx)); + if (svr_req_ctx != NULL) { + svr_req_ctx->value1 = 111111; + svr_req_ctx->value2 = 222222; +#ifdef DEBUG + fprintf(stderr, "server_verify: returning context at %p\n", + svr_req_ctx); +#endif + } + *pa_request_context = svr_req_ctx; + /* Note that preauthentication succeeded. */ enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; stats->successes++; @@ -516,6 +595,37 @@ server_return(krb5_context kcontext, return 0; } +/* Test server request context freeing */ +static krb5_error_code +server_free_reqctx(krb5_context kcontext, + void *pa_module_context, + void **pa_request_context) +{ + test_svr_req_ctx *svr_req_ctx; +#ifdef DEBUG + fprintf(stderr, "server_free_reqctx: entered!\n"); +#endif + if (pa_request_context == NULL) + return 0; + + svr_req_ctx = *pa_request_context; + if (svr_req_ctx == NULL) + return 0; + + if (svr_req_ctx->value1 != 111111 || svr_req_ctx->value2 != 222222) { + fprintf(stderr, "server_free_reqctx: got invalid req context " + "at %p with values %d and %d\n", + svr_req_ctx, svr_req_ctx->value1, svr_req_ctx->value2); + return EINVAL; + } +#ifdef DEBUG + fprintf(stderr, "server_free_reqctx: freeing context at %p\n", svr_req_ctx); +#endif + free(svr_req_ctx); + *pa_request_context = NULL; + return 0; +} + static int server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) { @@ -529,7 +639,7 @@ static krb5_preauthtype supported_server_pa_types[] = { KRB5_PADATA_CKSUM_BODY_REQ, 0, }; -struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0 = { +struct krb5plugin_preauth_client_ftable_v1 preauthentication_client_1 = { "cksum_body", /* name */ &supported_client_pa_types[0], /* pa_type_list */ NULL, /* enctype_list */ @@ -543,7 +653,7 @@ struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0 = { client_gic_opt /* get init creds opt function */ }; -struct krb5plugin_preauth_server_ftable_v0 preauthentication_server_0 = { +struct krb5plugin_preauth_server_ftable_v1 preauthentication_server_1 = { "cksum_body", &supported_server_pa_types[0], server_init, @@ -552,5 +662,5 @@ struct krb5plugin_preauth_server_ftable_v0 preauthentication_server_0 = { server_get_edata, server_verify, server_return, - NULL + server_free_reqctx }; diff --git a/src/plugins/preauth/pkinit/Makefile.in b/src/plugins/preauth/pkinit/Makefile.in new file mode 100644 index 0000000..7542e3c --- /dev/null +++ b/src/plugins/preauth/pkinit/Makefile.in @@ -0,0 +1,109 @@ +thisconfigdir=../../.. +myfulldir=plugins/preauth/pkinit +mydir=plugins/preauth/pkinit +BUILDTOP=$(REL)..$(S)..$(S).. +KRB5_RUN_ENV = @KRB5_RUN_ENV@ +KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ; +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) +MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR) +DEFS=@DEFS@ + +LOCALINCLUDES = -I../../../include/krb5 -I. + +LIBBASE=pkinit +LIBMAJOR=0 +LIBMINOR=0 +SO_EXT=.so +RELDIR=../plugins/preauth/pkinit +# Depends on libk5crypto and libkrb5 +SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(TOPLIBD)/libkrb5$(SHLIBEXT) +LIBS+= -lcrypto +SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto -ldl $(SUPPORT_LIB) $(LIBS) + +SHLIB_DIRS=-L$(TOPLIBD) +SHLIB_RDIRS=$(KRB5_LIBDIR) +STOBJLISTS=OBJS.ST +STLIBOBJS= \ + pkinit_accessor.o \ + pkinit_srv.o \ + pkinit_lib.o \ + pkinit_clnt.o \ + pkinit_profile.o \ + pkinit_identity.o \ + pkinit_matching.o \ + pkinit_crypto_openssl.o + +SRCS= \ + $(srcdir)/pkinit_accessor.c \ + $(srcdir)/pkinit_srv.c \ + $(srcdir)/pkinit_lib.c \ + $(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) +install-unix:: install-libs +clean-unix:: clean-libs clean-libobjs + +clean:: + $(RM) lib$(LIBBASE)$(SO_EXT) + +@libnover_frag@ +@libobj_frag@ + +# +++ Dependency line eater +++ +# +# Makefile dependencies follow. This must be the last section in +# the Makefile.in file +# +pkinit_accessor.so pkinit_accessor.po $(OUTPRE)pkinit_accessor.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(SRCTOP)/include/k5-err.h $(SRCTOP)/include/k5-int-pkinit.h \ + $(SRCTOP)/include/k5-int.h $(SRCTOP)/include/k5-platform.h \ + $(SRCTOP)/include/k5-plugin.h $(SRCTOP)/include/k5-thread.h \ + $(SRCTOP)/include/krb5.h $(SRCTOP)/include/krb5/locate_plugin.h \ + $(SRCTOP)/include/krb5/preauth_plugin.h $(SRCTOP)/include/port-sockets.h \ + $(SRCTOP)/include/socket-utils.h pkinit_accessor.c \ + pkinit_accessor.h +pkinit_srv.so pkinit_srv.po $(OUTPRE)pkinit_srv.$(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_srv.c +pkinit_lib.so pkinit_lib.po $(OUTPRE)pkinit_lib.$(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_lib.c +pkinit_clnt.so pkinit_clnt.po $(OUTPRE)pkinit_clnt.$(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_clnt.c pkinit_crypto.h +pkinit_profile.so pkinit_profile.po $(OUTPRE)pkinit_profile.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(SRCTOP)/include/k5-err.h $(SRCTOP)/include/k5-int-pkinit.h \ + $(SRCTOP)/include/k5-int.h $(SRCTOP)/include/k5-platform.h \ + $(SRCTOP)/include/k5-plugin.h $(SRCTOP)/include/k5-thread.h \ + $(SRCTOP)/include/krb5.h $(SRCTOP)/include/krb5/locate_plugin.h \ + $(SRCTOP)/include/krb5/preauth_plugin.h $(SRCTOP)/include/port-sockets.h \ + $(SRCTOP)/include/socket-utils.h pkinit.h pkinit_accessor.h \ + pkinit_crypto.h pkinit_profile.c +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 \ + pkinit.h pkinit_accessor.h pkinit_crypto.h pkinit_crypto_openssl.c \ + pkinit_crypto_openssl.h diff --git a/src/plugins/preauth/pkinit/README b/src/plugins/preauth/pkinit/README new file mode 100644 index 0000000..90522a7 --- /dev/null +++ b/src/plugins/preauth/pkinit/README @@ -0,0 +1,263 @@ +Building with pkinit enabled: +============================= +To build the code you will need OpenSSL headers. To build +with smartcard support, OpenSC is required. The code can +currently be built w/o smartcard support by defining +WITHOUT_PKCS11 in the CFLAGS. + +Although you will need OpenSC to build the code, at run time you just need a +pkcs11 module. It may be possible to build without having OpenSC installed +by changing the path to the pkcs11 include file and the name of the default +module. We have not tried this. + +Running with pkinit enabled: +============================= +In the following descriptions, options specified with "DIR:" +are assumed to point to an OpenSSL-style hashed CA directory +where each CA cert is stored in a file "<hash of CA cert>.0". +We assume that a CRL directory (pkinit_revoke) will contain CRL files +"<hash of CA cert>.r0". We encourage that users have such +infrastructure but we will also allow files that contain +certificates (or crls) in that directory that are not named +in this style. Basically, for a given directory, the code +will read each file in the directory and if the file contains +a certificate (or crl), we will include it. + +Configuration for a pkinit-enabled KDC: +--------------------------------------- +The following pkinit-specific configuration parameters can be specified +in kdc.conf in either the [kdcdefaults] stanza or the appropriate +realm entry: + +"pkinit_identity" + Specifies where to find the KDC's certificate and private key. + It is REQUIRED, and can be defined only once. It takes the form: + + FILE:<cert-file-name>[,<key-file-name>] + + If <key-file-name> is not specified, the private key is assumed + to be located in the same file with the certificate. + +"pkinit_anchors" + Specifies where the KDC will find trusted root CA certficates + to verify client certificates it receives. This parameter is + REQUIRED and may be specified multiple times. It can take the + forms: + + FILE:<ca-bundle-file-name> + DIR:<openssl-style-hashed-directory> + +"pkinit_pool" + Specifies where the KDC will find other (intermediate) CA + certificates it can use to build trust paths to the trusted + roots defined in "pkinit_anchors", while verifying client + certificates. This parameter is OPTIONAL and may be specified + multiple times. It can take the same forms as "pkinit_anchors". + +"pkinit_revoke" + Specifies where CRL information can be found to be used when + verifying client certificates. This parameter is OPTIONAL and + may by specified multiple times. It can take the same forms + as "pkinit_anchors". XXX???XXX + +"pkinit_dh_min_bits" + Specifies the minimum number of bits the KDC is willing to accept + for a client's Diffie-Hellman key. This is an OPTIONAL integer + value. The default is 1024. + + +Configuration for pkinit-enabled client: +---------------------------------------- +The following pkinit-specific configuration parameters can be specified +in krb5.conf in the [libdefaults] stanza. They may be specified as +"global" libdefaults or specified by realm within the [libdefaults] +stanza. + +i.e. + [libdefaults] + pkinit_anchors = DIR:/etc/ssl/trusted-anchors + pkinit_win2k = false + + EXAMPLE.COM = { + pkinit_win2k = true + pkinit_win2k_require_binding = true + pkinit_anchors = FILE:/etc/ssl/example.com.cas + } + +"pkinit_identity" + See the discussion for this option in the KDC description above. + For the client, this is OPTIONAL. The client can use either + of the following to specify its certificate and private key + locations: + + FILE:<cert-file-name>[,<key-file-name>] + + PKCS11:[module_name=]<module-name>[:slotid=<slot-id>] + [:token=<token-label>][:certid=<cert-id>] + [:certlabel=<cert-label>] + +"pkinit_anchors" + See the discussion for this option in the KDC description above. + For the client, this option is considered OPTIONAL, however it + is needed in practice to verify a KDC's certificate. + +"pkinit_pool" + See the discussion for these options in the KDC descriptions above. + +"pkinit_revoke" + See the discussion for these options in the KDC descriptions above. + +"pkinit_win2k" + This BOOLEAN option specifies whether the target realm is assumed + to support only the "old" version of the protocol. The default + is false. + +"pkinit_win2k_require_binding" + If this BOOLEAN option is set to true, it expects that the target + KDC is patched to return a reply with a checksum rather than a + nonce. The default is false. + +"pkinit_require_eku" + This BOOLEAN specifies whether the client insists that the KDC + certificate contain the appropriate Extended Key Usage value. + The default is true. + +"pkinit_require_krbtgt_otherName" + This BOOLEAN specifies whether the client insists that the KDC + certificate contain the appropriate SubjectAlternativeName + value. The default is true. + +"pkinit_require_hostname_match" + This BOOLEAN specifies whether the client insists that the + hostname within the SubjectAlternativeName must match the + hostname the client believes it is talking to. The default + is false. + + +Command-line options for kinit: +------------------------------- +Command-line options may be passed via the -X option to kinit. +The following values are currently accepted: + +X509_user_identity=<value> + Where <value> has the same format as the "pkinit_identity" + config option described above. + +X509_anchors=<value> + Where <value> has the same format as the "pkinit_anchors" + config option described above. + +flag_RSA_PROTOCOL + This boolean specifies that the RSA protocol (rather than + the Diffie-Hellman protocol) should be used while authenticating + with the KDC. + + +For a pkinit-enabled client using smartcards: +--------------------------------------------- +1. Install a pkcs11 module (smartcard enabled or otherwise). + We use OpenSC but any module that implements pkcs11 should work. + There are some instructions for setting up OpenSC at + http://www.citi.umich.edu/projects/pkinit/smartcard_setup.html +2. set the location of your trusted cas either by setting the + appropriate configuration file parameter or by specifying + "-X X509_anchors=<value>" on the command line. +3. Select a pkcs11 module. The default is opensc-pkcs11.so. To use a + different one, use the "module_name=" option (see below). +4. Select a pkcs11 slot/token. If you only have one with a token available, + that one will be used. Otherwise use the "slotid=" option. Or you can + specify a token label with the "token=" option and pkinit will attempt to + locate that token. +5. Select a client certificate to use. You can specify a certificate ID + with "certid=" or a certificate label with "certlabel=". If multiple + certs fit the selection criteria, the first one with the appropriate + capabilities will be used. +6. Options can be set either via the "pkcs11_identity" krb5.conf + parameter or via the -X parameter to kinit. + These two examples should produce the same result: + + pkinit_identity=PKCS11:module_name=/usr/local/VendorX/lib/libpkcs11.so:slotid=1:certid=4 + + kinit -X X509_user_identity=PKCS11:module_name=/usr/local/VendorX/lib/libpkcs11.so:slotid=1:certid=4 + + +For a pkinit-enabled client using the filesystem for Cert and Key: +------------------------------------------------------------------ +1. verify that the proper krb5.conf options are set +2. Specify the certificate and private key locations either via the + pkinit_identity config option, or via the "-X X509_user_identity=" + command line option. + These two examples should produce the same result: + + pkinit_identity=FILE:/home/bubba/ssl/foo.crt,/home/bubba/ssl/foo.key + + kinit -X X509_user_identity=FILE:/home/bubba/ssl/foo.crt,/home/bubba/ssl/foo.key + + +Testing +======= +We have tested the client code against our server, Heimdal server, +and Windows 2003 servers, but not yet against a Vista/Longhorn server. + +We have not yet been able to test a Windows client against our server. + +Most of our smartcard testing has been with Cryptoflex. + +Known Issues +============ +- The client principal must currently have the REQUIRES_PREAUTH attribute + set to cause the use of pkinit +- Some error reporting is incomplete + + +Information about using CRLs +============================ +N.B. The following describes a "pkinit_require_crl_checking" option +which currently doesn't actually exist. The current behavior is +as if this option were always set to false. + +We assume that the user will acquire needed CRLs and place them +in a local directory (using OpenSSL style) or a single file. During +verification of a certificate for each CA, the code looks for a CRL +issued by that CA. If a match is found for the certificate in a CRL, +verification fails. If the certificate being verified is not listed +in a CRL, or there is no CRL present for its issuing CA, and +pkinit_require_crl_checking is false, then verification succeeds. +However, if pkinit_require_crl_checking is true and there is no +CRL for the issuing CA, then verification fails. + +Basically, pkinit_require_crl_checking should be set to true if the +policy is such that up-to-date CRLs must be present for every CA. + + +=============== +NOT IMPLEMENTED +=============== +The following KDC options allowed by Heimdal are NOT CURRENTLY IMPLEMENTED: + +"pkinit_kdc_ocsp" + Specifies where the KDC will find information to use the Online + Certificate Status Protocol while verifying client certificates. + This parameter is OPTIONAL and there is no default. + +"pkinit_mappings_file" + Specifies where the KDC will find information to use to map the + DN information in a certificate to a KDC principal. This would + be used when the client certificate does not contain the pkinit-SAN + information defined by RFC 4556. + This parameter is OPTIONAL and there is no default. + +"pkinit_allow_proxy_certificate" + Specifies whether the KDC should accept proxy client certificates + for authentication. This is an OPTIONAL boolean parameter. The + default is false. + +"pkinit_principal_in_certificate" + Specifies whether the KDC should obtain the client principal name + from the SubjectAlternativeName in the certificate presented + for authentication. This is an OPTIONAL boolean parameter. The + default is true. + +pkinit_identity=PKCS11:[module_name=]<module-name>[:slotid=<slot-id>] + [:token=<token-label>][:certid=<cert-id>] + [:certlabel=<cert-label>] diff --git a/src/plugins/preauth/pkinit/README.developers b/src/plugins/preauth/pkinit/README.developers new file mode 100644 index 0000000..e095327 --- /dev/null +++ b/src/plugins/preauth/pkinit/README.developers @@ -0,0 +1,18 @@ +Experimental features: +1. If you want trustedCertifiers to be sent by the client, then set +X509_CA_BUNDLE to a ca-bundle file. +2. If you want to make our KDC act like a draft9 KDC, then modify pkinit_src.c +file. there is an "#if 0" for "supported_server_pa_types". if you change "if 0" +to "if 1", then the kdc will become draft9-only KDC. +3. If you like more debugging output, add "-DDEBUG" to CFLAGS and recompile +the code. +4. If you are debugging ASN1 encoding, add "-DDEBUG_ASN1" to CFLAGS and +recompile the code. After running, you'll get DER encoded structures stored +in /tmp. For example, /tmp/client_as_req will contains DER encoding of the +pkinit part of the AS-REQ. +5. Prior to having config options that manage EKU/SAN/CRL checking, you can +modify pkinit_lib.c in function pkinit_lib_init(), set + plgctx->require_eku = 1 -- will require presence of EKU in certs + plgctx->require_san = 1 -- will require presence of SAN in KDC's cert + plgctx->require_crl_checking = 1 -- will require presence of CRLs to + verify every certificate diff --git a/src/plugins/preauth/pkinit/configure.in b/src/plugins/preauth/pkinit/configure.in new file mode 100644 index 0000000..5819289 --- /dev/null +++ b/src/plugins/preauth/pkinit/configure.in @@ -0,0 +1,19 @@ +K5_AC_INIT(configure.in) +enable_shared=yes +build_dynobj=yes +CONFIG_RULES +AC_CHECK_HEADERS(unistd.h) +AC_TYPE_MODE_T +AC_TYPE_OFF_T + +AC_CHECK_FUNCS() + +# XXX This is incorrect, but should cause -lcrypto to be included by default +AC_CHECK_LIB(crypto, PKCS7_get_signer_info) + +KRB5_RUN_FLAGS +dnl The following is for check... +KRB5_BUILD_PROGRAM +KRB5_BUILD_LIBOBJS +KRB5_BUILD_LIBRARY +V5_AC_OUTPUT_MAKEFILE diff --git a/src/plugins/preauth/pkinit/pkcs11.h b/src/plugins/preauth/pkinit/pkcs11.h new file mode 100644 index 0000000..7d18f23 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkcs11.h @@ -0,0 +1,1357 @@ +/* pkcs11.h + Copyright 2006 g10 Code GmbH + Copyright 2006 Andreas Jellinghaus + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. */ + +/* Please submit changes back to the Scute project at + http://www.scute.org/ (or send them to marcus@g10code.com), so that + they can be picked up by other projects from there as well. */ + +/* This file is a modified implementation of the PKCS #11 standard by + RSA Security Inc. It is mostly a drop-in replacement, with the + following change: + + This header file does not require any macro definitions by the user + (like CK_DEFINE_FUNCTION etc). In fact, it defines those macros + for you (if useful, some are missing, let me know if you need + more). + + There is an additional API available that does comply better to the + GNU coding standard. It can be switched on by defining + CRYPTOKI_GNU before including this header file. For this, the + following changes are made to the specification: + + All structure types are changed to a "struct ck_foo" where CK_FOO + is the type name in PKCS #11. + + All non-structure types are changed to ck_foo_t where CK_FOO is the + lowercase version of the type name in PKCS #11. The basic types + (CK_ULONG et al.) are removed without substitute. + + All members of structures are modified in the following way: Type + indication prefixes are removed, and underscore characters are + inserted before words. Then the result is lowercased. + + Note that function names are still in the original case, as they + need for ABI compatibility. + + CK_FALSE, CK_TRUE and NULL_PTR are removed without substitute. Use + <stdbool.h>. + + If CRYPTOKI_COMPAT is defined before including this header file, + then none of the API changes above take place, and the API is the + one defined by the PKCS #11 standard. */ + +#ifndef PKCS11_H +#define PKCS11_H 1 + +#if defined(__cplusplus) +extern "C" { +#endif + + +/* The version of cryptoki we implement. The revision is changed with + each modification of this file. If you do not use the "official" + version of this file, please consider deleting the revision macro + (you may use a macro with a different name to keep track of your + versions). */ +#define CRYPTOKI_VERSION_MAJOR 2 +#define CRYPTOKI_VERSION_MINOR 20 +#define CRYPTOKI_VERSION_REVISION 6 + + +/* Compatibility interface is default, unless CRYPTOKI_GNU is + given. */ +#ifndef CRYPTOKI_GNU +#ifndef CRYPTOKI_COMPAT +#define CRYPTOKI_COMPAT 1 +#endif +#endif + +/* System dependencies. */ + +#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) + +/* There is a matching pop below. */ +#pragma pack(push, cryptoki, 1) + +#ifdef CRYPTOKI_EXPORTS +#define CK_SPEC __declspec(dllexport) +#else +#define CK_SPEC __declspec(dllimport) +#endif + +#else + +#define CK_SPEC + +#endif + + +#ifdef CRYPTOKI_COMPAT + /* If we are in compatibility mode, switch all exposed names to the + PKCS #11 variant. There are corresponding #undefs below. */ + +#define ck_flags_t CK_FLAGS +#define ck_version _CK_VERSION + +#define ck_info _CK_INFO +#define cryptoki_version cryptokiVersion +#define manufacturer_id manufacturerID +#define library_description libraryDescription +#define library_version libraryVersion + +#define ck_notification_t CK_NOTIFICATION +#define ck_slot_id_t CK_SLOT_ID + +#define ck_slot_info _CK_SLOT_INFO +#define slot_description slotDescription +#define hardware_version hardwareVersion +#define firmware_version firmwareVersion + +#define ck_token_info _CK_TOKEN_INFO +#define serial_number serialNumber +#define max_session_count ulMaxSessionCount +#define session_count ulSessionCount +#define max_rw_session_count ulMaxRwSessionCount +#define rw_session_count ulRwSessionCount +#define max_pin_len ulMaxPinLen +#define min_pin_len ulMinPinLen +#define total_public_memory ulTotalPublicMemory +#define free_public_memory ulFreePublicMemory +#define total_private_memory ulTotalPrivateMemory +#define free_private_memory ulFreePrivateMemory +#define utc_time utcTime + +#define ck_session_handle_t CK_SESSION_HANDLE +#define ck_user_type_t CK_USER_TYPE +#define ck_state_t CK_STATE + +#define ck_session_info _CK_SESSION_INFO +#define slot_id slotID +#define device_error ulDeviceError + +#define ck_object_handle_t CK_OBJECT_HANDLE +#define ck_object_class_t CK_OBJECT_CLASS +#define ck_hw_feature_type_t CK_HW_FEATURE_TYPE +#define ck_key_type_t CK_KEY_TYPE +#define ck_certificate_type_t CK_CERTIFICATE_TYPE +#define ck_attribute_type_t CK_ATTRIBUTE_TYPE + +#define ck_attribute _CK_ATTRIBUTE +#define value pValue +#define value_len ulValueLen + +#define ck_date _CK_DATE + +#define ck_mechanism_type_t CK_MECHANISM_TYPE + +#define ck_mechanism _CK_MECHANISM +#define parameter pParameter +#define parameter_len ulParameterLen + +#define ck_mechanism_info _CK_MECHANISM_INFO +#define min_key_size ulMinKeySize +#define max_key_size ulMaxKeySize + +#define ck_rv_t CK_RV +#define ck_notify_t CK_NOTIFY + +#define ck_function_list _CK_FUNCTION_LIST + +#define ck_createmutex_t CK_CREATEMUTEX +#define ck_destroymutex_t CK_DESTROYMUTEX +#define ck_lockmutex_t CK_LOCKMUTEX +#define ck_unlockmutex_t CK_UNLOCKMUTEX + +#define ck_c_initialize_args _CK_C_INITIALIZE_ARGS +#define create_mutex CreateMutex +#define destroy_mutex DestroyMutex +#define lock_mutex LockMutex +#define unlock_mutex UnlockMutex +#define reserved pReserved + +#endif /* CRYPTOKI_COMPAT */ + + + +typedef unsigned long ck_flags_t; + +struct ck_version +{ + unsigned char major; + unsigned char minor; +}; + + +struct ck_info +{ + struct ck_version cryptoki_version; + unsigned char manufacturer_id[32]; + ck_flags_t flags; + unsigned char library_description[32]; + struct ck_version library_version; +}; + + +typedef unsigned long ck_notification_t; + +#define CKN_SURRENDER (0) + + +typedef unsigned long ck_slot_id_t; + + +struct ck_slot_info +{ + unsigned char slot_description[64]; + unsigned char manufacturer_id[32]; + ck_flags_t flags; + struct ck_version hardware_version; + struct ck_version firmware_version; +}; + + +#define CKF_TOKEN_PRESENT (1 << 0) +#define CKF_REMOVABLE_DEVICE (1 << 1) +#define CKF_HW_SLOT (1 << 2) +#define CKF_ARRAY_ATTRIBUTE (1 << 30) + + +struct ck_token_info +{ + unsigned char label[32]; + unsigned char manufacturer_id[32]; + unsigned char model[16]; + unsigned char serial_number[16]; + ck_flags_t flags; + unsigned long max_session_count; + unsigned long session_count; + unsigned long max_rw_session_count; + unsigned long rw_session_count; + unsigned long max_pin_len; + unsigned long min_pin_len; + unsigned long total_public_memory; + unsigned long free_public_memory; + unsigned long total_private_memory; + unsigned long free_private_memory; + struct ck_version hardware_version; + struct ck_version firmware_version; + unsigned char utc_time[16]; +}; + + +#define CKF_RNG (1 << 0) +#define CKF_WRITE_PROTECTED (1 << 1) +#define CKF_LOGIN_REQUIRED (1 << 2) +#define CKF_USER_PIN_INITIALIZED (1 << 3) +#define CKF_RESTORE_KEY_NOT_NEEDED (1 << 5) +#define CKF_CLOCK_ON_TOKEN (1 << 6) +#define CKF_PROTECTED_AUTHENTICATION_PATH (1 << 8) +#define CKF_DUAL_CRYPTO_OPERATIONS (1 << 9) +#define CKF_TOKEN_INITIALIZED (1 << 10) +#define CKF_SECONDARY_AUTHENTICATION (1 << 11) +#define CKF_USER_PIN_COUNT_LOW (1 << 16) +#define CKF_USER_PIN_FINAL_TRY (1 << 17) +#define CKF_USER_PIN_LOCKED (1 << 18) +#define CKF_USER_PIN_TO_BE_CHANGED (1 << 19) +#define CKF_SO_PIN_COUNT_LOW (1 << 20) +#define CKF_SO_PIN_FINAL_TRY (1 << 21) +#define CKF_SO_PIN_LOCKED (1 << 22) +#define CKF_SO_PIN_TO_BE_CHANGED (1 << 23) + +#define CK_UNAVAILABLE_INFORMATION ((unsigned long) -1) +#define CK_EFFECTIVELY_INFINITE (0) + + +typedef unsigned long ck_session_handle_t; + +#define CK_INVALID_HANDLE (0) + + +typedef unsigned long ck_user_type_t; + +#define CKU_SO (0) +#define CKU_USER (1) +#define CKU_CONTEXT_SPECIFIC (2) + + +typedef unsigned long ck_state_t; + +#define CKS_RO_PUBLIC_SESSION (0) +#define CKS_RO_USER_FUNCTIONS (1) +#define CKS_RW_PUBLIC_SESSION (2) +#define CKS_RW_USER_FUNCTIONS (3) +#define CKS_RW_SO_FUNCTIONS (4) + + +struct ck_session_info +{ + ck_slot_id_t slot_id; + ck_state_t state; + ck_flags_t flags; + unsigned long device_error; +}; + +#define CKF_RW_SESSION (1 << 1) +#define CKF_SERIAL_SESSION (1 << 2) + + +typedef unsigned long ck_object_handle_t; + + +typedef unsigned long ck_object_class_t; + +#define CKO_DATA (0) +#define CKO_CERTIFICATE (1) +#define CKO_PUBLIC_KEY (2) +#define CKO_PRIVATE_KEY (3) +#define CKO_SECRET_KEY (4) +#define CKO_HW_FEATURE (5) +#define CKO_DOMAIN_PARAMETERS (6) +#define CKO_MECHANISM (7) +#define CKO_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +typedef unsigned long ck_hw_feature_type_t; + +#define CKH_MONOTONIC_COUNTER (1) +#define CKH_CLOCK (2) +#define CKH_USER_INTERFACE (3) +#define CKH_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +typedef unsigned long ck_key_type_t; + +#define CKK_RSA (0) +#define CKK_DSA (1) +#define CKK_DH (2) +#define CKK_ECDSA (3) +#define CKK_EC (3) +#define CKK_X9_42_DH (4) +#define CKK_KEA (5) +#define CKK_GENERIC_SECRET (0x10) +#define CKK_RC2 (0x11) +#define CKK_RC4 (0x12) +#define CKK_DES (0x13) +#define CKK_DES2 (0x14) +#define CKK_DES3 (0x15) +#define CKK_CAST (0x16) +#define CKK_CAST3 (0x17) +#define CKK_CAST128 (0x18) +#define CKK_RC5 (0x19) +#define CKK_IDEA (0x1a) +#define CKK_SKIPJACK (0x1b) +#define CKK_BATON (0x1c) +#define CKK_JUNIPER (0x1d) +#define CKK_CDMF (0x1e) +#define CKK_AES (0x1f) +#define CKK_BLOWFISH (0x20) +#define CKK_TWOFISH (0x21) +#define CKK_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +typedef unsigned long ck_certificate_type_t; + +#define CKC_X_509 (0) +#define CKC_X_509_ATTR_CERT (1) +#define CKC_WTLS (2) +#define CKC_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +typedef unsigned long ck_attribute_type_t; + +#define CKA_CLASS (0) +#define CKA_TOKEN (1) +#define CKA_PRIVATE (2) +#define CKA_LABEL (3) +#define CKA_APPLICATION (0x10) +#define CKA_VALUE (0x11) +#define CKA_OBJECT_ID (0x12) +#define CKA_CERTIFICATE_TYPE (0x80) +#define CKA_ISSUER (0x81) +#define CKA_SERIAL_NUMBER (0x82) +#define CKA_AC_ISSUER (0x83) +#define CKA_OWNER (0x84) +#define CKA_ATTR_TYPES (0x85) +#define CKA_TRUSTED (0x86) +#define CKA_CERTIFICATE_CATEGORY (0x87) +#define CKA_JAVA_MIDP_SECURITY_DOMAIN (0x88) +#define CKA_URL (0x89) +#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY (0x8a) +#define CKA_HASH_OF_ISSUER_PUBLIC_KEY (0x8b) +#define CKA_CHECK_VALUE (0x90) +#define CKA_KEY_TYPE (0x100) +#define CKA_SUBJECT (0x101) +#define CKA_ID (0x102) +#define CKA_SENSITIVE (0x103) +#define CKA_ENCRYPT (0x104) +#define CKA_DECRYPT (0x105) +#define CKA_WRAP (0x106) +#define CKA_UNWRAP (0x107) +#define CKA_SIGN (0x108) +#define CKA_SIGN_RECOVER (0x109) +#define CKA_VERIFY (0x10a) +#define CKA_VERIFY_RECOVER (0x10b) +#define CKA_DERIVE (0x10c) +#define CKA_START_DATE (0x110) +#define CKA_END_DATE (0x111) +#define CKA_MODULUS (0x120) +#define CKA_MODULUS_BITS (0x121) +#define CKA_PUBLIC_EXPONENT (0x122) +#define CKA_PRIVATE_EXPONENT (0x123) +#define CKA_PRIME_1 (0x124) +#define CKA_PRIME_2 (0x125) +#define CKA_EXPONENT_1 (0x126) +#define CKA_EXPONENT_2 (0x127) +#define CKA_COEFFICIENT (0x128) +#define CKA_PRIME (0x130) +#define CKA_SUBPRIME (0x131) +#define CKA_BASE (0x132) +#define CKA_PRIME_BITS (0x133) +#define CKA_SUB_PRIME_BITS (0x134) +#define CKA_VALUE_BITS (0x160) +#define CKA_VALUE_LEN (0x161) +#define CKA_EXTRACTABLE (0x162) +#define CKA_LOCAL (0x163) +#define CKA_NEVER_EXTRACTABLE (0x164) +#define CKA_ALWAYS_SENSITIVE (0x165) +#define CKA_KEY_GEN_MECHANISM (0x166) +#define CKA_MODIFIABLE (0x170) +#define CKA_ECDSA_PARAMS (0x180) +#define CKA_EC_PARAMS (0x180) +#define CKA_EC_POINT (0x181) +#define CKA_SECONDARY_AUTH (0x200) +#define CKA_AUTH_PIN_FLAGS (0x201) +#define CKA_ALWAYS_AUTHENTICATE (0x202) +#define CKA_WRAP_WITH_TRUSTED (0x210) +#define CKA_HW_FEATURE_TYPE (0x300) +#define CKA_RESET_ON_INIT (0x301) +#define CKA_HAS_RESET (0x302) +#define CKA_PIXEL_X (0x400) +#define CKA_PIXEL_Y (0x401) +#define CKA_RESOLUTION (0x402) +#define CKA_CHAR_ROWS (0x403) +#define CKA_CHAR_COLUMNS (0x404) +#define CKA_COLOR (0x405) +#define CKA_BITS_PER_PIXEL (0x406) +#define CKA_CHAR_SETS (0x480) +#define CKA_ENCODING_METHODS (0x481) +#define CKA_MIME_TYPES (0x482) +#define CKA_MECHANISM_TYPE (0x500) +#define CKA_REQUIRED_CMS_ATTRIBUTES (0x501) +#define CKA_DEFAULT_CMS_ATTRIBUTES (0x502) +#define CKA_SUPPORTED_CMS_ATTRIBUTES (0x503) +#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x211) +#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x212) +#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE | 0x600) +#define CKA_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +struct ck_attribute +{ + ck_attribute_type_t type; + void *value; + unsigned long value_len; +}; + + +struct ck_date +{ + unsigned char year[4]; + unsigned char month[2]; + unsigned char day[2]; +}; + + +typedef unsigned long ck_mechanism_type_t; + +#define CKM_RSA_PKCS_KEY_PAIR_GEN (0) +#define CKM_RSA_PKCS (1) +#define CKM_RSA_9796 (2) +#define CKM_RSA_X_509 (3) +#define CKM_MD2_RSA_PKCS (4) +#define CKM_MD5_RSA_PKCS (5) +#define CKM_SHA1_RSA_PKCS (6) +#define CKM_RIPEMD128_RSA_PKCS (7) +#define CKM_RIPEMD160_RSA_PKCS (8) +#define CKM_RSA_PKCS_OAEP (9) +#define CKM_RSA_X9_31_KEY_PAIR_GEN (0xa) +#define CKM_RSA_X9_31 (0xb) +#define CKM_SHA1_RSA_X9_31 (0xc) +#define CKM_RSA_PKCS_PSS (0xd) +#define CKM_SHA1_RSA_PKCS_PSS (0xe) +#define CKM_DSA_KEY_PAIR_GEN (0x10) +#define CKM_DSA (0x11) +#define CKM_DSA_SHA1 (0x12) +#define CKM_DH_PKCS_KEY_PAIR_GEN (0x20) +#define CKM_DH_PKCS_DERIVE (0x21) +#define CKM_X9_42_DH_KEY_PAIR_GEN (0x30) +#define CKM_X9_42_DH_DERIVE (0x31) +#define CKM_X9_42_DH_HYBRID_DERIVE (0x32) +#define CKM_X9_42_MQV_DERIVE (0x33) +#define CKM_SHA256_RSA_PKCS (0x40) +#define CKM_SHA384_RSA_PKCS (0x41) +#define CKM_SHA512_RSA_PKCS (0x42) +#define CKM_SHA256_RSA_PKCS_PSS (0x43) +#define CKM_SHA384_RSA_PKCS_PSS (0x44) +#define CKM_SHA512_RSA_PKCS_PSS (0x45) +#define CKM_RC2_KEY_GEN (0x100) +#define CKM_RC2_ECB (0x101) +#define CKM_RC2_CBC (0x102) +#define CKM_RC2_MAC (0x103) +#define CKM_RC2_MAC_GENERAL (0x104) +#define CKM_RC2_CBC_PAD (0x105) +#define CKM_RC4_KEY_GEN (0x110) +#define CKM_RC4 (0x111) +#define CKM_DES_KEY_GEN (0x120) +#define CKM_DES_ECB (0x121) +#define CKM_DES_CBC (0x122) +#define CKM_DES_MAC (0x123) +#define CKM_DES_MAC_GENERAL (0x124) +#define CKM_DES_CBC_PAD (0x125) +#define CKM_DES2_KEY_GEN (0x130) +#define CKM_DES3_KEY_GEN (0x131) +#define CKM_DES3_ECB (0x132) +#define CKM_DES3_CBC (0x133) +#define CKM_DES3_MAC (0x134) +#define CKM_DES3_MAC_GENERAL (0x135) +#define CKM_DES3_CBC_PAD (0x136) +#define CKM_CDMF_KEY_GEN (0x140) +#define CKM_CDMF_ECB (0x141) +#define CKM_CDMF_CBC (0x142) +#define CKM_CDMF_MAC (0x143) +#define CKM_CDMF_MAC_GENERAL (0x144) +#define CKM_CDMF_CBC_PAD (0x145) +#define CKM_MD2 (0x200) +#define CKM_MD2_HMAC (0x201) +#define CKM_MD2_HMAC_GENERAL (0x202) +#define CKM_MD5 (0x210) +#define CKM_MD5_HMAC (0x211) +#define CKM_MD5_HMAC_GENERAL (0x212) +#define CKM_SHA_1 (0x220) +#define CKM_SHA_1_HMAC (0x221) +#define CKM_SHA_1_HMAC_GENERAL (0x222) +#define CKM_RIPEMD128 (0x230) +#define CKM_RIPEMD128_HMAC (0x231) +#define CKM_RIPEMD128_HMAC_GENERAL (0x232) +#define CKM_RIPEMD160 (0x240) +#define CKM_RIPEMD160_HMAC (0x241) +#define CKM_RIPEMD160_HMAC_GENERAL (0x242) +#define CKM_SHA256 (0x250) +#define CKM_SHA256_HMAC (0x251) +#define CKM_SHA256_HMAC_GENERAL (0x252) +#define CKM_SHA384 (0x260) +#define CKM_SHA384_HMAC (0x261) +#define CKM_SHA384_HMAC_GENERAL (0x262) +#define CKM_SHA512 (0x270) +#define CKM_SHA512_HMAC (0x271) +#define CKM_SHA512_HMAC_GENERAL (0x272) +#define CKM_CAST_KEY_GEN (0x300) +#define CKM_CAST_ECB (0x301) +#define CKM_CAST_CBC (0x302) +#define CKM_CAST_MAC (0x303) +#define CKM_CAST_MAC_GENERAL (0x304) +#define CKM_CAST_CBC_PAD (0x305) +#define CKM_CAST3_KEY_GEN (0x310) +#define CKM_CAST3_ECB (0x311) +#define CKM_CAST3_CBC (0x312) +#define CKM_CAST3_MAC (0x313) +#define CKM_CAST3_MAC_GENERAL (0x314) +#define CKM_CAST3_CBC_PAD (0x315) +#define CKM_CAST5_KEY_GEN (0x320) +#define CKM_CAST128_KEY_GEN (0x320) +#define CKM_CAST5_ECB (0x321) +#define CKM_CAST128_ECB (0x321) +#define CKM_CAST5_CBC (0x322) +#define CKM_CAST128_CBC (0x322) +#define CKM_CAST5_MAC (0x323) +#define CKM_CAST128_MAC (0x323) +#define CKM_CAST5_MAC_GENERAL (0x324) +#define CKM_CAST128_MAC_GENERAL (0x324) +#define CKM_CAST5_CBC_PAD (0x325) +#define CKM_CAST128_CBC_PAD (0x325) +#define CKM_RC5_KEY_GEN (0x330) +#define CKM_RC5_ECB (0x331) +#define CKM_RC5_CBC (0x332) +#define CKM_RC5_MAC (0x333) +#define CKM_RC5_MAC_GENERAL (0x334) +#define CKM_RC5_CBC_PAD (0x335) +#define CKM_IDEA_KEY_GEN (0x340) +#define CKM_IDEA_ECB (0x341) +#define CKM_IDEA_CBC (0x342) +#define CKM_IDEA_MAC (0x343) +#define CKM_IDEA_MAC_GENERAL (0x344) +#define CKM_IDEA_CBC_PAD (0x345) +#define CKM_GENERIC_SECRET_KEY_GEN (0x350) +#define CKM_CONCATENATE_BASE_AND_KEY (0x360) +#define CKM_CONCATENATE_BASE_AND_DATA (0x362) +#define CKM_CONCATENATE_DATA_AND_BASE (0x363) +#define CKM_XOR_BASE_AND_DATA (0x364) +#define CKM_EXTRACT_KEY_FROM_KEY (0x365) +#define CKM_SSL3_PRE_MASTER_KEY_GEN (0x370) +#define CKM_SSL3_MASTER_KEY_DERIVE (0x371) +#define CKM_SSL3_KEY_AND_MAC_DERIVE (0x372) +#define CKM_SSL3_MASTER_KEY_DERIVE_DH (0x373) +#define CKM_TLS_PRE_MASTER_KEY_GEN (0x374) +#define CKM_TLS_MASTER_KEY_DERIVE (0x375) +#define CKM_TLS_KEY_AND_MAC_DERIVE (0x376) +#define CKM_TLS_MASTER_KEY_DERIVE_DH (0x377) +#define CKM_SSL3_MD5_MAC (0x380) +#define CKM_SSL3_SHA1_MAC (0x381) +#define CKM_MD5_KEY_DERIVATION (0x390) +#define CKM_MD2_KEY_DERIVATION (0x391) +#define CKM_SHA1_KEY_DERIVATION (0x392) +#define CKM_PBE_MD2_DES_CBC (0x3a0) +#define CKM_PBE_MD5_DES_CBC (0x3a1) +#define CKM_PBE_MD5_CAST_CBC (0x3a2) +#define CKM_PBE_MD5_CAST3_CBC (0x3a3) +#define CKM_PBE_MD5_CAST5_CBC (0x3a4) +#define CKM_PBE_MD5_CAST128_CBC (0x3a4) +#define CKM_PBE_SHA1_CAST5_CBC (0x3a5) +#define CKM_PBE_SHA1_CAST128_CBC (0x3a5) +#define CKM_PBE_SHA1_RC4_128 (0x3a6) +#define CKM_PBE_SHA1_RC4_40 (0x3a7) +#define CKM_PBE_SHA1_DES3_EDE_CBC (0x3a8) +#define CKM_PBE_SHA1_DES2_EDE_CBC (0x3a9) +#define CKM_PBE_SHA1_RC2_128_CBC (0x3aa) +#define CKM_PBE_SHA1_RC2_40_CBC (0x3ab) +#define CKM_PKCS5_PBKD2 (0x3b0) +#define CKM_PBA_SHA1_WITH_SHA1_HMAC (0x3c0) +#define CKM_KEY_WRAP_LYNKS (0x400) +#define CKM_KEY_WRAP_SET_OAEP (0x401) +#define CKM_SKIPJACK_KEY_GEN (0x1000) +#define CKM_SKIPJACK_ECB64 (0x1001) +#define CKM_SKIPJACK_CBC64 (0x1002) +#define CKM_SKIPJACK_OFB64 (0x1003) +#define CKM_SKIPJACK_CFB64 (0x1004) +#define CKM_SKIPJACK_CFB32 (0x1005) +#define CKM_SKIPJACK_CFB16 (0x1006) +#define CKM_SKIPJACK_CFB8 (0x1007) +#define CKM_SKIPJACK_WRAP (0x1008) +#define CKM_SKIPJACK_PRIVATE_WRAP (0x1009) +#define CKM_SKIPJACK_RELAYX (0x100a) +#define CKM_KEA_KEY_PAIR_GEN (0x1010) +#define CKM_KEA_KEY_DERIVE (0x1011) +#define CKM_FORTEZZA_TIMESTAMP (0x1020) +#define CKM_BATON_KEY_GEN (0x1030) +#define CKM_BATON_ECB128 (0x1031) +#define CKM_BATON_ECB96 (0x1032) +#define CKM_BATON_CBC128 (0x1033) +#define CKM_BATON_COUNTER (0x1034) +#define CKM_BATON_SHUFFLE (0x1035) +#define CKM_BATON_WRAP (0x1036) +#define CKM_ECDSA_KEY_PAIR_GEN (0x1040) +#define CKM_EC_KEY_PAIR_GEN (0x1040) +#define CKM_ECDSA (0x1041) +#define CKM_ECDSA_SHA1 (0x1042) +#define CKM_ECDH1_DERIVE (0x1050) +#define CKM_ECDH1_COFACTOR_DERIVE (0x1051) +#define CKM_ECMQV_DERIVE (0x1052) +#define CKM_JUNIPER_KEY_GEN (0x1060) +#define CKM_JUNIPER_ECB128 (0x1061) +#define CKM_JUNIPER_CBC128 (0x1062) +#define CKM_JUNIPER_COUNTER (0x1063) +#define CKM_JUNIPER_SHUFFLE (0x1064) +#define CKM_JUNIPER_WRAP (0x1065) +#define CKM_FASTHASH (0x1070) +#define CKM_AES_KEY_GEN (0x1080) +#define CKM_AES_ECB (0x1081) +#define CKM_AES_CBC (0x1082) +#define CKM_AES_MAC (0x1083) +#define CKM_AES_MAC_GENERAL (0x1084) +#define CKM_AES_CBC_PAD (0x1085) +#define CKM_DSA_PARAMETER_GEN (0x2000) +#define CKM_DH_PKCS_PARAMETER_GEN (0x2001) +#define CKM_X9_42_DH_PARAMETER_GEN (0x2002) +#define CKM_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + +struct ck_mechanism +{ + ck_mechanism_type_t mechanism; + void *parameter; + unsigned long parameter_len; +}; + + +struct ck_mechanism_info +{ + unsigned long min_key_size; + unsigned long max_key_size; + ck_flags_t flags; +}; + +#define CKF_HW (1 << 0) +#define CKF_ENCRYPT (1 << 8) +#define CKF_DECRYPT (1 << 9) +#define CKF_DIGEST (1 << 10) +#define CKF_SIGN (1 << 11) +#define CKF_SIGN_RECOVER (1 << 12) +#define CKF_VERIFY (1 << 13) +#define CKF_VERIFY_RECOVER (1 << 14) +#define CKF_GENERATE (1 << 15) +#define CKF_GENERATE_KEY_PAIR (1 << 16) +#define CKF_WRAP (1 << 17) +#define CKF_UNWRAP (1 << 18) +#define CKF_DERIVE (1 << 19) +#define CKF_EXTENSION ((unsigned long) (1 << 31)) + + +/* Flags for C_WaitForSlotEvent. */ +#define CKF_DONT_BLOCK (1) + + +typedef unsigned long ck_rv_t; + + +typedef ck_rv_t (*ck_notify_t) (ck_session_handle_t session, + ck_notification_t event, void *application); + +/* Forward reference. */ +struct ck_function_list; + +#define _CK_DECLARE_FUNCTION(name, args) \ +typedef ck_rv_t (*CK_ ## name) args; \ +ck_rv_t CK_SPEC name args + +_CK_DECLARE_FUNCTION (C_Initialize, (void *init_args)); +_CK_DECLARE_FUNCTION (C_Finalize, (void *reserved)); +_CK_DECLARE_FUNCTION (C_GetInfo, (struct ck_info *info)); +_CK_DECLARE_FUNCTION (C_GetFunctionList, + (struct ck_function_list **function_list)); + +_CK_DECLARE_FUNCTION (C_GetSlotList, + (unsigned char token_present, ck_slot_id_t *slot_list, + unsigned long *count)); +_CK_DECLARE_FUNCTION (C_GetSlotInfo, + (ck_slot_id_t slot_id, struct ck_slot_info *info)); +_CK_DECLARE_FUNCTION (C_GetTokenInfo, + (ck_slot_id_t slot_id, struct ck_token_info *info)); +_CK_DECLARE_FUNCTION (C_WaitForSlotEvent, + (ck_flags_t flags, ck_slot_id_t *slot, void *reserved)); +_CK_DECLARE_FUNCTION (C_GetMechanismList, + (ck_slot_id_t slot_id, + ck_mechanism_type_t *mechanism_list, + unsigned long *count)); +_CK_DECLARE_FUNCTION (C_GetMechanismInfo, + (ck_slot_id_t slot_id, ck_mechanism_type_t type, + struct ck_mechanism_info *info)); +_CK_DECLARE_FUNCTION (C_InitToken, + (ck_slot_id_t slot_id, unsigned char *pin, + unsigned long pin_len, unsigned char *label)); +_CK_DECLARE_FUNCTION (C_InitPIN, + (ck_session_handle_t session, unsigned char *pin, + unsigned long pin_len)); +_CK_DECLARE_FUNCTION (C_SetPIN, + (ck_session_handle_t session, unsigned char *old_pin, + unsigned long old_len, unsigned char *new_pin, + unsigned long new_len)); + +_CK_DECLARE_FUNCTION (C_OpenSession, + (ck_slot_id_t slot_id, ck_flags_t flags, + void *application, ck_notify_t notify, + ck_session_handle_t *session)); +_CK_DECLARE_FUNCTION (C_CloseSession, (ck_session_handle_t session)); +_CK_DECLARE_FUNCTION (C_CloseAllSessions, (ck_slot_id_t slot_id)); +_CK_DECLARE_FUNCTION (C_GetSessionInfo, + (ck_session_handle_t session, + struct ck_session_info *info)); +_CK_DECLARE_FUNCTION (C_GetOperationState, + (ck_session_handle_t session, + unsigned char *operation_state, + unsigned long *operation_state_len)); +_CK_DECLARE_FUNCTION (C_SetOperationState, + (ck_session_handle_t session, + unsigned char *operation_state, + unsigned long operation_state_len, + ck_object_handle_t encryption_key, + ck_object_handle_t authentiation_key)); +_CK_DECLARE_FUNCTION (C_Login, + (ck_session_handle_t session, ck_user_type_t user_type, + unsigned char *pin, unsigned long pin_len)); +_CK_DECLARE_FUNCTION (C_Logout, (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION (C_CreateObject, + (ck_session_handle_t session, + struct ck_attribute *templ, + unsigned long count, ck_object_handle_t *object)); +_CK_DECLARE_FUNCTION (C_CopyObject, + (ck_session_handle_t session, ck_object_handle_t object, + struct ck_attribute *templ, unsigned long count, + ck_object_handle_t *new_object)); +_CK_DECLARE_FUNCTION (C_DestroyObject, + (ck_session_handle_t session, + ck_object_handle_t object)); +_CK_DECLARE_FUNCTION (C_GetObjectSize, + (ck_session_handle_t session, + ck_object_handle_t object, + unsigned long *size)); +_CK_DECLARE_FUNCTION (C_GetAttributeValue, + (ck_session_handle_t session, + ck_object_handle_t object, + struct ck_attribute *templ, + unsigned long count)); +_CK_DECLARE_FUNCTION (C_SetAttributeValue, + (ck_session_handle_t session, + ck_object_handle_t object, + struct ck_attribute *templ, + unsigned long count)); +_CK_DECLARE_FUNCTION (C_FindObjectsInit, + (ck_session_handle_t session, + struct ck_attribute *templ, + unsigned long count)); +_CK_DECLARE_FUNCTION (C_FindObjects, + (ck_session_handle_t session, + ck_object_handle_t *object, + unsigned long max_object_count, + unsigned long *object_count)); +_CK_DECLARE_FUNCTION (C_FindObjectsFinal, + (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION (C_EncryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_Encrypt, + (ck_session_handle_t session, + unsigned char *data, unsigned long data_len, + unsigned char *encrypted_data, + unsigned long *encrypted_data_len)); +_CK_DECLARE_FUNCTION (C_EncryptUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len, + unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION (C_EncryptFinal, + (ck_session_handle_t session, + unsigned char *last_encrypted_part, + unsigned long *last_encrypted_part_len)); + +_CK_DECLARE_FUNCTION (C_DecryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_Decrypt, + (ck_session_handle_t session, + unsigned char *encrypted_data, + unsigned long encrypted_data_len, + unsigned char *data, unsigned long *data_len)); +_CK_DECLARE_FUNCTION (C_DecryptUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, + unsigned char *part, unsigned long *part_len)); +_CK_DECLARE_FUNCTION (C_DecryptFinal, + (ck_session_handle_t session, + unsigned char *last_part, + unsigned long *last_part_len)); + +_CK_DECLARE_FUNCTION (C_DigestInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism)); +_CK_DECLARE_FUNCTION (C_Digest, + (ck_session_handle_t session, + unsigned char *data, unsigned long data_len, + unsigned char *digest, + unsigned long *digest_len)); +_CK_DECLARE_FUNCTION (C_DigestUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len)); +_CK_DECLARE_FUNCTION (C_DigestKey, + (ck_session_handle_t session, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_DigestFinal, + (ck_session_handle_t session, + unsigned char *digest, + unsigned long *digest_len)); + +_CK_DECLARE_FUNCTION (C_SignInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_Sign, + (ck_session_handle_t session, + unsigned char *data, unsigned long data_len, + unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION (C_SignUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len)); +_CK_DECLARE_FUNCTION (C_SignFinal, + (ck_session_handle_t session, + unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION (C_SignRecoverInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_SignRecover, + (ck_session_handle_t session, + unsigned char *data, unsigned long data_len, + unsigned char *signature, + unsigned long *signature_len)); + +_CK_DECLARE_FUNCTION (C_VerifyInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_Verify, + (ck_session_handle_t session, + unsigned char *data, unsigned long data_len, + unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION (C_VerifyUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len)); +_CK_DECLARE_FUNCTION (C_VerifyFinal, + (ck_session_handle_t session, + unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION (C_VerifyRecoverInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t key)); +_CK_DECLARE_FUNCTION (C_VerifyRecover, + (ck_session_handle_t session, + unsigned char *signature, + unsigned long signature_len, + unsigned char *data, + unsigned long *data_len)); + +_CK_DECLARE_FUNCTION (C_DigestEncryptUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len, + unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION (C_DecryptDigestUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, + unsigned char *part, + unsigned long *part_len)); +_CK_DECLARE_FUNCTION (C_SignEncryptUpdate, + (ck_session_handle_t session, + unsigned char *part, unsigned long part_len, + unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION (C_DecryptVerifyUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, + unsigned char *part, + unsigned long *part_len)); + +_CK_DECLARE_FUNCTION (C_GenerateKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + struct ck_attribute *templ, + unsigned long count, + ck_object_handle_t *key)); +_CK_DECLARE_FUNCTION (C_GenerateKeyPair, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + struct ck_attribute *public_key_template, + unsigned long public_key_attribute_count, + struct ck_attribute *private_key_template, + unsigned long private_key_attribute_count, + ck_object_handle_t *public_key, + ck_object_handle_t *private_key)); +_CK_DECLARE_FUNCTION (C_WrapKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t wrapping_key, + ck_object_handle_t key, + unsigned char *wrapped_key, + unsigned long *wrapped_key_len)); +_CK_DECLARE_FUNCTION (C_UnwrapKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t unwrapping_key, + unsigned char *wrapped_key, + unsigned long wrapped_key_len, + struct ck_attribute *templ, + unsigned long attribute_count, + ck_object_handle_t *key)); +_CK_DECLARE_FUNCTION (C_DeriveKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t base_key, + struct ck_attribute *templ, + unsigned long attribute_count, + ck_object_handle_t *key)); + +_CK_DECLARE_FUNCTION (C_SeedRandom, + (ck_session_handle_t session, unsigned char *seed, + unsigned long seed_len)); +_CK_DECLARE_FUNCTION (C_GenerateRandom, + (ck_session_handle_t session, + unsigned char *random_data, + unsigned long random_len)); + +_CK_DECLARE_FUNCTION (C_GetFunctionStatus, (ck_session_handle_t session)); +_CK_DECLARE_FUNCTION (C_CancelFunction, (ck_session_handle_t session)); + + +struct ck_function_list +{ + struct ck_version version; + CK_C_Initialize C_Initialize; + CK_C_Finalize C_Finalize; + CK_C_GetInfo C_GetInfo; + CK_C_GetFunctionList C_GetFunctionList; + CK_C_GetSlotList C_GetSlotList; + CK_C_GetSlotInfo C_GetSlotInfo; + CK_C_GetTokenInfo C_GetTokenInfo; + CK_C_GetMechanismList C_GetMechanismList; + CK_C_GetMechanismInfo C_GetMechanismInfo; + CK_C_InitToken C_InitToken; + CK_C_InitPIN C_InitPIN; + CK_C_SetPIN C_SetPIN; + CK_C_OpenSession C_OpenSession; + CK_C_CloseSession C_CloseSession; + CK_C_CloseAllSessions C_CloseAllSessions; + CK_C_GetSessionInfo C_GetSessionInfo; + CK_C_GetOperationState C_GetOperationState; + CK_C_SetOperationState C_SetOperationState; + CK_C_Login C_Login; + CK_C_Logout C_Logout; + CK_C_CreateObject C_CreateObject; + CK_C_CopyObject C_CopyObject; + CK_C_DestroyObject C_DestroyObject; + CK_C_GetObjectSize C_GetObjectSize; + CK_C_GetAttributeValue C_GetAttributeValue; + CK_C_SetAttributeValue C_SetAttributeValue; + CK_C_FindObjectsInit C_FindObjectsInit; + CK_C_FindObjects C_FindObjects; + CK_C_FindObjectsFinal C_FindObjectsFinal; + CK_C_EncryptInit C_EncryptInit; + CK_C_Encrypt C_Encrypt; + CK_C_EncryptUpdate C_EncryptUpdate; + CK_C_EncryptFinal C_EncryptFinal; + CK_C_DecryptInit C_DecryptInit; + CK_C_Decrypt C_Decrypt; + CK_C_DecryptUpdate C_DecryptUpdate; + CK_C_DecryptFinal C_DecryptFinal; + CK_C_DigestInit C_DigestInit; + CK_C_Digest C_Digest; + CK_C_DigestUpdate C_DigestUpdate; + CK_C_DigestKey C_DigestKey; + CK_C_DigestFinal C_DigestFinal; + CK_C_SignInit C_SignInit; + CK_C_Sign C_Sign; + CK_C_SignUpdate C_SignUpdate; + CK_C_SignFinal C_SignFinal; + CK_C_SignRecoverInit C_SignRecoverInit; + CK_C_SignRecover C_SignRecover; + CK_C_VerifyInit C_VerifyInit; + CK_C_Verify C_Verify; + CK_C_VerifyUpdate C_VerifyUpdate; + CK_C_VerifyFinal C_VerifyFinal; + CK_C_VerifyRecoverInit C_VerifyRecoverInit; + CK_C_VerifyRecover C_VerifyRecover; + CK_C_DigestEncryptUpdate C_DigestEncryptUpdate; + CK_C_DecryptDigestUpdate C_DecryptDigestUpdate; + CK_C_SignEncryptUpdate C_SignEncryptUpdate; + CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate; + CK_C_GenerateKey C_GenerateKey; + CK_C_GenerateKeyPair C_GenerateKeyPair; + CK_C_WrapKey C_WrapKey; + CK_C_UnwrapKey C_UnwrapKey; + CK_C_DeriveKey C_DeriveKey; + CK_C_SeedRandom C_SeedRandom; + CK_C_GenerateRandom C_GenerateRandom; + CK_C_GetFunctionStatus C_GetFunctionStatus; + CK_C_CancelFunction C_CancelFunction; + CK_C_WaitForSlotEvent C_WaitForSlotEvent; +}; + + +typedef ck_rv_t (*ck_createmutex_t) (void **mutex); +typedef ck_rv_t (*ck_destroymutex_t) (void *mutex); +typedef ck_rv_t (*ck_lockmutex_t) (void *mutex); +typedef ck_rv_t (*ck_unlockmutex_t) (void *mutex); + + +struct ck_c_initialize_args +{ + ck_createmutex_t create_mutex; + ck_destroymutex_t destroy_mutex; + ck_lockmutex_t lock_mutex; + ck_unlockmutex_t unlock_mutex; + ck_flags_t flags; + void *reserved; +}; + + +#define CKF_LIBRARY_CANT_CREATE_OS_THREADS (1 << 0) +#define CKF_OS_LOCKING_OK (1 << 1) + +#define CKR_OK (0) +#define CKR_CANCEL (1) +#define CKR_HOST_MEMORY (2) +#define CKR_SLOT_ID_INVALID (3) +#define CKR_GENERAL_ERROR (5) +#define CKR_FUNCTION_FAILED (6) +#define CKR_ARGUMENTS_BAD (7) +#define CKR_NO_EVENT (8) +#define CKR_NEED_TO_CREATE_THREADS (9) +#define CKR_CANT_LOCK (0xa) +#define CKR_ATTRIBUTE_READ_ONLY (0x10) +#define CKR_ATTRIBUTE_SENSITIVE (0x11) +#define CKR_ATTRIBUTE_TYPE_INVALID (0x12) +#define CKR_ATTRIBUTE_VALUE_INVALID (0x13) +#define CKR_DATA_INVALID (0x20) +#define CKR_DATA_LEN_RANGE (0x21) +#define CKR_DEVICE_ERROR (0x30) +#define CKR_DEVICE_MEMORY (0x31) +#define CKR_DEVICE_REMOVED (0x32) +#define CKR_ENCRYPTED_DATA_INVALID (0x40) +#define CKR_ENCRYPTED_DATA_LEN_RANGE (0x41) +#define CKR_FUNCTION_CANCELED (0x50) +#define CKR_FUNCTION_NOT_PARALLEL (0x51) +#define CKR_FUNCTION_NOT_SUPPORTED (0x54) +#define CKR_KEY_HANDLE_INVALID (0x60) +#define CKR_KEY_SIZE_RANGE (0x62) +#define CKR_KEY_TYPE_INCONSISTENT (0x63) +#define CKR_KEY_NOT_NEEDED (0x64) +#define CKR_KEY_CHANGED (0x65) +#define CKR_KEY_NEEDED (0x66) +#define CKR_KEY_INDIGESTIBLE (0x67) +#define CKR_KEY_FUNCTION_NOT_PERMITTED (0x68) +#define CKR_KEY_NOT_WRAPPABLE (0x69) +#define CKR_KEY_UNEXTRACTABLE (0x6a) +#define CKR_MECHANISM_INVALID (0x70) +#define CKR_MECHANISM_PARAM_INVALID (0x71) +#define CKR_OBJECT_HANDLE_INVALID (0x82) +#define CKR_OPERATION_ACTIVE (0x90) +#define CKR_OPERATION_NOT_INITIALIZED (0x91) +#define CKR_PIN_INCORRECT (0xa0) +#define CKR_PIN_INVALID (0xa1) +#define CKR_PIN_LEN_RANGE (0xa2) +#define CKR_PIN_EXPIRED (0xa3) +#define CKR_PIN_LOCKED (0xa4) +#define CKR_SESSION_CLOSED (0xb0) +#define CKR_SESSION_COUNT (0xb1) +#define CKR_SESSION_HANDLE_INVALID (0xb3) +#define CKR_SESSION_PARALLEL_NOT_SUPPORTED (0xb4) +#define CKR_SESSION_READ_ONLY (0xb5) +#define CKR_SESSION_EXISTS (0xb6) +#define CKR_SESSION_READ_ONLY_EXISTS (0xb7) +#define CKR_SESSION_READ_WRITE_SO_EXISTS (0xb8) +#define CKR_SIGNATURE_INVALID (0xc0) +#define CKR_SIGNATURE_LEN_RANGE (0xc1) +#define CKR_TEMPLATE_INCOMPLETE (0xd0) +#define CKR_TEMPLATE_INCONSISTENT (0xd1) +#define CKR_TOKEN_NOT_PRESENT (0xe0) +#define CKR_TOKEN_NOT_RECOGNIZED (0xe1) +#define CKR_TOKEN_WRITE_PROTECTED (0xe2) +#define CKR_UNWRAPPING_KEY_HANDLE_INVALID (0xf0) +#define CKR_UNWRAPPING_KEY_SIZE_RANGE (0xf1) +#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT (0xf2) +#define CKR_USER_ALREADY_LOGGED_IN (0x100) +#define CKR_USER_NOT_LOGGED_IN (0x101) +#define CKR_USER_PIN_NOT_INITIALIZED (0x102) +#define CKR_USER_TYPE_INVALID (0x103) +#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN (0x104) +#define CKR_USER_TOO_MANY_TYPES (0x105) +#define CKR_WRAPPED_KEY_INVALID (0x110) +#define CKR_WRAPPED_KEY_LEN_RANGE (0x112) +#define CKR_WRAPPING_KEY_HANDLE_INVALID (0x113) +#define CKR_WRAPPING_KEY_SIZE_RANGE (0x114) +#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT (0x115) +#define CKR_RANDOM_SEED_NOT_SUPPORTED (0x120) +#define CKR_RANDOM_NO_RNG (0x121) +#define CKR_DOMAIN_PARAMS_INVALID (0x130) +#define CKR_BUFFER_TOO_SMALL (0x150) +#define CKR_SAVED_STATE_INVALID (0x160) +#define CKR_INFORMATION_SENSITIVE (0x170) +#define CKR_STATE_UNSAVEABLE (0x180) +#define CKR_CRYPTOKI_NOT_INITIALIZED (0x190) +#define CKR_CRYPTOKI_ALREADY_INITIALIZED (0x191) +#define CKR_MUTEX_BAD (0x1a0) +#define CKR_MUTEX_NOT_LOCKED (0x1a1) +#define CKR_FUNCTION_REJECTED (0x200) +#define CKR_VENDOR_DEFINED ((unsigned long) (1 << 31)) + + + +/* Compatibility layer. */ + +#ifdef CRYPTOKI_COMPAT + +#undef CK_DEFINE_FUNCTION +#define CK_DEFINE_FUNCTION(retval, name) retval CK_SPEC name + +/* For NULL. */ +#include <stddef.h> + +typedef unsigned char CK_BYTE; +typedef unsigned char CK_CHAR; +typedef unsigned char CK_UTF8CHAR; +typedef unsigned char CK_BBOOL; +typedef unsigned long int CK_ULONG; +typedef long int CK_LONG; +typedef CK_BYTE *CK_BYTE_PTR; +typedef CK_CHAR *CK_CHAR_PTR; +typedef CK_UTF8CHAR *CK_UTF8CHAR_PTR; +typedef CK_ULONG *CK_ULONG_PTR; +typedef void *CK_VOID_PTR; +typedef void **CK_VOID_PTR_PTR; +#define CK_FALSE 0 +#define CK_TRUE 1 +#ifndef CK_DISABLE_TRUE_FALSE +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#endif + +typedef struct ck_version CK_VERSION; +typedef struct ck_version *CK_VERSION_PTR; + +typedef struct ck_info CK_INFO; +typedef struct ck_info *CK_INFO_PTR; + +typedef ck_slot_id_t *CK_SLOT_ID_PTR; + +typedef struct ck_slot_info CK_SLOT_INFO; +typedef struct ck_slot_info *CK_SLOT_INFO_PTR; + +typedef struct ck_token_info CK_TOKEN_INFO; +typedef struct ck_token_info *CK_TOKEN_INFO_PTR; + +typedef ck_session_handle_t *CK_SESSION_HANDLE_PTR; + +typedef struct ck_session_info CK_SESSION_INFO; +typedef struct ck_session_info *CK_SESSION_INFO_PTR; + +typedef ck_object_handle_t *CK_OBJECT_HANDLE_PTR; + +typedef ck_object_class_t *CK_OBJECT_CLASS_PTR; + +typedef struct ck_attribute CK_ATTRIBUTE; +typedef struct ck_attribute *CK_ATTRIBUTE_PTR; + +typedef struct ck_date CK_DATE; +typedef struct ck_date *CK_DATE_PTR; + +typedef ck_mechanism_type_t *CK_MECHANISM_TYPE_PTR; + +typedef struct ck_mechanism CK_MECHANISM; +typedef struct ck_mechanism *CK_MECHANISM_PTR; + +typedef struct ck_mechanism_info CK_MECHANISM_INFO; +typedef struct ck_mechanism_info *CK_MECHANISM_INFO_PTR; + +typedef struct ck_function_list CK_FUNCTION_LIST; +typedef struct ck_function_list *CK_FUNCTION_LIST_PTR; +typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR; + +typedef struct ck_c_initialize_args CK_C_INITIALIZE_ARGS; +typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR; + +#define NULL_PTR NULL + +/* Delete the helper macros defined at the top of the file. */ +#undef ck_flags_t +#undef ck_version + +#undef ck_info +#undef cryptoki_version +#undef manufacturer_id +#undef library_description +#undef library_version + +#undef ck_notification_t +#undef ck_slot_id_t + +#undef ck_slot_info +#undef slot_description +#undef hardware_version +#undef firmware_version + +#undef ck_token_info +#undef serial_number +#undef max_session_count +#undef session_count +#undef max_rw_session_count +#undef rw_session_count +#undef max_pin_len +#undef min_pin_len +#undef total_public_memory +#undef free_public_memory +#undef total_private_memory +#undef free_private_memory +#undef utc_time + +#undef ck_session_handle_t +#undef ck_user_type_t +#undef ck_state_t + +#undef ck_session_info +#undef slot_id +#undef device_error + +#undef ck_object_handle_t +#undef ck_object_class_t +#undef ck_hw_feature_type_t +#undef ck_key_type_t +#undef ck_certificate_type_t +#undef ck_attribute_type_t + +#undef ck_attribute +#undef value +#undef value_len + +#undef ck_date + +#undef ck_mechanism_type_t + +#undef ck_mechanism +#undef parameter +#undef parameter_len + +#undef ck_mechanism_info +#undef min_key_size +#undef max_key_size + +#undef ck_rv_t +#undef ck_notify_t + +#undef ck_function_list + +#undef ck_createmutex_t +#undef ck_destroymutex_t +#undef ck_lockmutex_t +#undef ck_unlockmutex_t + +#undef ck_c_initialize_args +#undef create_mutex +#undef destroy_mutex +#undef lock_mutex +#undef unlock_mutex +#undef reserved + +#endif /* CRYPTOKI_COMPAT */ + + +/* System dependencies. */ +#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) +#pragma pack(pop, cryptoki) +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* PKCS11_H */ diff --git a/src/plugins/preauth/pkinit/pkinit.exports b/src/plugins/preauth/pkinit/pkinit.exports new file mode 100644 index 0000000..98e96c3 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit.exports @@ -0,0 +1,2 @@ +preauthentication_client_1 +preauthentication_server_1 diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h new file mode 100644 index 0000000..e18962e --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -0,0 +1,354 @@ +/* + * COPYRIGHT (C) 2006,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. + */ + +#ifndef _PKINIT_H +#define _PKINIT_H + +#include <krb5/krb5.h> +#include <krb5/preauth_plugin.h> +#include <k5-int-pkinit.h> +#include <profile.h> +#include "pkinit_accessor.h" + +/* + * It is anticipated that all the special checks currently + * required when talking to a Longhorn server will go away + * by the time it is officially released and all references + * to the longhorn global can be removed and any code + * #ifdef'd with LONGHORN_BETA_COMPAT can be removed. + * And this #define! + */ +#define LONGHORN_BETA_COMPAT 1 +#ifdef LONGHORN_BETA_COMPAT +extern int longhorn; /* XXX Talking to a Longhorn server? */ +#endif + + +#ifndef WITHOUT_PKCS11 +#include "pkcs11.h" + +#define PKCS11_MODNAME "opensc-pkcs11.so" +#define PK_SIGLEN_GUESS 1000 +#define PK_NOSLOT 999999 +#endif + +#define DH_PROTOCOL 1 +#define RSA_PROTOCOL 2 + +#define TD_TRUSTED_CERTIFIERS 104 +#define TD_INVALID_CERTIFICATES 105 +#define TD_DH_PARAMETERS 109 + +#define PKINIT_CTX_MAGIC 0x05551212 +#define PKINIT_REQ_CTX_MAGIC 0xdeadbeef + +#define PKINIT_DEFAULT_DH_MIN_BITS 2048 + +/* Make pkiDebug(fmt,...) print, or not. */ +#ifdef DEBUG +#define pkiDebug printf +#else +/* Still evaluates for side effects. */ +static inline void pkiDebug (const char *fmt, ...) { } +/* This is better if the compiler doesn't inline variadic functions + well, but gcc will warn about "left-hand operand of comma + expression has no effect". Still evaluates for side effects. */ +/* #define pkiDebug (void) */ +#endif + +/* Solaris compiler doesn't grok __FUNCTION__ + * hack for now. Fix all the uses eventually. */ +#define __FUNCTION__ __func__ + +/* Macros to deal with converting between various data types... */ +#define PADATA_TO_KRB5DATA(pad, k5d) \ + (k5d)->length = (pad)->length; (k5d)->data = (char *)(pad)->contents; +#define OCTETDATA_TO_KRB5DATA(octd, k5d) \ + (k5d)->length = (octd)->length; (k5d)->data = (char *)(octd)->data; + +extern const krb5_octet_data dh_oid; + +/* + * notes about crypto contexts: + * + * the basic idea is that there are crypto contexts that live at + * both the plugin level and request level. the identity context (that + * keeps info about your own certs and such) is separate because + * it is needed at different levels for the kdc and and the client. + * (the kdc's identity is at the plugin level, the client's identity + * information could change per-request.) + * the identity context is meant to have the entity's cert, + * a list of trusted and intermediate cas, a list of crls, and any + * pkcs11 information. the req context is meant to have the + * received certificate and the DH related information. the plugin + * context is meant to have global crypto information, i.e., OIDs + * and constant DH parameter information. + */ + +/* + * plugin crypto context should keep plugin common information, + * eg., OIDs, known DHparams + */ +typedef struct _pkinit_plg_crypto_context *pkinit_plg_crypto_context; + +/* + * request crypto context should keep reqyest common information, + * eg., received credentials, DH parameters of this request + */ +typedef struct _pkinit_req_crypto_context *pkinit_req_crypto_context; + +/* + * identity context should keep information about credentials + * for the request, eg., my credentials, trusted ca certs, + * intermediate ca certs, crls, pkcs11 info + */ +typedef struct _pkinit_identity_crypto_context *pkinit_identity_crypto_context; + +/* + * this structure keeps information about the config options + */ +typedef struct _pkinit_plg_opts { + int require_eku; /* require EKU checking (default is true) */ + int accept_secondary_eku;/* accept secondary EKU (default is false) */ + int allow_upn; /* allow UPN-SAN instead of pkinit-SAN */ + int dh_or_rsa; /* selects DH or RSA based pkinit */ + int require_crl_checking; /* require CRL for a CA (default is false) */ + int dh_min_bits; /* minimum DH modulus size allowed */ +} pkinit_plg_opts; + +/* + * this structure keeps options used for a given request + */ +typedef struct _pkinit_req_opts { + int require_eku; + int accept_secondary_eku; + int allow_upn; + int dh_or_rsa; + int require_crl_checking; + int dh_size; /* initial request DH modulus size (default=1024) */ + int require_hostname_match; + int win2k_target; + int win2k_require_cksum; +} pkinit_req_opts; + +/* + * information about identity from config file or command line + */ + +#define PKINIT_ID_OPT_USER_IDENTITY 1 +#define PKINIT_ID_OPT_ANCHOR_CAS 2 +#define PKINIT_ID_OPT_INTERMEDIATE_CAS 3 +#define PKINIT_ID_OPT_CRLS 4 +#define PKINIT_ID_OPT_OCSP 5 +#define PKINIT_ID_OPT_DN_MAPPING 6 /* XXX ? */ + +typedef struct _pkinit_identity_opts { + char *identity; + char **identity_alt; + char **anchors; + char **intermediates; + char **crls; + char *ocsp; + char *dn_mapping_file; + int idtype; + char *cert_filename; + char *key_filename; +#ifndef WITHOUT_PKCS11 + char *p11_module_name; + CK_SLOT_ID slotid; + char *token_label; + char *cert_id_string; + char *cert_label; +#endif +} pkinit_identity_opts; + + +/* + * Client's plugin context + */ +struct _pkinit_context { + int magic; + pkinit_plg_crypto_context cryptoctx; + pkinit_plg_opts *opts; + pkinit_identity_opts *idopts; +}; +typedef struct _pkinit_context *pkinit_context; + +/* + * Client's per-request context + */ +struct _pkinit_req_context { + int magic; + pkinit_req_crypto_context cryptoctx; + pkinit_req_opts *opts; + pkinit_identity_crypto_context idctx; + pkinit_identity_opts *idopts; + krb5_preauthtype pa_type; +}; +typedef struct _pkinit_kdc_context *pkinit_kdc_context; + +/* + * KDC's (per-realm) plugin context + */ +struct _pkinit_kdc_context { + int magic; + pkinit_plg_crypto_context cryptoctx; + pkinit_plg_opts *opts; + pkinit_identity_crypto_context idctx; + pkinit_identity_opts *idopts; + char *realmname; + unsigned int realmname_len; +}; +typedef struct _pkinit_req_context *pkinit_req_context; + +/* + * KDC's per-request context + */ +struct _pkinit_kdc_req_context { + int magic; + pkinit_req_crypto_context cryptoctx; + krb5_auth_pack *rcv_auth_pack; + krb5_auth_pack_draft9 *rcv_auth_pack9; + krb5_preauthtype pa_type; +}; +typedef struct _pkinit_kdc_req_context *pkinit_kdc_req_context; + +/* + * Functions in pkinit_lib.c + */ + +krb5_error_code pkinit_init_req_opts(pkinit_req_opts **); +void pkinit_fini_req_opts(pkinit_req_opts *); + +krb5_error_code pkinit_init_plg_opts(pkinit_plg_opts **); +void pkinit_fini_plg_opts(pkinit_plg_opts *); + +krb5_error_code pkinit_init_identity_opts(pkinit_identity_opts **idopts); +void pkinit_fini_identity_opts(pkinit_identity_opts *idopts); +krb5_error_code pkinit_dup_identity_opts(pkinit_identity_opts *src_opts, + pkinit_identity_opts **dest_opts); + +/* + * Functions in pkinit_identity.c + */ +char * idtype2string(int idtype); +char * catype2string(int catype); + +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 */ + 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 + */ +void init_krb5_pa_pk_as_req(krb5_pa_pk_as_req **in); +void init_krb5_pa_pk_as_req_draft9(krb5_pa_pk_as_req_draft9 **in); +void init_krb5_reply_key_pack(krb5_reply_key_pack **in); +void init_krb5_reply_key_pack_draft9(krb5_reply_key_pack_draft9 **in); + +void init_krb5_auth_pack(krb5_auth_pack **in); +void init_krb5_auth_pack_draft9(krb5_auth_pack_draft9 **in); +void init_krb5_pa_pk_as_rep(krb5_pa_pk_as_rep **in); +void init_krb5_pa_pk_as_rep_draft9(krb5_pa_pk_as_rep_draft9 **in); +void init_krb5_typed_data(krb5_typed_data **in); +void init_krb5_subject_pk_info(krb5_subject_pk_info **in); + +void free_krb5_pa_pk_as_req(krb5_pa_pk_as_req **in); +void free_krb5_pa_pk_as_req_draft9(krb5_pa_pk_as_req_draft9 **in); +void free_krb5_reply_key_pack(krb5_reply_key_pack **in); +void free_krb5_reply_key_pack_draft9(krb5_reply_key_pack_draft9 **in); +void free_krb5_auth_pack(krb5_auth_pack **in); +void free_krb5_auth_pack_draft9(krb5_context, krb5_auth_pack_draft9 **in); +void free_krb5_pa_pk_as_rep(krb5_pa_pk_as_rep **in); +void free_krb5_pa_pk_as_rep_draft9(krb5_pa_pk_as_rep_draft9 **in); +void free_krb5_external_principal_identifier(krb5_external_principal_identifier ***in); +void free_krb5_trusted_ca(krb5_trusted_ca ***in); +void free_krb5_typed_data(krb5_typed_data ***in); +void free_krb5_algorithm_identifiers(krb5_algorithm_identifier ***in); +void free_krb5_algorithm_identifier(krb5_algorithm_identifier *in); +void free_krb5_kdc_dh_key_info(krb5_kdc_dh_key_info **in); +void free_krb5_subject_pk_info(krb5_subject_pk_info **in); +krb5_error_code pkinit_copy_krb5_octet_data(krb5_octet_data *dst, const krb5_octet_data *src); + + +/* + * Functions in pkinit_profile.c + */ +krb5_error_code pkinit_kdcdefault_strings + (krb5_context context, const char *realmname, const char *option, + char ***ret_value); +krb5_error_code pkinit_kdcdefault_string + (krb5_context context, const char *realmname, const char *option, + char **ret_value); +krb5_error_code pkinit_kdcdefault_boolean + (krb5_context context, const char *realmname, const char *option, + int default_value, int *ret_value); +krb5_error_code pkinit_kdcdefault_integer + (krb5_context context, const char *realmname, const char *option, + int default_value, int *ret_value); + + +krb5_error_code pkinit_libdefault_strings + (krb5_context context, const krb5_data *realm, + const char *option, char ***ret_value); +krb5_error_code pkinit_libdefault_string + (krb5_context context, const krb5_data *realm, + const char *option, char **ret_value); +krb5_error_code pkinit_libdefault_boolean + (krb5_context context, const krb5_data *realm, const char *option, + int default_value, int *ret_value); +krb5_error_code pkinit_libdefault_integer + (krb5_context context, const krb5_data *realm, const char *option, + int default_value, int *ret_value); + +/* + * debugging functions + */ +void print_buffer(unsigned char *, unsigned int); +void print_buffer_bin(unsigned char *, unsigned int, char *); + +/* + * Now get crypto function declarations + */ +#include "pkinit_crypto.h" + +#endif /* _PKINIT_H */ diff --git a/src/plugins/preauth/pkinit/pkinit_accessor.c b/src/plugins/preauth/pkinit/pkinit_accessor.c new file mode 100644 index 0000000..e954ca3 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_accessor.c @@ -0,0 +1,118 @@ +/* + * COPYRIGHT (C) 2006,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 <k5-int.h> +#include "pkinit_accessor.h" + +#define DEF_FUNC_PTRS(type) \ +krb5_error_code (*k5int_encode_##type)(const type *, krb5_data **); \ +krb5_error_code (*k5int_decode_##type)(const krb5_data *, type **) + +#define DEF_FUNC_PTRS_ARRAY(type) \ +krb5_error_code (*k5int_encode_##type)(const type **, krb5_data **); \ +krb5_error_code (*k5int_decode_##type)(const krb5_data *, type ***) + +DEF_FUNC_PTRS(krb5_auth_pack); +DEF_FUNC_PTRS(krb5_auth_pack_draft9); +DEF_FUNC_PTRS(krb5_kdc_dh_key_info); +DEF_FUNC_PTRS(krb5_pa_pk_as_rep); +DEF_FUNC_PTRS(krb5_pa_pk_as_rep_draft9); +DEF_FUNC_PTRS(krb5_pa_pk_as_req); +DEF_FUNC_PTRS(krb5_pa_pk_as_req_draft9); +DEF_FUNC_PTRS(krb5_reply_key_pack); +DEF_FUNC_PTRS(krb5_reply_key_pack_draft9); +DEF_FUNC_PTRS_ARRAY(krb5_typed_data); + +/* special cases... */ +krb5_error_code (*k5int_decode_krb5_principal_name) + (const krb5_data *, krb5_principal_data **); + +krb5_error_code (*k5int_encode_krb5_td_dh_parameters) + (const krb5_algorithm_identifier **, krb5_data **code); +krb5_error_code (*k5int_decode_krb5_td_dh_parameters) + (const krb5_data *, krb5_algorithm_identifier ***); + +krb5_error_code (*k5int_encode_krb5_td_trusted_certifiers) + (const krb5_external_principal_identifier **, krb5_data **code); +krb5_error_code (*k5int_decode_krb5_td_trusted_certifiers) + (const krb5_data *, krb5_external_principal_identifier ***); + +krb5_error_code (*k5int_decode_krb5_as_req) + (const krb5_data *output, krb5_kdc_req **rep); +krb5_error_code (*k5int_encode_krb5_kdc_req_body) + (const krb5_kdc_req *rep, krb5_data **code); +void KRB5_CALLCONV (*k5int_krb5_free_kdc_req) + (krb5_context, krb5_kdc_req * ); +void (*k5int_set_prompt_types) + (krb5_context, krb5_prompt_type *); +krb5_error_code (*k5int_encode_krb5_authdata_elt) + (const krb5_authdata *rep, krb5_data **code); + + + +/* + * Grab internal function pointers from the krb5int_accessor + * structure and make them available + */ +krb5_error_code +pkinit_accessor_init(void) +{ + krb5_error_code retval; + krb5int_access k5int; + + retval = krb5int_accessor(&k5int, KRB5INT_ACCESS_VERSION); + if (retval) + return retval; +#define SET_PTRS(type) \ +k5int_encode_##type = k5int.encode_##type; \ +k5int_decode_##type = k5int.decode_##type; + + SET_PTRS(krb5_auth_pack); + SET_PTRS(krb5_auth_pack_draft9); + SET_PTRS(krb5_kdc_dh_key_info); + SET_PTRS(krb5_pa_pk_as_rep); + SET_PTRS(krb5_pa_pk_as_rep_draft9); + SET_PTRS(krb5_pa_pk_as_req); + SET_PTRS(krb5_pa_pk_as_req_draft9); + SET_PTRS(krb5_reply_key_pack); + SET_PTRS(krb5_reply_key_pack_draft9); + SET_PTRS(krb5_td_dh_parameters); + SET_PTRS(krb5_td_trusted_certifiers); + SET_PTRS(krb5_typed_data); + + /* special cases... */ + k5int_decode_krb5_principal_name = k5int.decode_krb5_principal_name; + k5int_decode_krb5_as_req = k5int.decode_krb5_as_req; + k5int_encode_krb5_kdc_req_body = k5int.encode_krb5_kdc_req_body; + k5int_krb5_free_kdc_req = k5int.krb5_free_kdc_req; + k5int_set_prompt_types = k5int.krb5int_set_prompt_types; + k5int_encode_krb5_authdata_elt = k5int.encode_krb5_authdata_elt; + return 0; +} diff --git a/src/plugins/preauth/pkinit/pkinit_accessor.h b/src/plugins/preauth/pkinit/pkinit_accessor.h new file mode 100644 index 0000000..ba82533 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_accessor.h @@ -0,0 +1,83 @@ +/* + * COPYRIGHT (C) 2006,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. + */ + +#ifndef _PKINIT_ACCESSOR_H +#define _PKINIT_ACCESSOR_H + +/* + * Function prototypes + */ +krb5_error_code pkinit_accessor_init(void); + +#define DEF_EXT_FUNC_PTRS(type) \ +extern krb5_error_code (*k5int_encode_##type)(const type *, krb5_data **); \ +extern krb5_error_code (*k5int_decode_##type)(const krb5_data *, type **) + +#define DEF_EXT_FUNC_PTRS_ARRAY(type) \ +extern krb5_error_code (*k5int_encode_##type)(const type **, krb5_data **); \ +extern krb5_error_code (*k5int_decode_##type)(const krb5_data *, type ***) + +DEF_EXT_FUNC_PTRS(krb5_auth_pack); +DEF_EXT_FUNC_PTRS(krb5_auth_pack_draft9); +DEF_EXT_FUNC_PTRS(krb5_kdc_dh_key_info); +DEF_EXT_FUNC_PTRS(krb5_pa_pk_as_rep); +DEF_EXT_FUNC_PTRS(krb5_pa_pk_as_rep_draft9); +DEF_EXT_FUNC_PTRS(krb5_pa_pk_as_req); +DEF_EXT_FUNC_PTRS(krb5_pa_pk_as_req_draft9); +DEF_EXT_FUNC_PTRS(krb5_reply_key_pack); +DEF_EXT_FUNC_PTRS(krb5_reply_key_pack_draft9); +DEF_EXT_FUNC_PTRS_ARRAY(krb5_typed_data); + +/* special cases... */ +extern krb5_error_code (*k5int_decode_krb5_principal_name) + (const krb5_data *, krb5_principal_data **); + +extern krb5_error_code (*k5int_encode_krb5_td_dh_parameters) + (const krb5_algorithm_identifier **, krb5_data **code); +extern krb5_error_code (*k5int_decode_krb5_td_dh_parameters) + (const krb5_data *, krb5_algorithm_identifier ***); + +extern krb5_error_code (*k5int_encode_krb5_td_trusted_certifiers) + (const krb5_external_principal_identifier **, krb5_data **code); +extern krb5_error_code (*k5int_decode_krb5_td_trusted_certifiers) + (const krb5_data *, krb5_external_principal_identifier ***); + +extern krb5_error_code (*k5int_decode_krb5_as_req) + (const krb5_data *output, krb5_kdc_req **rep); +extern krb5_error_code (*k5int_encode_krb5_kdc_req_body) + (const krb5_kdc_req *rep, krb5_data **code); +extern void KRB5_CALLCONV (*k5int_krb5_free_kdc_req) + (krb5_context, krb5_kdc_req * ); +extern void (*k5int_set_prompt_types) + (krb5_context, krb5_prompt_type *); +extern krb5_error_code (*k5int_encode_krb5_authdata_elt) + (const krb5_authdata *rep, krb5_data **code); + +#endif /* _PKINIT_ACCESSOR_H */ diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c new file mode 100644 index 0000000..0d6da4d --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -0,0 +1,1492 @@ +/* + * COPYRIGHT (C) 2006,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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <dlfcn.h> +#include <sys/stat.h> + +#include "pkinit.h" + +#ifdef LONGHORN_BETA_COMPAT +/* + * It is anticipated that all the special checks currently + * required when talking to a Longhorn server will go away + * by the time it is officially released and all references + * to the longhorn global can be removed and any code + * #ifdef'd with LONGHORN_BETA_COMPAT can be removed. + * + * Current testing (20070620) is against a patched Beta 3 + * version of Longhorn. Most, if not all, problems should + * be fixed in SP1 of Longhorn. + */ +int longhorn = 0; /* Talking to a Longhorn server? */ +#endif + +krb5_error_code pkinit_client_process + (krb5_context context, void *plugin_context, void *request_context, + 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, + krb5_data *encoded_previous_request, krb5_pa_data *in_padata, + krb5_prompter_fct prompter, void *prompter_data, + preauth_get_as_key_proc gak_fct, void *gak_data, + krb5_data * salt, krb5_data * s2kparams, + krb5_keyblock * as_key, krb5_pa_data *** out_padata); + +krb5_error_code pkinit_client_tryagain + (krb5_context context, void *plugin_context, void *request_context, + 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, + krb5_data *encoded_previous_request, + krb5_pa_data *in_padata, krb5_error *err_reply, + krb5_prompter_fct prompter, void *prompter_data, + preauth_get_as_key_proc gak_fct, void *gak_data, + krb5_data * salt, krb5_data * s2kparams, + krb5_keyblock * as_key, krb5_pa_data *** out_padata); + +void pkinit_client_req_init + (krb5_context contex, void *plugin_context, void **request_context); + +void pkinit_client_req_fini + (krb5_context context, void *plugin_context, void *request_context); + +krb5_error_code pa_pkinit_gen_req + (krb5_context context, pkinit_context plgctx, + 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 *gic_opt); + +krb5_error_code pkinit_as_req_create + (krb5_context context, pkinit_context plgctx, + pkinit_req_context reqctx, krb5_timestamp ctsec, + krb5_int32 cusec, krb5_ui_4 nonce, + const krb5_checksum * cksum, krb5_principal server, + krb5_data ** as_req); + +krb5_error_code pkinit_as_rep_parse + (krb5_context context, pkinit_context plgctx, + pkinit_req_context reqctx, krb5_preauthtype pa_type, + krb5_kdc_req * request, const krb5_data * as_rep, + krb5_keyblock * key_block, krb5_enctype etype, krb5_data *); + +krb5_error_code pa_pkinit_parse_rep + (krb5_context context, pkinit_context plgctx, + pkinit_req_context reqcxt, krb5_kdc_req * request, + krb5_pa_data * in_padata, krb5_enctype etype, + krb5_keyblock * as_key, krb5_data *); + +static int pkinit_client_plugin_init(krb5_context context, void **blob); +static void pkinit_client_plugin_fini(krb5_context context, void *blob); + +krb5_error_code +pa_pkinit_gen_req(krb5_context context, + pkinit_context plgctx, + 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 *gic_opt) +{ + + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_data *out_data = NULL; + krb5_timestamp ctsec = 0; + krb5_int32 cusec = 0; + krb5_ui_4 nonce = 0; + krb5_checksum cksum; + krb5_data *der_req = NULL; + krb5_pa_data **return_pa_data = NULL; + + cksum.contents = NULL; + reqctx->pa_type = in_padata->pa_type; + + pkiDebug("kdc_options = 0x%x till = %d\n", + request->kdc_options, request->till); + /* If we don't have a client, we're done */ + if (request->client == NULL) { + pkiDebug("No request->client; aborting PKINIT\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + 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 = k5int_encode_krb5_kdc_req_body(request, &der_req); + if (retval) { + pkiDebug("encode_krb5_kdc_req_body returned %d\n", (int) retval); + goto cleanup; + } + + retval = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, + der_req, &cksum); + if (retval) + goto cleanup; +#ifdef DEBUG_CKSUM + pkiDebug("calculating checksum on buf size (%d)\n", der_req->length); + print_buffer(der_req->data, der_req->length); +#endif + + retval = krb5_us_timeofday(context, &ctsec, &cusec); + if (retval) + goto cleanup; + + /* XXX PKINIT RFC says that nonce in PKAuthenticator doesn't have be the + * same as in the AS_REQ. However, if we pick a different nonce, then we + * need to remember that info when AS_REP is returned. I'm choosing to + * reuse the AS_REQ nonce. + */ + nonce = request->nonce; + + retval = pkinit_as_req_create(context, plgctx, reqctx, ctsec, cusec, + nonce, &cksum, request->server, &out_data); + if (retval || !out_data->length) { + pkiDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", + (int) retval); + goto cleanup; + } + retval = ENOMEM; + /* + * The most we'll return is two pa_data, normally just one. + * We need to make room for the NULL terminator. + */ + return_pa_data = (krb5_pa_data **) malloc(3 * sizeof(krb5_pa_data *)); + if (return_pa_data == NULL) + goto cleanup; + + return_pa_data[1] = NULL; /* in case of an early trip to cleanup */ + return_pa_data[2] = NULL; /* Terminate the list */ + + return_pa_data[0] = (krb5_pa_data *) malloc(sizeof(krb5_pa_data)); + if (return_pa_data[0] == NULL) + goto cleanup; + + return_pa_data[1] = (krb5_pa_data *) malloc(sizeof(krb5_pa_data)); + if (return_pa_data[1] == NULL) + goto cleanup; + + return_pa_data[0]->magic = KV5M_PA_DATA; + + if (in_padata->pa_type == KRB5_PADATA_PK_AS_REQ_OLD) + return_pa_data[0]->pa_type = KRB5_PADATA_PK_AS_REP_OLD; + else + return_pa_data[0]->pa_type = in_padata->pa_type; + return_pa_data[0]->length = out_data->length; + return_pa_data[0]->contents = (krb5_octet *) out_data->data; + +#ifdef LONGHORN_BETA_COMPAT + /* + * LH Beta 3 requires the extra pa-data, even for RFC requests, + * in order to get the Checksum rather than a Nonce in the reply. + * This can be removed when LH SP1 is released. + */ + if ((return_pa_data[0]->pa_type == KRB5_PADATA_PK_AS_REP_OLD + && reqctx->opts->win2k_require_cksum) || (longhorn == 1)) { +#else + if ((return_pa_data[0]->pa_type == KRB5_PADATA_PK_AS_REP_OLD + && reqctx->opts->win2k_require_cksum)) { +#endif + return_pa_data[1]->pa_type = 132; + return_pa_data[1]->length = 0; + return_pa_data[1]->contents = NULL; + } else { + free(return_pa_data[1]); + return_pa_data[1] = NULL; /* Move the list terminator */ + } + *out_padata = return_pa_data; + retval = 0; + + cleanup: + if (der_req != NULL) + krb5_free_data(context, der_req); + + if (out_data != NULL) + free(out_data); + + if (retval) { + if (return_pa_data) { + if (return_pa_data[0] != NULL) + free(return_pa_data[0]); + if (return_pa_data[1] != NULL) + free(return_pa_data[1]); + free(return_pa_data); + } + if (out_data) { + free(out_data->data); + free(out_data); + } + } + return retval; +} + +krb5_error_code +pkinit_as_req_create(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + krb5_timestamp ctsec, + krb5_int32 cusec, + krb5_ui_4 nonce, + const krb5_checksum * cksum, + krb5_principal server, + krb5_data ** as_req) +{ + krb5_error_code retval = ENOMEM; + krb5_subject_pk_info *info = NULL; + krb5_data *coded_auth_pack = NULL; + krb5_auth_pack *auth_pack = NULL; + krb5_pa_pk_as_req *req = NULL; + krb5_auth_pack_draft9 *auth_pack9 = NULL; + krb5_pa_pk_as_req_draft9 *req9 = NULL; + int protocol = reqctx->opts->dh_or_rsa; + + pkiDebug("pkinit_as_req_create pa_type = %d\n", reqctx->pa_type); + + /* Create the authpack */ + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ_OLD: + protocol = RSA_PROTOCOL; + init_krb5_auth_pack_draft9(&auth_pack9); + if (auth_pack9 == NULL) + goto cleanup; + auth_pack9->pkAuthenticator.ctime = ctsec; + auth_pack9->pkAuthenticator.cusec = cusec; + auth_pack9->pkAuthenticator.nonce = nonce; + auth_pack9->pkAuthenticator.kdcName = server; + auth_pack9->pkAuthenticator.kdcRealm.magic = 0; + auth_pack9->pkAuthenticator.kdcRealm.data = + (unsigned char *)server->realm.data; + auth_pack9->pkAuthenticator.kdcRealm.length = server->realm.length; + free(cksum->contents); + break; + case KRB5_PADATA_PK_AS_REQ: + init_krb5_subject_pk_info(&info); + if (info == NULL) + goto cleanup; + init_krb5_auth_pack(&auth_pack); + if (auth_pack == NULL) + goto cleanup; + auth_pack->pkAuthenticator.ctime = ctsec; + auth_pack->pkAuthenticator.cusec = cusec; + auth_pack->pkAuthenticator.nonce = nonce; + auth_pack->pkAuthenticator.paChecksum = *cksum; + auth_pack->clientDHNonce.length = 0; + auth_pack->clientPublicValue = info; + + /* add List of CMS algorithms */ + retval = create_krb5_supportedCMSTypes(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, + &auth_pack->supportedCMSTypes); + if (retval) + goto cleanup; + break; + default: + pkiDebug("as_req: unrecognized pa_type = %d\n", + (int)reqctx->pa_type); + retval = -1; + goto cleanup; + } + + switch(protocol) { + case DH_PROTOCOL: + pkiDebug("as_req: DH key transport algorithm\n"); + retval = pkinit_copy_krb5_octet_data(&info->algorithm.algorithm, &dh_oid); + if (retval) { + pkiDebug("failed to copy dh_oid\n"); + goto cleanup; + } + + /* create client-side DH keys */ + if ((retval = client_create_dh(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, reqctx->opts->dh_size, + &info->algorithm.parameters.data, + &info->algorithm.parameters.length, + &info->subjectPublicKey.data, + &info->subjectPublicKey.length)) != 0) { + pkiDebug("failed to create dh parameters\n"); + goto cleanup; + } + break; + case RSA_PROTOCOL: + pkiDebug("as_req: RSA key transport algorithm\n"); + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ_OLD: + auth_pack9->clientPublicValue = NULL; + break; + case KRB5_PADATA_PK_AS_REQ: + free_krb5_subject_pk_info(&info); + auth_pack->clientPublicValue = NULL; + break; + } + break; + default: + pkiDebug("as_req: unknown key transport protocol %d\n", + protocol); + retval = -1; + goto cleanup; + } + + /* Encode the authpack */ + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + retval = k5int_encode_krb5_auth_pack(auth_pack, &coded_auth_pack); + break; + case KRB5_PADATA_PK_AS_REQ_OLD: + retval = k5int_encode_krb5_auth_pack_draft9(auth_pack9, + &coded_auth_pack); + break; + } + if (retval) { + pkiDebug("failed to encode the AuthPack %d\n", retval); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)coded_auth_pack->data, + coded_auth_pack->length, + "/tmp/client_auth_pack"); +#endif + + /* create PKCS7 object from authpack */ + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + init_krb5_pa_pk_as_req(&req); + if (req == NULL) { + retval = ENOMEM; + goto cleanup; + } + retval = cms_signeddata_create(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, CMS_SIGN_CLIENT, 1, + (unsigned char *)coded_auth_pack->data, coded_auth_pack->length, + &req->signedAuthPack.data, &req->signedAuthPack.length); +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)req->signedAuthPack.data, + req->signedAuthPack.length, + "/tmp/client_signed_data"); +#endif + break; + case KRB5_PADATA_PK_AS_REQ_OLD: + init_krb5_pa_pk_as_req_draft9(&req9); + if (req9 == NULL) { + retval = ENOMEM; + goto cleanup; + } + retval = cms_signeddata_create(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, CMS_SIGN_DRAFT9, 1, + (unsigned char *)coded_auth_pack->data, coded_auth_pack->length, + &req9->signedAuthPack.data, &req9->signedAuthPack.length); + break; +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)req9->signedAuthPack.data, + req9->signedAuthPack.length, + "/tmp/client_signed_data_draft9"); +#endif + } + krb5_free_data(context, coded_auth_pack); + if (retval) { + pkiDebug("failed to create pkcs7 signed data\n"); + goto cleanup; + } + + /* create a list of trusted CAs */ + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + retval = create_krb5_trustedCertifiers(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, &req->trustedCertifiers); + if (retval) + goto cleanup; + retval = create_issuerAndSerial(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, &req->kdcPkId.data, + &req->kdcPkId.length); + if (retval) + goto cleanup; + + /* Encode the as-req */ + retval = k5int_encode_krb5_pa_pk_as_req(req, as_req); + break; + case KRB5_PADATA_PK_AS_REQ_OLD: +#if 0 + /* W2K3 KDC doesn't like this */ + retval = create_krb5_trustedCas(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, 1, &req9->trustedCertifiers); + if (retval) + goto cleanup; + +#endif + retval = create_issuerAndSerial(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, &req9->kdcCert.data, + &req9->kdcCert.length); + if (retval) + goto cleanup; + /* Encode the as-req */ + retval = k5int_encode_krb5_pa_pk_as_req_draft9(req9, as_req); + break; + } +#ifdef DEBUG_ASN1 + if (!retval) + print_buffer_bin((unsigned char *)(*as_req)->data, (*as_req)->length, + "/tmp/client_as_req"); +#endif + +cleanup: + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + free_krb5_auth_pack(&auth_pack); + free_krb5_pa_pk_as_req(&req); + break; + case KRB5_PADATA_PK_AS_REQ_OLD: + free_krb5_pa_pk_as_req_draft9(&req9); + free(auth_pack9); + break; + } + + + pkiDebug("pkinit_as_req_create retval=%d\n", (int) retval); + + return retval; +} + +krb5_error_code +pa_pkinit_parse_rep(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + krb5_kdc_req * request, + krb5_pa_data * in_padata, + krb5_enctype etype, + krb5_keyblock * as_key, + krb5_data *encoded_request) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_data asRep = { 0, 0, NULL}; + + /* + * One way or the other - success or failure - no other PA systems can + * work if the server sent us a PKINIT reply, since only we know how to + * decrypt the key. + */ + if ((in_padata == NULL) || (in_padata->length == 0)) { + pkiDebug("pa_pkinit_parse_rep: no in_padata\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + asRep.data = (char *) in_padata->contents; + asRep.length = in_padata->length; + + retval = + pkinit_as_rep_parse(context, plgctx, reqctx, in_padata->pa_type, + request, &asRep, as_key, etype, encoded_request); + if (retval) { + pkiDebug("pkinit_as_rep_parse returned %d (%s)\n", + retval, error_message(retval)); + goto cleanup; + } + + retval = 0; + +cleanup: + + return retval; +} + +static krb5_error_code +verify_kdc_san(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + krb5_principal kdcprinc, + int *valid_san, + int *need_eku_checking) +{ + krb5_error_code retval; + char **certhosts = NULL, **cfghosts = NULL; + krb5_principal *princs = NULL; + unsigned char ***get_dns; + int i, j; + + *valid_san = 0; + *need_eku_checking = 1; + + retval = pkinit_libdefault_strings(context, + krb5_princ_realm(context, kdcprinc), + "pkinit_kdc_hostname", + &cfghosts); + if (retval || cfghosts == NULL) { + pkiDebug("%s: No pkinit_kdc_hostname values found in config file\n", + __FUNCTION__); + get_dns = NULL; + } else { + pkiDebug("%s: pkinit_kdc_hostname values found in config file\n", + __FUNCTION__); + get_dns = (unsigned char ***)&certhosts; + } + + retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, + &princs, NULL, get_dns); + if (retval) { + pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__); + retval = KRB5KDC_ERR_KDC_NAME_MISMATCH; + goto out; + } +#if 0 + retval = call_san_checking_plugins(context, plgctx, reqctx, idctx, + princs, hosts, &plugin_decision, + need_eku_checking); + pkiDebug("%s: call_san_checking_plugins() returned retval %d\n", + __FUNCTION__); + if (retval) { + retval = KRB5KDC_ERR_KDC_NAME_MISMATCH; + goto out; + } + pkiDebug("%s: call_san_checking_plugins() returned decision %d and " + "need_eku_checking %d\n", + __FUNCTION__, plugin_decision, *need_eku_checking); + if (plugin_decision != NO_DECISION) { + retval = plugin_decision; + goto out; + } +#endif + + pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__); + for (i = 0; princs != NULL && princs[i] != NULL; i++) { + if (krb5_principal_compare(context, princs[i], kdcprinc)) { + pkiDebug("%s: pkinit san match found\n", __FUNCTION__); + *valid_san = 1; + *need_eku_checking = 0; + retval = 0; + goto out; + } + } + pkiDebug("%s: no pkinit san match found\n", __FUNCTION__); + + if (certhosts == NULL) { + pkiDebug("%s: no certhosts (or we wouldn't accept them anyway)\n", + __FUNCTION__); + retval = KRB5KDC_ERR_KDC_NAME_MISMATCH; + goto out; + } + + for (i = 0; certhosts[i] != NULL; i++) { + for (j = 0; cfghosts != NULL && cfghosts[j] != NULL; j++) { + pkiDebug("%s: comparing cert name '%s' with config name '%s'\n", + __FUNCTION__, certhosts[i], cfghosts[j]); + if (strcmp(certhosts[i], cfghosts[j]) == 0) { + pkiDebug("%s: we have a dnsName match\n", __FUNCTION__); + *valid_san = 1; + retval = 0; + goto out; + } + } + } + pkiDebug("%s: no dnsName san match found\n", __FUNCTION__); + + /* We found no match */ + retval = 0; + +out: + if (princs != NULL) { + for (i = 0; princs[i] != NULL; i++) + krb5_free_principal(context, princs[i]); + free(princs); + } + if (certhosts != NULL) { + for (i = 0; certhosts[i] != NULL; i++) + free(certhosts[i]); + free(certhosts); + } + if (cfghosts != NULL) + profile_free_list(cfghosts); + + pkiDebug("%s: returning retval %d, valid_san %d, need_eku_checking %d\n", + __FUNCTION__, retval, *valid_san, *need_eku_checking); + return retval; +} + +static krb5_error_code +verify_kdc_eku(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + int *eku_accepted) +{ + krb5_error_code retval; + + *eku_accepted = 0; + + if (reqctx->opts->require_eku == 0) { + pkiDebug("%s: configuration requests no EKU checking\n", __FUNCTION__); + *eku_accepted = 1; + retval = 0; + goto out; + } + retval = crypto_check_cert_eku(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, + 1, /* kdc cert */ + reqctx->opts->accept_secondary_eku, + eku_accepted); + if (retval) { + pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n", + __FUNCTION__, retval, error_message(retval)); + goto out; + } + +out: + pkiDebug("%s: returning retval %d, eku_accepted %d\n", + __FUNCTION__, retval, *eku_accepted); + return retval; +} + +/* + * Parse PA-PK-AS-REP message. Optionally evaluates the message's + * certificate chain. + * Optionally returns various components. + */ +krb5_error_code +pkinit_as_rep_parse(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + krb5_preauthtype pa_type, + krb5_kdc_req *request, + const krb5_data *as_rep, + krb5_keyblock *key_block, + krb5_enctype etype, + krb5_data *encoded_request) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_pa_pk_as_rep *kdc_reply = NULL; + krb5_kdc_dh_key_info *kdc_dh = NULL; + krb5_reply_key_pack *key_pack = NULL; + krb5_reply_key_pack_draft9 *key_pack9 = NULL; + krb5_octet_data dh_data = { 0, 0, NULL }; + unsigned char *client_key = NULL, *kdc_hostname = NULL; + unsigned int client_key_len = 0; + krb5_checksum cksum = {0, 0, 0, NULL}; + krb5_data k5data; + int valid_san = 0; + int valid_eku = 0; + int need_eku_checking = 1; + + assert((as_rep != NULL) && (key_block != NULL)); + +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)as_rep->data, as_rep->length, + "/tmp/client_as_rep"); +#endif + + if ((retval = k5int_decode_krb5_pa_pk_as_rep(as_rep, &kdc_reply))) { + pkiDebug("decode_krb5_as_rep failed %d\n", retval); + return retval; + } + + switch(kdc_reply->choice) { + case choice_pa_pk_as_rep_dhInfo: + pkiDebug("as_rep: DH key transport algorithm\n"); +#ifdef DEBUG_ASN1 + print_buffer_bin(kdc_reply->u.dh_Info.dhSignedData.data, + kdc_reply->u.dh_Info.dhSignedData.length, "/tmp/client_kdc_signeddata"); +#endif + if ((retval = cms_signeddata_verify(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, CMS_SIGN_SERVER, + reqctx->opts->require_crl_checking, + kdc_reply->u.dh_Info.dhSignedData.data, + kdc_reply->u.dh_Info.dhSignedData.length, + &dh_data.data, &dh_data.length, NULL, NULL)) != 0) { + pkiDebug("failed to verify pkcs7 signed data\n"); + goto cleanup; + } + + break; + case choice_pa_pk_as_rep_encKeyPack: + pkiDebug("as_rep: RSA key transport algorithm\n"); + if ((retval = cms_envelopeddata_verify(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, pa_type, + reqctx->opts->require_crl_checking, + kdc_reply->u.encKeyPack.data, + kdc_reply->u.encKeyPack.length, + &dh_data.data, &dh_data.length)) != 0) { + pkiDebug("failed to verify pkcs7 enveloped data\n"); + goto cleanup; + } + break; + default: + pkiDebug("unknown as_rep type %d\n", kdc_reply->choice); + retval = -1; + goto cleanup; + } + + retval = verify_kdc_san(context, plgctx, reqctx, request->server, + &valid_san, &need_eku_checking); + if (retval) + goto cleanup; + if (!valid_san) { + pkiDebug("%s: did not find an acceptable SAN in KDC certificate\n", + __FUNCTION__); + retval = KRB5KDC_ERR_KDC_NAME_MISMATCH; + goto cleanup; + } + + if (need_eku_checking) { + retval = verify_kdc_eku(context, plgctx, reqctx, + &valid_eku); + if (retval) + goto cleanup; + if (!valid_eku) { + pkiDebug("%s: did not find an acceptable EKU in KDC certificate\n", + __FUNCTION__); + retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; + goto cleanup; + } + } else + pkiDebug("%s: skipping EKU check\n", __FUNCTION__); + + OCTETDATA_TO_KRB5DATA(&dh_data, &k5data); + + switch(kdc_reply->choice) { + case choice_pa_pk_as_rep_dhInfo: +#ifdef DEBUG_ASN1 + print_buffer_bin(dh_data.data, dh_data.length, + "/tmp/client_dh_key"); +#endif + if ((retval = k5int_decode_krb5_kdc_dh_key_info(&k5data, + &kdc_dh)) != 0) { + pkiDebug("failed to decode kdc_dh_key_info\n"); + goto cleanup; + } + + /* client after KDC reply */ + if ((retval = client_process_dh(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, + kdc_dh->subjectPublicKey.data, + kdc_dh->subjectPublicKey.length, + &client_key, &client_key_len)) != 0) { + pkiDebug("failed to process dh params\n"); + goto cleanup; + } + + retval = pkinit_octetstring2key(context, etype, client_key, + client_key_len, key_block); + if (retval) { + pkiDebug("failed to create key pkinit_octetstring2key %s\n", + error_message(retval)); + goto cleanup; + } + + break; + case choice_pa_pk_as_rep_encKeyPack: +#ifdef DEBUG_ASN1 + print_buffer_bin(dh_data.data, dh_data.length, + "/tmp/client_key_pack"); +#endif + if ((retval = k5int_decode_krb5_reply_key_pack(&k5data, + &key_pack)) != 0) { + pkiDebug("failed to decode reply_key_pack\n"); +#ifdef LONGHORN_BETA_COMPAT + /* + * LH Beta 3 requires the extra pa-data, even for RFC requests, + * in order to get the Checksum rather than a Nonce in the reply. + * This can be removed when LH SP1 is released. + */ + if (pa_type == KRB5_PADATA_PK_AS_REP && longhorn == 0) +#else + if (pa_type == KRB5_PADATA_PK_AS_REP) +#endif + goto cleanup; + else { + if ((retval = + k5int_decode_krb5_reply_key_pack_draft9(&k5data, + &key_pack9)) != 0) { + pkiDebug("failed to decode reply_key_pack_draft9\n"); + goto cleanup; + } + pkiDebug("decode reply_key_pack_draft9\n"); + if (key_pack9->nonce != request->nonce) { + pkiDebug("nonce in AS_REP=%d doesn't match AS_REQ=%d\n", key_pack9->nonce, request->nonce); + retval = -1; + goto cleanup; + } + krb5_copy_keyblock_contents(context, &key_pack9->replyKey, + key_block); + break; + } + } + /* + * This is hack but Windows sends back SHA1 checksum + * with checksum type of 14. There is currently no + * checksum type of 14 defined. + */ + if (key_pack->asChecksum.checksum_type == 14) + key_pack->asChecksum.checksum_type = CKSUMTYPE_NIST_SHA; + retval = krb5_c_make_checksum(context, + key_pack->asChecksum.checksum_type, + &key_pack->replyKey, + KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + encoded_request, &cksum); + if (retval) { + pkiDebug("failed to make a checksum\n"); + goto cleanup; + } + + if ((cksum.length != key_pack->asChecksum.length) || + memcmp(cksum.contents, key_pack->asChecksum.contents, + cksum.length)) { + pkiDebug("failed to match the checksums\n"); +#ifdef DEBUG_CKSUM + pkiDebug("calculating checksum on buf size (%d)\n", + encoded_request->length); + print_buffer(encoded_request->data, encoded_request->length); + pkiDebug("encrypting key (%d)\n", key_pack->replyKey.length); + print_buffer(key_pack->replyKey.contents, + key_pack->replyKey.length); + pkiDebug("received checksum type=%d size=%d ", + key_pack->asChecksum.checksum_type, + key_pack->asChecksum.length); + print_buffer(key_pack->asChecksum.contents, + key_pack->asChecksum.length); + pkiDebug("expected checksum type=%d size=%d ", + cksum.checksum_type, cksum.length); + print_buffer(cksum.contents, cksum.length); +#endif + goto cleanup; + } else + pkiDebug("checksums match\n"); + + krb5_copy_keyblock_contents(context, &key_pack->replyKey, + key_block); + + break; + default: + pkiDebug("unknow as_rep type %d\n", kdc_reply->choice); + goto cleanup; + } + + retval = 0; + +cleanup: + if (dh_data.data != NULL) + free(dh_data.data); + if (client_key != NULL) + free(client_key); + free_krb5_kdc_dh_key_info(&kdc_dh); + free_krb5_pa_pk_as_rep(&kdc_reply); + + if (key_pack != NULL) { + free_krb5_reply_key_pack(&key_pack); + if (cksum.contents != NULL) + free(cksum.contents); + } + if (key_pack9 != NULL) + free_krb5_reply_key_pack_draft9(&key_pack9); + + if (kdc_hostname != NULL) + free(kdc_hostname); + + pkiDebug("pkinit_as_rep_parse returning %d (%s)\n", + retval, error_message(retval)); + return retval; +} + +static void +pkinit_client_profile(krb5_context context, + pkinit_context plgctx, + pkinit_req_context reqctx, + krb5_kdc_req *request) +{ + char *eku_string = NULL; + + pkiDebug("pkinit_client_profile %p %p %p %p\n", + context, plgctx, reqctx, request); + + pkinit_libdefault_boolean(context, &request->server->realm, + "pkinit_win2k", + reqctx->opts->win2k_target, + &reqctx->opts->win2k_target); + pkinit_libdefault_boolean(context, &request->server->realm, + "pkinit_win2k_require_binding", + reqctx->opts->win2k_require_cksum, + &reqctx->opts->win2k_require_cksum); + pkinit_libdefault_boolean(context, &request->server->realm, + "pkinit_require_crl_checking", + reqctx->opts->require_crl_checking, + &reqctx->opts->require_crl_checking); + pkinit_libdefault_integer(context, &request->server->realm, + "pkinit_dh_min_bits", + reqctx->opts->dh_size, + &reqctx->opts->dh_size); + if (reqctx->opts->dh_size != 1024 && reqctx->opts->dh_size != 2048 + && reqctx->opts->dh_size != 4096) { + pkiDebug("%s: invalid value (%d) for pkinit_dh_min_bits, " + "using default value (%d) instead\n", __FUNCTION__, + reqctx->opts->dh_size, PKINIT_DEFAULT_DH_MIN_BITS); + reqctx->opts->dh_size = PKINIT_DEFAULT_DH_MIN_BITS; + } + pkinit_libdefault_string(context, &request->server->realm, + "pkinit_eku_checking", + &eku_string); + if (eku_string != NULL) { + if (strcasecmp(eku_string, "kpKDC") == 0) { + reqctx->opts->require_eku = 1; + reqctx->opts->accept_secondary_eku = 0; + } else if (strcasecmp(eku_string, "kpServerAuth") == 0) { + reqctx->opts->require_eku = 1; + reqctx->opts->accept_secondary_eku = 1; + } else if (strcasecmp(eku_string, "none") == 0) { + reqctx->opts->require_eku = 0; + reqctx->opts->accept_secondary_eku = 0; + } else { + pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n", + __FUNCTION__, eku_string); + } + free(eku_string); + } +#ifdef LONGHORN_BETA_COMPAT + /* Temporarily just set global flag from config file */ + pkinit_libdefault_boolean(context, &request->server->realm, + "pkinit_longhorn", + 0, + &longhorn); +#endif + + /* Only process anchors here if they were not specified on command line */ + if (reqctx->idopts->anchors == NULL) + pkinit_libdefault_strings(context, &request->server->realm, + "pkinit_anchors", + &reqctx->idopts->anchors); + pkinit_libdefault_strings(context, &request->server->realm, + "pkinit_pool", + &reqctx->idopts->intermediates); + pkinit_libdefault_strings(context, &request->server->realm, + "pkinit_revoke", + &reqctx->idopts->crls); + pkinit_libdefault_strings(context, &request->server->realm, + "pkinit_identities", + &reqctx->idopts->identity_alt); +} + +krb5_error_code +pkinit_client_process(krb5_context context, + void *plugin_context, + void *request_context, + 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, + krb5_data *encoded_previous_request, + krb5_pa_data *in_padata, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data ***out_padata) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_enctype enctype = -1; + krb5_data *cdata = NULL; + int processing_request = 0; + pkinit_context plgctx = (pkinit_context)plugin_context; + pkinit_req_context reqctx = (pkinit_req_context)request_context; + + pkiDebug("pkinit_client_process %p %p %p %p\n", + context, plgctx, reqctx, request); + + if (plgctx == NULL || reqctx == NULL) + return EINVAL; + + switch ((int) in_padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n"); + processing_request = 1; + break; + + case KRB5_PADATA_PK_AS_REP: + pkiDebug("processing KRB5_PADATA_PK_AS_REP\n"); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + if (in_padata->length == 0) { + pkiDebug("processing KRB5_PADATA_PK_AS_REQ_OLD\n"); + in_padata->pa_type = KRB5_PADATA_PK_AS_REQ_OLD; + processing_request = 1; + } else { + pkiDebug("processing KRB5_PADATA_PK_AS_REP_OLD\n"); + in_padata->pa_type = KRB5_PADATA_PK_AS_REP_OLD; + } + break; + default: + pkiDebug("unrecognized patype = %d for PKINIT\n", + in_padata->pa_type); + return EINVAL; + } + + if (processing_request) { + pkinit_client_profile(context, plgctx, reqctx, request); + pkinit_identity_set_prompter(reqctx->idctx, prompter, prompter_data); + 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)); + return retval; + } + retval = pa_pkinit_gen_req(context, plgctx, reqctx, request, + in_padata, out_padata, prompter, + prompter_data, gic_opt); + } else { + /* + * Get the enctype of the reply. + */ + retval = (*get_data_proc)(context, rock, + krb5plugin_preauth_client_get_etype, &cdata); + if (retval) { + pkiDebug("get_data_proc returned %d (%s)\n", + retval, error_message(retval)); + return retval; + } + enctype = *((krb5_enctype *)cdata->data); + (*get_data_proc)(context, rock, + krb5plugin_preauth_client_free_etype, &cdata); + retval = pa_pkinit_parse_rep(context, plgctx, reqctx, request, + in_padata, enctype, as_key, + encoded_previous_request); + } + + pkiDebug("pkinit_client_process: returning %d (%s)\n", + retval, error_message(retval)); + return retval; +} + +krb5_error_code +pkinit_client_tryagain(krb5_context context, + void *plugin_context, + void *request_context, + 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, + krb5_data *encoded_previous_request, + krb5_pa_data *in_padata, + krb5_error *err_reply, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data ***out_padata) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + pkinit_context plgctx = (pkinit_context)plugin_context; + pkinit_req_context reqctx = (pkinit_req_context)request_context; + krb5_typed_data **typed_data = NULL; + krb5_data scratch; + krb5_external_principal_identifier **krb5_trusted_certifiers = NULL; + krb5_algorithm_identifier **algId = NULL; + int do_again = 0; + + pkiDebug("pkinit_client_tryagain %p %p %p %p\n", + context, plgctx, reqctx, request); + + if (reqctx->pa_type != in_padata->pa_type) + return retval; + +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)err_reply->e_data.data, + err_reply->e_data.length, "/tmp/client_edata"); +#endif + retval = k5int_decode_krb5_typed_data(&err_reply->e_data, &typed_data); + if (retval) { + pkiDebug("decode_krb5_typed_data failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin(typed_data[0]->data, typed_data[0]->length, + "/tmp/client_typed_data"); +#endif + OCTETDATA_TO_KRB5DATA(typed_data[0], &scratch); + + switch(typed_data[0]->type) { + case TD_TRUSTED_CERTIFIERS: + case TD_INVALID_CERTIFICATES: + retval = k5int_decode_krb5_td_trusted_certifiers(&scratch, + &krb5_trusted_certifiers); + if (retval) { + pkiDebug("failed to decode sequence of trusted certifiers\n"); + goto cleanup; + } + retval = pkinit_process_td_trusted_certifiers(context, + plgctx->cryptoctx, reqctx->cryptoctx, reqctx->idctx, + krb5_trusted_certifiers, typed_data[0]->type); + if (!retval) + do_again = 1; + break; + case TD_DH_PARAMETERS: + retval = k5int_decode_krb5_td_dh_parameters(&scratch, &algId); + if (retval) { + pkiDebug("failed to decode td_dh_parameters\n"); + goto cleanup; + } + retval = pkinit_process_td_dh_params(context, plgctx->cryptoctx, + reqctx->cryptoctx, reqctx->idctx, algId, + &reqctx->opts->dh_size); + if (!retval) + do_again = 1; + break; + default: + break; + } + + if (do_again) { + retval = pa_pkinit_gen_req(context, plgctx, reqctx, request, in_padata, + out_padata, prompter, prompter_data, gic_opt); + if (retval) + goto cleanup; + } + + retval = 0; +cleanup: + if (krb5_trusted_certifiers != NULL) + free_krb5_external_principal_identifier(&krb5_trusted_certifiers); + + if (typed_data != NULL) + free_krb5_typed_data(&typed_data); + + if (algId != NULL) + free_krb5_algorithm_identifiers(&algId); + + pkiDebug("pkinit_client_tryagain: returning %d (%s)\n", + retval, error_message(retval)); + return retval; +} + +static int +pkinit_client_get_flags(krb5_context kcontext, krb5_preauthtype patype) +{ + return PA_REAL; +} + +static krb5_preauthtype supported_client_pa_types[] = { + KRB5_PADATA_PK_AS_REP, + KRB5_PADATA_PK_AS_REQ, + KRB5_PADATA_PK_AS_REP_OLD, + KRB5_PADATA_PK_AS_REQ_OLD, + 0 +}; + +void +pkinit_client_req_init(krb5_context context, + void *plugin_context, + void **request_context) +{ + krb5_error_code retval = ENOMEM; + struct _pkinit_req_context *reqctx = NULL; + struct _pkinit_context *plgctx = (struct _pkinit_context *)plugin_context; + + *request_context = NULL; + + reqctx = (struct _pkinit_req_context *) malloc(sizeof(*reqctx)); + if (reqctx == NULL) + return; + memset(reqctx, 0, sizeof(*reqctx)); + + reqctx->magic = PKINIT_REQ_CTX_MAGIC; + reqctx->cryptoctx = NULL; + reqctx->opts = NULL; + reqctx->idctx = NULL; + reqctx->idopts = NULL; + + retval = pkinit_init_req_opts(&reqctx->opts); + if (retval) + goto cleanup; + + reqctx->opts->require_eku = plgctx->opts->require_eku; + reqctx->opts->accept_secondary_eku = plgctx->opts->accept_secondary_eku; + reqctx->opts->dh_or_rsa = plgctx->opts->dh_or_rsa; + reqctx->opts->allow_upn = plgctx->opts->allow_upn; + reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking; + + retval = pkinit_init_req_crypto(&reqctx->cryptoctx); + if (retval) + goto cleanup; + + retval = pkinit_init_identity_crypto(&reqctx->idctx); + if (retval) + goto cleanup; + + retval = pkinit_dup_identity_opts(plgctx->idopts, &reqctx->idopts); + if (retval) + goto cleanup; + + *request_context = (void *) reqctx; + pkiDebug("%s: returning reqctx at %p\n", __FUNCTION__, reqctx); + +cleanup: + if (retval) { + if (reqctx->idctx != NULL) + pkinit_fini_identity_crypto(reqctx->idctx); + if (reqctx->cryptoctx != NULL) + pkinit_fini_req_crypto(reqctx->cryptoctx); + if (reqctx->opts != NULL) + pkinit_fini_req_opts(reqctx->opts); + if (reqctx->idopts != NULL) + pkinit_fini_identity_opts(reqctx->idopts); + free(reqctx); + } + + return; +} + +void +pkinit_client_req_fini(krb5_context context, + void *plugin_context, + void *request_context) +{ + struct _pkinit_req_context *reqctx = + (struct _pkinit_req_context *)request_context; + + pkiDebug("%s: received reqctx at %p\n", __FUNCTION__, reqctx); + if (reqctx == NULL) + return; + if (reqctx->magic != PKINIT_REQ_CTX_MAGIC) { + pkiDebug("%s: Bad magic value (%x) in req ctx\n", + __FUNCTION__, reqctx->magic); + return; + } + if (reqctx->opts != NULL) + pkinit_fini_req_opts(reqctx->opts); + + if (reqctx->cryptoctx != NULL) + pkinit_fini_req_crypto(reqctx->cryptoctx); + + if (reqctx->idctx != NULL) + pkinit_fini_identity_crypto(reqctx->idctx); + + if (reqctx->idopts != NULL) + pkinit_fini_identity_opts(reqctx->idopts); + + free(reqctx); + return; +} + +static void +pkinit_fini_client_profile(krb5_context context, pkinit_context plgctx) +{ + /* This should clean up anything allocated in pkinit_init_client_profile */ +} + +static krb5_error_code +pkinit_init_client_profile(krb5_context context, pkinit_context plgctx) +{ + return 0; +} + +static int +pkinit_client_plugin_init(krb5_context context, void **blob) +{ + krb5_error_code retval = ENOMEM; + struct _pkinit_context *ctx = NULL; + + ctx = (struct _pkinit_context *)calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return ENOMEM; + memset(ctx, 0, sizeof(*ctx)); + ctx->magic = PKINIT_CTX_MAGIC; + ctx->opts = NULL; + ctx->cryptoctx = NULL; + ctx->idopts = NULL; + + retval = pkinit_accessor_init(); + if (retval) + goto errout; + + retval = pkinit_init_plg_opts(&ctx->opts); + if (retval) + goto errout; + + retval = pkinit_init_plg_crypto(&ctx->cryptoctx); + if (retval) + goto errout; + + retval = pkinit_init_identity_opts(&ctx->idopts); + if (retval) + goto errout; + + retval = pkinit_init_client_profile(context, ctx); + if (retval) + goto errout; + + *blob = ctx; + + pkiDebug("%s: returning plgctx at %p\n", __FUNCTION__, ctx); + +errout: + if (retval) + pkinit_client_plugin_fini(context, ctx); + + return retval; +} + +static void +pkinit_client_plugin_fini(krb5_context context, void *blob) +{ + struct _pkinit_context *ctx = (struct _pkinit_context *)blob; + + if (ctx == NULL || ctx->magic != PKINIT_CTX_MAGIC) { + pkiDebug("pkinit_lib_fini: got bad plgctx (%p)!\n", ctx); + return; + } + pkiDebug("%s: got plgctx at %p\n", __FUNCTION__, ctx); + + pkinit_fini_client_profile(context, ctx); + pkinit_fini_identity_opts(ctx->idopts); + pkinit_fini_plg_crypto(ctx->cryptoctx); + pkinit_fini_plg_opts(ctx->opts); + free(ctx); + +} + +static krb5_error_code +add_string_to_array(krb5_context context, char ***array, const char *addition) +{ + char **out = NULL; + + if (*array == NULL) { + out = malloc(2 * sizeof(char *)); + if (out == NULL) + return ENOMEM; + out[1] = NULL; + out[0] = strdup(addition); + if (out[0] == NULL) { + free(out); + return ENOMEM; + } + } else { + int i; + char **a = *array; + for (i = 0; a[i] != NULL; i++); + out = malloc( (i + 2) * sizeof(char *)); + if (out == NULL) + return ENOMEM; + for (i = 0; a[i] != NULL; i++) { + out[i] = a[i]; + } + out[i++] = strdup(addition); + if (out == NULL) { + free(out); + return ENOMEM; + } + out[i] = NULL; + free(*array); + } + *array = out; + + return 0; +} +static krb5_error_code +handle_gic_opt(krb5_context context, + struct _pkinit_context *plgctx, + const char *attr, + const char *value) +{ + krb5_error_code retval; + + if (strcmp(attr, "X509_user_identity") == 0) { + if (plgctx->idopts->identity != NULL) { + krb5_set_error_message(context, KRB5_PREAUTH_FAILED, + "X509_user_identity can not be given twice\n"); + return KRB5_PREAUTH_FAILED; + } + plgctx->idopts->identity = strdup(value); + if (plgctx->idopts->identity == NULL) { + krb5_set_error_message(context, ENOMEM, + "Could not duplicate X509_user_identity value\n"); + return ENOMEM; + } + } else if (strcmp(attr, "X509_anchors") == 0) { + retval = add_string_to_array(context, &plgctx->idopts->anchors, value); + if (retval) + return retval; + } else if (strcmp(attr, "flag_RSA_PROTOCOL") == 0) { + if (strcmp(value, "yes") == 0) { + pkiDebug("Setting flag to use RSA_PROTOCOL\n"); + plgctx->opts->dh_or_rsa = RSA_PROTOCOL; + } + } + return 0; +} + +static krb5_error_code +pkinit_client_gic_opt(krb5_context context, + void *plugin_context, + krb5_get_init_creds_opt *gic_opt, + const char *attr, + const char *value) +{ + krb5_error_code retval; + struct _pkinit_context *plgctx = (struct _pkinit_context *)plugin_context; + + pkiDebug("(pkinit) received '%s' = '%s'\n", attr, value); + retval = handle_gic_opt(context, plgctx, attr, value); + if (retval) + return retval; + + return 0; +} + +struct krb5plugin_preauth_client_ftable_v1 preauthentication_client_1 = { + "pkinit", /* name */ + supported_client_pa_types, /* pa_type_list */ + NULL, /* enctype_list */ + pkinit_client_plugin_init, /* (*init) */ + pkinit_client_plugin_fini, /* (*fini) */ + pkinit_client_get_flags, /* (*flags) */ + pkinit_client_req_init, /* (*client_req_init) */ + pkinit_client_req_fini, /* (*client_req_fini) */ + pkinit_client_process, /* (*process) */ + pkinit_client_tryagain, /* (*tryagain) */ + pkinit_client_gic_opt /* (*gic_opt) */ +}; diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h new file mode 100644 index 0000000..779c08c --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_crypto.h @@ -0,0 +1,623 @@ +/* + * 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. + */ + +/* + * This header defines the cryptographic interface + */ + +#ifndef _PKINIT_CRYPTO_H +#define _PKINIT_CRYPTO_H + +#include <krb5/krb5.h> +#include <krb5/preauth_plugin.h> +#include <k5-int-pkinit.h> +#include <profile.h> +#include "pkinit_accessor.h" + +/* + * these describe the CMS message types + */ +enum cms_msg_types { + CMS_SIGN_CLIENT, + CMS_SIGN_DRAFT9, + CMS_SIGN_SERVER, + CMS_ENVEL_SERVER +}; + +/* + * 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 *); +void pkinit_fini_plg_crypto(pkinit_plg_crypto_context); + +krb5_error_code pkinit_init_req_crypto(pkinit_req_crypto_context *); +void pkinit_fini_req_crypto(pkinit_req_crypto_context); + +krb5_error_code pkinit_init_identity_crypto(pkinit_identity_crypto_context *); +void pkinit_fini_identity_crypto(pkinit_identity_crypto_context); + +/* + * this function creates a CMS message where eContentType is SignedData + */ +krb5_error_code cms_signeddata_create + (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 cms_msg_type, /* IN + specifies CMS_SIGN_CLIENT for client-side CMS message + and CMS_SIGN_SERVER for kdc-side */ + int include_certchain, /* IN + specifies where certificates field in SignedData + should contain certificate path */ + unsigned char *auth_pack, /* IN + contains DER encoded AuthPack (CMS_SIGN_CLIENT) + or DER encoded DHRepInfo (CMS_SIGN_SERVER) */ + unsigned int auth_pack_len, /* IN + contains length of auth_pack */ + unsigned char **signed_data, /* OUT + for CMS_SIGN_CLIENT receives DER encoded + SignedAuthPack (CMS_SIGN_CLIENT) or DER + encoded DHInfo (CMS_SIGN_SERVER) */ + unsigned int *signed_data_len); /* OUT + receives length of signed_data */ + +/* + * this function verifies a CMS message where eContentType is SignedData + */ +krb5_error_code cms_signeddata_verify + (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 cms_msg_type, /* IN + specifies CMS_SIGN_CLIENT for client-side + CMS message and CMS_SIGN_SERVER for kdc-side */ + int require_crl_checking, /* IN + specifies whether CRL checking should be + strictly enforced, i.e. if no CRLs available + for the CA then fail verification. + note, if the value is 0, crls are still + checked if present */ + unsigned char *signed_data, /* IN + contains DER encoded SignedAuthPack (CMS_SIGN_CLIENT) + or DER encoded DHInfo (CMS_SIGN_SERVER) */ + unsigned int signed_data_len, /* IN + contains length of signed_data*/ + unsigned char **auth_pack, /* OUT + receives DER encoded AuthPack (CMS_SIGN_CLIENT) + or DER encoded DHRepInfo (CMS_SIGN_SERVER)*/ + unsigned int *auth_pack_len, /* OUT + receives length of auth_pack */ + unsigned char **authz_data, /* OUT + receives required authorization data that + contains the verified certificate chain + (only used by the KDC) */ + unsigned int *authz_data_len); /* OUT + receives length of authz_data */ + +/* + * this function creates a CMS message where eContentType is EnvelopedData + */ +krb5_error_code cms_envelopeddata_create + (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 */ + krb5_preauthtype pa_type, /* IN */ + int include_certchain, /* IN + specifies whether the certificates field in + SignedData should contain certificate path */ + unsigned char *key_pack, /* IN + contains DER encoded ReplyKeyPack */ + unsigned int key_pack_len, /* IN + contains length of key_pack */ + unsigned char **envel_data, /* OUT + receives DER encoded encKeyPack */ + unsigned int *envel_data_len); /* OUT + receives length of envel_data */ + +/* + * this function creates a CMS message where eContentType is EnvelopedData + */ +krb5_error_code cms_envelopeddata_verify + (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 */ + krb5_preauthtype pa_type, /* IN */ + int require_crl_checking, /* IN + specifies whether CRL checking should be + strictly enforced */ + unsigned char *envel_data, /* IN + contains DER encoded encKeyPack */ + unsigned int envel_data_len, /* IN + contains length of envel_data */ + unsigned char **signed_data, /* OUT + receives ReplyKeyPack */ + unsigned int *signed_data_len); /* OUT + receives length of signed_data */ + +/* + * this function returns SAN information found in the + * received certificate. at least one of pkinit_sans, + * upn_sans, or kdc_hostnames must be non-NULL. + */ +krb5_error_code crypto_retrieve_cert_sans + (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 */ + krb5_principal **pkinit_sans, /* OUT + if non-NULL, a null-terminated array of + id-pkinit-san values found in the certificate + are returned */ + krb5_principal **upn_sans, /* OUT + if non-NULL, a null-terminated array of + id-ms-upn-san values found in the certificate + are returned */ + unsigned char ***kdc_hostname); /* OUT + if non-NULL, a null-terminated array of + dNSName (hostname) SAN values found in the + certificate are returned */ + +/* + * this function checks for acceptable key usage values + * in the received certificate. + * + * when checking a received kdc certificate, it looks for + * the kpKdc key usage. if allow_secondary_usage is + * non-zero, it will also accept kpServerAuth. + * + * when checking a received user certificate, it looks for + * kpClientAuth key usage. if allow_secondary_usage is + * non-zero, it will also accept id-ms-sc-logon EKU. + * + * this function must also assert that the digitalSignature + * key usage is consistent. + */ +krb5_error_code crypto_check_cert_eku + (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 checking_kdc_cert, /* IN + specifies if the received certificate is + a KDC certificate (non-zero), + or a user certificate (zero) */ + int allow_secondary_usage, /* IN + specifies if the secondary key usage + should be accepted or not (see above) */ + int *eku_valid); /* OUT + receives non-zero if an acceptable EKU was found */ + +/* + * this functions takes in generated DH secret key and converts + * it in to a kerberos session key. it takes into the account the + * enc type and then follows the procedure specified in the RFC p 22. + */ +krb5_error_code pkinit_octetstring2key + (krb5_context context, /* IN */ + krb5_enctype etype, /* IN + specifies the enc type */ + unsigned char *key, /* IN + contains the DH secret key */ + unsigned int key_len, /* IN + contains length of key */ + krb5_keyblock * krb5key); /* OUT + receives kerberos session key */ + +/* + * this function implements clients first part of the DH protocol. + * client selects its DH parameters and pub key + */ +krb5_error_code client_create_dh + (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 dh_size, /* IN + specifies the DH modulous, eg 1024, 2048, or 4096 */ + unsigned char **dh_paramas, /* OUT + contains DER encoded DH params */ + unsigned int *dh_params_len, /* OUT + contains length of dh_parmas */ + unsigned char **dh_pubkey, /* OUT + receives DER encoded DH pub key */ + unsigned int *dh_pubkey_len); /* OUT + receives length of dh_pubkey */ + +/* + * this function completes client's the DH protocol. client + * processes received DH pub key from the KDC and computes + * the DH secret key + */ +krb5_error_code client_process_dh + (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 */ + unsigned char *dh_pubkey, /* IN + contains client's DER encoded DH pub key */ + unsigned int dh_pubkey_len, /* IN + contains length of dh_pubkey */ + unsigned char **dh_session_key, /* OUT + receives DH secret key */ + unsigned int *dh_session_key_len); /* OUT + receives length of dh_session_key */ + +/* + * this function implements the KDC first part of the DH protocol. + * it decodes the client's DH parameters and pub key and checks + * if they are acceptable. + */ +krb5_error_code server_check_dh + (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 */ + krb5_octet_data *dh_params, /* IN + ???? */ + int minbits); /* IN + the mininum number of key bits acceptable */ + +/* + * this function completes the KDC's DH protocol. The KDC generates + * its DH pub key and computes the DH secret key + */ +krb5_error_code server_process_dh + (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 */ + unsigned char *received_pubkey, /* IN + contains client's DER encoded DH pub key */ + unsigned int received_pub_len, /* IN + contains length of received_pubkey */ + unsigned char **dh_pubkey, /* OUT + receives KDC's DER encoded DH pub key */ + unsigned int *dh_pubkey_len, /* OUT + receives length of dh_pubkey */ + unsigned char **server_key, /* OUT + receives DH secret key */ + unsigned int *server_key_len); /* OUT + receives length of server_key */ + +/* + * this functions takes in crypto specific representation of + * supportedCMSTypes and creates a list of + * krb5_algorithm_identifier + */ +krb5_error_code create_krb5_supportedCMSTypes + (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 */ + krb5_algorithm_identifier ***supportedCMSTypes); /* OUT */ + +/* + * this functions takes in crypto specific representation of + * trustedCertifiers and creates a list of + * krb5_external_principal_identifier + */ +krb5_error_code create_krb5_trustedCertifiers + (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 */ + krb5_external_principal_identifier ***trustedCertifiers); /* OUT */ + +/* + * this functions takes in crypto specific representation of + * trustedCas (draft9) and creates a list of krb5_trusted_ca (draft9). + * draft9 trustedCAs is a CHOICE. we only support choices for + * [1] caName and [2] issuerAndSerial. there is no config + * option available to select the choice yet. default = 1. + */ +krb5_error_code create_krb5_trustedCas + (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 flag, /* IN + specifies the tag of the CHOICE */ + krb5_trusted_ca ***trustedCas); /* OUT */ + +/* + * this functions takes in crypto specific representation of the + * KDC's certificate and creates a DER encoded kdcPKId + */ +krb5_error_code create_issuerAndSerial + (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 */ + unsigned char **kdcId_buf, /* OUT + receives DER encoded kdcPKId */ + unsigned int *kdcId_len); /* OUT + receives length of encoded kdcPKId */ + +/* + * process the values from idopts and obtain the cert(s) + * specified by those options, populating the id_cryptoctx. + */ +krb5_error_code crypto_load_certs + (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 */ + 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 + * intermediate certificates, or crls specified by idtype, + * catype, and id + */ +krb5_error_code crypto_load_cas_and_crls + (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 */ + int idtype, /* IN + defines the storage type (file, directory, etc) */ + int catype, /* IN + defines the ca type (anchor, intermediate, 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, /* 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 + */ +krb5_error_code pkinit_create_td_dh_parameters + (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_plg_opts *opts, /* IN */ + krb5_data **edata); /* OUT */ + +/* + * this function processes edata that contains TD-DH-PARAMETERS. + * the client processes the received acceptable by KDC DH + * parameters and picks the first acceptable to it. it matches + * them against the known DH parameters. + */ +krb5_error_code pkinit_process_td_dh_params + (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 */ + krb5_algorithm_identifier **algId, /* IN */ + int *new_dh_size); /* OUT + receives the new DH modulus to use in the new AS-REQ */ + +/* + * this function creates edata that contains TD-INVALID-CERTIFICATES + */ +krb5_error_code pkinit_create_td_invalid_certificate + (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 */ + krb5_data **edata); /* OUT */ + +/* + * this function creates edata that contains TD-TRUSTED-CERTIFIERS + */ +krb5_error_code pkinit_create_td_trusted_certifiers + (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 */ + krb5_data **edata); /* OUT */ + +/* + * this function processes edata that contains either + * TD-TRUSTED-CERTIFICATES or TD-INVALID-CERTIFICATES. + * current implementation only decodes the received message + * but does not act on it + */ +krb5_error_code pkinit_process_td_trusted_certifiers + (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 */ + krb5_external_principal_identifier **trustedCertifiers, /* IN */ + int td_type); /* IN */ + +/* + * this function checks if the received kdcPKId matches + * the KDC's certificate + */ +krb5_error_code pkinit_check_kdc_pkid + (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 */ + unsigned char *pdid_buf, /* IN + contains DER encoded kdcPKId */ + unsigned int pkid_len, /* IN + contains length of pdid_buf */ + int *valid_kdcPkId); /* OUT + 1 if kdcPKId matches, otherwise 0 */ + +krb5_error_code pkinit_identity_set_prompter + (pkinit_identity_crypto_context id_cryptoctx, /* IN */ + krb5_prompter_fct prompter, /* IN */ + void *prompter_data); /* IN */ + +#endif /* _PKINIT_CRYPTO_H */ diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c new file mode 100644 index 0000000..1859b4f --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -0,0 +1,5612 @@ +/* + * COPYRIGHT (C) 2006,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 <dlfcn.h> +#include <unistd.h> +#include <dirent.h> + +/* + * Q: What is this SILLYDECRYPT stuff about? + * A: When using the ActivCard Linux pkcs11 library (v2.0.1), + * the decrypt function fails. By inserting an extra + * function call, which serves nothing but to change the + * stack, we were able to work around the issue. If the + * ActivCard library is fixed in the future, this + * definition and related code can be removed. + */ +#define SILLYDECRYPT + +#include "pkinit_crypto_openssl.h" + +static struct pkcs11_errstrings { + short code; + char *text; +} pkcs11_errstrings[] = { + { 0x0, "ok" }, + { 0x1, "cancel" }, + { 0x2, "host memory" }, + { 0x3, "slot id invalid" }, + { 0x5, "general error" }, + { 0x6, "function failed" }, + { 0x7, "arguments bad" }, + { 0x8, "no event" }, + { 0x9, "need to create threads" }, + { 0xa, "cant lock" }, + { 0x10, "attribute read only" }, + { 0x11, "attribute sensitive" }, + { 0x12, "attribute type invalid" }, + { 0x13, "attribute value invalid" }, + { 0x20, "data invalid" }, + { 0x21, "data len range" }, + { 0x30, "device error" }, + { 0x31, "device memory" }, + { 0x32, "device removed" }, + { 0x40, "encrypted data invalid" }, + { 0x41, "encrypted data len range" }, + { 0x50, "function canceled" }, + { 0x51, "function not parallel" }, + { 0x54, "function not supported" }, + { 0x60, "key handle invalid" }, + { 0x62, "key size range" }, + { 0x63, "key type inconsistent" }, + { 0x64, "key not needed" }, + { 0x65, "key changed" }, + { 0x66, "key needed" }, + { 0x67, "key indigestible" }, + { 0x68, "key function not permitted" }, + { 0x69, "key not wrappable" }, + { 0x6a, "key unextractable" }, + { 0x70, "mechanism invalid" }, + { 0x71, "mechanism param invalid" }, + { 0x82, "object handle invalid" }, + { 0x90, "operation active" }, + { 0x91, "operation not initialized" }, + { 0xa0, "pin incorrect" }, + { 0xa1, "pin invalid" }, + { 0xa2, "pin len range" }, + { 0xa3, "pin expired" }, + { 0xa4, "pin locked" }, + { 0xb0, "session closed" }, + { 0xb1, "session count" }, + { 0xb3, "session handle invalid" }, + { 0xb4, "session parallel not supported" }, + { 0xb5, "session read only" }, + { 0xb6, "session exists" }, + { 0xb7, "session read only exists" }, + { 0xb8, "session read write so exists" }, + { 0xc0, "signature invalid" }, + { 0xc1, "signature len range" }, + { 0xd0, "template incomplete" }, + { 0xd1, "template inconsistent" }, + { 0xe0, "token not present" }, + { 0xe1, "token not recognized" }, + { 0xe2, "token write protected" }, + { 0xf0, "unwrapping key handle invalid" }, + { 0xf1, "unwrapping key size range" }, + { 0xf2, "unwrapping key type inconsistent" }, + { 0x100, "user already logged in" }, + { 0x101, "user not logged in" }, + { 0x102, "user pin not initialized" }, + { 0x103, "user type invalid" }, + { 0x104, "user another already logged in" }, + { 0x105, "user too many types" }, + { 0x110, "wrapped key invalid" }, + { 0x112, "wrapped key len range" }, + { 0x113, "wrapping key handle invalid" }, + { 0x114, "wrapping key size range" }, + { 0x115, "wrapping key type inconsistent" }, + { 0x120, "random seed not supported" }, + { 0x121, "random no rng" }, + { 0x130, "domain params invalid" }, + { 0x150, "buffer too small" }, + { 0x160, "saved state invalid" }, + { 0x170, "information sensitive" }, + { 0x180, "state unsaveable" }, + { 0x190, "cryptoki not initialized" }, + { 0x191, "cryptoki already initialized" }, + { 0x1a0, "mutex bad" }, + { 0x1a1, "mutex not locked" }, + { 0x200, "function rejected" }, + { -1, NULL } +}; + +/* DH parameters */ +unsigned char pkinit_1024_dhprime[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +unsigned char pkinit_2048_dhprime[2048/8] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +unsigned char pkinit_4096_dhprime[4096/8] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, + 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, + 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, + 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, + 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, + 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, + 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, + 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, + 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, + 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, + 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, + 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, + 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, + 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, + 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, + 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, + 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, + 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, + 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, + 0xAD, 0x33, 0x17, 0x0D, 0x04, 0x50, 0x7A, 0x33, + 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, + 0x8A, 0xEA, 0x71, 0x57, 0x5D, 0x06, 0x0C, 0x7D, + 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, + 0x1E, 0x8C, 0x94, 0xE0, 0x4A, 0x25, 0x61, 0x9D, + 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, + 0xD8, 0x76, 0x02, 0x73, 0x3E, 0xC8, 0x6A, 0x64, + 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, + 0x77, 0x09, 0x88, 0xC0, 0xBA, 0xD9, 0x46, 0xE2, + 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, + 0x4B, 0x82, 0xD1, 0x20, 0xA9, 0x21, 0x08, 0x01, + 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, + 0x99, 0xC3, 0x27, 0x18, 0x6A, 0xF4, 0xE2, 0x3C, + 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, + 0xDB, 0xBB, 0xC2, 0xDB, 0x04, 0xDE, 0x8E, 0xF9, + 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, + 0x99, 0xB2, 0x96, 0x4F, 0xA0, 0x90, 0xC3, 0xA2, + 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, + 0xB8, 0x1B, 0xDD, 0x76, 0x21, 0x70, 0x48, 0x1C, + 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, + 0x86, 0xFF, 0xB7, 0xDC, 0x90, 0xA6, 0xC0, 0x8F, + 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static int pkinit_oids_refs = 0; + +krb5_error_code +pkinit_init_plg_crypto(pkinit_plg_crypto_context *cryptoctx) { + + krb5_error_code retval = ENOMEM; + pkinit_plg_crypto_context ctx = NULL; + + /* initialize openssl routines */ + openssl_init(); + + ctx = (pkinit_plg_crypto_context)malloc(sizeof(*ctx)); + if (ctx == NULL) + goto out; + memset(ctx, 0, sizeof(*ctx)); + + pkiDebug("%s: initializing openssl crypto context at %p\n", + __FUNCTION__, ctx); + retval = pkinit_init_pkinit_oids(ctx); + if (retval) + goto out; + + retval = pkinit_init_dh_params(ctx); + if (retval) + goto out; + + *cryptoctx = ctx; + +out: + if (retval && ctx != NULL) + pkinit_fini_plg_crypto(ctx); + + return retval; +} + +void +pkinit_fini_plg_crypto(pkinit_plg_crypto_context cryptoctx) +{ + pkiDebug("%s: freeing context at %p\n", __FUNCTION__, cryptoctx); + + if (cryptoctx == NULL) + return; + pkinit_fini_pkinit_oids(cryptoctx); + pkinit_fini_dh_params(cryptoctx); + free(cryptoctx); +} + +krb5_error_code +pkinit_init_identity_crypto(pkinit_identity_crypto_context *idctx) +{ + krb5_error_code retval = ENOMEM; + pkinit_identity_crypto_context ctx = NULL; + + ctx = (pkinit_identity_crypto_context)malloc(sizeof(*ctx)); + if (ctx == NULL) + goto out; + memset(ctx, 0, sizeof(*ctx)); + + retval = pkinit_init_certs(ctx); + if (retval) + goto out; + + retval = pkinit_init_pkcs11(ctx); + if (retval) + goto out; + + pkiDebug("%s: returning ctx at %p\n", __FUNCTION__, ctx); + *idctx = ctx; + +out: + if (retval) { + if (ctx) + pkinit_fini_identity_crypto(ctx); + } + + return retval; +} + +void +pkinit_fini_identity_crypto(pkinit_identity_crypto_context idctx) +{ + if (idctx == NULL) + return; + + pkiDebug("%s: freeing ctx at %p\n", __FUNCTION__, idctx); + pkinit_fini_certs(idctx); + pkinit_fini_pkcs11(idctx); + free(idctx); +} + +krb5_error_code +pkinit_init_req_crypto(pkinit_req_crypto_context *cryptoctx) +{ + + krb5_error_code retval = ENOMEM; + pkinit_req_crypto_context ctx = NULL; + + ctx = (pkinit_req_crypto_context)malloc(sizeof(*ctx)); + if (ctx == NULL) + goto out; + memset(ctx, 0, sizeof(*ctx)); + + ctx->dh = NULL; + ctx->received_cert = NULL; + + *cryptoctx = ctx; + + pkiDebug("%s: returning ctx at %p\n", __FUNCTION__, ctx); + retval = 0; +out: + if (retval) + free(ctx); + + return retval; +} + +void +pkinit_fini_req_crypto(pkinit_req_crypto_context req_cryptoctx) +{ + if (req_cryptoctx == NULL) + return; + + pkiDebug("%s: freeing ctx at %p\n", __FUNCTION__, req_cryptoctx); + if (req_cryptoctx->dh != NULL) + DH_free(req_cryptoctx->dh); + if (req_cryptoctx->received_cert != NULL) + X509_free(req_cryptoctx->received_cert); + + free(req_cryptoctx); +} + +static krb5_error_code +pkinit_init_pkinit_oids(pkinit_plg_crypto_context ctx) +{ + krb5_error_code retval = ENOMEM; + int nid = 0; + + /* + * If OpenSSL already knows about the OID, use the + * existing definition. Otherwise, create an OID object. + */ + #define CREATE_OBJ_IF_NEEDED(oid, vn, sn, ln) \ + nid = OBJ_txt2nid(oid); \ + if (nid == NID_undef) { \ + nid = OBJ_create(oid, sn, ln); \ + if (nid == NID_undef) { \ + pkiDebug("Error creating oid object for '%s'\n", oid); \ + goto out; \ + } \ + } \ + ctx->vn = OBJ_nid2obj(nid); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.2", id_pkinit_san, + "id-pkinit-san", "KRB5PrincipalName"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.3.1", id_pkinit_authData, + "id-pkinit-authdata", "PKINIT signedAuthPack"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.3.2", id_pkinit_DHKeyData, + "id-pkinit-DHKeyData", "PKINIT dhSignedData"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.3.3", id_pkinit_rkeyData, + "id-pkinit-rkeyData", "PKINIT encKeyPack"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.3.4", id_pkinit_KPClientAuth, + "id-pkinit-KPClientAuth", "PKINIT Client EKU"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.2.3.5", id_pkinit_KPKdc, + "id-pkinit-KPKdc", "KDC EKU"); + +#if 0 + CREATE_OBJ_IF_NEEDED("1.2.840.113549.1.7.1", id_pkinit_authData9, + "id-pkcs7-data", "PKCS7 data"); +#else + /* See note in pkinit_pkcs7type2oid() */ + ctx->id_pkinit_authData9 = NULL; +#endif + + CREATE_OBJ_IF_NEEDED("1.3.6.1.4.1.311.20.2.2", id_ms_kp_sc_logon, + "id-ms-kp-sc-logon EKU", "Microsoft SmartCard Login EKU"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.4.1.311.20.2.3", id_ms_san_upn, + "id-ms-san-upn", "Microsoft Universal Principal Name"); + + CREATE_OBJ_IF_NEEDED("1.3.6.1.5.5.7.3.1", id_kp_serverAuth, + "id-kp-serverAuth EKU", "Server Authentication EKU"); + + /* Success */ + retval = 0; + pkinit_oids_refs++; + +out: + return retval; +} + +static krb5_error_code +get_cert(char *filename, X509 **retcert) +{ + X509 *cert = NULL; + BIO *tmp = NULL; + int code; + krb5_error_code retval; + + if (filename == NULL || retcert == NULL) + return EINVAL; + + *retcert = NULL; + + tmp = BIO_new(BIO_s_file()); + if (tmp == NULL) + return ENOMEM; + + code = BIO_read_filename(tmp, filename); + if (code == 0) { + retval = errno; + goto cleanup; + } + + cert = (X509 *) PEM_read_bio_X509(tmp, NULL, NULL, NULL); + if (cert == NULL) { + retval = EIO; + pkiDebug("failed to read certificate from %s\n", filename); + goto cleanup; + } + *retcert = cert; + retval = 0; +cleanup: + if (tmp != NULL) + BIO_free(tmp); + return retval; +} + +static krb5_error_code +get_key(char *filename, EVP_PKEY **retkey) +{ + EVP_PKEY *pkey = NULL; + BIO *tmp = NULL; + int code; + krb5_error_code retval; + + if (filename == NULL || retkey == NULL) + return EINVAL; + + tmp = BIO_new(BIO_s_file()); + if (tmp == NULL) + return ENOMEM; + + code = BIO_read_filename(tmp, filename); + if (code == 0) { + retval = errno; + goto cleanup; + } + pkey = (EVP_PKEY *) PEM_read_bio_PrivateKey(tmp, NULL, NULL, NULL); + if (pkey == NULL) { + retval = EIO; + pkiDebug("failed to read private key from %s\n", filename); + goto cleanup; + } + *retkey = pkey; + retval = 0; +cleanup: + if (tmp != NULL) + BIO_free(tmp); + return retval; +} + +static void +pkinit_fini_pkinit_oids(pkinit_plg_crypto_context ctx) +{ + if (ctx == NULL) + return; + + /* Only call OBJ_cleanup once! */ + if (--pkinit_oids_refs == 0) + OBJ_cleanup(); +} + +static krb5_error_code +pkinit_init_dh_params(pkinit_plg_crypto_context plgctx) +{ + krb5_error_code retval = ENOMEM; + + plgctx->dh_1024 = DH_new(); + if (plgctx->dh_1024 == NULL) + goto cleanup; + plgctx->dh_1024->p = BN_bin2bn(pkinit_1024_dhprime, + sizeof(pkinit_1024_dhprime), NULL); + if ((plgctx->dh_1024->g = BN_new()) == NULL || + (plgctx->dh_1024->q = BN_new()) == NULL) + goto cleanup; + BN_set_word(plgctx->dh_1024->g, DH_GENERATOR_2); + BN_rshift1(plgctx->dh_1024->q, plgctx->dh_1024->p); + + plgctx->dh_2048 = DH_new(); + if (plgctx->dh_2048 == NULL) + goto cleanup; + plgctx->dh_2048->p = BN_bin2bn(pkinit_2048_dhprime, + sizeof(pkinit_2048_dhprime), NULL); + if ((plgctx->dh_2048->g = BN_new()) == NULL || + (plgctx->dh_2048->q = BN_new()) == NULL) + goto cleanup; + BN_set_word(plgctx->dh_2048->g, DH_GENERATOR_2); + BN_rshift1(plgctx->dh_2048->q, plgctx->dh_2048->p); + + plgctx->dh_4096 = DH_new(); + if (plgctx->dh_4096 == NULL) + goto cleanup; + plgctx->dh_4096->p = BN_bin2bn(pkinit_4096_dhprime, + sizeof(pkinit_4096_dhprime), NULL); + if ((plgctx->dh_4096->g = BN_new()) == NULL || + (plgctx->dh_4096->q = BN_new()) == NULL) + goto cleanup; + BN_set_word(plgctx->dh_4096->g, DH_GENERATOR_2); + BN_rshift1(plgctx->dh_4096->q, plgctx->dh_4096->p); + + retval = 0; + +cleanup: + if (retval) + pkinit_fini_dh_params(plgctx); + + return retval; +} + +static void +pkinit_fini_dh_params(pkinit_plg_crypto_context plgctx) +{ + if (plgctx->dh_1024 != NULL) + DH_free(plgctx->dh_1024); + if (plgctx->dh_2048 != NULL) + DH_free(plgctx->dh_2048); + if (plgctx->dh_4096 != NULL) + DH_free(plgctx->dh_4096); + + plgctx->dh_1024 = plgctx->dh_2048 = plgctx->dh_4096 = NULL; +} + +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; + ctx->trustedCAs = NULL; + ctx->intermediateCAs = NULL; + ctx->revoked = NULL; + + retval = 0; + return retval; +} + +static void +pkinit_fini_certs(pkinit_identity_crypto_context ctx) +{ + if (ctx == NULL) + return; + + if (ctx->my_certs != NULL) + sk_X509_pop_free(ctx->my_certs, X509_free); + + if (ctx->my_key != NULL) + EVP_PKEY_free(ctx->my_key); + + if (ctx->trustedCAs != NULL) + sk_X509_pop_free(ctx->trustedCAs, X509_free); + + if (ctx->intermediateCAs != NULL) + sk_X509_pop_free(ctx->intermediateCAs, X509_free); + + if (ctx->revoked != NULL) + sk_X509_CRL_pop_free(ctx->revoked, X509_CRL_free); +} + +static krb5_error_code +pkinit_init_pkcs11(pkinit_identity_crypto_context ctx) +{ + krb5_error_code retval = ENOMEM; + +#ifndef WITHOUT_PKCS11 + ctx->p11_module_name = strdup(PKCS11_MODNAME); + if (ctx->p11_module_name == NULL) + return retval; + ctx->p11_module = NULL; + ctx->slotid = PK_NOSLOT; + ctx->token_label = NULL; + ctx->cert_label = NULL; + ctx->session = CK_INVALID_HANDLE; + ctx->p11 = NULL; +#endif + ctx->pkcs11_method = 0; + + retval = 0; + return retval; +} + +static void +pkinit_fini_pkcs11(pkinit_identity_crypto_context ctx) +{ +#ifndef WITHOUT_PKCS11 + if (ctx == NULL) + return; + + if (ctx->p11 != NULL) { + if (ctx->session) { + ctx->p11->C_CloseSession(ctx->session); + ctx->session = CK_INVALID_HANDLE; + } + ctx->p11->C_Finalize(NULL_PTR); + ctx->p11 = NULL; + } + if (ctx->p11_module != NULL) { + pkinit_C_UnloadModule(ctx->p11_module); + ctx->p11_module = NULL; + } + if (ctx->p11_module_name != NULL) + free(ctx->p11_module_name); + if (ctx->token_label != NULL) + free(ctx->token_label); + if (ctx->cert_id != NULL) + free(ctx->cert_id); + if (ctx->cert_label != NULL) + free(ctx->cert_label); +#endif +} + +krb5_error_code +pkinit_identity_set_prompter(pkinit_identity_crypto_context id_cryptoctx, + krb5_prompter_fct prompter, + void *prompter_data) +{ + id_cryptoctx->prompter = prompter; + id_cryptoctx->prompter_data = prompter_data; + + return 0; +} + +krb5_error_code +cms_signeddata_create(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int cms_msg_type, + int include_certchain, + unsigned char *data, + unsigned int data_len, + unsigned char **signed_data, + unsigned int *signed_data_len) +{ + krb5_error_code retval = ENOMEM; + PKCS7 *p7 = NULL, *inner_p7 = NULL; + PKCS7_SIGNED *p7s = NULL; + PKCS7_SIGNER_INFO *p7si = NULL; + unsigned char *p; + ASN1_TYPE *pkinit_data = NULL; + STACK_OF(X509) * cert_stack = NULL; + ASN1_OCTET_STRING *digest_attr = NULL; + EVP_MD_CTX ctx, ctx2; + const EVP_MD *md_tmp = NULL; + unsigned char md_data[EVP_MAX_MD_SIZE], md_data2[EVP_MAX_MD_SIZE]; + unsigned char *digestInfo_buf = NULL, *abuf = NULL; + unsigned int md_len, md_len2, alen, digestInfo_len; + STACK_OF(X509_ATTRIBUTE) * sk; + unsigned char *sig = NULL; + unsigned int sig_len = 0; + X509_ALGOR *alg = NULL; + ASN1_OCTET_STRING *digest = NULL; + unsigned int alg_len = 0, digest_len = 0; + unsigned char *y = NULL, *alg_buf = NULL, *digest_buf = NULL; + X509 *cert = NULL; + ASN1_OBJECT *oid = NULL; + + /* start creating PKCS7 data */ + if ((p7 = PKCS7_new()) == NULL) + goto cleanup; + p7->type = OBJ_nid2obj(NID_pkcs7_signed); + + if ((p7s = PKCS7_SIGNED_new()) == NULL) + goto cleanup; + p7->d.sign = p7s; + if (!ASN1_INTEGER_set(p7s->version, 3)) + goto cleanup; + + /* create a cert chain that has at least the signer's certificate */ + if ((cert_stack = sk_X509_new_null()) == NULL) + goto cleanup; + + cert = sk_X509_value(id_cryptoctx->my_certs, id_cryptoctx->cert_index); + if (!include_certchain) { + pkiDebug("only including signer's certificate\n"); + sk_X509_push(cert_stack, X509_dup(cert)); + } else { + /* create a cert chain */ + X509_STORE *certstore = NULL; + X509_STORE_CTX certctx; + STACK_OF(X509) *certstack = NULL; + char buf[DN_BUF_LEN]; + int i = 0, size = 0; + + if ((certstore = X509_STORE_new()) == NULL) + goto cleanup; + pkiDebug("building certificate chain\n"); + X509_STORE_set_verify_cb_func(certstore, openssl_callback); + X509_STORE_CTX_init(&certctx, certstore, cert, + id_cryptoctx->intermediateCAs); + X509_STORE_CTX_trusted_stack(&certctx, id_cryptoctx->trustedCAs); + if (!X509_verify_cert(&certctx)) { + pkiDebug("failed to create a certificate chain: %s\n", + X509_verify_cert_error_string(X509_STORE_CTX_get_error(&certctx))); + if (!sk_X509_num(id_cryptoctx->trustedCAs)) + pkiDebug("No trusted CAs found. Check your X509_anchors\n"); + goto cleanup; + } + certstack = X509_STORE_CTX_get1_chain(&certctx); + size = sk_X509_num(certstack); + pkiDebug("size of certificate chain = %d\n", size); + for(i = 0; i < size - 1; i++) { + X509 *x = sk_X509_value(certstack, i); + X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf)); + pkiDebug("cert #%d: %s\n", i, buf); + sk_X509_push(cert_stack, X509_dup(x)); + } + X509_STORE_CTX_cleanup(&certctx); + X509_STORE_free(certstore); + sk_X509_pop_free(certstack, X509_free); + } + p7s->cert = cert_stack; + + /* fill-in PKCS7_SIGNER_INFO */ + if ((p7si = PKCS7_SIGNER_INFO_new()) == NULL) + goto cleanup; + if (!ASN1_INTEGER_set(p7si->version, 1)) + goto cleanup; + if (!X509_NAME_set(&p7si->issuer_and_serial->issuer, + X509_get_issuer_name(cert))) + goto cleanup; + /* because ASN1_INTEGER_set is used to set a 'long' we will do + * things the ugly way. */ + M_ASN1_INTEGER_free(p7si->issuer_and_serial->serial); + if (!(p7si->issuer_and_serial->serial = + M_ASN1_INTEGER_dup(X509_get_serialNumber(cert)))) + goto cleanup; + + /* will not fill-out EVP_PKEY because it's on the smartcard */ + + /* Set digest algs */ + p7si->digest_alg->algorithm = OBJ_nid2obj(NID_sha1); + + if (p7si->digest_alg->parameter != NULL) + ASN1_TYPE_free(p7si->digest_alg->parameter); + if ((p7si->digest_alg->parameter = ASN1_TYPE_new()) == NULL) + goto cleanup; + p7si->digest_alg->parameter->type = V_ASN1_NULL; + + /* Set sig algs */ + if (p7si->digest_enc_alg->parameter != NULL) + ASN1_TYPE_free(p7si->digest_enc_alg->parameter); + p7si->digest_enc_alg->algorithm = OBJ_nid2obj(NID_sha1WithRSAEncryption); + if (!(p7si->digest_enc_alg->parameter = ASN1_TYPE_new())) + goto cleanup; + p7si->digest_enc_alg->parameter->type = V_ASN1_NULL; + + /* pick the correct oid for the eContentInfo */ + oid = pkinit_pkcs7type2oid(plg_cryptoctx, cms_msg_type); + if (oid == NULL) + goto cleanup; + + if (cms_msg_type == CMS_SIGN_DRAFT9) { + /* don't include signed attributes for pa-type 15 request */ + abuf = data; + alen = data_len; + } else { + /* add signed attributes */ + /* compute sha1 digest over the EncapsulatedContentInfo */ + EVP_MD_CTX_init(&ctx); + EVP_DigestInit_ex(&ctx, EVP_sha1(), NULL); + EVP_DigestUpdate(&ctx, data, data_len); + md_tmp = EVP_MD_CTX_md(&ctx); + EVP_DigestFinal_ex(&ctx, md_data, &md_len); + + /* create a message digest attr */ + digest_attr = ASN1_OCTET_STRING_new(); + ASN1_OCTET_STRING_set(digest_attr, md_data, (int)md_len); + PKCS7_add_signed_attribute(p7si, NID_pkcs9_messageDigest, + V_ASN1_OCTET_STRING, (char *) digest_attr); + + /* create a content-type attr */ + PKCS7_add_signed_attribute(p7si, NID_pkcs9_contentType, + V_ASN1_OBJECT, oid); + + /* create the signature over signed attributes. get DER encoded value */ + /* This is the place where smartcard signature needs to be calculated */ + sk = p7si->auth_attr; + alen = ASN1_item_i2d((ASN1_VALUE *) sk, &abuf, + ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)); + if (abuf == NULL) + goto cleanup2; + } + +#ifndef WITHOUT_PKCS11 + /* Some tokens can only do RSAEncryption without sha1 hash */ + /* to compute sha1WithRSAEncryption, encode the algorithm ID for the hash + * function and the hash value into an ASN.1 value of type DigestInfo + * DigestInfo::=SEQUENCE { + * digestAlgorithm AlgorithmIdentifier, + * digest OCTET STRING } + */ + if (id_cryptoctx->pkcs11_method == 1 && + id_cryptoctx->mech == CKM_RSA_PKCS) { + pkiDebug("mech = CKM_RSA_PKCS\n"); + EVP_MD_CTX_init(&ctx2); + /* if this is not draft9 request, include digest signed attribute */ + if (cms_msg_type != CMS_SIGN_DRAFT9) + EVP_DigestInit_ex(&ctx2, md_tmp, NULL); + else + EVP_DigestInit_ex(&ctx2, EVP_sha1(), NULL); + EVP_DigestUpdate(&ctx2, abuf, alen); + EVP_DigestFinal_ex(&ctx2, md_data2, &md_len2); + + alg = X509_ALGOR_new(); + if (alg == NULL) + goto cleanup2; + alg->algorithm = OBJ_nid2obj(NID_sha1); + alg->parameter = NULL; + alg_len = i2d_X509_ALGOR(alg, NULL); + alg_buf = (unsigned char *)malloc(alg_len); + if (alg_buf == NULL) + goto cleanup2; + + digest = ASN1_OCTET_STRING_new(); + if (digest == NULL) + goto cleanup2; + ASN1_OCTET_STRING_set(digest, md_data2, (int)md_len2); + digest_len = i2d_ASN1_OCTET_STRING(digest, NULL); + digest_buf = (unsigned char *)malloc(digest_len); + if (digest_buf == NULL) + goto cleanup2; + + digestInfo_len = ASN1_object_size(1, (int)(alg_len + digest_len), + V_ASN1_SEQUENCE); + y = digestInfo_buf = (unsigned char *)malloc(digestInfo_len); + if (digestInfo_buf == NULL) + goto cleanup2; + ASN1_put_object(&y, 1, (int)(alg_len + digest_len), V_ASN1_SEQUENCE, + V_ASN1_UNIVERSAL); + i2d_X509_ALGOR(alg, &y); + i2d_ASN1_OCTET_STRING(digest, &y); +#ifdef DEBUG_SIG + pkiDebug("signing buffer\n"); + print_buffer(digestInfo_buf, digestInfo_len); + print_buffer_bin(digestInfo_buf, digestInfo_len, "/tmp/pkcs7_tosign"); +#endif + retval = pkinit_sign_data(context, id_cryptoctx, digestInfo_buf, + digestInfo_len, &sig, &sig_len); + } else +#endif + { + pkiDebug("mech = %s\n", + id_cryptoctx->pkcs11_method == 1 ? "CKM_SHA1_RSA_PKCS" : "FS"); + retval = pkinit_sign_data(context, id_cryptoctx, abuf, alen, + &sig, &sig_len); + } +#ifdef DEBUG_SIG + print_buffer(sig, sig_len); +#endif + if (cms_msg_type != CMS_SIGN_DRAFT9) + free(abuf); + if (retval) + goto cleanup2; + + /* Add signature */ + if (!ASN1_STRING_set(p7si->enc_digest, (unsigned char *) sig, + (int)sig_len)) { + unsigned long err = ERR_peek_error(); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("failed to add a signed digest attribute\n"); + goto cleanup2; + } + /* adder signer_info to pkcs7 signed */ + if (!PKCS7_add_signer(p7, p7si)) + goto cleanup2; + + /* start on adding data to the pkcs7 signed */ + if ((inner_p7 = PKCS7_new()) == NULL) + goto cleanup2; + if ((pkinit_data = ASN1_TYPE_new()) == NULL) + goto cleanup2; + pkinit_data->type = V_ASN1_OCTET_STRING; + if ((pkinit_data->value.octet_string = ASN1_OCTET_STRING_new()) == NULL) + goto cleanup2; + if (!ASN1_OCTET_STRING_set(pkinit_data->value.octet_string, data, + (int)data_len)) { + unsigned long err = ERR_peek_error(); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("failed to add pkcs7 data\n"); + goto cleanup2; + } + + if (!PKCS7_set0_type_other(inner_p7, OBJ_obj2nid(oid), pkinit_data)) + goto cleanup2; + + if (p7s->contents != NULL) + PKCS7_free(p7s->contents); + p7s->contents = inner_p7; + + *signed_data_len = i2d_PKCS7(p7, NULL); + if (!(*signed_data_len)) { + unsigned long err = ERR_peek_error(); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("failed to der encode pkcs7\n"); + goto cleanup2; + } + if ((p = *signed_data = + (unsigned char *) malloc((size_t)*signed_data_len)) == NULL) + goto cleanup2; + + /* DER encode PKCS7 data */ + retval = i2d_PKCS7(p7, &p); + if (!retval) { + unsigned long err = ERR_peek_error(); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("failed to der encode pkcs7\n"); + goto cleanup2; + } + retval = 0; + +#ifdef DEBUG_ASN1 + if (cms_msg_type == CMS_SIGN_CLIENT) { + print_buffer_bin(*signed_data, *signed_data_len, + "/tmp/client_pkcs7_signeddata"); + } else { + if (cms_msg_type == CMS_SIGN_SERVER) { + print_buffer_bin(*signed_data, *signed_data_len, + "/tmp/kdc_pkcs7_signeddata"); + } else { + print_buffer_bin(*signed_data, *signed_data_len, + "/tmp/draft9_pkcs7_signeddata"); + } + } +#endif + + cleanup2: + if (cms_msg_type != CMS_SIGN_DRAFT9) + EVP_MD_CTX_cleanup(&ctx); +#ifndef WITHOUT_PKCS11 + if (id_cryptoctx->pkcs11_method == 1 && + id_cryptoctx->mech == CKM_RSA_PKCS) { + EVP_MD_CTX_cleanup(&ctx2); + if (digest_buf != NULL) + free(digest_buf); + if (digestInfo_buf != NULL) + free(digestInfo_buf); + if (alg_buf != NULL) + free(alg_buf); + if (digest != NULL) + ASN1_OCTET_STRING_free(digest); + } +#endif + if (alg != NULL) + X509_ALGOR_free(alg); + cleanup: + if (p7 != NULL) + PKCS7_free(p7); + if (sig != NULL) + free(sig); + + return retval; +} + +krb5_error_code +cms_signeddata_verify(krb5_context context, + pkinit_plg_crypto_context plgctx, + pkinit_req_crypto_context reqctx, + pkinit_identity_crypto_context idctx, + int cms_msg_type, + int require_crl_checking, + unsigned char *signed_data, + unsigned int signed_data_len, + unsigned char **data, + unsigned int *data_len, + unsigned char **authz_data, + unsigned int *authz_data_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + PKCS7 *p7 = NULL; + BIO *out = NULL; + int flags = PKCS7_NOVERIFY, i = 0; + unsigned int vflags = 0, size = 0; + const unsigned char *p = signed_data; + STACK_OF(PKCS7_SIGNER_INFO) *si_sk = NULL; + PKCS7_SIGNER_INFO *si = NULL; + X509 *x = NULL; + X509_STORE *store = NULL; + X509_STORE_CTX cert_ctx; + STACK_OF(X509) *intermediateCAs = NULL; + STACK_OF(X509_CRL) *revoked = NULL; + STACK_OF(X509) *verified_chain = NULL; + ASN1_OBJECT *oid = NULL; + krb5_external_principal_identifier **krb5_verified_chain = NULL; + krb5_data *authz = NULL; + char buf[DN_BUF_LEN]; + +#ifdef DEBUG_ASN1 + print_buffer_bin(signed_data, signed_data_len, + "/tmp/client_received_pkcs7_signeddata"); +#endif + + /* Do this early enough to create the shadow OID for pkcs7-data if needed */ + oid = pkinit_pkcs7type2oid(plgctx, cms_msg_type); + if (oid == NULL) + goto cleanup; + + /* decode received PKCS7 message */ + if ((p7 = d2i_PKCS7(NULL, &p, (int)signed_data_len)) == NULL) { + unsigned long err = ERR_peek_error(); + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("%s: failed to decode message: %s\n", + __FUNCTION__, ERR_error_string(err, NULL)); + goto cleanup; + } + + /* verify that the received message is PKCS7 SignedData message */ + if (OBJ_obj2nid(p7->type) != NID_pkcs7_signed) { + pkiDebug("Expected id-signedData PKCS7 msg (received type = %d)\n", + OBJ_obj2nid(p7->type)); + krb5_set_error_message(context, retval, "wrong oid\n"); + goto cleanup; + } + + /* setup to verify X509 certificate used to sign PKCS7 message */ + if (!(store = X509_STORE_new())) + goto cleanup; + + /* check if we are inforcing CRL checking */ + vflags = X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL; + if (require_crl_checking) + X509_STORE_set_verify_cb_func(store, openssl_callback); + else + X509_STORE_set_verify_cb_func(store, openssl_callback_ignore_crls); + X509_STORE_set_flags(store, vflags); + + /* get the signer's information from the PKCS7 message */ + if ((si_sk = PKCS7_get_signer_info(p7)) == NULL) + goto cleanup; + if ((si = sk_PKCS7_SIGNER_INFO_value(si_sk, 0)) == NULL) + goto cleanup; + if ((x = PKCS7_cert_from_signer_info(p7, si)) == NULL) + goto cleanup; + + /* create available CRL information (get local CRLs and include CRLs + * received in the PKCS7 message + */ + if (idctx->revoked == NULL) + revoked = p7->d.sign->crl; + else if (p7->d.sign->crl == NULL) + revoked = idctx->revoked; + else { + size = sk_X509_CRL_num(idctx->revoked); + revoked = sk_X509_CRL_new_null(); + for (i = 0; i < size; i++) + sk_X509_CRL_push(revoked, sk_X509_CRL_value(idctx->revoked, i)); + size = sk_X509_num(p7->d.sign->crl); + for (i = 0; i < size; i++) + sk_X509_CRL_push(revoked, sk_X509_CRL_value(p7->d.sign->crl, i)); + } + + /* create available intermediate CAs chains (get local intermediateCAs and + * include the CA chain received in the PKCS7 message + */ + if (idctx->intermediateCAs == NULL) + intermediateCAs = p7->d.sign->cert; + else if (p7->d.sign->cert == NULL) + intermediateCAs = idctx->intermediateCAs; + else { + size = sk_X509_num(idctx->intermediateCAs); + intermediateCAs = sk_X509_new_null(); + for (i = 0; i < size; i++) { + sk_X509_push(intermediateCAs, + sk_X509_value(idctx->intermediateCAs, i)); + } + size = sk_X509_num(p7->d.sign->cert); + for (i = 0; i < size; i++) { + sk_X509_push(intermediateCAs, sk_X509_value(p7->d.sign->cert, i)); + } + } + + /* initialize x509 context with the received certificate and + * trusted and intermediate CA chains and CRLs + */ + if (!X509_STORE_CTX_init(&cert_ctx, store, x, intermediateCAs)) + goto cleanup; + + X509_STORE_CTX_set0_crls(&cert_ctx, revoked); + + /* add trusted CAs certificates for cert verification */ + if (idctx->trustedCAs != NULL) + X509_STORE_CTX_trusted_stack(&cert_ctx, idctx->trustedCAs); + else { + pkiDebug("unable to find any trusted CAs\n"); + goto cleanup; + } +#ifdef DEBUG_CERTCHAIN + if (intermediateCAs != NULL) { + size = sk_X509_num(intermediateCAs); + pkiDebug("untrusted cert chain of size %d\n", size); + for (i = 0; i < size; i++) { + X509_NAME_oneline(X509_get_subject_name( + sk_X509_value(intermediateCAs, i)), buf, sizeof(buf)); + pkiDebug("cert #%d: %s\n", i, buf); + } + } + if (idctx->trustedCAs != NULL) { + size = sk_X509_num(idctx->trustedCAs); + pkiDebug("trusted cert chain of size %d\n", size); + for (i = 0; i < size; i++) { + X509_NAME_oneline(X509_get_subject_name( + sk_X509_value(idctx->trustedCAs, i)), buf, sizeof(buf)); + pkiDebug("cert #%d: %s\n", i, buf); + } + } + if (revoked != NULL) { + size = sk_X509_CRL_num(revoked); + pkiDebug("CRL chain of size %d\n", size); + for (i = 0; i < size; i++) { + X509_CRL *crl = sk_X509_CRL_value(revoked, i); + X509_NAME_oneline(X509_CRL_get_issuer(crl), buf, sizeof(buf)); + pkiDebug("crls by CA #%d: %s\n", i , buf); + } + } +#endif + + i = X509_verify_cert(&cert_ctx); + if (i <= 0) { + int j = X509_STORE_CTX_get_error(&cert_ctx); + + reqctx->received_cert = X509_dup(cert_ctx.current_cert); + switch(j) { + case X509_V_ERR_CERT_REVOKED: + retval = KRB5KDC_ERR_REVOKED_CERTIFICATE; + break; + case X509_V_ERR_UNABLE_TO_GET_CRL: + retval = KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN; + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + retval = KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE; + break; + default: + retval = KRB5KDC_ERR_INVALID_CERTIFICATE; + } + X509_NAME_oneline(X509_get_subject_name( + reqctx->received_cert), buf, sizeof(buf)); + pkiDebug("problem with cert DN = %s (error=%d) %s\n", buf, j, + X509_verify_cert_error_string(j)); + krb5_set_error_message(context, retval, "%s\n", + X509_verify_cert_error_string(j)); +#ifdef DEBUG_CERTCHAIN + size = sk_X509_num(p7->d.sign->cert); + pkiDebug("received cert chain of size %d\n", size); + for (j = 0; j < size; j++) { + X509 *tmp_cert = sk_X509_value(p7->d.sign->cert, j); + X509_NAME_oneline(X509_get_subject_name(tmp_cert), buf, sizeof(buf)); + pkiDebug("cert #%d: %s\n", j, buf); + } +#endif + } else { + /* retrieve verified certificate chain */ + if (cms_msg_type == CMS_SIGN_CLIENT || cms_msg_type == CMS_SIGN_DRAFT9) + verified_chain = X509_STORE_CTX_get1_chain(&cert_ctx); + } + X509_STORE_CTX_cleanup(&cert_ctx); + if (i <= 0) + goto cleanup; + + out = BIO_new(BIO_s_mem()); + if (cms_msg_type == CMS_SIGN_DRAFT9) + flags |= PKCS7_NOATTR; + if (PKCS7_verify(p7, NULL, store, NULL, out, flags)) { + int valid_oid = 0; + + if (!OBJ_cmp(p7->d.sign->contents->type, oid)) + valid_oid = 1; + else if (cms_msg_type == CMS_SIGN_DRAFT9) { + /* + * Various implementations of the pa-type 15 request use + * different OIDS. We check that the returned object + * has any of the acceptable OIDs + */ + ASN1_OBJECT *client_oid = NULL, *server_oid = NULL, *rsa_oid = NULL; + client_oid = pkinit_pkcs7type2oid(plgctx, CMS_SIGN_CLIENT); + server_oid = pkinit_pkcs7type2oid(plgctx, CMS_SIGN_SERVER); + rsa_oid = pkinit_pkcs7type2oid(plgctx, CMS_ENVEL_SERVER); + if (!OBJ_cmp(p7->d.sign->contents->type, client_oid) || + !OBJ_cmp(p7->d.sign->contents->type, server_oid) || + !OBJ_cmp(p7->d.sign->contents->type, rsa_oid)) + valid_oid = 1; + } + + if (valid_oid) + pkiDebug("PKCS7 Verification successful\n"); + else { + pkiDebug("wrong oid in eContentType\n"); + print_buffer(p7->d.sign->contents->type->data, + (unsigned int)p7->d.sign->contents->type->length); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "wrong oid\n"); + goto cleanup; + } + } + else { + unsigned long err = ERR_peek_error(); + switch(ERR_GET_REASON(err)) { + case PKCS7_R_DIGEST_FAILURE: + retval = KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED; + break; + case PKCS7_R_SIGNATURE_FAILURE: + default: + retval = KRB5KDC_ERR_INVALID_SIG; + } + pkiDebug("PKCS7 Verification failure\n"); + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + goto cleanup; + } + + /* transfer the data from PKCS7 message into return buffer */ + for (size = 0;;) { + if ((*data = realloc(*data, size + 1024 * 10)) == NULL) + goto cleanup; + i = BIO_read(out, &((*data)[size]), 1024 * 10); + if (i <= 0) + break; + else + size += i; + } + *data_len = size; + + reqctx->received_cert = X509_dup(x); + + /* generate authorization data */ + if (cms_msg_type == CMS_SIGN_CLIENT || cms_msg_type == CMS_SIGN_DRAFT9) { + + if (authz_data == NULL || authz_data_len == NULL) + goto out; + + *authz_data = NULL; + retval = create_identifiers_from_stack(verified_chain, + &krb5_verified_chain); + if (retval) { + pkiDebug("create_identifiers_from_stack failed\n"); + goto cleanup; + } + + retval = k5int_encode_krb5_td_trusted_certifiers((const krb5_external_principal_identifier **)krb5_verified_chain, &authz); + if (retval) { + pkiDebug("encode_krb5_td_trusted_certifiers failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)authz->data, authz->length, + "/tmp/kdc_ad_initial_verified_cas"); +#endif + *authz_data = (unsigned char *)malloc(authz->length); + if (*authz_data == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(*authz_data, authz->data, authz->length); + *authz_data_len = authz->length; + } + out: + retval = 0; + + cleanup: + if (out != NULL) + BIO_free(out); + if (store != NULL) + X509_STORE_free(store); + if (p7 != NULL) { + if (idctx->intermediateCAs != NULL && p7->d.sign->cert) + sk_X509_free(intermediateCAs); + if (idctx->revoked != NULL && p7->d.sign->crl) + sk_X509_CRL_free(revoked); + PKCS7_free(p7); + } + if (verified_chain != NULL) + sk_X509_pop_free(verified_chain, X509_free); + if (krb5_verified_chain != NULL) + free_krb5_external_principal_identifier(&krb5_verified_chain); + if (authz != NULL) + krb5_free_data(context, authz); + + return retval; +} + +krb5_error_code +cms_envelopeddata_create(krb5_context context, + pkinit_plg_crypto_context plgctx, + pkinit_req_crypto_context reqctx, + pkinit_identity_crypto_context idctx, + krb5_preauthtype pa_type, + int include_certchain, + unsigned char *key_pack, + unsigned int key_pack_len, + unsigned char **out, + unsigned int *out_len) +{ + + krb5_error_code retval = ENOMEM; + PKCS7 *p7 = NULL; + BIO *in = NULL; + unsigned char *p = NULL, *signed_data = NULL, *enc_data = NULL; + int signed_data_len = 0, enc_data_len = 0, flags = PKCS7_BINARY; + STACK_OF(X509) *encerts = NULL; + const EVP_CIPHER *cipher = NULL; + int cms_msg_type; + + /* create the PKCS7 SignedData portion of the PKCS7 EnvelopedData */ + switch ((int)pa_type) { + case KRB5_PADATA_PK_AS_REQ_OLD: + case KRB5_PADATA_PK_AS_REP_OLD: + cms_msg_type = CMS_SIGN_DRAFT9; + break; + case KRB5_PADATA_PK_AS_REQ: + cms_msg_type = CMS_ENVEL_SERVER; + break; + default: + goto cleanup; + } + + retval = cms_signeddata_create(context, plgctx, reqctx, idctx, + cms_msg_type, include_certchain, key_pack, key_pack_len, + &signed_data, (unsigned int *)&signed_data_len); + if (retval) { + pkiDebug("failed to create pkcs7 signed data\n"); + goto cleanup; + } + + /* check we have client's certificate */ + if (reqctx->received_cert == NULL) { + retval = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + encerts = sk_X509_new_null(); + sk_X509_push(encerts, reqctx->received_cert); + + cipher = EVP_des_ede3_cbc(); + in = BIO_new(BIO_s_mem()); + switch (pa_type) { + case KRB5_PADATA_PK_AS_REQ: + prepare_enc_data(signed_data, signed_data_len, &enc_data, + &enc_data_len); + retval = BIO_write(in, enc_data, enc_data_len); + if (retval != enc_data_len) { + pkiDebug("BIO_write only wrote %d\n", retval); + goto cleanup; + } + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + retval = BIO_write(in, signed_data, signed_data_len); + if (retval != signed_data_len) { + pkiDebug("BIO_write only wrote %d\n", retval); + goto cleanup; + } + break; + default: + retval = -1; + goto cleanup; + } + + p7 = PKCS7_encrypt(encerts, in, cipher, flags); + if (p7 == NULL) { + pkiDebug("failed to encrypt PKCS7 object\n"); + retval = -1; + goto cleanup; + } + switch (pa_type) { + case KRB5_PADATA_PK_AS_REQ: + p7->d.enveloped->enc_data->content_type = + OBJ_nid2obj(NID_pkcs7_signed); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + p7->d.enveloped->enc_data->content_type = + OBJ_nid2obj(NID_pkcs7_data); + break; + break; + break; +break; + } + + *out_len = i2d_PKCS7(p7, NULL); + if (!*out_len || (p = *out = (unsigned char *)malloc(*out_len)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + retval = i2d_PKCS7(p7, &p); + if (!retval) { + pkiDebug("unable to write pkcs7 object\n"); + goto cleanup; + } + retval = 0; + +#ifdef DEBUG_ASN1 + print_buffer_bin(*out, *out_len, "/tmp/kdc_enveloped_data"); +#endif + +cleanup: + if (p7 != NULL) + PKCS7_free(p7); + if (in != NULL) + BIO_free(in); + if (signed_data != NULL) + free(signed_data); + if (enc_data != NULL) + free(enc_data); + if (encerts != NULL) + sk_X509_free(encerts); + + return retval; +} + +krb5_error_code +cms_envelopeddata_verify(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_preauthtype pa_type, + int require_crl_checking, + unsigned char *enveloped_data, + unsigned int enveloped_data_len, + unsigned char **data, + unsigned int *data_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + PKCS7 *p7 = NULL; + BIO *out = NULL; + int i = 0; + unsigned int size = 0; + const unsigned char *p = enveloped_data; + unsigned int tmp_buf_len = 0, tmp_buf2_len = 0, vfy_buf_len = 0; + unsigned char *tmp_buf = NULL, *tmp_buf2 = NULL, *vfy_buf = NULL; + int msg_type = 0; + +#ifdef DEBUG_ASN1 + print_buffer_bin(enveloped_data, enveloped_data_len, + "/tmp/client_envelopeddata"); +#endif + /* decode received PKCS7 message */ + if ((p7 = d2i_PKCS7(NULL, &p, (int)enveloped_data_len)) == NULL) { + unsigned long err = ERR_peek_error(); + pkiDebug("failed to decode pkcs7\n"); + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + goto cleanup; + } + + /* verify that the received message is PKCS7 EnvelopedData message */ + if (OBJ_obj2nid(p7->type) != NID_pkcs7_enveloped) { + pkiDebug("Expected id-enveloped PKCS7 msg (received type = %d)\n", + OBJ_obj2nid(p7->type)); + krb5_set_error_message(context, retval, "wrong oid\n"); + goto cleanup; + } + + /* decrypt received PKCS7 message */ + out = BIO_new(BIO_s_mem()); + if (pkcs7_decrypt(context, id_cryptoctx, p7, out)) { + pkiDebug("PKCS7 decryption successful\n"); + } else { + unsigned long err = ERR_peek_error(); + if (err != 0) + krb5_set_error_message(context, retval, "%s\n", + ERR_error_string(err, NULL)); + pkiDebug("PKCS7 decryption failed\n"); + goto cleanup; + } + + /* transfer the decoded PKCS7 SignedData message into a separate buffer */ + for (;;) { + if ((tmp_buf = realloc(tmp_buf, size + 1024 * 10)) == NULL) + goto cleanup; + i = BIO_read(out, &(tmp_buf[size]), 1024 * 10); + if (i <= 0) + break; + else + size += i; + } + tmp_buf_len = size; + +#ifdef DEBUG_ASN1 + print_buffer_bin(tmp_buf, tmp_buf_len, "/tmp/client_enc_keypack"); +#endif + /* verify PKCS7 SignedData message */ + switch (pa_type) { + case KRB5_PADATA_PK_AS_REP: + msg_type = CMS_ENVEL_SERVER; + + break; + case KRB5_PADATA_PK_AS_REP_OLD: + msg_type = CMS_SIGN_DRAFT9; + break; + default: + pkiDebug("%s: unrecognized pa_type = %d\n", __FUNCTION__, pa_type); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + /* + * If this is the RFC style, wrap the signed data to make + * decoding easier in the verify routine. + * For draft9-compatible, we don't do anything because it + * is already wrapped. + */ +#ifdef LONGHORN_BETA_COMPAT + /* + * The Longhorn server returns the expected RFC-style data, but + * it is missing the sequence tag and length, so it requires + * special processing when wrapping. + * This will hopefully be fixed before the final release and + * this can all be removed. + */ + if (msg_type == CMS_ENVEL_SERVER || longhorn == 1) { + retval = wrap_signeddata(tmp_buf, tmp_buf_len, + &tmp_buf2, &tmp_buf2_len, longhorn); + if (retval) { + pkiDebug("failed to encode signeddata\n"); + goto cleanup; + } + vfy_buf = tmp_buf2; + vfy_buf_len = tmp_buf2_len; + + } else { + vfy_buf = tmp_buf; + vfy_buf_len = tmp_buf_len; + } +#else + if (msg_type == CMS_ENVEL_SERVER) { + retval = wrap_signeddata(tmp_buf, tmp_buf_len, + &tmp_buf2, &tmp_buf2_len); + if (retval) { + pkiDebug("failed to encode signeddata\n"); + goto cleanup; + } + vfy_buf = tmp_buf2; + vfy_buf_len = tmp_buf2_len; + + } else { + vfy_buf = tmp_buf; + vfy_buf_len = tmp_buf_len; + } +#endif + +#ifdef DEBUG_ASN1 + print_buffer_bin(vfy_buf, vfy_buf_len, "/tmp/client_enc_keypack2"); +#endif + + retval = cms_signeddata_verify(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, msg_type, + require_crl_checking, + vfy_buf, vfy_buf_len, + data, data_len, NULL, NULL); + + if (!retval) + pkiDebug("PKCS7 Verification Success\n"); + else { + pkiDebug("PKCS7 Verification Failure\n"); + goto cleanup; + } + + retval = 0; + + cleanup: + + if (p7 != NULL) + PKCS7_free(p7); + if (out != NULL) + BIO_free(out); + if (tmp_buf != NULL) + free(tmp_buf); + if (tmp_buf2 != NULL) + free(tmp_buf2); + + return retval; +} + +static krb5_error_code +crypto_retrieve_X509_sans(krb5_context context, + pkinit_plg_crypto_context plgctx, + pkinit_req_crypto_context reqctx, + X509 *cert, + krb5_principal **princs_ret, + krb5_principal **upn_ret, + unsigned char ***dns_ret) +{ + krb5_error_code retval = EINVAL; + char buf[DN_BUF_LEN]; + int p = 0, u = 0, d = 0; + krb5_principal *princs = NULL; + krb5_principal *upns = NULL; + unsigned char **dnss = NULL; + int i, num_found = 0; + + if (princs_ret == NULL && upn_ret == NULL && dns_ret == NULL) { + pkiDebug("%s: nowhere to return any values!\n", __FUNCTION__); + return retval; + } + + if (cert == NULL) { + pkiDebug("%s: no certificate!\n", __FUNCTION__); + return retval; + } + + X509_NAME_oneline(X509_get_subject_name(cert), + buf, sizeof(buf)); + pkiDebug("%s: looking for SANs in cert = %s\n", __FUNCTION__, buf); + + if ((i = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1)) >= 0) { + X509_EXTENSION *ext = NULL; + GENERAL_NAMES *ialt = NULL; + GENERAL_NAME *gen = NULL; + int ret = 0; + unsigned int num_sans = 0; + + if (!(ext = X509_get_ext(cert, i)) || !(ialt = X509V3_EXT_d2i(ext))) { + pkiDebug("%s: found no subject alt name extensions\n", + __FUNCTION__); + goto cleanup; + } + num_sans = sk_GENERAL_NAME_num(ialt); + + pkiDebug("%s: found %d subject alt name extension(s)\n", + __FUNCTION__, num_sans); + + /* OK, we're likely returning something. Allocate return values */ + if (princs_ret != NULL) { + princs = calloc(num_sans + 1, sizeof(krb5_principal)); + if (princs == NULL) { + retval = ENOMEM; + goto cleanup; + } + } + if (upn_ret != NULL) { + upns = calloc(num_sans + 1, sizeof(krb5_principal)); + if (upns == NULL) { + retval = ENOMEM; + goto cleanup; + } + } + if (dns_ret != NULL) { + dnss = calloc(num_sans + 1, sizeof(*dnss)); + if (dnss == NULL) { + retval = ENOMEM; + goto cleanup; + } + } + + for (i = 0; i < num_sans; i++) { + krb5_data name = { 0, 0, NULL }; + + gen = sk_GENERAL_NAME_value(ialt, i); + switch (gen->type) { + case GEN_OTHERNAME: + name.length = gen->d.otherName->value->value.sequence->length; + name.data = (char *)gen->d.otherName->value->value.sequence->data; + if (princs != NULL + && OBJ_cmp(plgctx->id_pkinit_san, + gen->d.otherName->type_id) == 0) { +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)name.data, name.length, + "/tmp/pkinit_san"); +#endif + ret = k5int_decode_krb5_principal_name(&name, &princs[p]); + if (ret) { + pkiDebug("%s: failed decoding pkinit san value\n", + __FUNCTION__); + } else { + p++; + num_found++; + } + } else if (upns != NULL + && OBJ_cmp(plgctx->id_ms_san_upn, + gen->d.otherName->type_id) == 0) { + ret = krb5_parse_name(context, name.data, &upns[u]); + if (ret) { + pkiDebug("%s: failed parsing ms-upn san value\n", + __FUNCTION__); + } else { + u++; + num_found++; + } + } else { + pkiDebug("%s: unrecognized othername oid in SAN\n", + __FUNCTION__); + continue; + } + + break; + case GEN_DNS: + if (dnss != NULL) { + pkiDebug("%s: found dns name = %s\n", + __FUNCTION__, gen->d.dNSName->data); + dnss[d] = (unsigned char *) + strdup((char *)gen->d.dNSName->data); + if (dnss[d] == NULL) { + pkiDebug("%s: failed to duplicate dns name\n", + __FUNCTION__); + } else { + d++; + num_found++; + } + } + break; + default: + pkiDebug("%s: SAN type = %d expecting %d\n", + __FUNCTION__, gen->type, GEN_OTHERNAME); + } + } + sk_GENERAL_NAME_pop_free(ialt, GENERAL_NAME_free); + } + + retval = 0; + if (princs) + *princs_ret = princs; + if (upns) + *upn_ret = upns; + if (dnss) + *dns_ret = dnss; + + cleanup: + if (retval) { + if (princs != NULL) { + for (i = 0; princs[i] != NULL; i++) + krb5_free_principal(context, princs[i]); + free(princs); + } + if (upns != NULL) { + for (i = 0; upns[i] != NULL; i++) + krb5_free_principal(context, upns[i]); + free(upns); + } + if (dnss != NULL) { + for (i = 0; dnss[i] != NULL; i++) + free(dnss[i]); + free(dnss); + } + } + return retval; +} + +krb5_error_code +crypto_retrieve_cert_sans(krb5_context context, + pkinit_plg_crypto_context plgctx, + pkinit_req_crypto_context reqctx, + pkinit_identity_crypto_context idctx, + krb5_principal **princs_ret, + krb5_principal **upn_ret, + unsigned char ***dns_ret) +{ + krb5_error_code retval = EINVAL; + + if (reqctx->received_cert == NULL) { + pkiDebug("%s: No certificate!\n", __FUNCTION__); + return retval; + } + + return crypto_retrieve_X509_sans(context, plgctx, reqctx, + reqctx->received_cert, princs_ret, + upn_ret, dns_ret); +} + +krb5_error_code +crypto_check_cert_eku(krb5_context context, + pkinit_plg_crypto_context plgctx, + pkinit_req_crypto_context reqctx, + pkinit_identity_crypto_context idctx, + int checking_kdc_cert, + int allow_secondary_usage, + int *valid_eku) +{ + char buf[DN_BUF_LEN]; + int found_eku = 0; + krb5_error_code retval = EINVAL; + int i; + + *valid_eku = 0; + if (reqctx->received_cert == NULL) + goto cleanup; + + X509_NAME_oneline(X509_get_subject_name(reqctx->received_cert), + buf, sizeof(buf)); + pkiDebug("%s: looking for EKUs in cert = %s\n", __FUNCTION__, buf); + + if ((i = X509_get_ext_by_NID(reqctx->received_cert, + NID_ext_key_usage, -1)) >= 0) { + EXTENDED_KEY_USAGE *extusage; + + extusage = X509_get_ext_d2i(reqctx->received_cert, NID_ext_key_usage, + NULL, NULL); + if (extusage) { + pkiDebug("%s: found eku info in the cert\n", __FUNCTION__); + for (i = 0; found_eku == 0 && i < sk_ASN1_OBJECT_num(extusage); i++) { + ASN1_OBJECT *tmp_oid; + + tmp_oid = sk_ASN1_OBJECT_value(extusage, i); + pkiDebug("%s: checking eku %d of %d, allow_secondary = %d\n", + __FUNCTION__, i+1, sk_ASN1_OBJECT_num(extusage), + allow_secondary_usage); + if (checking_kdc_cert) { + if ((OBJ_cmp(tmp_oid, plgctx->id_pkinit_KPKdc) == 0) + || (allow_secondary_usage + && OBJ_cmp(tmp_oid, plgctx->id_kp_serverAuth) == 0)) + found_eku = 1; + } else { + if ((OBJ_cmp(tmp_oid, plgctx->id_pkinit_KPClientAuth) == 0) + || (allow_secondary_usage + && OBJ_cmp(tmp_oid, plgctx->id_ms_kp_sc_logon) == 0)) + found_eku = 1; + } + } + } + EXTENDED_KEY_USAGE_free(extusage); + + if (found_eku) { + ASN1_BIT_STRING *usage = NULL; + pkiDebug("%s: found acceptable EKU, checking for digitalSignature\n", __FUNCTION__); + + /* check that digitalSignature KeyUsage is present */ + if ((usage = X509_get_ext_d2i(reqctx->received_cert, + NID_key_usage, NULL, NULL))) { + + if (!ku_reject(reqctx->received_cert, + X509v3_KU_DIGITAL_SIGNATURE)) { + pkiDebug("%s: found digitalSignature KU\n", + __FUNCTION__); + *valid_eku = 1; + } else + pkiDebug("%s: didn't find digitalSignature KU\n", + __FUNCTION__); + } + ASN1_BIT_STRING_free(usage); + } + } + retval = 0; +cleanup: + pkiDebug("%s: returning retval %d, valid_eku %d\n", + __FUNCTION__, retval, *valid_eku); + return retval; +} + +krb5_error_code +pkinit_octetstring2key(krb5_context context, + krb5_enctype etype, + unsigned char *key, + unsigned int dh_key_len, + krb5_keyblock * key_block) +{ + krb5_error_code retval; + unsigned char *buf = NULL; + unsigned char md[SHA_DIGEST_LENGTH]; + unsigned char counter; + size_t keybytes, keylength, offset; + krb5_data random_data; + + + if ((buf = (unsigned char *) malloc(dh_key_len)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + memset(buf, 0, dh_key_len); + + counter = 0; + offset = 0; + do { + SHA_CTX c; + + SHA1_Init(&c); + SHA1_Update(&c, &counter, 1); + SHA1_Update(&c, key, dh_key_len); + SHA1_Final(md, &c); + + if (dh_key_len - offset < sizeof(md)) + memcpy(buf + offset, md, dh_key_len - offset); + else + memcpy(buf + offset, md, sizeof(md)); + + offset += sizeof(md); + counter++; + } while (offset < dh_key_len); + + key_block->magic = 0; + key_block->enctype = etype; + + retval = krb5_c_keylengths(context, etype, &keybytes, &keylength); + if (retval) + goto cleanup; + + key_block->length = keylength; + key_block->contents = calloc(keylength, sizeof(unsigned char *)); + if (key_block->contents == NULL) { + retval = ENOMEM; + goto cleanup; + } + + random_data.length = keybytes; + random_data.data = (char *)buf; + + retval = krb5_c_random_to_key(context, etype, &random_data, key_block); + + cleanup: + if (buf != NULL) + free(buf); + if (retval && key_block->contents != NULL && key_block->length != 0) { + memset(key_block->contents, 0, key_block->length); + key_block->length = 0; + } + + return retval; +} + +krb5_error_code +client_create_dh(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int dh_size, + unsigned char **dh_params, + unsigned int *dh_params_len, + unsigned char **dh_pubkey, + unsigned int *dh_pubkey_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + unsigned char *buf = NULL; + int dh_err = 0; + ASN1_INTEGER *pub_key = NULL; + + if (cryptoctx->dh == NULL) { + if ((cryptoctx->dh = DH_new()) == NULL) + goto cleanup; + if ((cryptoctx->dh->g = BN_new()) == NULL || + (cryptoctx->dh->q = BN_new()) == NULL) + goto cleanup; + + switch(dh_size) { + case 1024: + pkiDebug("client uses 1024 DH keys\n"); + cryptoctx->dh->p = get_rfc2409_prime_1024(NULL); + break; + case 2048: + pkiDebug("client uses 2048 DH keys\n"); + cryptoctx->dh->p = BN_bin2bn(pkinit_2048_dhprime, + sizeof(pkinit_2048_dhprime), NULL); + break; + case 4096: + pkiDebug("client uses 4096 DH keys\n"); + cryptoctx->dh->p = BN_bin2bn(pkinit_4096_dhprime, + sizeof(pkinit_4096_dhprime), NULL); + break; + default: + goto cleanup; + } + + BN_set_word((cryptoctx->dh->g), DH_GENERATOR_2); + BN_rshift1(cryptoctx->dh->q, cryptoctx->dh->p); + } + + DH_generate_key(cryptoctx->dh); + DH_check(cryptoctx->dh, &dh_err); + if (dh_err != 0) { + pkiDebug("Warning: dh_check failed with %d\n", dh_err); + if (dh_err & DH_CHECK_P_NOT_PRIME) + pkiDebug("p value is not prime\n"); + if (dh_err & DH_CHECK_P_NOT_SAFE_PRIME) + pkiDebug("p value is not a safe prime\n"); + if (dh_err & DH_UNABLE_TO_CHECK_GENERATOR) + pkiDebug("unable to check the generator value\n"); + if (dh_err & DH_NOT_SUITABLE_GENERATOR) + pkiDebug("the g value is not a generator\n"); + } +#ifdef DEBUG_DH + print_dh(cryptoctx->dh, "client's DH params\n"); + print_pubkey(cryptoctx->dh->pub_key, "client's pub_key="); +#endif + + DH_check_pub_key(cryptoctx->dh, cryptoctx->dh->pub_key, &dh_err); + if (dh_err != 0) { + pkiDebug("dh_check_pub_key failed with %d\n", dh_err); + goto cleanup; + } + + /* pack DHparams */ + /* aglo: usually we could just call i2d_DHparams to encode DH params + * however, PKINIT requires RFC3279 encoding and openssl does pkcs#3. + */ + retval = pkinit_encode_dh_params(cryptoctx->dh->p, cryptoctx->dh->g, + cryptoctx->dh->q, dh_params, dh_params_len); + if (retval) + goto cleanup; + + /* pack DH public key */ + /* Diffie-Hellman public key must be ASN1 encoded as an INTEGER; this + * encoding shall be used as the contents (the value) of the + * subjectPublicKey component (a BIT STRING) of the SubjectPublicKeyInfo + * data element + */ + if ((pub_key = BN_to_ASN1_INTEGER(cryptoctx->dh->pub_key, NULL)) == NULL) + goto cleanup; + *dh_pubkey_len = i2d_ASN1_INTEGER(pub_key, NULL); + if ((buf = *dh_pubkey = (unsigned char *) + malloc((size_t) *dh_pubkey_len)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + i2d_ASN1_INTEGER(pub_key, &buf); + + if (pub_key != NULL) + ASN1_INTEGER_free(pub_key); + + retval = 0; + return retval; + + cleanup: + if (cryptoctx->dh != NULL) + DH_free(cryptoctx->dh); + cryptoctx->dh = NULL; + if (*dh_params != NULL) + free(*dh_params); + *dh_params = NULL; + if (*dh_pubkey != NULL) + free(*dh_pubkey); + *dh_pubkey = NULL; + if (pub_key != NULL) + ASN1_INTEGER_free(pub_key); + + return retval; +} + +krb5_error_code +client_process_dh(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *subjectPublicKey_data, + unsigned int subjectPublicKey_length, + unsigned char **client_key, + unsigned int *client_key_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + BIGNUM *server_pub_key = NULL; + ASN1_INTEGER *pub_key = NULL; + const unsigned char *p = NULL; + unsigned char *data = NULL; + long data_len; + + /* decode subjectPublicKey (retrieve INTEGER from OCTET_STRING) */ + + if (der_decode_data(subjectPublicKey_data, (long)subjectPublicKey_length, + &data, &data_len) != 0) { + pkiDebug("failed to decode subjectPublicKey\n"); + retval = -1; + goto cleanup; + } + + *client_key_len = DH_size(cryptoctx->dh); + if ((*client_key = (unsigned char *) + malloc((size_t) *client_key_len)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + p = data; + if ((pub_key = d2i_ASN1_INTEGER(NULL, &p, data_len)) == NULL) + goto cleanup; + if ((server_pub_key = ASN1_INTEGER_to_BN(pub_key, NULL)) == NULL) + goto cleanup; + + DH_compute_key(*client_key, server_pub_key, cryptoctx->dh); +#ifdef DEBUG_DH + print_pubkey(server_pub_key, "server's pub_key="); + pkiDebug("client secret key (%d)= ", *client_key_len); + print_buffer(*client_key, *client_key_len); +#endif + + retval = 0; + if (server_pub_key != NULL) + BN_free(server_pub_key); + if (pub_key != NULL) + ASN1_INTEGER_free(pub_key); + if (data != NULL) + free (data); + + return retval; + + cleanup: + if (*client_key != NULL) + free(*client_key); + *client_key = NULL; + if (pub_key != NULL) + ASN1_INTEGER_free(pub_key); + if (data != NULL) + free (data); + + return retval; +} + +krb5_error_code +server_check_dh(krb5_context context, + pkinit_plg_crypto_context cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_octet_data *dh_params, + int minbits) +{ + DH *dh = NULL; + unsigned char *tmp = NULL; + int dh_prime_bits; + krb5_error_code retval = KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED; + + tmp = dh_params->data; + dh = DH_new(); + dh = pkinit_decode_dh_params(&dh, &tmp, dh_params->length); + if (dh == NULL) { + pkiDebug("failed to decode dhparams\n"); + goto cleanup; + } + + /* KDC SHOULD check to see if the key parameters satisfy its policy */ + dh_prime_bits = BN_num_bits(dh->p); + if (minbits && dh_prime_bits < minbits) { + pkiDebug("client sent dh params with %d bits, we require %d\n", + dh_prime_bits, minbits); + goto cleanup; + } + + /* check dhparams is group 2 */ + if (pkinit_check_dh_params(cryptoctx->dh_1024->p, + dh->p, dh->g, dh->q) == 0) { + retval = 0; + goto cleanup; + } + + /* check dhparams is group 14 */ + if (pkinit_check_dh_params(cryptoctx->dh_2048->p, + dh->p, dh->g, dh->q) == 0) { + retval = 0; + goto cleanup; + } + + /* check dhparams is group 16 */ + if (pkinit_check_dh_params(cryptoctx->dh_4096->p, + dh->p, dh->g, dh->q) == 0) { + retval = 0; + goto cleanup; + } + + cleanup: + if (retval == 0) + req_cryptoctx->dh = dh; + else + DH_free(dh); + + return retval; +} + +/* kdc's dh function */ +krb5_error_code +server_process_dh(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **dh_pubkey, + unsigned int *dh_pubkey_len, + unsigned char **server_key, + unsigned int *server_key_len) +{ + krb5_error_code retval = ENOMEM; + DH *dh = NULL, *dh_server = NULL; + unsigned char *p = NULL; + ASN1_INTEGER *pub_key = NULL; + + /* get client's received DH parameters that we saved in server_check_dh */ + dh = cryptoctx->dh; + + dh_server = DH_new(); + if (dh_server == NULL) + goto cleanup; + dh_server->p = BN_dup(dh->p); + dh_server->g = BN_dup(dh->g); + dh_server->q = BN_dup(dh->q); + + /* decode client's public key */ + p = data; + pub_key = d2i_ASN1_INTEGER(NULL, (const unsigned char **)&p, (int)data_len); + if (pub_key == NULL) + goto cleanup; + dh->pub_key = ASN1_INTEGER_to_BN(pub_key, NULL); + if (dh->pub_key == NULL) + goto cleanup; + ASN1_INTEGER_free(pub_key); + + if (!DH_generate_key(dh_server)) + goto cleanup; + + /* generate DH session key */ + *server_key_len = DH_size(dh_server); + if ((*server_key = (unsigned char *) malloc((size_t)*server_key_len)) == NULL) + goto cleanup; + DH_compute_key(*server_key, dh->pub_key, dh_server); + +#ifdef DEBUG_DH + print_dh(dh_server, "client&server's DH params\n"); + print_pubkey(dh->pub_key, "client's pub_key="); + print_pubkey(dh_server->pub_key, "server's pub_key="); + pkiDebug("server secret key="); + print_buffer(*server_key, *server_key_len); +#endif + + /* KDC reply */ + /* pack DH public key */ + /* Diffie-Hellman public key must be ASN1 encoded as an INTEGER; this + * encoding shall be used as the contents (the value) of the + * subjectPublicKey component (a BIT STRING) of the SubjectPublicKeyInfo + * data element + */ + if ((pub_key = BN_to_ASN1_INTEGER(dh_server->pub_key, NULL)) == NULL) + goto cleanup; + *dh_pubkey_len = i2d_ASN1_INTEGER(pub_key, NULL); + if ((p = *dh_pubkey = (unsigned char *) malloc((size_t)*dh_pubkey_len)) == NULL) + goto cleanup; + i2d_ASN1_INTEGER(pub_key, &p); + if (pub_key != NULL) + ASN1_INTEGER_free(pub_key); + + retval = 0; + + if (dh_server != NULL) + DH_free(dh_server); + return retval; + + cleanup: + if (dh_server != NULL) + DH_free(dh_server); + if (*dh_pubkey != NULL) + free(*dh_pubkey); + if (*server_key != NULL) + free(*server_key); + + return retval; +} + +static void +openssl_init() +{ + static int did_init = 0; + + if (!did_init) { + /* initialize openssl routines */ + CRYPTO_malloc_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + did_init++; + } +} + +static krb5_error_code +pkinit_encode_dh_params(BIGNUM *p, BIGNUM *g, BIGNUM *q, + unsigned char **buf, unsigned int *buf_len) +{ + krb5_error_code retval = ENOMEM; + int bufsize = 0, r = 0; + unsigned char *tmp = NULL; + ASN1_INTEGER *ap = NULL, *ag = NULL, *aq = NULL; + + if ((ap = BN_to_ASN1_INTEGER(p, NULL)) == NULL) + goto cleanup; + if ((ag = BN_to_ASN1_INTEGER(g, NULL)) == NULL) + goto cleanup; + if ((aq = BN_to_ASN1_INTEGER(q, NULL)) == NULL) + goto cleanup; + bufsize = i2d_ASN1_INTEGER(ap, NULL); + bufsize += i2d_ASN1_INTEGER(ag, NULL); + bufsize += i2d_ASN1_INTEGER(aq, NULL); + + r = ASN1_object_size(1, bufsize, V_ASN1_SEQUENCE); + + tmp = *buf = (unsigned char *)malloc((size_t) r); + if (tmp == NULL) + goto cleanup; + + ASN1_put_object(&tmp, 1, bufsize, V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + + i2d_ASN1_INTEGER(ap, &tmp); + i2d_ASN1_INTEGER(ag, &tmp); + i2d_ASN1_INTEGER(aq, &tmp); + + *buf_len = r; + + retval = 0; + +cleanup: + if (ap != NULL) + ASN1_INTEGER_free(ap); + if (ag != NULL) + ASN1_INTEGER_free(ag); + if (aq != NULL) + ASN1_INTEGER_free(aq); + + return retval; +} + +static DH * +pkinit_decode_dh_params(DH ** a, unsigned char **pp, unsigned int len) +{ + ASN1_INTEGER ai, *aip = NULL; + long length = (long) len; + + M_ASN1_D2I_vars(a, DH *, DH_new); + + M_ASN1_D2I_Init(); + M_ASN1_D2I_start_sequence(); + aip = &ai; + ai.data = NULL; + ai.length = 0; + M_ASN1_D2I_get_x(ASN1_INTEGER, aip, d2i_ASN1_INTEGER); + if (aip == NULL) + return NULL; + else { + (*a)->p = ASN1_INTEGER_to_BN(aip, NULL); + if ((*a)->p == NULL) + return NULL; + if (ai.data != NULL) { + OPENSSL_free(ai.data); + ai.data = NULL; + ai.length = 0; + } + } + M_ASN1_D2I_get_x(ASN1_INTEGER, aip, d2i_ASN1_INTEGER); + if (aip == NULL) + return NULL; + else { + (*a)->g = ASN1_INTEGER_to_BN(aip, NULL); + if ((*a)->g == NULL) + return NULL; + if (ai.data != NULL) { + OPENSSL_free(ai.data); + ai.data = NULL; + ai.length = 0; + } + + } + M_ASN1_D2I_get_x(ASN1_INTEGER, aip, d2i_ASN1_INTEGER); + if (aip == NULL) + return NULL; + else { + (*a)->q = ASN1_INTEGER_to_BN(aip, NULL); + if ((*a)->q == NULL) + return NULL; + if (ai.data != NULL) { + OPENSSL_free(ai.data); + ai.data = NULL; + ai.length = 0; + } + + } + M_ASN1_D2I_end_sequence(); + M_ASN1_D2I_Finish(a, DH_free, 0); + +} + +static krb5_error_code +pkinit_create_sequence_of_principal_identifiers( + krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int type, + krb5_data **out_data) +{ + krb5_error_code retval = KRB5KRB_ERR_GENERIC; + krb5_external_principal_identifier **krb5_trusted_certifiers = NULL; + krb5_data *td_certifiers = NULL, *data = NULL; + krb5_typed_data **typed_data = NULL; + + switch(type) { + case TD_TRUSTED_CERTIFIERS: + retval = create_krb5_trustedCertifiers(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, &krb5_trusted_certifiers); + if (retval) { + pkiDebug("create_krb5_trustedCertifiers failed\n"); + goto cleanup; + } + break; + case TD_INVALID_CERTIFICATES: + retval = create_krb5_invalidCertificates(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, &krb5_trusted_certifiers); + if (retval) { + pkiDebug("create_krb5_invalidCertificates failed\n"); + goto cleanup; + } + break; + default: + retval = -1; + goto cleanup; + } + + retval = k5int_encode_krb5_td_trusted_certifiers((const krb5_external_principal_identifier **)krb5_trusted_certifiers, &td_certifiers); + if (retval) { + pkiDebug("encode_krb5_td_trusted_certifiers failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)td_certifiers->data, + td_certifiers->length, "/tmp/kdc_td_certifiers"); +#endif + typed_data = malloc (2 * sizeof(krb5_typed_data *)); + if (typed_data == NULL) { + retval = ENOMEM; + goto cleanup; + } + typed_data[1] = NULL; + init_krb5_typed_data(&typed_data[0]); + if (typed_data[0] == NULL) { + retval = ENOMEM; + goto cleanup; + } + typed_data[0]->type = type; + typed_data[0]->length = td_certifiers->length; + typed_data[0]->data = (unsigned char *)td_certifiers->data; + retval = k5int_encode_krb5_typed_data((const krb5_typed_data **)typed_data, + &data); + if (retval) { + pkiDebug("encode_krb5_typed_data failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)data->data, data->length, + "/tmp/kdc_edata"); +#endif + *out_data = (krb5_data *)malloc(sizeof(krb5_data)); + (*out_data)->length = data->length; + (*out_data)->data = (char *)malloc(data->length); + memcpy((*out_data)->data, data->data, data->length); + + retval = 0; + +cleanup: + if (krb5_trusted_certifiers != NULL) + free_krb5_external_principal_identifier(&krb5_trusted_certifiers); + + if (data != NULL) { + if (data->data != NULL) + free(data->data); + free(data); + } + + if (td_certifiers != NULL) + free(td_certifiers); + + if (typed_data != NULL) + free_krb5_typed_data(&typed_data); + + return retval; +} + +krb5_error_code +pkinit_create_td_trusted_certifiers(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_data **out_data) +{ + krb5_error_code retval = KRB5KRB_ERR_GENERIC; + + retval = pkinit_create_sequence_of_principal_identifiers(context, + plg_cryptoctx, req_cryptoctx, id_cryptoctx, + TD_TRUSTED_CERTIFIERS, out_data); + + return retval; +} + +krb5_error_code +pkinit_create_td_invalid_certificate( + krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_data **out_data) +{ + krb5_error_code retval = KRB5KRB_ERR_GENERIC; + + retval = pkinit_create_sequence_of_principal_identifiers(context, + plg_cryptoctx, req_cryptoctx, id_cryptoctx, + TD_INVALID_CERTIFICATES, out_data); + + return retval; +} + +krb5_error_code +pkinit_create_td_dh_parameters(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + pkinit_plg_opts *opts, + krb5_data **out_data) +{ + krb5_error_code retval = ENOMEM; + unsigned int buf1_len = 0, buf2_len = 0, buf3_len = 0, i = 0; + unsigned char *buf1 = NULL, *buf2 = NULL, *buf3 = NULL; + krb5_typed_data **typed_data = NULL; + krb5_data *data = NULL, *encoded_algId = NULL; + krb5_algorithm_identifier **algId = NULL; + + if (opts->dh_min_bits > 4096) + goto cleanup; + + if (opts->dh_min_bits <= 1024) { + retval = pkinit_encode_dh_params(plg_cryptoctx->dh_1024->p, + plg_cryptoctx->dh_1024->g, plg_cryptoctx->dh_1024->q, + &buf1, &buf1_len); + if (retval) + goto cleanup; + } + if (opts->dh_min_bits <= 2048) { + retval = pkinit_encode_dh_params(plg_cryptoctx->dh_2048->p, + plg_cryptoctx->dh_2048->g, plg_cryptoctx->dh_2048->q, + &buf2, &buf2_len); + if (retval) + goto cleanup; + } + retval = pkinit_encode_dh_params(plg_cryptoctx->dh_4096->p, + plg_cryptoctx->dh_4096->g, plg_cryptoctx->dh_4096->q, + &buf3, &buf3_len); + if (retval) + goto cleanup; + + if (opts->dh_min_bits <= 1024) { + algId = malloc(4 * sizeof(krb5_algorithm_identifier *)); + if (algId == NULL) + goto cleanup; + algId[3] = NULL; + algId[0] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[0] == NULL) + goto cleanup; + algId[0]->parameters.data = (unsigned char *)malloc(buf2_len); + if (algId[0]->parameters.data == NULL) + goto cleanup; + memcpy(algId[0]->parameters.data, buf2, buf2_len); + algId[0]->parameters.length = buf2_len; + algId[0]->algorithm = dh_oid; + + algId[1] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[1] == NULL) + goto cleanup; + algId[1]->parameters.data = (unsigned char *)malloc(buf3_len); + if (algId[1]->parameters.data == NULL) + goto cleanup; + memcpy(algId[1]->parameters.data, buf3, buf3_len); + algId[1]->parameters.length = buf3_len; + algId[1]->algorithm = dh_oid; + + algId[2] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[2] == NULL) + goto cleanup; + algId[2]->parameters.data = (unsigned char *)malloc(buf1_len); + if (algId[2]->parameters.data == NULL) + goto cleanup; + memcpy(algId[2]->parameters.data, buf1, buf1_len); + algId[2]->parameters.length = buf1_len; + algId[2]->algorithm = dh_oid; + + } else if (opts->dh_min_bits <= 2048) { + algId = malloc(3 * sizeof(krb5_algorithm_identifier *)); + if (algId == NULL) + goto cleanup; + algId[2] = NULL; + algId[0] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[0] == NULL) + goto cleanup; + algId[0]->parameters.data = (unsigned char *)malloc(buf2_len); + if (algId[0]->parameters.data == NULL) + goto cleanup; + memcpy(algId[0]->parameters.data, buf2, buf2_len); + algId[0]->parameters.length = buf2_len; + algId[0]->algorithm = dh_oid; + + algId[1] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[1] == NULL) + goto cleanup; + algId[1]->parameters.data = (unsigned char *)malloc(buf3_len); + if (algId[1]->parameters.data == NULL) + goto cleanup; + memcpy(algId[1]->parameters.data, buf3, buf3_len); + algId[1]->parameters.length = buf3_len; + algId[1]->algorithm = dh_oid; + + } else if (opts->dh_min_bits <= 4096) { + algId = malloc(2 * sizeof(krb5_algorithm_identifier *)); + if (algId == NULL) + goto cleanup; + algId[1] = NULL; + algId[0] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (algId[0] == NULL) + goto cleanup; + algId[0]->parameters.data = (unsigned char *)malloc(buf3_len); + if (algId[0]->parameters.data == NULL) + goto cleanup; + memcpy(algId[0]->parameters.data, buf3, buf3_len); + algId[0]->parameters.length = buf3_len; + algId[0]->algorithm = dh_oid; + + } + retval = k5int_encode_krb5_td_dh_parameters((const krb5_algorithm_identifier **)algId, &encoded_algId); + if (retval) + goto cleanup; +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)encoded_algId->data, + encoded_algId->length, "/tmp/kdc_td_dh_params"); +#endif + typed_data = malloc (2 * sizeof(krb5_typed_data *)); + if (typed_data == NULL) { + retval = ENOMEM; + goto cleanup; + } + typed_data[1] = NULL; + init_krb5_typed_data(&typed_data[0]); + if (typed_data == NULL) { + retval = ENOMEM; + goto cleanup; + } + typed_data[0]->type = TD_DH_PARAMETERS; + typed_data[0]->length = encoded_algId->length; + typed_data[0]->data = (unsigned char *)encoded_algId->data; + retval = k5int_encode_krb5_typed_data((const krb5_typed_data**)typed_data, + &data); + if (retval) { + pkiDebug("encode_krb5_typed_data failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)data->data, data->length, + "/tmp/kdc_edata"); +#endif + *out_data = (krb5_data *)malloc(sizeof(krb5_data)); + if (*out_data == NULL) + goto cleanup; + (*out_data)->length = data->length; + (*out_data)->data = (char *)malloc(data->length); + if ((*out_data)->data == NULL) { + free(*out_data); + *out_data = NULL; + goto cleanup; + } + memcpy((*out_data)->data, data->data, data->length); + + retval = 0; +cleanup: + + if (buf1 != NULL) + free(buf1); + if (buf2 != NULL) + free(buf2); + if (buf3 != NULL) + free(buf3); + if (data != NULL) { + if (data->data != NULL) + free(data->data); + free(data); + } + if (typed_data != NULL) + free_krb5_typed_data(&typed_data); + if (encoded_algId != NULL) + free(encoded_algId); + + if (algId != NULL) { + while(algId[i] != NULL) { + if (algId[i]->parameters.data != NULL) + free(algId[i]->parameters.data); + free(algId[i]); + i++; + } + free(algId); + } + + return retval; +} + +krb5_error_code +pkinit_check_kdc_pkid(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *pdid_buf, + unsigned int pkid_len, + int *valid_kdcPkId) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + PKCS7_ISSUER_AND_SERIAL *is = NULL; + const unsigned char *p = pdid_buf; + int status = 1; + X509 *kdc_cert = sk_X509_value(id_cryptoctx->my_certs, id_cryptoctx->cert_index); + + *valid_kdcPkId = 0; + pkiDebug("found kdcPkId in AS REQ\n"); + is = d2i_PKCS7_ISSUER_AND_SERIAL(NULL, &p, (int)pkid_len); + if (is == NULL) + goto cleanup; + + status = X509_NAME_cmp(X509_get_issuer_name(kdc_cert), is->issuer); + if (!status) { + status = ASN1_INTEGER_cmp(X509_get_serialNumber(kdc_cert), is->serial); + if (!status) + *valid_kdcPkId = 1; + } + + retval = 0; +cleanup: + X509_NAME_free(is->issuer); + ASN1_INTEGER_free(is->serial); + free(is); + + return retval; +} + +static int +pkinit_check_dh_params(BIGNUM * p1, BIGNUM * p2, BIGNUM * g1, BIGNUM * q1) +{ + BIGNUM *g2 = NULL, *q2 = NULL; + int retval = -1; + + if (!BN_cmp(p1, p2)) { + g2 = BN_new(); + BN_set_word(g2, DH_GENERATOR_2); + if (!BN_cmp(g1, g2)) { + q2 = BN_new(); + BN_rshift1(q2, p1); + if (!BN_cmp(q1, q2)) { + pkiDebug("good %d dhparams\n", BN_num_bits(p1)); + retval = 0; + } else + pkiDebug("bad group 2 q dhparameter\n"); + BN_free(q2); + } else + pkiDebug("bad g dhparameter\n"); + BN_free(g2); + } else + pkiDebug("p is not well-known group 2 dhparameter\n"); + + return retval; +} + +krb5_error_code +pkinit_process_td_dh_params(krb5_context context, + pkinit_plg_crypto_context cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_algorithm_identifier **algId, + int *new_dh_size) +{ + krb5_error_code retval = KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED; + int i = 0, use_sent_dh = 0, ok = 0; + + pkiDebug("dh parameters\n"); + + while (algId[i] != NULL) { + DH *dh = NULL; + unsigned char *tmp = NULL; + int dh_prime_bits = 0; + + if (algId[i]->algorithm.length != dh_oid.length || + memcmp(algId[i]->algorithm.data, dh_oid.data, dh_oid.length)) + goto cleanup; + + tmp = algId[i]->parameters.data; + dh = DH_new(); + dh = pkinit_decode_dh_params(&dh, &tmp, algId[i]->parameters.length); + dh_prime_bits = BN_num_bits(dh->p); + pkiDebug("client sent %d DH bits server prefers %d DH bits\n", + *new_dh_size, dh_prime_bits); + switch(dh_prime_bits) { + case 1024: + if (pkinit_check_dh_params(cryptoctx->dh_1024->p, dh->p, + dh->g, dh->q) == 0) { + *new_dh_size = 1024; + ok = 1; + } + break; + case 2048: + if (pkinit_check_dh_params(cryptoctx->dh_2048->p, dh->p, + dh->g, dh->q) == 0) { + *new_dh_size = 2048; + ok = 1; + } + break; + case 4096: + if (pkinit_check_dh_params(cryptoctx->dh_4096->p, dh->p, + dh->g, dh->q) == 0) { + *new_dh_size = 4096; + ok = 1; + } + break; + default: + break; + } + if (!ok) { + DH_check(dh, &retval); + if (retval != 0) { + pkiDebug("DH parameters provided by server are unacceptable\n"); + retval = KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED; + } + else { + use_sent_dh = 1; + ok = 1; + } + } + if (!use_sent_dh) + DH_free(dh); + if (ok) { + if (req_cryptoctx->dh != NULL) { + DH_free(req_cryptoctx->dh); + req_cryptoctx->dh = NULL; + } + if (use_sent_dh) + req_cryptoctx->dh = dh; + break; + } + i++; + } + + if (ok) + retval = 0; + +cleanup: + return retval; +} + +static int +openssl_callback(int ok, X509_STORE_CTX * ctx) +{ +#ifdef DEBUG + if (!ok) { + char buf[DN_BUF_LEN]; + + X509_NAME_oneline(X509_get_subject_name(ctx->current_cert), buf, sizeof(buf)); + pkiDebug("cert = %s\n", buf); + pkiDebug("callback function: %d (%s)\n", ctx->error, + X509_verify_cert_error_string(ctx->error)); + } +#endif + return ok; +} + +static int +openssl_callback_ignore_crls(int ok, X509_STORE_CTX * ctx) +{ + if (!ok) { + switch (ctx->error) { + case X509_V_ERR_UNABLE_TO_GET_CRL: + return 1; + default: + return 0; + } + } + return ok; +} + +static ASN1_OBJECT * +pkinit_pkcs7type2oid(pkinit_plg_crypto_context cryptoctx, int pkcs7_type) +{ + int nid; + + switch (pkcs7_type) { + case CMS_SIGN_CLIENT: + return cryptoctx->id_pkinit_authData; + case CMS_SIGN_DRAFT9: + /* + * Delay creating this OID until we know we need it. + * It shadows an existing OpenSSL oid. If it + * is created too early, it breaks things like + * the use of pkcs12 (which uses pkcs7 structures). + * We need this shadow version because our code + * depends on the "other" type to be unknown to the + * OpenSSL code. + */ + if (cryptoctx->id_pkinit_authData9 == NULL) { + pkiDebug("%s: Creating shadow instance of pkcs7-data oid\n", + __FUNCTION__); + nid = OBJ_create("1.2.840.113549.1.7.1", "id-pkcs7-data", + "PKCS7 data"); + if (nid == NID_undef) + return NULL; + cryptoctx->id_pkinit_authData9 = OBJ_nid2obj(nid); + } + return cryptoctx->id_pkinit_authData9; + case CMS_SIGN_SERVER: + return cryptoctx->id_pkinit_DHKeyData; + case CMS_ENVEL_SERVER: + return cryptoctx->id_pkinit_rkeyData; + default: + return NULL; + } + +} + +#ifdef LONGHORN_BETA_COMPAT +#if 0 +/* + * This is a version that worked with Longhorn Beta 3. + */ +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len, + int is_longhorn_server) +{ + + unsigned int orig_len = 0, oid_len = 0, tot_len = 0; + ASN1_OBJECT *oid = NULL; + unsigned char *p = NULL; + + pkiDebug("%s: This is the Longhorn version and is_longhorn_server = %d\n", + __FUNCTION__, is_longhorn_server); + + /* Get length to wrap the original data with SEQUENCE tag */ + tot_len = orig_len = ASN1_object_size(1, (int)data_len, V_ASN1_SEQUENCE); + + if (is_longhorn_server == 0) { + /* Add the signedData OID and adjust lengths */ + oid = OBJ_nid2obj(NID_pkcs7_signed); + oid_len = i2d_ASN1_OBJECT(oid, NULL); + + tot_len = ASN1_object_size(1, (int)(orig_len+oid_len), V_ASN1_SEQUENCE); + } + + p = *out = (unsigned char *)malloc(tot_len); + if (p == NULL) return -1; + + if (is_longhorn_server == 0) { + ASN1_put_object(&p, 1, (int)(orig_len+oid_len), + V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + + i2d_ASN1_OBJECT(oid, &p); + + ASN1_put_object(&p, 1, (int)data_len, 0, V_ASN1_CONTEXT_SPECIFIC); + } else { + ASN1_put_object(&p, 1, (int)data_len, V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + } + memcpy(p, data, data_len); + + *out_len = tot_len; + + return 0; +} +#else +/* + * This is a version that works with a patched Longhorn KDC. + * (Which should match SP1 ??). + */ +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len, + int is_longhorn_server) +{ + + unsigned int oid_len = 0, tot_len = 0, wrap_len = 0, tag_len = 0; + ASN1_OBJECT *oid = NULL; + unsigned char *p = NULL; + + pkiDebug("%s: This is the Longhorn version and is_longhorn_server = %d\n", + __FUNCTION__, is_longhorn_server); + + /* New longhorn is missing another sequence */ + if (is_longhorn_server == 1) + wrap_len = ASN1_object_size(1, (int)(data_len), V_ASN1_SEQUENCE); + else + wrap_len = data_len; + + /* Get length to wrap the original data with SEQUENCE tag */ + tag_len = ASN1_object_size(1, (int)wrap_len, V_ASN1_SEQUENCE); + + /* Always add oid */ + oid = OBJ_nid2obj(NID_pkcs7_signed); + oid_len = i2d_ASN1_OBJECT(oid, NULL); + oid_len += tag_len; + + tot_len = ASN1_object_size(1, (int)(oid_len), V_ASN1_SEQUENCE); + + p = *out = (unsigned char *)malloc(tot_len); + if (p == NULL) + return -1; + + ASN1_put_object(&p, 1, (int)(oid_len), + V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + + i2d_ASN1_OBJECT(oid, &p); + + ASN1_put_object(&p, 1, (int)wrap_len, 0, V_ASN1_CONTEXT_SPECIFIC); + + /* Wrap in extra seq tag */ + if (is_longhorn_server == 1) { + ASN1_put_object(&p, 1, (int)data_len, V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + } + memcpy(p, data, data_len); + + *out_len = tot_len; + + return 0; +} + +#endif +#else +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len) +{ + + unsigned int orig_len = 0, oid_len = 0, tot_len = 0; + ASN1_OBJECT *oid = NULL; + unsigned char *p = NULL; + + /* Get length to wrap the original data with SEQUENCE tag */ + tot_len = orig_len = ASN1_object_size(1, (int)data_len, V_ASN1_SEQUENCE); + + /* Add the signedData OID and adjust lengths */ + oid = OBJ_nid2obj(NID_pkcs7_signed); + oid_len = i2d_ASN1_OBJECT(oid, NULL); + + tot_len = ASN1_object_size(1, (int)(orig_len+oid_len), V_ASN1_SEQUENCE); + + p = *out = (unsigned char *)malloc(tot_len); + if (p == NULL) return -1; + + ASN1_put_object(&p, 1, (int)(orig_len+oid_len), + V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); + + i2d_ASN1_OBJECT(oid, &p); + + ASN1_put_object(&p, 1, (int)data_len, 0, V_ASN1_CONTEXT_SPECIFIC); + memcpy(p, data, data_len); + + *out_len = tot_len; + + return 0; +} +#endif + +static int +prepare_enc_data(unsigned char *indata, + int indata_len, + unsigned char **outdata, + int *outdata_len) +{ + int retval = -1; + ASN1_const_CTX c; + long length = indata_len; + int Ttag, Tclass; + long Tlen; + + c.pp = (const unsigned char **)&indata; + c.q = *(const unsigned char **)&indata; + c.error = ERR_R_NESTED_ASN1_ERROR; + c.p= *(const unsigned char **)&indata; + c.max = (length == 0)?0:(c.p+length); + + asn1_GetSequence(&c,&length); + + ASN1_get_object(&c.p,&Tlen,&Ttag,&Tclass,c.slen); + c.p += Tlen; + ASN1_get_object(&c.p,&Tlen,&Ttag,&Tclass,c.slen); + + asn1_const_Finish(&c); + + *outdata = (unsigned char *)malloc((size_t)Tlen); + if (outdata == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(*outdata, c.p, (size_t)Tlen); + *outdata_len = Tlen; + + retval = 0; +cleanup: + + return retval; +} + +#ifndef WITHOUT_PKCS11 +static void * +pkinit_C_LoadModule(const char *modname, CK_FUNCTION_LIST_PTR_PTR p11p) +{ + void *handle; + CK_RV (*getflist)(CK_FUNCTION_LIST_PTR_PTR); + + pkiDebug("loading module \"%s\"... ", modname); + handle = dlopen(modname, RTLD_NOW); + if (handle == NULL) { + pkiDebug("not found\n"); + return NULL; + } + getflist = (CK_RV (*)(CK_FUNCTION_LIST_PTR_PTR)) dlsym(handle, "C_GetFunctionList"); + if (getflist == NULL || (*getflist)(p11p) != CKR_OK) { + dlclose(handle); + pkiDebug("failed\n"); + return NULL; + } + pkiDebug("ok\n"); + return handle; +} + +static CK_RV +pkinit_C_UnloadModule(void *handle) +{ + dlclose(handle); + return CKR_OK; +} + +static krb5_error_code +pkinit_login(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + CK_TOKEN_INFO *tip) +{ + krb5_data rdat; + char *prompt; + krb5_prompt kprompt; + krb5_prompt_type prompt_type; + int r = 0; + + if (tip->flags & CKF_PROTECTED_AUTHENTICATION_PATH) { + rdat.data = NULL; + rdat.length = 0; + } else { + if ((prompt = (char *) malloc(sizeof (tip->label) + 32)) == NULL) + return ENOMEM; + sprintf(prompt, "%.*s PIN", sizeof (tip->label), tip->label); + if (tip->flags & CKF_USER_PIN_LOCKED) + strcat(prompt, " (Warning: PIN locked)"); + else if (tip->flags & CKF_USER_PIN_FINAL_TRY) + strcat(prompt, " (Warning: PIN final try)"); + else if (tip->flags & CKF_USER_PIN_COUNT_LOW) + strcat(prompt, " (Warning: PIN count low)"); + rdat.data = (char *)malloc(tip->ulMaxPinLen + 2); + rdat.length = tip->ulMaxPinLen + 1; + + kprompt.prompt = prompt; + kprompt.hidden = 1; + kprompt.reply = &rdat; + prompt_type = KRB5_PROMPT_TYPE_PREAUTH; + + /* PROMPTER_INVOCATION */ + k5int_set_prompt_types(context, &prompt_type); + r = (*id_cryptoctx->prompter)(context, id_cryptoctx->prompter_data, + NULL, NULL, 1, &kprompt); + k5int_set_prompt_types(context, 0); + free(prompt); + } + + if (r == 0) { + r = id_cryptoctx->p11->C_Login(id_cryptoctx->session, CKU_USER, + (u_char *) rdat.data, rdat.length); + + if (r != CKR_OK) { + pkiDebug("C_Login: %s\n", pkinit_pkcs11_code_to_text(r)); + r = KRB5KDC_ERR_PREAUTH_FAILED; + } + } + if (rdat.data) + free(rdat.data); + + return r; +} + +static krb5_error_code +pkinit_open_session(krb5_context context, + pkinit_identity_crypto_context cctx) +{ + int i, r; + unsigned char *cp; + CK_ULONG count = 0; + CK_SLOT_ID_PTR slotlist; + CK_TOKEN_INFO tinfo; + + if (cctx->p11_module != NULL) + return 0; /* session already open */ + + /* Load module */ + cctx->p11_module = + pkinit_C_LoadModule(cctx->p11_module_name, &cctx->p11); + if (cctx->p11_module == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + + /* Init */ + if ((r = cctx->p11->C_Initialize(NULL)) != CKR_OK) { + pkiDebug("C_Initialize: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* Get the list of available slots */ + if (cctx->slotid != PK_NOSLOT) { + /* A slot was specified, so that's the only one in the list */ + count = 1; + slotlist = (CK_SLOT_ID_PTR) malloc(sizeof (CK_SLOT_ID)); + slotlist[0] = cctx->slotid; + } else { + if (cctx->p11->C_GetSlotList(TRUE, NULL, &count) != CKR_OK) + return KRB5KDC_ERR_PREAUTH_FAILED; + if (count == 0) + return KRB5KDC_ERR_PREAUTH_FAILED; + slotlist = (CK_SLOT_ID_PTR) malloc(count * sizeof (CK_SLOT_ID)); + if (cctx->p11->C_GetSlotList(TRUE, slotlist, &count) != CKR_OK) + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* Look for the given token label, or if none given take the first one */ + for (i = 0; i < count; i++) { + /* Open session */ + if ((r = cctx->p11->C_OpenSession(slotlist[i], CKF_SERIAL_SESSION, + NULL, NULL, &cctx->session)) != CKR_OK) { + pkiDebug("C_OpenSession: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* Get token info */ + if ((r = cctx->p11->C_GetTokenInfo(slotlist[i], &tinfo)) != CKR_OK) { + pkiDebug("C_GetTokenInfo: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + for (cp = tinfo.label + sizeof (tinfo.label) - 1; + *cp == '\0' || *cp == ' '; cp--) + *cp = '\0'; + pkiDebug("open_session: slotid %d token \"%s\"\n", + (int) slotlist[i], tinfo.label); + if (cctx->token_label == NULL || + !strcmp((char *) cctx->token_label, (char *) tinfo.label)) + break; + cctx->p11->C_CloseSession(cctx->session); + } + if (i >= count) { + free(slotlist); + pkiDebug("open_session: no matching token found\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + cctx->slotid = slotlist[i]; + free(slotlist); + pkiDebug("open_session: slotid %d (%d of %d)\n", (int) cctx->slotid, + i + 1, (int) count); + + /* Login if needed */ + if (tinfo.flags & CKF_LOGIN_REQUIRED) + r = pkinit_login(context, cctx, &tinfo); + + return r; +} + +/* + * Look for a key that's: + * 1. private + * 2. capable of the specified operation (usually signing or decrypting) + * 3. RSA (this may be wrong but it's all we can do for now) + * 4. matches the id of the cert we chose + * + * You must call pkinit_get_certs before calling pkinit_find_private_key + * (that's because we need the ID of the private key) + * + * pkcs11 says the id of the key doesn't have to match that of the cert, but + * I can't figure out any other way to decide which key to use. + * + * We should only find one key that fits all the requirements. + * If there are more than one, we just take the first one. + */ + +krb5_error_code +pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, + CK_ATTRIBUTE_TYPE usage, + CK_OBJECT_HANDLE *objp) +{ + CK_OBJECT_CLASS cls; + CK_ATTRIBUTE attrs[4]; + CK_ULONG count; + CK_KEY_TYPE keytype; + unsigned int nattrs = 0; + int r; +#ifdef PKINIT_USE_KEY_USAGE + CK_BBOOL true_false; +#endif + + cls = CKO_PRIVATE_KEY; + attrs[nattrs].type = CKA_CLASS; + attrs[nattrs].pValue = &cls; + attrs[nattrs].ulValueLen = sizeof cls; + nattrs++; + +#ifdef PKINIT_USE_KEY_USAGE + /* + * Some cards get confused if you try to specify a key usage, + * so don't, and hope for the best. This will fail if you have + * several keys with the same id and different usages but I have + * not seen this on real cards. + */ + true_false = TRUE; + attrs[nattrs].type = usage; + attrs[nattrs].pValue = &true_false; + attrs[nattrs].ulValueLen = sizeof true_false; + nattrs++; +#endif + + keytype = CKK_RSA; + attrs[nattrs].type = CKA_KEY_TYPE; + attrs[nattrs].pValue = &keytype; + attrs[nattrs].ulValueLen = sizeof keytype; + nattrs++; + + attrs[nattrs].type = CKA_ID; + attrs[nattrs].pValue = id_cryptoctx->cert_id; + attrs[nattrs].ulValueLen = id_cryptoctx->cert_id_len; + nattrs++; + + r = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, nattrs); + if (r != CKR_OK) { + pkiDebug("krb5_pkinit_sign_data: C_FindObjectsInit: %s\n", + pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + r = id_cryptoctx->p11->C_FindObjects(id_cryptoctx->session, objp, 1, &count); + id_cryptoctx->p11->C_FindObjectsFinal(id_cryptoctx->session); + pkiDebug("found %d private keys (%s)\n", (int) count, pkinit_pkcs11_code_to_text(r)); + if (r != CKR_OK || count < 1) + return KRB5KDC_ERR_PREAUTH_FAILED; + return 0; +} +#endif + +static krb5_error_code +pkinit_decode_data_fs(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **decoded_data, + unsigned int *decoded_data_len) +{ + if (decode_data(decoded_data, decoded_data_len, data, data_len, + id_cryptoctx->my_key, sk_X509_value(id_cryptoctx->my_certs, + id_cryptoctx->cert_index)) <= 0) { + pkiDebug("failed to decode data\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + return 0; +} + +#ifndef WITHOUT_PKCS11 +#ifdef SILLYDECRYPT +CK_RV +pkinit_C_Decrypt(pkinit_identity_crypto_context id_cryptoctx, + CK_BYTE_PTR pEncryptedData, + CK_ULONG ulEncryptedDataLen, + CK_BYTE_PTR pData, + CK_ULONG_PTR pulDataLen) +{ + CK_RV rv = CKR_OK; + + rv = id_cryptoctx->p11->C_Decrypt(id_cryptoctx->session, pEncryptedData, + ulEncryptedDataLen, pData, pulDataLen); + if (rv == CKR_OK) { + pkiDebug("pData %x *pulDataLen %d\n", (int) pData, (int) *pulDataLen); + } + return rv; +} +#endif + +static krb5_error_code +pkinit_decode_data_pkcs11(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **decoded_data, + unsigned int *decoded_data_len) +{ + CK_OBJECT_HANDLE obj; + CK_ULONG len; + CK_MECHANISM mech; + unsigned char *cp; + int r; + + if (pkinit_open_session(context, id_cryptoctx)) { + pkiDebug("can't open pkcs11 session\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + pkinit_find_private_key(id_cryptoctx, CKA_DECRYPT, &obj); + + mech.mechanism = CKM_RSA_PKCS; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + if ((r = id_cryptoctx->p11->C_DecryptInit(id_cryptoctx->session, &mech, + obj)) != CKR_OK) { + pkiDebug("C_DecryptInit: 0x%x\n", (int) r); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + pkiDebug("data_len = %d\n", data_len); + cp = (unsigned char *)malloc((size_t) data_len); + if (cp == NULL) + return ENOMEM; + len = data_len; +#ifdef SILLYDECRYPT + pkiDebug("session %x edata %x edata_len %d data %x datalen @%x %d\n", + (int) id_cryptoctx->session, (int) data, (int) data_len, (int) cp, + (int) &len, (int) len); + if ((r = pkinit_C_Decrypt(id_cryptoctx, data, (CK_ULONG) data_len, + cp, &len)) != CKR_OK) { +#else + if ((r = id_cryptoctx->p11->C_Decrypt(id_cryptoctx->session, data, + (CK_ULONG) data_len, cp, &len)) != CKR_OK) { +#endif + pkiDebug("C_Decrypt: %s\n", pkinit_pkcs11_code_to_text(r)); + if (r == CKR_BUFFER_TOO_SMALL) + pkiDebug("decrypt %d needs %d\n", (int) data_len, (int) len); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + pkiDebug("decrypt %d -> %d\n", (int) data_len, (int) len); + *decoded_data_len = len; + *decoded_data = cp; + + return 0; +} +#endif + +krb5_error_code +pkinit_decode_data(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **decoded_data, + unsigned int *decoded_data_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + + if (id_cryptoctx->pkcs11_method != 1) + retval = pkinit_decode_data_fs(context, id_cryptoctx, data, data_len, + decoded_data, decoded_data_len); +#ifndef WITHOUT_PKCS11 + else + retval = pkinit_decode_data_pkcs11(context, id_cryptoctx, data, + data_len, decoded_data, decoded_data_len); +#endif + + return retval; +} + +static krb5_error_code +pkinit_sign_data_fs(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **sig, + unsigned int *sig_len) +{ + if (create_signature(sig, sig_len, data, data_len, + id_cryptoctx->my_key) != 0) { + pkiDebug("failed to create the signature\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + return 0; +} + +#ifndef WITHOUT_PKCS11 +static krb5_error_code +pkinit_sign_data_pkcs11(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **sig, + unsigned int *sig_len) +{ + CK_OBJECT_HANDLE obj; + CK_ULONG len; + CK_MECHANISM mech; + unsigned char *cp; + int r; + + if (pkinit_open_session(context, id_cryptoctx)) { + pkiDebug("can't open pkcs11 session\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj); + + mech.mechanism = id_cryptoctx->mech; + mech.pParameter = NULL; + mech.ulParameterLen = 0; + + if ((r = id_cryptoctx->p11->C_SignInit(id_cryptoctx->session, &mech, + obj)) != CKR_OK) { + pkiDebug("C_SignInit: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* + * Key len would give an upper bound on sig size, but there's no way to + * get that. So guess, and if it's too small, re-malloc. + */ + len = PK_SIGLEN_GUESS; + cp = (unsigned char *)malloc((size_t) len); + if (cp == NULL) + return ENOMEM; + + r = id_cryptoctx->p11->C_Sign(id_cryptoctx->session, data, + (CK_ULONG) data_len, cp, &len); + if (r == CKR_BUFFER_TOO_SMALL || (r == CKR_OK && len >= PK_SIGLEN_GUESS)) { + free(cp); + pkiDebug("C_Sign realloc %d\n", (int) len); + cp = (unsigned char *)malloc((size_t) len); + r = id_cryptoctx->p11->C_Sign(id_cryptoctx->session, data, + (CK_ULONG) data_len, cp, &len); + } + if (r != CKR_OK) { + pkiDebug("C_Sign: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + pkiDebug("sign %d -> %d\n", (int) data_len, (int) len); + *sig_len = len; + *sig = cp; + + return 0; +} +#endif + +krb5_error_code +pkinit_sign_data(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, + unsigned int data_len, + unsigned char **sig, + unsigned int *sig_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + + if (id_cryptoctx == NULL || id_cryptoctx->pkcs11_method != 1) + retval = pkinit_sign_data_fs(context, id_cryptoctx, data, data_len, + sig, sig_len); +#ifndef WITHOUT_PKCS11 + else + retval = pkinit_sign_data_pkcs11(context, id_cryptoctx, data, data_len, + sig, sig_len); +#endif + + return retval; +} + + +static krb5_error_code +decode_data(unsigned char **out_data, unsigned int *out_data_len, + unsigned char *data, unsigned int data_len, + EVP_PKEY *pkey, X509 *cert) +{ + krb5_error_code retval = ENOMEM; + unsigned char *buf = NULL; + int buf_len = 0; + + if (cert && !X509_check_private_key(cert, pkey)) { + pkiDebug("private key does not match certificate\n"); + goto cleanup; + } + + buf_len = EVP_PKEY_size(pkey); + buf = (unsigned char *)malloc((size_t) buf_len + 10); + if (buf == NULL) + goto cleanup; + + retval = EVP_PKEY_decrypt(buf, data, (int)data_len, pkey); + if (retval <= 0) { + pkiDebug("unable to decrypt received data (len=%d)\n", data_len); + goto cleanup; + } + *out_data = buf; + *out_data_len = retval; + + cleanup: + if (retval == ENOMEM) + free(buf); + + return retval; +} + +static krb5_error_code +create_signature(unsigned char **sig, unsigned int *sig_len, + unsigned char *data, unsigned int data_len, EVP_PKEY *pkey) +{ + krb5_error_code retval = ENOMEM; + EVP_MD_CTX md_ctx; + + if (pkey == NULL) + return retval; + + EVP_VerifyInit(&md_ctx, EVP_sha1()); + EVP_SignUpdate(&md_ctx, data, data_len); + *sig_len = EVP_PKEY_size(pkey); + if ((*sig = (unsigned char *) malloc((size_t) *sig_len)) == NULL) + goto cleanup; + EVP_SignFinal(&md_ctx, *sig, sig_len, pkey); + + retval = 0; + + cleanup: + EVP_MD_CTX_cleanup(&md_ctx); + + return retval; +} + +/* + * Note: + * This is not the routine the KDC uses to get its certificate. + * This routine is intended to be called by the client + * to obtain the KDC's certificate from some local storage + * to be sent as a hint in its request to the KDC. + */ +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, + krb5_principal princ) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + + req_cryptoctx->received_cert = NULL; + retval = 0; + return retval; +} + +static krb5_error_code +pkinit_get_certs_pkcs12(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 = KRB5KDC_ERR_PREAUTH_FAILED; + X509 *x = NULL; + PKCS12 *p12 = NULL; + int ret; + FILE *fp; + EVP_PKEY *y = NULL; + + if (idopts->cert_filename == NULL) { + pkiDebug("%s: failed to get user's cert location\n", __FUNCTION__); + goto cleanup; + } + + if (idopts->key_filename == NULL) { + pkiDebug("%s: failed to get user's private key location\n", __FUNCTION__); + goto cleanup; + } + + fp = fopen(idopts->cert_filename, "rb"); + if (fp == NULL) { + pkiDebug("Failed to open PKCS12 file '%s', error %d\n", + idopts->cert_filename, errno); + goto cleanup; + } + + p12 = d2i_PKCS12_fp(fp, NULL); + fclose(fp); + if (p12 == NULL) { + pkiDebug("Failed to decode PKCS12 file '%s' contents\n", + idopts->cert_filename); + goto cleanup; + } + /* + * Try parsing with no pass phrase first. If that fails, + * prompt for the pass phrase and try again. + */ + ret = PKCS12_parse(p12, NULL, &y, &x, NULL); + if (ret == 0) { + krb5_data rdat; + krb5_prompt kprompt; + krb5_prompt_type prompt_type; + int r = 0; + char prompt_string[128]; + char prompt_reply[128]; + char prompt_prefix[] = "Pass phrase for"; + + pkiDebug("Initial PKCS12_parse with no password failed\n"); + + memset(prompt_reply, '\0', sizeof(prompt_reply)); + rdat.data = prompt_reply; + rdat.length = sizeof(prompt_reply); + + r = snprintf(prompt_string, sizeof(prompt_string), "%s %s", + prompt_prefix, idopts->cert_filename); + if (r >= sizeof(prompt_string)) { + pkiDebug("Prompt string, '%s %s', is too long!\n", + prompt_prefix, idopts->cert_filename); + goto cleanup; + } + kprompt.prompt = prompt_string; + kprompt.hidden = 1; + kprompt.reply = &rdat; + prompt_type = KRB5_PROMPT_TYPE_PREAUTH; + + /* PROMPTER_INVOCATION */ + k5int_set_prompt_types(context, &prompt_type); + r = (*id_cryptoctx->prompter)(context, id_cryptoctx->prompter_data, + NULL, NULL, 1, &kprompt); + k5int_set_prompt_types(context, 0); + + ret = PKCS12_parse(p12, rdat.data, &y, &x, NULL); + if (ret == 0) { + pkiDebug("Seconde PKCS12_parse with password failed\n"); + goto cleanup; + } + } + id_cryptoctx->creds[0] = malloc(sizeof(struct _pkinit_cred_info)); + if (id_cryptoctx->creds[0] == NULL) + goto cleanup; + 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; + +cleanup: + if (p12) + PKCS12_free(p12); + if (retval) { + if (x != NULL) + X509_free(x); + if (y != NULL) + EVP_PKEY_free(y); + } + return retval; +} + +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; + X509 *x = NULL; + EVP_PKEY *y = NULL; + + /* load the certificate */ + retval = get_cert(certname, &x); + if (retval != 0 || x == NULL) { + pkiDebug("failed to load user's certificate from '%s'\n", certname); + goto cleanup; + } + retval = get_key(keyname, &y); + if (retval != 0 || 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) { + retval = ENOMEM; + 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, + krb5_principal princ) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + + if (idopts->cert_filename == NULL) { + pkiDebug("%s: failed to get user's cert location\n", __FUNCTION__); + goto cleanup; + } + + if (idopts->key_filename == NULL) { + pkiDebug("%s: failed to get user's private key location\n", + __FUNCTION__); + 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; + } + + 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; + } + + if (i == 0) { + pkiDebug("%s: No cert/key pairs found in directory '%s'\n", + __FUNCTION__, idopts->cert_filename); + retval = ENOENT; + goto cleanup; + } + + retval = 0; + + cleanup: + if (d) + closedir(d); + + return retval; +} + +#ifndef WITHOUT_PKCS11 +static krb5_error_code +pkinit_get_certs_pkcs11(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) +{ +#ifdef PKINIT_USE_MECH_LIST + CK_MECHANISM_TYPE_PTR mechp; + CK_MECHANISM_INFO info; +#endif + CK_OBJECT_CLASS cls; + CK_OBJECT_HANDLE obj; + CK_ATTRIBUTE attrs[4]; + CK_ULONG count; + CK_CERTIFICATE_TYPE certtype; + CK_BYTE_PTR cert = NULL, cert_id; + const unsigned char *cp; + int i, r; + unsigned int nattrs; + X509 *x = NULL; + + /* Copy stuff from idopts -> id_cryptoctx */ + if (idopts->p11_module_name != NULL) { + id_cryptoctx->p11_module_name = strdup(idopts->p11_module_name); + if (id_cryptoctx->p11_module_name == NULL) + return ENOMEM; + } + if (idopts->token_label != NULL) { + id_cryptoctx->token_label = strdup(idopts->token_label); + if (id_cryptoctx->token_label == NULL) + return ENOMEM; + } + if (idopts->cert_label != NULL) { + id_cryptoctx->cert_label = strdup(idopts->cert_label); + if (id_cryptoctx->cert_label == NULL) + return ENOMEM; + } + /* Convert the ascii cert_id string into a binary blob */ + if (idopts->cert_id_string != NULL) { + BIGNUM *bn = NULL; + BN_hex2bn(&bn, idopts->cert_id_string); + if (bn == NULL) + return ENOMEM; + id_cryptoctx->cert_id_len = BN_num_bytes(bn); + id_cryptoctx->cert_id = malloc((size_t) id_cryptoctx->cert_id_len); + if (id_cryptoctx->cert_id == NULL) { + BN_free(bn); + return ENOMEM; + } + BN_bn2bin(bn, id_cryptoctx->cert_id); + BN_free(bn); + } + id_cryptoctx->slotid = idopts->slotid; + id_cryptoctx->pkcs11_method = 1; + + + + if (pkinit_open_session(context, id_cryptoctx)) { + pkiDebug("can't open pkcs11 session\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + +#ifndef PKINIT_USE_MECH_LIST + /* + * We'd like to use CKM_SHA1_RSA_PKCS for signing if it's available, but + * many cards seems to be confused about whether they are capable of + * this or not. The safe thing seems to be to ignore the mechanism list, + * always use CKM_RSA_PKCS and calculate the sha1 digest ourselves. + */ + + id_cryptoctx->mech = CKM_RSA_PKCS; +#else + if ((r = id_cryptoctx->p11->C_GetMechanismList(id_cryptoctx->slotid, NULL, + &count)) != CKR_OK || count <= 0) { + pkiDebug("C_GetMechanismList: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + mechp = (CK_MECHANISM_TYPE_PTR) malloc(count * sizeof (CK_MECHANISM_TYPE)); + if (mechp == NULL) + return ENOMEM; + if ((r = id_cryptoctx->p11->C_GetMechanismList(id_cryptoctx->slotid, + mechp, &count)) != CKR_OK) + return KRB5KDC_ERR_PREAUTH_FAILED; + for (i = 0; i < count; i++) { + if ((r = id_cryptoctx->p11->C_GetMechanismInfo(id_cryptoctx->slotid, + mechp[i], &info)) != CKR_OK) + return KRB5KDC_ERR_PREAUTH_FAILED; +#ifdef DEBUG_MECHINFO + pkiDebug("mech %x flags %x\n", (int) mechp[i], (int) info.flags); + if ((info.flags & (CKF_SIGN|CKF_DECRYPT)) == (CKF_SIGN|CKF_DECRYPT)) + pkiDebug(" this mech is good for sign & decrypt\n"); +#endif + if (mechp[i] == CKM_RSA_PKCS) { + /* This seems backwards... */ + id_cryptoctx->mech = + (info.flags & CKF_SIGN) ? CKM_SHA1_RSA_PKCS : CKM_RSA_PKCS; + } + } + free(mechp); + + pkiDebug("got %d mechs from card\n", (int) count); +#endif + + cls = CKO_CERTIFICATE; + attrs[0].type = CKA_CLASS; + attrs[0].pValue = &cls; + attrs[0].ulValueLen = sizeof cls; + + certtype = CKC_X_509; + attrs[1].type = CKA_CERTIFICATE_TYPE; + attrs[1].pValue = &certtype; + attrs[1].ulValueLen = sizeof certtype; + + nattrs = 2; + + /* If a cert id and/or label were given, use them too */ + if (id_cryptoctx->cert_id_len > 0) { + attrs[nattrs].type = CKA_ID; + attrs[nattrs].pValue = id_cryptoctx->cert_id; + attrs[nattrs].ulValueLen = id_cryptoctx->cert_id_len; + nattrs++; + } + if (id_cryptoctx->cert_label != NULL) { + attrs[nattrs].type = CKA_LABEL; + attrs[nattrs].pValue = id_cryptoctx->cert_label; + attrs[nattrs].ulValueLen = strlen(id_cryptoctx->cert_label); + nattrs++; + } + + r = id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, attrs, nattrs); + if (r != CKR_OK) { + pkiDebug("C_FindObjectsInit: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + for (i = 0; ; i++) { + if (i >= MAX_CREDS_ALLOWED) + return KRB5KDC_ERR_PREAUTH_FAILED; + + /* Look for x.509 cert */ + if ((r = id_cryptoctx->p11->C_FindObjects(id_cryptoctx->session, + &obj, 1, &count)) != CKR_OK || count <= 0) { + id_cryptoctx->creds[i] = NULL; + break; + } + + /* Get cert and id len */ + attrs[0].type = CKA_VALUE; + attrs[0].pValue = NULL; + attrs[0].ulValueLen = 0; + + attrs[1].type = CKA_ID; + attrs[1].pValue = NULL; + attrs[1].ulValueLen = 0; + + if ((r = id_cryptoctx->p11->C_GetAttributeValue(id_cryptoctx->session, + obj, attrs, 2)) != CKR_OK && r != CKR_BUFFER_TOO_SMALL) { + pkiDebug("C_GetAttributeValue: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + cert = (CK_BYTE_PTR) malloc((size_t) attrs[0].ulValueLen + 1); + cert_id = (CK_BYTE_PTR) malloc((size_t) attrs[1].ulValueLen + 1); + if (cert == NULL || cert_id == NULL) + return ENOMEM; + + /* Read the cert and id off the card */ + + attrs[0].type = CKA_VALUE; + attrs[0].pValue = cert; + + attrs[1].type = CKA_ID; + attrs[1].pValue = cert_id; + + if ((r = id_cryptoctx->p11->C_GetAttributeValue(id_cryptoctx->session, + obj, attrs, 2)) != CKR_OK) { + pkiDebug("C_GetAttributeValue: %s\n", pkinit_pkcs11_code_to_text(r)); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + pkiDebug("cert %d size %d id %d idlen %d\n", i, + (int) attrs[0].ulValueLen, (int) cert_id[0], + (int) attrs[1].ulValueLen); + + cp = (unsigned char *) cert; + x = d2i_X509(NULL, &cp, (int) attrs[0].ulValueLen); + if (x == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + id_cryptoctx->creds[i] = malloc(sizeof(struct _pkinit_cred_info)); + if (id_cryptoctx->creds[i] == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + 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); + if (cert == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + return 0; +} +#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]); + id_cryptoctx->creds[i] = NULL; + } + } + 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, + krb5_principal princ) +{ + krb5_error_code retval; + + switch(idopts->idtype) { + case IDTYPE_FILE: + retval = pkinit_get_certs_fs(context, plg_cryptoctx, + req_cryptoctx, idopts, + 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, princ); + break; +#endif + case IDTYPE_PKCS12: + retval = pkinit_get_certs_pkcs12(context, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, princ); + break; + default: + retval = EINVAL; + } + if (retval) + goto cleanup; + +cleanup: + return retval; +} + +/* + * 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 +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; + + if (ret_ku_bits == NULL && ret_eku_bits == NULL) + return EINVAL; + + 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) { + sk_X509_pop_free(cd->idctx->my_certs, X509_free); + } + 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) +{ + krb5_error_code retval; + int cert_count = 0; + + 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 errout; + } + if (cert_count != 1) { + pkiDebug("%s: ERROR: There are %d certs to choose from, " + "but there must be exactly one.\n", + __FUNCTION__, cert_count); + retval = EINVAL; + goto errout; + } + /* copy the selected cert into our id_cryptoctx */ + if (id_cryptoctx->my_certs != NULL) { + sk_X509_pop_free(id_cryptoctx->my_certs, X509_free); + } + id_cryptoctx->my_certs = sk_X509_new_null(); + 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 = id_cryptoctx->creds[0]->key; + id_cryptoctx->creds[0]->key = NULL; /* Don't free it twice */ + } +#ifndef WITHOUT_PKCS11 + else { + 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 + retval = 0; +errout: + return retval; +} + + + +static krb5_error_code +load_cas_and_crls(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int catype, + char *filename) +{ + STACK_OF(X509_INFO) *sk = NULL; + STACK_OF(X509) *ca_certs = NULL; + STACK_OF(X509_CRL) *ca_crls = NULL; + BIO *in = NULL; + krb5_error_code retval = ENOMEM; + int i = 0; + + /* If there isn't already a stack in the context, + * create a temporary one now */ + switch(catype) { + case CATYPE_ANCHORS: + if (id_cryptoctx->trustedCAs != NULL) + ca_certs = id_cryptoctx->trustedCAs; + else { + ca_certs = sk_X509_new_null(); + if (ca_certs == NULL) + return ENOMEM; + } + break; + case CATYPE_INTERMEDIATES: + if (id_cryptoctx->intermediateCAs != NULL) + ca_certs = id_cryptoctx->intermediateCAs; + else { + ca_certs = sk_X509_new_null(); + if (ca_certs == NULL) + return ENOMEM; + } + break; + case CATYPE_CRLS: + if (id_cryptoctx->revoked != NULL) + ca_crls = id_cryptoctx->revoked; + else { + ca_crls = sk_X509_CRL_new_null(); + if (ca_crls == NULL) + return ENOMEM; + } + break; + default: + return ENOTSUP; + } + + if (!(in = BIO_new_file(filename, "r"))) { + retval = errno; + pkiDebug("%s: error opening file '%s': %s\n", __FUNCTION__, + filename, error_message(errno)); + goto cleanup; + } + + /* This loads from a file, a stack of x509/crl/pkey sets */ + if ((sk = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL)) == NULL) { + pkiDebug("%s: error reading file '%s'\n", __FUNCTION__, filename); + retval = EIO; + goto cleanup; + } + + /* scan over the stack created from loading the file contents, + * weed out duplicates, and push new ones onto the return stack + */ + for (i = 0; i < sk_X509_INFO_num(sk); i++) { + X509_INFO *xi = sk_X509_INFO_value(sk, i); + if (xi != NULL && xi->x509 != NULL && catype != CATYPE_CRLS) { + int j = 0, size = sk_X509_num(ca_certs), flag = 0; + + if (!size) { + sk_X509_push(ca_certs, xi->x509); + xi->x509 = NULL; + continue; + } + for (j = 0; j < size; j++) { + X509 *x = sk_X509_value(ca_certs, j); + flag = X509_cmp(x, xi->x509); + if (flag == 0) + break; + else + continue; + } + if (flag != 0) { + sk_X509_push(ca_certs, X509_dup(xi->x509)); + } + } else if (xi != NULL && xi->crl != NULL && catype == CATYPE_CRLS) { + int j = 0, size = sk_X509_CRL_num(ca_crls), flag = 0; + if (!size) { + sk_X509_CRL_push(ca_crls, xi->crl); + xi->crl = NULL; + continue; + } + for (j = 0; j < size; j++) { + X509_CRL *x = sk_X509_CRL_value(ca_crls, j); + flag = X509_CRL_cmp(x, xi->crl); + if (flag == 0) + break; + else + continue; + } + if (flag != 0) { + sk_X509_push(ca_crls, X509_CRL_dup(xi->crl)); + } + } + } + + /* If we added something and there wasn't a stack in the + * context before, add the temporary stack to the context. + */ + switch(catype) { + case CATYPE_ANCHORS: + if (sk_X509_num(ca_certs) == 0) { + pkiDebug("no anchors in file, %s\n", filename); + if (id_cryptoctx->trustedCAs == NULL) + sk_X509_free(ca_certs); + } else { + if (id_cryptoctx->trustedCAs == NULL) + id_cryptoctx->trustedCAs = ca_certs; + } + break; + case CATYPE_INTERMEDIATES: + if (sk_X509_num(ca_certs) == 0) { + pkiDebug("no intermediates in file, %s\n", filename); + if (id_cryptoctx->intermediateCAs == NULL) + sk_X509_free(ca_certs); + } else { + if (id_cryptoctx->intermediateCAs == NULL) + id_cryptoctx->intermediateCAs = ca_certs; + } + break; + case CATYPE_CRLS: + if (sk_X509_num(ca_crls) == 0) { + pkiDebug("no crls in file, %s\n", filename); + if (id_cryptoctx->revoked == NULL) + sk_X509_CRL_free(ca_crls); + } else { + if (id_cryptoctx->revoked == NULL) + id_cryptoctx->revoked = ca_crls; + } + break; + default: + /* Should have been caught above! */ + retval = EINVAL; + goto cleanup; + break; + } + + retval = 0; + + cleanup: + if (in != NULL) + BIO_free(in); + if (sk != NULL) + sk_X509_INFO_pop_free(sk, X509_INFO_free); + + return retval; +} + +static krb5_error_code +load_cas_and_crls_dir(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int catype, + char *dirname) +{ + krb5_error_code retval = EINVAL; + DIR *d = NULL; + struct dirent *dentry = NULL; + char filename[1024]; + + if (dirname == NULL) + return EINVAL; + + d = opendir(dirname); + if (d == NULL) + return ENOENT; + + while ((dentry = readdir(d))) { + if (strlen(dirname) + strlen(dentry->d_name) + 2 > sizeof(filename)) { + pkiDebug("%s: Path too long -- directory '%s' and file '%s'\n", + __FUNCTION__, dirname, dentry->d_name); + 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); + + retval = load_cas_and_crls(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, catype, filename); + if (retval) + goto cleanup; + } + + retval = 0; + + cleanup: + if (d != NULL) + closedir(d); + + return retval; +} + +krb5_error_code +crypto_load_cas_and_crls(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 idtype, + int catype, + char *id) +{ + pkiDebug("%s: called with idtype %s and catype %s\n", + __FUNCTION__, idtype2string(idtype), catype2string(catype)); + switch (idtype) { + case IDTYPE_FILE: + return load_cas_and_crls(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, catype, id); + break; + case IDTYPE_DIR: + return load_cas_and_crls_dir(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, catype, id); + break; + default: + return ENOTSUP; + break; + } +} + +static krb5_error_code +create_identifiers_from_stack(STACK_OF(X509) *sk, + krb5_external_principal_identifier *** ids) +{ + krb5_error_code retval = ENOMEM; + int i = 0, sk_size = sk_X509_num(sk); + krb5_external_principal_identifier **krb5_cas = NULL; + X509 *x = NULL; + X509_NAME *xn = NULL; + unsigned char *p = NULL; + int len = 0; + PKCS7_ISSUER_AND_SERIAL *is = NULL; + char buf[DN_BUF_LEN]; + + *ids = NULL; + + krb5_cas = + malloc((sk_size + 1) * sizeof(krb5_external_principal_identifier *)); + if (krb5_cas == NULL) + return ENOMEM; + krb5_cas[sk_size] = NULL; + + for (i = 0; i < sk_size; i++) { + krb5_cas[i] = (krb5_external_principal_identifier *)malloc(sizeof(krb5_external_principal_identifier)); + + x = sk_X509_value(sk, i); + + X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf)); + pkiDebug("#%d cert= %s\n", i, buf); + + /* fill-in subjectName */ + krb5_cas[i]->subjectName.magic = 0; + krb5_cas[i]->subjectName.length = 0; + krb5_cas[i]->subjectName.data = NULL; + + xn = X509_get_subject_name(x); + len = i2d_X509_NAME(xn, NULL); + if ((p = krb5_cas[i]->subjectName.data = (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_X509_NAME(xn, &p); + krb5_cas[i]->subjectName.length = len; + + /* fill-in issuerAndSerialNumber */ + krb5_cas[i]->issuerAndSerialNumber.length = 0; + krb5_cas[i]->issuerAndSerialNumber.magic = 0; + krb5_cas[i]->issuerAndSerialNumber.data = NULL; + +#ifdef LONGHORN_BETA_COMPAT +if (longhorn == 0) { /* XXX Longhorn doesn't like this */ +#endif + is = PKCS7_ISSUER_AND_SERIAL_new(); + X509_NAME_set(&is->issuer, X509_get_issuer_name(x)); + M_ASN1_INTEGER_free(is->serial); + is->serial = M_ASN1_INTEGER_dup(X509_get_serialNumber(x)); + len = i2d_PKCS7_ISSUER_AND_SERIAL(is, NULL); + if ((p = krb5_cas[i]->issuerAndSerialNumber.data = + (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_PKCS7_ISSUER_AND_SERIAL(is, &p); + krb5_cas[i]->issuerAndSerialNumber.length = len; +#ifdef LONGHORN_BETA_COMPAT +} +#endif + + /* fill-in subjectKeyIdentifier */ + krb5_cas[i]->subjectKeyIdentifier.length = 0; + krb5_cas[i]->subjectKeyIdentifier.magic = 0; + krb5_cas[i]->subjectKeyIdentifier.data = NULL; + + +#ifdef LONGHORN_BETA_COMPAT +if (longhorn == 0) { /* XXX Longhorn doesn't like this */ +#endif + if (X509_get_ext_by_NID(x, NID_subject_key_identifier, -1) >= 0) { + ASN1_OCTET_STRING *ikeyid = NULL; + + if ((ikeyid = X509_get_ext_d2i(x, NID_subject_key_identifier, NULL, + NULL))) { + len = i2d_ASN1_OCTET_STRING(ikeyid, NULL); + if ((p = krb5_cas[i]->subjectKeyIdentifier.data = + (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_ASN1_OCTET_STRING(ikeyid, &p); + krb5_cas[i]->subjectKeyIdentifier.length = len; + } + if (ikeyid != NULL) + ASN1_OCTET_STRING_free(ikeyid); + } +#ifdef LONGHORN_BETA_COMPAT +} +#endif + if (is != NULL) { + if (is->issuer != NULL) + X509_NAME_free(is->issuer); + if (is->serial != NULL) + ASN1_INTEGER_free(is->serial); + free(is); + } + } + + *ids = krb5_cas; + + retval = 0; + cleanup: + if (retval) + free_krb5_external_principal_identifier(&krb5_cas); + + return retval; +} + +static krb5_error_code +create_krb5_invalidCertificates(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_external_principal_identifier *** ids) +{ + + krb5_error_code retval = ENOMEM; + STACK_OF(X509) *sk = NULL; + + *ids = NULL; + if (req_cryptoctx->received_cert == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + + sk = sk_X509_new_null(); + if (sk == NULL) + goto cleanup; + sk_X509_push(sk, req_cryptoctx->received_cert); + + retval = create_identifiers_from_stack(sk, ids); + + sk_X509_free(sk); +cleanup: + + return retval; +} + +krb5_error_code +create_krb5_supportedCMSTypes(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_algorithm_identifier ***oids) +{ + + krb5_error_code retval = ENOMEM; + krb5_algorithm_identifier **loids = NULL; + krb5_octet_data des3oid = {0, 8, (unsigned char *)"\x2A\x86\x48\x86\xF7\x0D\x03\x07" }; + + *oids = NULL; + loids = malloc(2 * sizeof(krb5_algorithm_identifier *)); + if (loids == NULL) + goto cleanup; + loids[1] = NULL; + loids[0] = (krb5_algorithm_identifier *)malloc(sizeof(krb5_algorithm_identifier)); + if (loids[0] == NULL) { + free(loids); + goto cleanup; + } + retval = pkinit_copy_krb5_octet_data(&loids[0]->algorithm, &des3oid); + if (retval) { + free(loids[0]); + free(loids); + goto cleanup; + } + loids[0]->parameters.length = 0; + loids[0]->parameters.data = NULL; + + *oids = loids; + retval = 0; +cleanup: + + return retval; +} + +krb5_error_code +create_krb5_trustedCertifiers(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_external_principal_identifier *** ids) +{ + + krb5_error_code retval = ENOMEM; + STACK_OF(X509) *sk = id_cryptoctx->trustedCAs; + + *ids = NULL; + if (id_cryptoctx->trustedCAs == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + + retval = create_identifiers_from_stack(sk, ids); + + return retval; +} + +krb5_error_code +create_krb5_trustedCas(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int flag, + krb5_trusted_ca *** ids) +{ + krb5_error_code retval = ENOMEM; + STACK_OF(X509) *sk = id_cryptoctx->trustedCAs; + int i = 0, len = 0, sk_size = sk_X509_num(sk); + krb5_trusted_ca **krb5_cas = NULL; + X509 *x = NULL; + char buf[DN_BUF_LEN]; + X509_NAME *xn = NULL; + unsigned char *p = NULL; + PKCS7_ISSUER_AND_SERIAL *is = NULL; + + *ids = NULL; + if (id_cryptoctx->trustedCAs == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + + krb5_cas = malloc((sk_size + 1) * sizeof(krb5_trusted_ca *)); + if (krb5_cas == NULL) + return ENOMEM; + krb5_cas[sk_size] = NULL; + + for (i = 0; i < sk_size; i++) { + krb5_cas[i] = (krb5_trusted_ca *)malloc(sizeof(krb5_trusted_ca)); + if (krb5_cas[i] == NULL) + goto cleanup; + x = sk_X509_value(sk, i); + + X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf)); + pkiDebug("#%d cert= %s\n", i, buf); + + switch (flag) { + case choice_trusted_cas_principalName: + krb5_cas[i]->choice = choice_trusted_cas_principalName; + break; + case choice_trusted_cas_caName: + krb5_cas[i]->choice = choice_trusted_cas_caName; + krb5_cas[i]->u.caName.data = NULL; + krb5_cas[i]->u.caName.length = 0; + xn = X509_get_subject_name(x); + len = i2d_X509_NAME(xn, NULL); + if ((p = krb5_cas[i]->u.caName.data = + (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_X509_NAME(xn, &p); + krb5_cas[i]->u.caName.length = len; + break; + case choice_trusted_cas_issuerAndSerial: + krb5_cas[i]->choice = choice_trusted_cas_issuerAndSerial; + krb5_cas[i]->u.issuerAndSerial.data = NULL; + krb5_cas[i]->u.issuerAndSerial.length = 0; + is = PKCS7_ISSUER_AND_SERIAL_new(); + X509_NAME_set(&is->issuer, X509_get_issuer_name(x)); + M_ASN1_INTEGER_free(is->serial); + is->serial = M_ASN1_INTEGER_dup(X509_get_serialNumber(x)); + len = i2d_PKCS7_ISSUER_AND_SERIAL(is, NULL); + if ((p = krb5_cas[i]->u.issuerAndSerial.data = + (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_PKCS7_ISSUER_AND_SERIAL(is, &p); + krb5_cas[i]->u.issuerAndSerial.length = len; + if (is != NULL) { + if (is->issuer != NULL) + X509_NAME_free(is->issuer); + if (is->serial != NULL) + ASN1_INTEGER_free(is->serial); + free(is); + } + break; + default: break; + } + } + retval = 0; + *ids = krb5_cas; +cleanup: + if (retval) + free_krb5_trusted_ca(&krb5_cas); + + return retval; +} + +krb5_error_code +create_issuerAndSerial(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + unsigned char **out, + unsigned int *out_len) +{ + unsigned char *p = NULL; + PKCS7_ISSUER_AND_SERIAL *is = NULL; + int len = 0; + krb5_error_code retval = ENOMEM; + X509 *cert = req_cryptoctx->received_cert; + + *out = NULL; + *out_len = 0; + if (req_cryptoctx->received_cert == NULL) + return 0; + + is = PKCS7_ISSUER_AND_SERIAL_new(); + X509_NAME_set(&is->issuer, X509_get_issuer_name(cert)); + M_ASN1_INTEGER_free(is->serial); + is->serial = M_ASN1_INTEGER_dup(X509_get_serialNumber(cert)); + len = i2d_PKCS7_ISSUER_AND_SERIAL(is, NULL); + if ((p = *out = (unsigned char *)malloc((size_t) len)) == NULL) + goto cleanup; + i2d_PKCS7_ISSUER_AND_SERIAL(is, &p); + *out_len = len; + retval = 0; + +cleanup: + X509_NAME_free(is->issuer); + ASN1_INTEGER_free(is->serial); + free(is); + + return retval; +} + +static int +pkcs7_decrypt(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + PKCS7 *p7, + BIO *data) +{ + BIO *tmpmem = NULL; + int retval = 0, i = 0; + char buf[4096]; + + if(p7 == NULL) + return 0; + + if(!PKCS7_type_is_enveloped(p7)) { + pkiDebug("wrong pkcs7 content type\n"); + return 0; + } + + if(!(tmpmem = pkcs7_dataDecode(context, id_cryptoctx, p7))) { + pkiDebug("unable to decrypt pkcs7 object\n"); + return 0; + } + + for(;;) { + i = BIO_read(tmpmem, buf, sizeof(buf)); + if (i <= 0) break; + BIO_write(data, buf, i); + BIO_free_all(tmpmem); + return 1; + } + return retval; +} + +krb5_error_code +pkinit_process_td_trusted_certifiers( + krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_external_principal_identifier **krb5_trusted_certifiers, + int td_type) +{ + krb5_error_code retval = ENOMEM; + STACK_OF(X509_NAME) *sk_xn = NULL; + X509_NAME *xn = NULL; + PKCS7_ISSUER_AND_SERIAL *is = NULL; + ASN1_OCTET_STRING *id = NULL; + const unsigned char *p = NULL; + char buf[DN_BUF_LEN]; + int i = 0; + + if (td_type == TD_TRUSTED_CERTIFIERS) + pkiDebug("received trusted certifiers\n"); + else + pkiDebug("received invalid certificate\n"); + + sk_xn = sk_X509_NAME_new_null(); + while(krb5_trusted_certifiers[i] != NULL) { + if (krb5_trusted_certifiers[i]->subjectName.data != NULL) { + p = krb5_trusted_certifiers[i]->subjectName.data; + xn = d2i_X509_NAME(NULL, &p, + (int)krb5_trusted_certifiers[i]->subjectName.length); + if (xn == NULL) + goto cleanup; + X509_NAME_oneline(xn, buf, sizeof(buf)); + if (td_type == TD_TRUSTED_CERTIFIERS) + pkiDebug("#%d cert = %s is trusted by kdc\n", i, buf); + else + pkiDebug("#%d cert = %s is invalid\n", i, buf); + sk_X509_NAME_push(sk_xn, xn); + } + + if (krb5_trusted_certifiers[i]->issuerAndSerialNumber.data != NULL) { + p = krb5_trusted_certifiers[i]->issuerAndSerialNumber.data; + is = d2i_PKCS7_ISSUER_AND_SERIAL(NULL, &p, + (int)krb5_trusted_certifiers[i]->issuerAndSerialNumber.length); + if (is == NULL) + goto cleanup; + X509_NAME_oneline(is->issuer, buf, sizeof(buf)); + if (td_type == TD_TRUSTED_CERTIFIERS) + pkiDebug("#%d issuer = %s serial = %ld is trusted bu kdc\n", i, + buf, ASN1_INTEGER_get(is->serial)); + else + pkiDebug("#%d issuer = %s serial = %ld is invalid\n", i, buf, + ASN1_INTEGER_get(is->serial)); + PKCS7_ISSUER_AND_SERIAL_free(is); + } + + if (krb5_trusted_certifiers[i]->subjectKeyIdentifier.data != NULL) { + p = krb5_trusted_certifiers[i]->subjectKeyIdentifier.data; + id = d2i_ASN1_OCTET_STRING(NULL, &p, + (int)krb5_trusted_certifiers[i]->subjectKeyIdentifier.length); + if (id == NULL) + goto cleanup; + /* XXX */ + ASN1_OCTET_STRING_free(id); + } + i++; + } + /* XXX Since we not doing anything with received trusted certifiers + * return an error. this is the place where we can pick a different + * client certificate based on the information in td_trusted_certifiers + */ + retval = KRB5KDC_ERR_PREAUTH_FAILED; +cleanup: + if (sk_xn != NULL) + sk_X509_NAME_pop_free(sk_xn, X509_NAME_free); + + return retval; +} + +static BIO * +pkcs7_dataDecode(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + PKCS7 *p7) +{ + int i = 0; + unsigned int jj = 0, tmp_len = 0; + BIO *out=NULL,*etmp=NULL,*bio=NULL; + unsigned char *tmp=NULL; + ASN1_OCTET_STRING *data_body=NULL; + const EVP_CIPHER *evp_cipher=NULL; + EVP_CIPHER_CTX *evp_ctx=NULL; + X509_ALGOR *enc_alg=NULL; + STACK_OF(PKCS7_RECIP_INFO) *rsk=NULL; + X509_ALGOR *xalg=NULL; + PKCS7_RECIP_INFO *ri=NULL; + X509 *cert = sk_X509_value(id_cryptoctx->my_certs, + id_cryptoctx->cert_index); + + p7->state=PKCS7_S_HEADER; + + rsk=p7->d.enveloped->recipientinfo; + enc_alg=p7->d.enveloped->enc_data->algorithm; + data_body=p7->d.enveloped->enc_data->enc_data; + evp_cipher=EVP_get_cipherbyobj(enc_alg->algorithm); + if (evp_cipher == NULL) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE,PKCS7_R_UNSUPPORTED_CIPHER_TYPE); + goto cleanup; + } + xalg=p7->d.enveloped->enc_data->algorithm; + + if ((etmp=BIO_new(BIO_f_cipher())) == NULL) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE,ERR_R_BIO_LIB); + goto cleanup; + } + + /* It was encrypted, we need to decrypt the secret key + * with the private key */ + + /* Find the recipientInfo which matches the passed certificate + * (if any) + */ + + if (cert) { + for (i=0; i<sk_PKCS7_RECIP_INFO_num(rsk); i++) { + int tmp_ret = 0; + ri=sk_PKCS7_RECIP_INFO_value(rsk,i); + tmp_ret = X509_NAME_cmp(ri->issuer_and_serial->issuer, + cert->cert_info->issuer); + if (!tmp_ret) { + tmp_ret = M_ASN1_INTEGER_cmp(cert->cert_info->serialNumber, + ri->issuer_and_serial->serial); + if (!tmp_ret) + break; + } + ri=NULL; + } + if (ri == NULL) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE, + PKCS7_R_NO_RECIPIENT_MATCHES_CERTIFICATE); + goto cleanup; + } + + } + + /* If we haven't got a certificate try each ri in turn */ + + if (cert == NULL) { + for (i=0; i<sk_PKCS7_RECIP_INFO_num(rsk); i++) { + ri=sk_PKCS7_RECIP_INFO_value(rsk,i); + jj = pkinit_decode_data(context, id_cryptoctx, + M_ASN1_STRING_data(ri->enc_key), + (unsigned int) M_ASN1_STRING_length(ri->enc_key), + &tmp, &tmp_len); + if (jj) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE, ERR_R_EVP_LIB); + goto cleanup; + } + + if (!jj && tmp_len > 0) { + jj = tmp_len; + break; + } + + ERR_clear_error(); + ri = NULL; + } + + if (ri == NULL) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE, PKCS7_R_NO_RECIPIENT_MATCHES_KEY); + goto cleanup; + } + } + else { + jj = pkinit_decode_data(context, id_cryptoctx, + M_ASN1_STRING_data(ri->enc_key), + (unsigned int) M_ASN1_STRING_length(ri->enc_key), + &tmp, &tmp_len); + if (jj || tmp_len <= 0) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE, ERR_R_EVP_LIB); + goto cleanup; + } + jj = tmp_len; + } + + evp_ctx=NULL; + BIO_get_cipher_ctx(etmp,&evp_ctx); + if (EVP_CipherInit_ex(evp_ctx,evp_cipher,NULL,NULL,NULL,0) <= 0) + goto cleanup; + if (EVP_CIPHER_asn1_to_param(evp_ctx,enc_alg->parameter) < 0) + goto cleanup; + + if (jj != EVP_CIPHER_CTX_key_length(evp_ctx)) { + /* Some S/MIME clients don't use the same key + * and effective key length. The key length is + * determined by the size of the decrypted RSA key. + */ + if(!EVP_CIPHER_CTX_set_key_length(evp_ctx, (int)jj)) { + PKCS7err(PKCS7_F_PKCS7_DATADECODE, + PKCS7_R_DECRYPTED_KEY_IS_WRONG_LENGTH); + goto cleanup; + } + } + if (EVP_CipherInit_ex(evp_ctx,NULL,NULL,tmp,NULL,0) <= 0) + goto cleanup; + + OPENSSL_cleanse(tmp,jj); + + if (out == NULL) + out=etmp; + else + BIO_push(out,etmp); + etmp=NULL; + + if (data_body->length > 0) + bio = BIO_new_mem_buf(data_body->data, data_body->length); + else { + bio=BIO_new(BIO_s_mem()); + BIO_set_mem_eof_return(bio,0); + } + BIO_push(out,bio); + bio=NULL; + + if (0) { +cleanup: + if (out != NULL) BIO_free_all(out); + if (etmp != NULL) BIO_free_all(etmp); + if (bio != NULL) BIO_free_all(bio); + out=NULL; + } + + if (tmp != NULL) + free(tmp); + + return(out); +} + +static krb5_error_code +der_decode_data(unsigned char *data, long data_len, + unsigned char **out, long *out_len) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + ASN1_OCTET_STRING *s = NULL; + const unsigned char *p = data; + + if ((s = d2i_ASN1_BIT_STRING(NULL, &p, data_len)) == NULL) + goto cleanup; + *out_len = s->length; + if ((*out = (unsigned char *) malloc((size_t) *out_len + 1)) == NULL) { + retval = ENOMEM; + goto cleanup; + } + memcpy(*out, s->data, (size_t) s->length); + (*out)[s->length] = '\0'; + + retval = 0; + cleanup: + if (s != NULL) + ASN1_OCTET_STRING_free(s); + + return retval; +} + + +#ifdef DEBUG_DH +static void +print_dh(DH * dh, char *msg) +{ + BIO *bio_err = NULL; + + bio_err = BIO_new(BIO_s_file()); + BIO_set_fp(bio_err, stderr, BIO_NOCLOSE | BIO_FP_TEXT); + + if (msg) + BIO_puts(bio_err, (const char *)msg); + if (dh) + DHparams_print(bio_err, dh); + + BN_print(bio_err, dh->q); + BIO_puts(bio_err, (const char *)"\n"); + BIO_free(bio_err); + +} + +static void +print_pubkey(BIGNUM * key, char *msg) +{ + BIO *bio_err = NULL; + + bio_err = BIO_new(BIO_s_file()); + BIO_set_fp(bio_err, stderr, BIO_NOCLOSE | BIO_FP_TEXT); + + if (msg) + BIO_puts(bio_err, (const char *)msg); + if (key) + BN_print(bio_err, key); + BIO_puts(bio_err, "\n"); + + BIO_free(bio_err); + +} +#endif + +static char * +pkinit_pkcs11_code_to_text(int err) +{ + int i; + static char uc[32]; + + for (i = 0; pkcs11_errstrings[i].text != NULL; i++) + if (pkcs11_errstrings[i].code == err) + break; + if (pkcs11_errstrings[i].text != NULL) + return (pkcs11_errstrings[i].text); + sprintf(uc, "unknown code 0x%x", err); + return (uc); +} diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h new file mode 100644 index 0000000..9c824c8 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.h @@ -0,0 +1,265 @@ +/* + * COPYRIGHT (C) 2006,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. + */ + +#ifndef _PKINIT_CRYPTO_OPENSSL_H +#define _PKINIT_CRYPTO_OPENSSL_H + +#include <openssl/bn.h> +#include <openssl/dh.h> +#include <openssl/x509.h> +#include <openssl/pkcs7.h> +#include <openssl/pkcs12.h> +#include <openssl/obj_mac.h> +#include <openssl/x509v3.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/asn1_mac.h> +#include <openssl/sha.h> +#include <openssl/asn1.h> +#include <openssl/pem.h> + +#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 */ + STACK_OF(X509) *trustedCAs; /* available trusted ca certs */ + STACK_OF(X509) *intermediateCAs; /* available intermediate ca certs */ + STACK_OF(X509_CRL) *revoked; /* available crls */ + int pkcs11_method; + krb5_prompter_fct prompter; + void *prompter_data; +#ifndef WITHOUT_PKCS11 + char *p11_module_name; + CK_SLOT_ID slotid; + char *token_label; + char *cert_label; + /* These are crypto-specific */ + void *p11_module; + CK_SESSION_HANDLE session; + CK_FUNCTION_LIST_PTR p11; + CK_BYTE_PTR cert_id; + int cert_id_len; + CK_MECHANISM_TYPE mech; +#endif +}; + +struct _pkinit_plg_crypto_context { + DH *dh_1024; + DH *dh_2048; + DH *dh_4096; + ASN1_OBJECT *id_pkinit_authData; + ASN1_OBJECT *id_pkinit_authData9; + ASN1_OBJECT *id_pkinit_DHKeyData; + ASN1_OBJECT *id_pkinit_rkeyData; + ASN1_OBJECT *id_pkinit_san; + ASN1_OBJECT *id_ms_san_upn; + ASN1_OBJECT *id_pkinit_KPClientAuth; + ASN1_OBJECT *id_pkinit_KPKdc; + ASN1_OBJECT *id_ms_kp_sc_logon; + ASN1_OBJECT *id_kp_serverAuth; +}; + +struct _pkinit_req_crypto_context { + X509 *received_cert; + DH *dh; +}; + +#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; +}; + +static void openssl_init(void); + +static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context ); +static void pkinit_fini_pkinit_oids(pkinit_plg_crypto_context ); + +static krb5_error_code pkinit_init_dh_params(pkinit_plg_crypto_context ); +static void pkinit_fini_dh_params(pkinit_plg_crypto_context ); + +static krb5_error_code pkinit_init_certs(pkinit_identity_crypto_context ctx); +static void pkinit_fini_certs(pkinit_identity_crypto_context ctx); + +static krb5_error_code pkinit_init_pkcs11(pkinit_identity_crypto_context ctx); +static void pkinit_fini_pkcs11(pkinit_identity_crypto_context ctx); + +static krb5_error_code pkinit_encode_dh_params + (BIGNUM *, BIGNUM *, BIGNUM *, unsigned char **, unsigned int *); +static DH *pkinit_decode_dh_params + (DH **, unsigned char **, unsigned int ); +static int pkinit_check_dh_params + (BIGNUM * p1, BIGNUM * p2, BIGNUM * g1, BIGNUM * q1); + +static krb5_error_code pkinit_sign_data + (krb5_context context, pkinit_identity_crypto_context cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); + +static krb5_error_code create_signature + (unsigned char **, unsigned int *, unsigned char *, unsigned int, + EVP_PKEY *pkey); + +static krb5_error_code pkinit_decode_data + (krb5_context context, pkinit_identity_crypto_context cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **decoded, unsigned int *decoded_len); + +static krb5_error_code decode_data + (unsigned char **, unsigned int *, unsigned char *, unsigned int, + EVP_PKEY *pkey, X509 *cert); + +#ifdef DEBUG_DH +static void print_dh(DH *, char *); +static void print_pubkey(BIGNUM *, char *); +#endif + +static int prepare_enc_data + (unsigned char *indata, int indata_len, unsigned char **outdata, + int *outdata_len); + +static int openssl_callback (int, X509_STORE_CTX *); +static int openssl_callback_ignore_crls (int, X509_STORE_CTX *); + +static int pkcs7_decrypt + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + PKCS7 *p7, BIO *bio); + +static BIO * pkcs7_dataDecode + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + PKCS7 *p7); + +static ASN1_OBJECT * pkinit_pkcs7type2oid + (pkinit_plg_crypto_context plg_cryptoctx, int pkcs7_type); + +static krb5_error_code pkinit_create_sequence_of_principal_identifiers + (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + int type, krb5_data **out_data); + +#ifndef WITHOUT_PKCS11 +static krb5_error_code pkinit_find_private_key + (pkinit_identity_crypto_context, CK_ATTRIBUTE_TYPE usage, + CK_OBJECT_HANDLE *objp); +static krb5_error_code pkinit_login + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + CK_TOKEN_INFO *tip); +static krb5_error_code pkinit_open_session + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx); +static void * pkinit_C_LoadModule(const char *modname, CK_FUNCTION_LIST_PTR_PTR p11p); +static CK_RV pkinit_C_UnloadModule(void *handle); +#ifdef SILLYDECRYPT +CK_RV pkinit_C_Decrypt + (pkinit_identity_crypto_context id_cryptoctx, + CK_BYTE_PTR pEncryptedData, CK_ULONG ulEncryptedDataLen, + CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen); +#endif + +static krb5_error_code pkinit_sign_data_pkcs11 + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); +static krb5_error_code pkinit_decode_data_pkcs11 + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **decoded_data, unsigned int *decoded_data_len); +#endif /* WITHOUT_PKCS11 */ + +static krb5_error_code pkinit_sign_data_fs + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **sig, unsigned int *sig_len); +static krb5_error_code pkinit_decode_data_fs + (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, + unsigned char *data, unsigned int data_len, + unsigned char **decoded_data, unsigned int *decoded_data_len); + +static krb5_error_code der_decode_data + (unsigned char *, long, unsigned char **, long *); + +static krb5_error_code +create_krb5_invalidCertificates(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + krb5_external_principal_identifier *** ids); + +static krb5_error_code +create_identifiers_from_stack(STACK_OF(X509) *sk, + krb5_external_principal_identifier *** ids); +#ifdef LONGHORN_BETA_COMPAT +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len, + int is_longhorn_server); +#else +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len); +#endif + +/* This handy macro borrowed from crypto/x509v3/v3_purp.c */ +#define ku_reject(x, usage) \ + (((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage))) + +static char * +pkinit_pkcs11_code_to_text(int err); + +#endif /* _PKINIT_CRYPTO_OPENSSL_H */ diff --git a/src/plugins/preauth/pkinit/pkinit_identity.c b/src/plugins/preauth/pkinit/pkinit_identity.c new file mode 100644 index 0000000..227c55d --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_identity.c @@ -0,0 +1,668 @@ +/* + * 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 <dlfcn.h> +#include <unistd.h> +#include <dirent.h> + +#include "pkinit.h" + +static void +free_list(char **list) +{ + int i; + + if (list == NULL) + return; + + for (i = 0; list[i] != NULL; i++) + free(list[i]); + free(list); +} + +static krb5_error_code +copy_list(char ***dst, char **src) +{ + int i; + char **newlist; + + if (dst == NULL) + return EINVAL; + *dst = NULL; + + if (src == NULL) + return 0; + + for (i = 0; src[i] != NULL; i++); + + newlist = calloc(1, (i + 1) * sizeof(*newlist)); + if (newlist == NULL) + return ENOMEM; + + for (i = 0; src[i] != NULL; i++) { + newlist[i] = strdup(src[i]); + if (newlist[i] == NULL) + goto cleanup; + } + newlist[i] = NULL; + *dst = newlist; + return 0; +cleanup: + free_list(newlist); + return ENOMEM; +} + +char * +idtype2string(int idtype) +{ + switch(idtype) { + case IDTYPE_FILE: return "FILE"; break; + case IDTYPE_DIR: return "DIR"; break; + case IDTYPE_PKCS11: return "PKCS11"; break; + case IDTYPE_PKCS12: return "PKCS12"; break; + case IDTYPE_ENVVAR: return "ENV"; break; + default: return "INVALID"; break; + } +} + +char * +catype2string(int catype) +{ + switch(catype) { + case CATYPE_ANCHORS: return "ANCHORS"; break; + case CATYPE_INTERMEDIATES: return "INTERMEDIATES"; break; + case CATYPE_CRLS: return "CRLS"; break; + default: return "INVALID"; break; + } +} + +krb5_error_code +pkinit_init_identity_opts(pkinit_identity_opts **idopts) +{ + pkinit_identity_opts *opts = NULL; + + *idopts = NULL; + opts = (pkinit_identity_opts *) calloc(1, sizeof(pkinit_identity_opts)); + if (opts == NULL) + return ENOMEM; + + opts->identity = NULL; + opts->anchors = NULL; + opts->intermediates = NULL; + opts->crls = NULL; + opts->ocsp = NULL; + opts->dn_mapping_file = NULL; + + opts->cert_filename = NULL; + opts->key_filename = NULL; +#ifndef WITHOUT_PKCS11 + opts->p11_module_name = NULL; + opts->slotid = PK_NOSLOT; + opts->token_label = NULL; + opts->cert_id_string = NULL; + opts->cert_label = NULL; +#endif + + *idopts = opts; + + return 0; +} + +krb5_error_code +pkinit_dup_identity_opts(pkinit_identity_opts *src_opts, + pkinit_identity_opts **dest_opts) +{ + pkinit_identity_opts *newopts; + krb5_error_code retval; + + *dest_opts = NULL; + retval = pkinit_init_identity_opts(&newopts); + if (retval) + return retval; + + retval = ENOMEM; + + if (src_opts->identity != NULL) { + newopts->identity = strdup(src_opts->identity); + if (newopts->identity == NULL) + goto cleanup; + } + + retval = copy_list(&newopts->anchors, src_opts->anchors); + if (retval) + goto cleanup; + + retval = copy_list(&newopts->intermediates,src_opts->intermediates); + if (retval) + goto cleanup; + + retval = copy_list(&newopts->crls, src_opts->crls); + if (retval) + goto cleanup; + + if (src_opts->ocsp != NULL) { + newopts->ocsp = strdup(src_opts->ocsp); + if (newopts->ocsp == NULL) + goto cleanup; + } + + if (src_opts->cert_filename != NULL) { + newopts->cert_filename = strdup(src_opts->cert_filename); + if (newopts->cert_filename == NULL) + goto cleanup; + } + + if (src_opts->key_filename != NULL) { + newopts->key_filename = strdup(src_opts->key_filename); + if (newopts->key_filename == NULL) + 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) + goto cleanup; + } + + newopts->slotid = src_opts->slotid; + + if (src_opts->token_label != NULL) { + newopts->token_label = strdup(src_opts->token_label); + if (newopts->token_label == NULL) + goto cleanup; + } + + if (src_opts->cert_id_string != NULL) { + newopts->cert_id_string = strdup(src_opts->cert_id_string); + if (newopts->cert_id_string == NULL) + goto cleanup; + } + + if (src_opts->cert_label != NULL) { + newopts->cert_label = strdup(src_opts->cert_label); + if (newopts->cert_label == NULL) + goto cleanup; + } +#endif + + + *dest_opts = newopts; + return 0; +cleanup: + pkinit_fini_identity_opts(newopts); + return retval; +} + +void +pkinit_fini_identity_opts(pkinit_identity_opts *idopts) +{ + if (idopts == NULL) + return; + + if (idopts->identity != NULL) + free(idopts->identity); + free_list(idopts->anchors); + free_list(idopts->intermediates); + free_list(idopts->crls); + free_list(idopts->identity_alt); + + if (idopts->cert_filename != NULL) + free(idopts->cert_filename); + if (idopts->key_filename != NULL) + free(idopts->key_filename); +#ifndef WITHOUT_PKCS11 + if (idopts->p11_module_name != NULL) + free(idopts->p11_module_name); + if (idopts->token_label != NULL) + free(idopts->token_label); + if (idopts->cert_id_string != NULL) + free(idopts->cert_id_string); + if (idopts->cert_label != NULL) + free(idopts->cert_label); +#endif + free(idopts); +} + +#ifndef WITHOUT_PKCS11 +static krb5_error_code +parse_pkcs11_options(krb5_context context, + pkinit_identity_opts *idopts, + const char *residual) +{ + char *s, *cp, *vp; + krb5_error_code retval = ENOMEM; + + if (residual == NULL || residual[0] == '\0') + return 0; + + /* Split string into attr=value substrings */ + s = strdup(residual); + if (s == NULL) + return retval; + + for ((cp = strtok(s, ":")); cp; (cp = strtok(NULL, ":"))) { + vp = strchr(cp, '='); + + /* If there is no "=", this is a pkcs11 module name */ + if (vp == NULL) { + if (idopts->p11_module_name != NULL) + free(idopts->p11_module_name); + idopts->p11_module_name = strdup(cp); + if (idopts->p11_module_name == NULL) + goto cleanup; + continue; + } + *vp++ = '\0'; + if (!strcmp(cp, "module_name")) { + if (idopts->p11_module_name != NULL) + free(idopts->p11_module_name); + idopts->p11_module_name = strdup(vp); + if (idopts->p11_module_name == NULL) + goto cleanup; + } else if (!strcmp(cp, "slotid")) { + long slotid = strtol(vp, NULL, 10); + if ((slotid == LONG_MIN || slotid == LONG_MAX) && errno != 0) { + retval = EINVAL; + goto cleanup; + } + if ((long) (int) slotid != slotid) { + retval = EINVAL; + goto cleanup; + } + idopts->slotid = slotid; + } else if (!strcmp(cp, "token")) { + if (idopts->token_label != NULL) + free(idopts->token_label); + idopts->token_label = strdup(vp); + if (idopts->token_label == NULL) + goto cleanup; + } else if (!strcmp(cp, "certid")) { + if (idopts->cert_id_string != NULL) + free(idopts->cert_id_string); + idopts->cert_id_string = strdup(vp); + if (idopts->cert_id_string == NULL) + goto cleanup; + } else if (!strcmp(cp, "certlabel")) { + if (idopts->cert_label != NULL) + free(idopts->cert_label); + idopts->cert_label = strdup(vp); + if (idopts->cert_label == NULL) + goto cleanup; + } + } + retval = 0; +cleanup: + free(s); + return retval; +} +#endif + +static krb5_error_code +parse_fs_options(krb5_context context, + pkinit_identity_opts *idopts, + const char *residual) +{ + char *certname, *keyname; + krb5_error_code retval = ENOMEM; + + if (residual == NULL || residual[0] == '\0') + return 0; + + certname = strdup(residual); + if (certname == NULL) + goto cleanup; + + certname = strtok(certname, ","); + keyname = strtok(NULL, ","); + + idopts->cert_filename = strdup(certname); + if (idopts->cert_filename == NULL) + goto cleanup; + + idopts->key_filename = strdup(keyname ? keyname : certname); + if (idopts->key_filename == NULL) + goto cleanup; + + retval = 0; +cleanup: + if (certname != NULL) + free(certname); + return retval; +} + +static krb5_error_code +parse_pkcs12_options(krb5_context context, + pkinit_identity_opts *idopts, + const char *residual) +{ + krb5_error_code retval = ENOMEM; + + if (residual == NULL || residual[0] == '\0') + return 0; + + idopts->cert_filename = strdup(residual); + if (idopts->cert_filename == NULL) + goto cleanup; + + idopts->key_filename = strdup(residual); + if (idopts->key_filename == NULL) + goto cleanup; + + pkiDebug("%s: cert_filename '%s' key_filename '%s'\n", + __FUNCTION__, idopts->cert_filename, + idopts->key_filename); + retval = 0; +cleanup: + return retval; +} + +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) +{ + const char *residual; + int idtype; + krb5_error_code retval = 0; + + pkiDebug("%s: processing value '%s'\n", + __FUNCTION__, value ? value : "NULL"); + if (value == NULL) + return EINVAL; + + residual = strchr(value, ':'); + if (residual != NULL) { + unsigned int typelen; + residual++; /* skip past colon */ + typelen = residual - value; + if (strncmp(value, "FILE:", typelen) == 0) { + idtype = IDTYPE_FILE; +#ifndef WITHOUT_PKCS11 + } else if (strncmp(value, "PKCS11:", typelen) == 0) { + idtype = IDTYPE_PKCS11; +#endif + } else if (strncmp(value, "PKCS12:", typelen) == 0) { + idtype = IDTYPE_PKCS12; + } else if (strncmp(value, "DIR:", typelen) == 0) { + idtype = IDTYPE_DIR; + } else if (strncmp(value, "ENV:", typelen) == 0) { + idtype = IDTYPE_ENVVAR; + } else { + pkiDebug("%s: Unsupported type while processing '%s'\n", + __FUNCTION__, value); + krb5_set_error_message(context, KRB5_PREAUTH_FAILED, + "Unsupported type while processing '%s'\n", + value); + return KRB5_PREAUTH_FAILED; + } + } else { + idtype = IDTYPE_FILE; + residual = value; + } + + idopts->idtype = idtype; + pkiDebug("%s: idtype is %s\n", __FUNCTION__, idtype2string(idopts->idtype)); + switch (idtype) { + case IDTYPE_ENVVAR: + 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); + break; + case IDTYPE_PKCS12: + retval = parse_pkcs12_options(context, idopts, residual); + break; +#ifndef WITHOUT_PKCS11 + case IDTYPE_PKCS11: + retval = parse_pkcs11_options(context, idopts, residual); + break; +#endif + case IDTYPE_DIR: + idopts->cert_filename = strdup(residual); + if (idopts->cert_filename == NULL) + retval = ENOMEM; + break; + default: + krb5_set_error_message(context, KRB5_PREAUTH_FAILED, + "Internal error parsing X509_user_identity\n"); + retval = EINVAL; + break; + } + 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, + int catype) +{ + char *residual; + unsigned int typelen; + int idtype; + + pkiDebug("%s: processing catype %s, value '%s'\n", + __FUNCTION__, catype2string(catype), value); + residual = strchr(value, ':'); + if (residual == NULL) { + pkiDebug("No type given for '%s'\n", value); + return EINVAL; + } + residual++; /* skip past colon */ + typelen = residual - value; + if (strncmp(value, "FILE:", typelen) == 0) { + idtype = IDTYPE_FILE; + } else if (strncmp(value, "DIR:", typelen) == 0) { + idtype = IDTYPE_DIR; + } else { + return ENOTSUP; + } + return crypto_load_cas_and_crls(context, + 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, + const char *value) +{ + krb5_error_code retval = 0; + + switch (attr) { + case PKINIT_ID_OPT_USER_IDENTITY: + 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, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, value, + CATYPE_ANCHORS); + break; + case PKINIT_ID_OPT_INTERMEDIATE_CAS: + 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, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, + value, CATYPE_CRLS); + break; + case PKINIT_ID_OPT_OCSP: + retval = ENOTSUP; + break; + default: + retval = EINVAL; + break; + } + return retval; +} + +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, + int do_matching, + krb5_principal princ) +{ + krb5_error_code retval = EINVAL; + int i; + + pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx); + if (idopts == NULL || id_cryptoctx == NULL) + goto errout; + + /* + * If identity was specified, use that. (For the kdc, this + * is specified as pkinit_identity in the kdc.conf. For users, + * this is specified on the command line via X509_user_identity.) + * If a user did not specify identity on the command line, + * then we will try alternatives which may have been specified + * in the config file. + */ + if (idopts->identity != NULL) { + 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, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, + PKINIT_ID_OPT_USER_IDENTITY, + idopts->identity_alt[i]); + } else { + pkiDebug("%s: no user identity options specified\n", __FUNCTION__); + goto errout; + } + 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, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, + PKINIT_ID_OPT_ANCHOR_CAS, + idopts->anchors[i]); + if (retval) + goto errout; + } + for (i = 0; idopts->intermediates != NULL + && idopts->intermediates[i] != NULL; i++) { + 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, 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, plg_cryptoctx, + req_cryptoctx, idopts, + id_cryptoctx, + PKINIT_ID_OPT_OCSP, + idopts->ocsp); + if (retval) + goto errout; + } + +errout: + return retval; +} + diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c new file mode 100644 index 0000000..f49ef5e --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_lib.c @@ -0,0 +1,477 @@ +/* + * COPYRIGHT (C) 2006,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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +#include "pkinit.h" + +#define FAKECERT + +const krb5_octet_data + dh_oid = { 0, 7, (unsigned char *)"\x2A\x86\x48\xce\x3e\x02\x01" }; + + +krb5_error_code +pkinit_init_req_opts(pkinit_req_opts **reqopts) +{ + krb5_error_code retval = ENOMEM; + pkinit_req_opts *opts = NULL; + + *reqopts = NULL; + opts = (pkinit_req_opts *) calloc(1, sizeof(pkinit_req_opts)); + if (opts == NULL) + return retval; + + opts->require_eku = 1; + opts->accept_secondary_eku = 0; + opts->allow_upn = 0; + opts->dh_or_rsa = DH_PROTOCOL; + opts->require_crl_checking = 0; + opts->dh_size = PKINIT_DEFAULT_DH_MIN_BITS; + opts->win2k_target = 0; + opts->win2k_require_cksum = 0; + + *reqopts = opts; + + return 0; +} + +void +pkinit_fini_req_opts(pkinit_req_opts *opts) +{ + if (opts != NULL) + free(opts); + return; +} + +krb5_error_code +pkinit_init_plg_opts(pkinit_plg_opts **plgopts) +{ + krb5_error_code retval = ENOMEM; + pkinit_plg_opts *opts = NULL; + + *plgopts = NULL; + opts = (pkinit_plg_opts *) calloc(1, sizeof(pkinit_plg_opts)); + if (opts == NULL) + return retval; + + opts->require_eku = 1; + opts->accept_secondary_eku = 0; + opts->dh_or_rsa = DH_PROTOCOL; + opts->allow_upn = 0; + opts->require_crl_checking = 0; + + opts->dh_min_bits = PKINIT_DEFAULT_DH_MIN_BITS; + + *plgopts = opts; + + return 0; +} + +void +pkinit_fini_plg_opts(pkinit_plg_opts *opts) +{ + if (opts != NULL) + free(opts); + return; +} + +void +free_krb5_pa_pk_as_req(krb5_pa_pk_as_req **in) +{ + if (*in == NULL) return; + if ((*in)->signedAuthPack.data != NULL) + free((*in)->signedAuthPack.data); + if ((*in)->trustedCertifiers != NULL) + free_krb5_external_principal_identifier(&(*in)->trustedCertifiers); + if ((*in)->kdcPkId.data != NULL) + free((*in)->kdcPkId.data); + free(*in); +} + +void +free_krb5_pa_pk_as_req_draft9(krb5_pa_pk_as_req_draft9 **in) +{ + if (*in == NULL) return; + if ((*in)->signedAuthPack.data != NULL) + free((*in)->signedAuthPack.data); + if ((*in)->kdcCert.data != NULL) + free((*in)->kdcCert.data); + if ((*in)->encryptionCert.data != NULL) + free((*in)->encryptionCert.data); + if ((*in)->trustedCertifiers != NULL) + free_krb5_trusted_ca(&(*in)->trustedCertifiers); + free(*in); +} + +void +free_krb5_reply_key_pack(krb5_reply_key_pack **in) +{ + if (*in == NULL) return; + if ((*in)->replyKey.contents != NULL) + free((*in)->replyKey.contents); + if ((*in)->asChecksum.contents != NULL) + free((*in)->asChecksum.contents); + free(*in); +} + +void +free_krb5_reply_key_pack_draft9(krb5_reply_key_pack_draft9 **in) +{ + if (*in == NULL) return; + if ((*in)->replyKey.contents != NULL) + free((*in)->replyKey.contents); + free(*in); +} + +void +free_krb5_auth_pack(krb5_auth_pack **in) +{ + if ((*in) == NULL) return; + if ((*in)->clientPublicValue != NULL) { + if ((*in)->clientPublicValue->algorithm.algorithm.data != NULL) + free((*in)->clientPublicValue->algorithm.algorithm.data); + if ((*in)->clientPublicValue->algorithm.parameters.data != NULL) + free((*in)->clientPublicValue->algorithm.parameters.data); + if ((*in)->clientPublicValue->subjectPublicKey.data != NULL) + free((*in)->clientPublicValue->subjectPublicKey.data); + free((*in)->clientPublicValue); + } + if ((*in)->pkAuthenticator.paChecksum.contents != NULL) + free((*in)->pkAuthenticator.paChecksum.contents); + if ((*in)->supportedCMSTypes != NULL) + free_krb5_algorithm_identifiers(&((*in)->supportedCMSTypes)); + free(*in); +} + +void +free_krb5_auth_pack_draft9(krb5_context context, + krb5_auth_pack_draft9 **in) +{ + if ((*in) == NULL) return; + krb5_free_principal(context, (*in)->pkAuthenticator.kdcName); + free(*in); +} + +void +free_krb5_pa_pk_as_rep(krb5_pa_pk_as_rep **in) +{ + if (*in == NULL) return; + switch ((*in)->choice) { + case choice_pa_pk_as_rep_dhInfo: + if ((*in)->u.dh_Info.dhSignedData.data != NULL) + free((*in)->u.dh_Info.dhSignedData.data); + break; + case choice_pa_pk_as_rep_encKeyPack: + if ((*in)->u.encKeyPack.data != NULL) + free((*in)->u.encKeyPack.data); + break; + default: + break; + } + free(*in); +} + +void +free_krb5_pa_pk_as_rep_draft9(krb5_pa_pk_as_rep_draft9 **in) +{ + if (*in == NULL) return; + if ((*in)->u.encKeyPack.data != NULL) + free((*in)->u.encKeyPack.data); + free(*in); +} + +void +free_krb5_external_principal_identifier(krb5_external_principal_identifier ***in) +{ + int i = 0; + if (*in == NULL) return; + while ((*in)[i] != NULL) { + if ((*in)[i]->subjectName.data != NULL) + free((*in)[i]->subjectName.data); + if ((*in)[i]->issuerAndSerialNumber.data != NULL) + free((*in)[i]->issuerAndSerialNumber.data); + if ((*in)[i]->subjectKeyIdentifier.data != NULL) + free((*in)[i]->subjectKeyIdentifier.data); + free((*in)[i]); + i++; + } + free(*in); +} + +void +free_krb5_trusted_ca(krb5_trusted_ca ***in) +{ + int i = 0; + if (*in == NULL) return; + while ((*in)[i] != NULL) { + switch((*in)[i]->choice) { + case choice_trusted_cas_principalName: + break; + case choice_trusted_cas_caName: + if ((*in)[i]->u.caName.data != NULL) + free((*in)[i]->u.caName.data); + break; + case choice_trusted_cas_issuerAndSerial: + if ((*in)[i]->u.issuerAndSerial.data != NULL) + free((*in)[i]->u.issuerAndSerial.data); + break; + case choice_trusted_cas_UNKNOWN: + break; + } + free((*in)[i]); + i++; + } + free(*in); +} + +void +free_krb5_typed_data(krb5_typed_data ***in) +{ + int i = 0; + if (*in == NULL) return; + while ((*in)[i] != NULL) { + if ((*in)[i]->data != NULL) + free((*in)[i]->data); + free((*in)[i]); + i++; + } + free(*in); +} + +void +free_krb5_algorithm_identifier(krb5_algorithm_identifier *in) +{ + if (in == NULL) + return; + if (in->algorithm.data != NULL) + free(in->algorithm.data); + if (in->parameters.data != NULL) + free(in->parameters.data); + free(in); +} + +void +free_krb5_algorithm_identifiers(krb5_algorithm_identifier ***in) +{ + int i; + if (in == NULL || *in == NULL) + return; + for (i = 0; (*in)[i] != NULL; i++) { + free_krb5_algorithm_identifier((*in)[i]); + } + free(*in); +} + +void +free_krb5_subject_pk_info(krb5_subject_pk_info **in) +{ + if ((*in) == NULL) return; + if ((*in)->algorithm.parameters.data != NULL) + free((*in)->algorithm.parameters.data); + if ((*in)->subjectPublicKey.data != NULL) + free((*in)->subjectPublicKey.data); + free(*in); +} + +void +free_krb5_kdc_dh_key_info(krb5_kdc_dh_key_info **in) +{ + if (*in == NULL) return; + if ((*in)->subjectPublicKey.data != NULL) + free((*in)->subjectPublicKey.data); + free(*in); +} + +void +init_krb5_pa_pk_as_req(krb5_pa_pk_as_req **in) +{ + (*in) = malloc(sizeof(krb5_pa_pk_as_req)); + if ((*in) == NULL) return; + (*in)->signedAuthPack.data = NULL; + (*in)->signedAuthPack.length = 0; + (*in)->trustedCertifiers = NULL; + (*in)->kdcPkId.data = NULL; + (*in)->kdcPkId.length = 0; +} + +void +init_krb5_pa_pk_as_req_draft9(krb5_pa_pk_as_req_draft9 **in) +{ + (*in) = malloc(sizeof(krb5_pa_pk_as_req_draft9)); + if ((*in) == NULL) return; + (*in)->signedAuthPack.data = NULL; + (*in)->signedAuthPack.length = 0; + (*in)->trustedCertifiers = NULL; + (*in)->kdcCert.data = NULL; + (*in)->kdcCert.length = 0; + (*in)->encryptionCert.data = NULL; + (*in)->encryptionCert.length = 0; +} + +void +init_krb5_reply_key_pack(krb5_reply_key_pack **in) +{ + (*in) = malloc(sizeof(krb5_reply_key_pack)); + if ((*in) == NULL) return; + (*in)->replyKey.contents = NULL; + (*in)->replyKey.length = 0; + (*in)->asChecksum.contents = NULL; + (*in)->asChecksum.length = 0; +} + +void +init_krb5_reply_key_pack_draft9(krb5_reply_key_pack_draft9 **in) +{ + (*in) = malloc(sizeof(krb5_reply_key_pack_draft9)); + if ((*in) == NULL) return; + (*in)->replyKey.contents = NULL; + (*in)->replyKey.length = 0; +} + +void +init_krb5_auth_pack(krb5_auth_pack **in) +{ + (*in) = malloc(sizeof(krb5_auth_pack)); + if ((*in) == NULL) return; + (*in)->clientPublicValue = NULL; + (*in)->supportedCMSTypes = NULL; + (*in)->clientDHNonce.length = 0; + (*in)->clientDHNonce.data = NULL; + (*in)->pkAuthenticator.paChecksum.contents = NULL; +} + +void +init_krb5_auth_pack_draft9(krb5_auth_pack_draft9 **in) +{ + (*in) = malloc(sizeof(krb5_auth_pack_draft9)); + if ((*in) == NULL) return; + (*in)->clientPublicValue = NULL; +} + +void +init_krb5_pa_pk_as_rep(krb5_pa_pk_as_rep **in) +{ + (*in) = malloc(sizeof(krb5_pa_pk_as_rep)); + if ((*in) == NULL) return; + (*in)->u.dh_Info.serverDHNonce.length = 0; + (*in)->u.dh_Info.serverDHNonce.data = NULL; + (*in)->u.dh_Info.dhSignedData.length = 0; + (*in)->u.dh_Info.dhSignedData.data = NULL; + (*in)->u.encKeyPack.length = 0; + (*in)->u.encKeyPack.data = NULL; +} + +void +init_krb5_pa_pk_as_rep_draft9(krb5_pa_pk_as_rep_draft9 **in) +{ + (*in) = malloc(sizeof(krb5_pa_pk_as_rep_draft9)); + if ((*in) == NULL) return; + (*in)->u.dhSignedData.length = 0; + (*in)->u.dhSignedData.data = NULL; + (*in)->u.encKeyPack.length = 0; + (*in)->u.encKeyPack.data = NULL; +} + +void +init_krb5_typed_data(krb5_typed_data **in) +{ + (*in) = malloc(sizeof(krb5_typed_data)); + if ((*in) == NULL) return; + (*in)->type = 0; + (*in)->length = 0; + (*in)->data = NULL; +} + +void +init_krb5_subject_pk_info(krb5_subject_pk_info **in) +{ + (*in) = malloc(sizeof(krb5_subject_pk_info)); + if ((*in) == NULL) return; + (*in)->algorithm.parameters.data = NULL; + (*in)->algorithm.parameters.length = 0; + (*in)->subjectPublicKey.data = NULL; + (*in)->subjectPublicKey.length = 0; +} + +krb5_error_code +pkinit_copy_krb5_octet_data(krb5_octet_data *dst, const krb5_octet_data *src) +{ + if (dst == NULL || src == NULL) + return EINVAL; + if (src->data == NULL) { + dst->data = NULL; + dst->length = 0; + return 0; + } + dst->data = malloc(src->length); + if (dst->data == NULL) + return ENOMEM; + memcpy(dst->data, src->data, src->length); + dst->length = src->length; + return 0; +} + +/* debugging functions */ +void +print_buffer(unsigned char *buf, unsigned int len) +{ + int i = 0; + if (len <= 0) + return; + + for (i = 0; i < len; i++) + pkiDebug("%02x ", buf[i]); + pkiDebug("\n"); +} + +void +print_buffer_bin(unsigned char *buf, unsigned int len, char *filename) +{ + FILE *f = NULL; + int i = 0; + + if (len <= 0 || filename == NULL) + return; + + if ((f = fopen(filename, "w")) == NULL) + return; + + for (i = 0; i < len; i++) + fputc(buf[i], f); + + fclose(f); +} diff --git a/src/plugins/preauth/pkinit/pkinit_matching.c b/src/plugins/preauth/pkinit/pkinit_matching.c new file mode 100644 index 0000000..b790c38 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_matching.c @@ -0,0 +1,830 @@ +/* + * 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 = NULL; + 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 = NULL, *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: + if (md->sans == NULL) + break; + 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 = NULL; + + 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 = NULL; + + /* 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) { + pkiDebug("%s: Ignoring invalid rule pkinit_cert_match = '%s'\n", + __FUNCTION__, 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_profile.c b/src/plugins/preauth/pkinit/pkinit_profile.c new file mode 100644 index 0000000..403068a --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_profile.c @@ -0,0 +1,376 @@ +/* + * COPYRIGHT (C) 2006,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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include "k5-int.h" +#include "pkinit.h" + +/* + * Routines for handling profile [config file] options + */ + +/* Forward prototypes */ +static int _krb5_conf_boolean(const char *s); + +/* + * XXX + * The following is duplicated verbatim from src/lib/krb5/krb/get_in_tkt.c, + * which is duplicated from somewhere else. :-/ + * XXX + */ +static const char *const conf_yes[] = { + "y", "yes", "true", "t", "1", "on", + 0, +}; + +static const char *const conf_no[] = { + "n", "no", "false", "nil", "0", "off", + 0, +}; + +static int +_krb5_conf_boolean(const char *s) +{ + const char *const *p; + + for(p=conf_yes; *p; p++) { + if (strcasecmp(*p,s) == 0) + return 1; + } + + for(p=conf_no; *p; p++) { + if (strcasecmp(*p,s) == 0) + return 0; + } + + /* Default to "no" */ + return 0; +} + +/* + * XXX + * End duplicated code from src/lib/krb5/krb/get_in_tkt.c + * XXX + */ + +/* + * The following are based on krb5_libdefault_* functions in + * src/lib/krb5/krb/get_in_tkt.c + * N.B. This assumes that context->default_realm has + * already been established. + */ +krb5_error_code +pkinit_kdcdefault_strings(krb5_context context, const char *realmname, + const char *option, char ***ret_value) +{ + profile_t profile = NULL; + const char *names[5]; + char **values = NULL; + krb5_error_code retval; + + if (context == NULL) + return KV5M_CONTEXT; + + profile = context->profile; + + if (realmname != NULL) { + /* + * Try number one: + * + * [realms] + * REALM = { + * option = <value> + * } + */ + + names[0] = "realms"; + names[1] = realmname; + names[2] = option; + names[3] = 0; + retval = profile_get_values(profile, names, &values); + if (retval == 0 && values != NULL) + goto goodbye; + } + + /* + * Try number two: + * + * [kdcdefaults] + * option = <value> + */ + + names[0] = "kdcdefaults"; + names[1] = option; + names[2] = 0; + retval = profile_get_values(profile, names, &values); + if (retval == 0 && values != NULL) + goto goodbye; + +goodbye: + if (values == NULL) + retval = ENOENT; + + *ret_value = values; + + return retval; + +} + +krb5_error_code +pkinit_kdcdefault_string(krb5_context context, const char *realmname, + const char *option, char **ret_value) +{ + krb5_error_code retval; + char **values = NULL; + + retval = pkinit_kdcdefault_strings(context, realmname, option, &values); + if (retval) + return retval; + + if (values[0] == NULL) { + retval = ENOENT; + } else { + *ret_value = malloc(strlen(values[0]) + 1); + if (*ret_value == NULL) + retval = ENOMEM; + else + strcpy(*ret_value, values[0]); + } + + profile_free_list(values); + return retval; +} + +krb5_error_code +pkinit_kdcdefault_boolean(krb5_context context, const char *realmname, + const char *option, int default_value, int *ret_value) +{ + char *string = NULL; + krb5_error_code retval; + + retval = pkinit_kdcdefault_string(context, realmname, option, &string); + + if (retval == 0) { + *ret_value = _krb5_conf_boolean(string); + free(string); + } else + *ret_value = default_value; + + return 0; +} + +krb5_error_code +pkinit_kdcdefault_integer(krb5_context context, const char *realmname, + const char *option, int default_value, int *ret_value) +{ + char *string = NULL; + krb5_error_code retval; + + retval = pkinit_kdcdefault_string(context, realmname, option, &string); + + if (retval == 0) { + char *endptr; + long l; + l = strtol(string, &endptr, 0); + if (endptr == string) + *ret_value = default_value; + else + *ret_value = l; + free(string); + } else + *ret_value = default_value; + + return 0; +} + + +/* + * krb5_libdefault_string() is defined as static in + * src/lib/krb5/krb/get_in_tkt.c. Create local versions of + * krb5_libdefault_* functions here. We need a libdefaults_strings() + * function which is not currently supported there anyway. Also, + * add the ability to supply a default value for the boolean and + * integer functions. + */ + +krb5_error_code +pkinit_libdefault_strings(krb5_context context, const krb5_data *realm, + const char *option, char ***ret_value) +{ + profile_t profile; + const char *names[5]; + char **values = NULL; + krb5_error_code retval; + char realmstr[1024]; + + if (realm != NULL && realm->length > sizeof(realmstr)-1) + return EINVAL; + + if (realm != NULL) { + strncpy(realmstr, realm->data, realm->length); + realmstr[realm->length] = '\0'; + } + + if (!context || (context->magic != KV5M_CONTEXT)) + return KV5M_CONTEXT; + + profile = context->profile; + + + if (realm != NULL) { + /* + * Try number one: + * + * [libdefaults] + * REALM = { + * option = <value> + * } + */ + + names[0] = "libdefaults"; + names[1] = realmstr; + names[2] = option; + names[3] = 0; + retval = profile_get_values(profile, names, &values); + if (retval == 0 && values != NULL && values[0] != NULL) + goto goodbye; + + /* + * Try number two: + * + * [realms] + * REALM = { + * option = <value> + * } + */ + + names[0] = "realms"; + names[1] = realmstr; + names[2] = option; + names[3] = 0; + retval = profile_get_values(profile, names, &values); + if (retval == 0 && values != NULL && values[0] != NULL) + goto goodbye; + } + + /* + * Try number three: + * + * [libdefaults] + * option = <value> + */ + + names[0] = "libdefaults"; + names[1] = option; + names[2] = 0; + retval = profile_get_values(profile, names, &values); + if (retval == 0 && values != NULL && values[0] != NULL) + goto goodbye; + +goodbye: + if (values == NULL) + return ENOENT; + + *ret_value = values; + + return retval; +} + +krb5_error_code +pkinit_libdefault_string(krb5_context context, const krb5_data *realm, + const char *option, char **ret_value) +{ + krb5_error_code retval; + char **values = NULL; + + retval = pkinit_libdefault_strings(context, realm, option, &values); + if (retval) + return retval; + + if (values[0] == NULL) { + retval = ENOENT; + } else { + *ret_value = malloc(strlen(values[0]) + 1); + if (*ret_value == NULL) + retval = ENOMEM; + else + strcpy(*ret_value, values[0]); + } + + profile_free_list(values); + return retval; +} + +krb5_error_code +pkinit_libdefault_boolean(krb5_context context, const krb5_data *realm, + const char *option, int default_value, + int *ret_value) +{ + char *string = NULL; + krb5_error_code retval; + + retval = pkinit_libdefault_string(context, realm, option, &string); + + if (retval == 0) { + *ret_value = _krb5_conf_boolean(string); + free(string); + } else + *ret_value = default_value; + + return 0; +} + +krb5_error_code +pkinit_libdefault_integer(krb5_context context, const krb5_data *realm, + const char *option, int default_value, + int *ret_value) +{ + char *string = NULL; + krb5_error_code retval; + + retval = pkinit_libdefault_string(context, realm, option, &string); + + if (retval == 0) { + char *endptr; + long l; + l = strtol(string, &endptr, 0); + if (endptr == string) + *ret_value = default_value; + else + *ret_value = l; + free(string); + } + + return retval; +} diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c new file mode 100644 index 0000000..595a3d0 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_srv.c @@ -0,0 +1,1399 @@ +/* + * COPYRIGHT (C) 2006,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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include "pkinit.h" + +static krb5_error_code +pkinit_server_get_edata(krb5_context context, + krb5_kdc_req * request, + struct _krb5_db_entry_new * client, + struct _krb5_db_entry_new * server, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + krb5_pa_data * data); + +static krb5_error_code +pkinit_server_verify_padata(krb5_context context, + struct _krb5_db_entry_new * client, + krb5_data *req_pkt, + krb5_kdc_req * request, + krb5_enc_tkt_part * enc_tkt_reply, + krb5_pa_data * data, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + void **pa_request_context, + krb5_data **e_data, + krb5_authdata ***authz_data); + +static krb5_error_code +pkinit_server_return_padata(krb5_context context, + krb5_pa_data * padata, + struct _krb5_db_entry_new * client, + krb5_data *req_pkt, + krb5_kdc_req * request, + krb5_kdc_rep * reply, + struct _krb5_key_data * client_key, + krb5_keyblock * encrypting_key, + krb5_pa_data ** send_pa, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + void **pa_request_context); + +static int pkinit_server_get_flags + (krb5_context kcontext, krb5_preauthtype patype); + +static krb5_error_code pkinit_init_kdc_req_context + (krb5_context, void **blob); + +static void pkinit_fini_kdc_req_context + (krb5_context context, void *blob); + +static int pkinit_server_plugin_init_realm + (krb5_context context, const char *realmname, + pkinit_kdc_context *pplgctx); + +static void pkinit_server_plugin_fini_realm + (krb5_context context, pkinit_kdc_context plgctx); + +static int pkinit_server_plugin_init + (krb5_context context, void **blob, const char **realmnames); + +static void pkinit_server_plugin_fini + (krb5_context context, void *blob); + +static pkinit_kdc_context pkinit_find_realm_context + (krb5_context context, void *pa_plugin_context, krb5_principal princ); + +static krb5_error_code +pkinit_create_edata(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + pkinit_plg_opts *opts, + krb5_error_code err_code, + krb5_data **e_data) +{ + krb5_error_code retval = KRB5KRB_ERR_GENERIC; + + pkiDebug("pkinit_create_edata: creating edata for error %d (%s)\n", + err_code, error_message(err_code)); + switch(err_code) { + case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE: + retval = pkinit_create_td_trusted_certifiers(context, + plg_cryptoctx, req_cryptoctx, id_cryptoctx, e_data); + break; + case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED: + retval = pkinit_create_td_dh_parameters(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, opts, e_data); + break; + case KRB5KDC_ERR_INVALID_CERTIFICATE: + case KRB5KDC_ERR_REVOKED_CERTIFICATE: + retval = pkinit_create_td_invalid_certificate(context, + plg_cryptoctx, req_cryptoctx, id_cryptoctx, e_data); + break; + default: + pkiDebug("no edata needed for error %d (%s)\n", + err_code, error_message(err_code)); + retval = 0; + goto cleanup; + } + +cleanup: + + return retval; +} + +static krb5_error_code +pkinit_server_get_edata(krb5_context context, + krb5_kdc_req * request, + struct _krb5_db_entry_new * client, + struct _krb5_db_entry_new * server, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + krb5_pa_data * data) +{ + krb5_error_code retval = 0; + pkinit_kdc_context plgctx = NULL; + + pkiDebug("pkinit_server_get_edata: entered!\n"); + + /* + * If we don't have a realm context for the given realm, + * don't tell the client that we support pkinit! + */ + plgctx = pkinit_find_realm_context(context, pa_plugin_context, + request->server); + if (plgctx == NULL) + retval = EINVAL; + + return retval; +} + +static krb5_error_code +verify_client_san(krb5_context context, + pkinit_kdc_context plgctx, + pkinit_kdc_req_context reqctx, + krb5_principal client, + int *valid_san) +{ + krb5_error_code retval; + krb5_principal *princs = NULL; + krb5_principal *upns = NULL; + int i; +#ifdef DEBUG_SAN_INFO + char *client_string = NULL, *san_string; +#endif + + retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, + &princs, + plgctx->opts->allow_upn ? &upns : NULL, + NULL); + if (retval) { + pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__); + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; + goto out; + } + /* XXX Verify this is consistent with client side XXX */ +#if 0 + retval = call_san_checking_plugins(context, plgctx, reqctx, princs, + upns, NULL, &plugin_decision, &ignore); + pkiDebug("%s: call_san_checking_plugins() returned retval %d\n", + __FUNCTION__); + if (retval) { + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; + goto cleanup; + } + pkiDebug("%s: call_san_checking_plugins() returned decision %d\n", + __FUNCTION__, plugin_decision); + if (plugin_decision != NO_DECISION) { + retval = plugin_decision; + goto out; + } +#endif + +#ifdef DEBUG_SAN_INFO + krb5_unparse_name(context, client, &client_string); +#endif + pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__); + for (i = 0; princs != NULL && princs[i] != NULL; i++) { +#ifdef DEBUG_SAN_INFO + krb5_unparse_name(context, princs[i], &san_string); + pkiDebug("%s: Comparing client '%s' to pkinit san value '%s'\n", + __FUNCTION__, client_string, san_string); + krb5_free_unparsed_name(context, san_string); +#endif + if (krb5_principal_compare(context, princs[i], client)) { + pkiDebug("%s: pkinit san match found\n", __FUNCTION__); + *valid_san = 1; + retval = 0; + goto out; + } + } + pkiDebug("%s: no pkinit san match found\n", __FUNCTION__); + /* + * XXX if cert has names but none match, should we + * be returning KRB5KDC_ERR_CLIENT_NAME_MISMATCH here? + */ + + if (upns == NULL) { + pkiDebug("%s: no upn sans (or we wouldn't accept them anyway)\n", + __FUNCTION__); + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; + goto out; + } + + pkiDebug("%s: Checking upn sans\n", __FUNCTION__); + for (i = 0; upns[i] != NULL; i++) { +#ifdef DEBUG_SAN_INFO + krb5_unparse_name(context, upns[i], &san_string); + pkiDebug("%s: Comparing client '%s' to upn san value '%s'\n", + __FUNCTION__, client_string, san_string); + krb5_free_unparsed_name(context, san_string); +#endif + if (krb5_principal_compare(context, upns[i], client)) { + pkiDebug("%s: upn san match found\n", __FUNCTION__); + *valid_san = 1; + retval = 0; + goto out; + } + } + pkiDebug("%s: no upn san match found\n", __FUNCTION__); + + /* We found no match */ + if (princs != NULL || upns != NULL) { + *valid_san = 0; + /* XXX ??? If there was one or more name in the cert, but + * none matched the client name, then return mismatch? */ + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; + } + retval = 0; + +out: + if (princs != NULL) { + for (i = 0; princs[i] != NULL; i++) + krb5_free_principal(context, princs[i]); + free(princs); + } + if (upns != NULL) { + for (i = 0; upns[i] != NULL; i++) + krb5_free_principal(context, upns[i]); + free(upns); + } +#ifdef DEBUG_SAN_INFO + if (client_string != NULL) + krb5_free_unparsed_name(context, client_string); +#endif + pkiDebug("%s: returning retval %d, valid_san %d\n", + __FUNCTION__, retval, *valid_san); + return retval; +} + +static krb5_error_code +verify_client_eku(krb5_context context, + pkinit_kdc_context plgctx, + pkinit_kdc_req_context reqctx, + int *eku_accepted) +{ + krb5_error_code retval; + + *eku_accepted = 0; + + if (plgctx->opts->require_eku == 0) { + pkiDebug("%s: configuration requests no EKU checking\n", __FUNCTION__); + *eku_accepted = 1; + retval = 0; + goto out; + } + + retval = crypto_check_cert_eku(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, + 0, /* kdc cert */ + plgctx->opts->accept_secondary_eku, + eku_accepted); + if (retval) { + pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n", + __FUNCTION__, retval, error_message(retval)); + goto out; + } + +out: + pkiDebug("%s: returning retval %d, eku_accepted %d\n", + __FUNCTION__, retval, *eku_accepted); + return retval; +} + +static krb5_error_code +pkinit_server_verify_padata(krb5_context context, + struct _krb5_db_entry_new * client, + krb5_data *req_pkt, + krb5_kdc_req * request, + krb5_enc_tkt_part * enc_tkt_reply, + krb5_pa_data * data, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + void **pa_request_context, + krb5_data **e_data, + krb5_authdata ***authz_data) +{ + krb5_error_code retval = 0; + krb5_octet_data authp_data = {0, 0, NULL}, krb5_authz = {0, 0, NULL}; + krb5_data *encoded_pkinit_authz_data = NULL; + krb5_pa_pk_as_req *reqp = NULL; + krb5_pa_pk_as_req_draft9 *reqp9 = NULL; + krb5_auth_pack *auth_pack = NULL; + krb5_auth_pack_draft9 *auth_pack9 = NULL; + pkinit_kdc_context plgctx = NULL; + pkinit_kdc_req_context reqctx; + krb5_preauthtype pa_type; + krb5_checksum cksum = {0, 0, 0, NULL}; + krb5_data *der_req = NULL; + int valid_eku = 0, valid_san = 0; + krb5_authdata **my_authz_data = NULL, *pkinit_authz_data = NULL; + krb5_kdc_req *tmp_as_req = NULL; + krb5_data k5data; + + pkiDebug("pkinit_verify_padata: entered!\n"); + if (data == NULL || data->length <= 0 || data->contents == NULL) + return 0; + + if (pa_plugin_context == NULL || e_data == NULL) + return EINVAL; + + plgctx = pkinit_find_realm_context(context, pa_plugin_context, + request->server); + if (plgctx == NULL) + return 0; + +#ifdef DEBUG_ASN1 + print_buffer_bin(data->contents, data->length, "/tmp/kdc_as_req"); +#endif + /* create a per-request context */ + retval = pkinit_init_kdc_req_context(context, (void **)&reqctx); + if (retval) + goto cleanup; + reqctx->pa_type = data->pa_type; + + PADATA_TO_KRB5DATA(data, &k5data); + + switch ((int)data->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n"); + pa_type = (int)data->pa_type; + retval = k5int_decode_krb5_pa_pk_as_req(&k5data, &reqp); + if (retval) { + pkiDebug("decode_krb5_pa_pk_as_req failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin(reqp->signedAuthPack.data, + reqp->signedAuthPack.length, + "/tmp/kdc_signed_data"); +#endif + retval = cms_signeddata_verify(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_CLIENT, + plgctx->opts->require_crl_checking, + reqp->signedAuthPack.data, reqp->signedAuthPack.length, + &authp_data.data, &authp_data.length, &krb5_authz.data, + &krb5_authz.length); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + pkiDebug("processing KRB5_PADATA_PK_AS_REQ_OLD\n"); + pa_type = KRB5_PADATA_PK_AS_REQ_OLD; + retval = k5int_decode_krb5_pa_pk_as_req_draft9(&k5data, &reqp9); + if (retval) { + pkiDebug("decode_krb5_pa_pk_as_req_draft9 failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin(reqp9->signedAuthPack.data, + reqp9->signedAuthPack.length, + "/tmp/kdc_signed_data_draft9"); +#endif + + retval = cms_signeddata_verify(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_DRAFT9, + plgctx->opts->require_crl_checking, + reqp9->signedAuthPack.data, reqp9->signedAuthPack.length, + &authp_data.data, &authp_data.length, &krb5_authz.data, + &krb5_authz.length); + break; + default: + pkiDebug("unrecognized pa_type = %d\n", data->pa_type); + retval = EINVAL; + goto cleanup; + } + if (retval) { + pkiDebug("pkcs7_signeddata_verify failed\n"); + goto cleanup; + } + + retval = verify_client_san(context, plgctx, reqctx, request->client, + &valid_san); + if (retval) + goto cleanup; + if (!valid_san) { + pkiDebug("%s: did not find an acceptable SAN in user certificate\n", + __FUNCTION__); + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; + goto cleanup; + } + retval = verify_client_eku(context, plgctx, reqctx, &valid_eku); + if (retval) + goto cleanup; + + if (!valid_eku) { + pkiDebug("%s: did not find an acceptable EKU in user certificate\n", + __FUNCTION__); + retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; + goto cleanup; + } + +#ifdef DEBUG_ASN1 + print_buffer_bin(authp_data.data, authp_data.length, "/tmp/kdc_auth_pack"); +#endif + + OCTETDATA_TO_KRB5DATA(&authp_data, &k5data); + switch ((int)data->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + retval = k5int_decode_krb5_auth_pack(&k5data, &auth_pack); + if (retval) { + pkiDebug("failed to decode krb5_auth_pack\n"); + goto cleanup; + } + + /* check dh parameters */ + if (auth_pack->clientPublicValue != NULL) { + retval = server_check_dh(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, + &auth_pack->clientPublicValue->algorithm.parameters, + plgctx->opts->dh_min_bits); + + if (retval) { + pkiDebug("bad dh parameters\n"); + goto cleanup; + } + } + /* + * The KDC may have modified the request after decoding it. + * We need to compute the checksum on the data that + * came from the client. Therefore, we use the original + * packet contents. + */ + retval = k5int_decode_krb5_as_req(req_pkt, &tmp_as_req); + if (retval) { + pkiDebug("decode_krb5_as_req returned %d\n", (int)retval); + goto cleanup; + } + + retval = k5int_encode_krb5_kdc_req_body(tmp_as_req, &der_req); + if (retval) { + pkiDebug("encode_krb5_kdc_req_body returned %d\n", (int) retval); + goto cleanup; + } + retval = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, + 0, der_req, &cksum); + if (retval) { + pkiDebug("unable to calculate AS REQ checksum\n"); + goto cleanup; + } + if (cksum.length != auth_pack->pkAuthenticator.paChecksum.length || + memcmp(cksum.contents, + auth_pack->pkAuthenticator.paChecksum.contents, + cksum.length)) { + pkiDebug("failed to match the checksum\n"); +#ifdef DEBUG_CKSUM + pkiDebug("calculating checksum on buf size (%d)\n", + req_pkt->length); + print_buffer(req_pkt->data, req_pkt->length); + pkiDebug("received checksum type=%d size=%d ", + auth_pack->pkAuthenticator.paChecksum.checksum_type, + auth_pack->pkAuthenticator.paChecksum.length); + print_buffer(auth_pack->pkAuthenticator.paChecksum.contents, + auth_pack->pkAuthenticator.paChecksum.length); + pkiDebug("expected checksum type=%d size=%d ", + cksum.checksum_type, cksum.length); + print_buffer(cksum.contents, cksum.length); +#endif + + retval = KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED; + goto cleanup; + } + + /* check if kdcPkId present and match KDC's subjectIdentifier */ + if (reqp->kdcPkId.data != NULL) { + int valid_kdcPkId = 0; + retval = pkinit_check_kdc_pkid(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, + reqp->kdcPkId.data, reqp->kdcPkId.length, &valid_kdcPkId); + if (retval) + goto cleanup; + if (!valid_kdcPkId) + pkiDebug("kdcPkId in AS_REQ does not match KDC's cert" + "RFC says to ignore and proceed\n"); + + } + /* remember the decoded auth_pack for verify_padata routine */ + reqctx->rcv_auth_pack = auth_pack; + auth_pack = NULL; + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + retval = k5int_decode_krb5_auth_pack_draft9(&k5data, &auth_pack9); + if (retval) { + pkiDebug("failed to decode krb5_auth_pack_draft9\n"); + goto cleanup; + } + if (auth_pack9->clientPublicValue != NULL) { + retval = server_check_dh(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, + &auth_pack9->clientPublicValue->algorithm.parameters, + plgctx->opts->dh_min_bits); + + if (retval) { + pkiDebug("bad dh parameters\n"); + goto cleanup; + } + } + /* remember the decoded auth_pack for verify_padata routine */ + reqctx->rcv_auth_pack9 = auth_pack9; + auth_pack9 = NULL; + break; + } + + /* return authorization data to be included in the ticket */ + switch ((int)data->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + my_authz_data = malloc(2 * sizeof(*my_authz_data)); + if (my_authz_data == NULL) { + retval = ENOMEM; + pkiDebug("Couldn't allocate krb5_authdata ptr array\n"); + goto cleanup; + } + my_authz_data[1] = NULL; + my_authz_data[0] = malloc(sizeof(krb5_authdata)); + if (my_authz_data[0] == NULL) { + retval = ENOMEM; + pkiDebug("Couldn't allocate krb5_authdata\n"); + free(my_authz_data); + goto cleanup; + } + /* AD-INITIAL-VERIFIED-CAS must be wrapped in AD-IF-RELEVANT */ + my_authz_data[0]->magic = KV5M_AUTHDATA; + my_authz_data[0]->ad_type = KRB5_AUTHDATA_IF_RELEVANT; + + /* create an internal AD-INITIAL-VERIFIED-CAS data */ + pkinit_authz_data = malloc(sizeof(krb5_authdata)); + if (pkinit_authz_data == NULL) { + retval = ENOMEM; + pkiDebug("Couldn't allocate krb5_authdata\n"); + free(my_authz_data[0]); + free(my_authz_data); + goto cleanup; + } + pkinit_authz_data->ad_type = KRB5_AUTHDATA_INITIAL_VERIFIED_CAS; + /* content of this ad-type contains the certification + path with which the client certificate was validated + */ + pkinit_authz_data->contents = krb5_authz.data; + pkinit_authz_data->length = krb5_authz.length; + retval = k5int_encode_krb5_authdata_elt(pkinit_authz_data, + &encoded_pkinit_authz_data); +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)encoded_pkinit_authz_data->data, + encoded_pkinit_authz_data->length, + "/tmp/kdc_pkinit_authz_data"); +#endif + free(pkinit_authz_data); + if (retval) { + pkiDebug("k5int_encode_krb5_authdata_elt failed\n"); + free(my_authz_data[0]); + free(my_authz_data); + goto cleanup; + } + + my_authz_data[0]->contents = + (krb5_octet *) encoded_pkinit_authz_data->data; + my_authz_data[0]->length = encoded_pkinit_authz_data->length; + *authz_data = my_authz_data; + pkiDebug("Returning %d bytes of authorization data\n", + krb5_authz.length); + encoded_pkinit_authz_data->data = NULL; /* Don't free during cleanup*/ + free(encoded_pkinit_authz_data); + break; + default: + *authz_data = NULL; + } + /* remember to set the PREAUTH flag in the reply */ + enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; + *pa_request_context = reqctx; + reqctx = NULL; + + cleanup: + if (retval && data->pa_type == KRB5_PADATA_PK_AS_REQ) { + pkiDebug("pkinit_verify_padata failed: creating e-data\n"); + if (pkinit_create_edata(context, plgctx->cryptoctx, reqctx->cryptoctx, + plgctx->idctx, plgctx->opts, retval, e_data)) + pkiDebug("pkinit_create_edata failed\n"); + } + + switch ((int)data->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + free_krb5_pa_pk_as_req(&reqp); + if (cksum.contents != NULL) + free(cksum.contents); + if (der_req != NULL) + krb5_free_data(context, der_req); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + free_krb5_pa_pk_as_req_draft9(&reqp9); + } + if (tmp_as_req != NULL) + k5int_krb5_free_kdc_req(context, tmp_as_req); + if (authp_data.data != NULL) + free(authp_data.data); + if (krb5_authz.data != NULL) + free(krb5_authz.data); + if (reqctx != NULL) + pkinit_fini_kdc_req_context(context, reqctx); + if (auth_pack != NULL) + free_krb5_auth_pack(&auth_pack); + if (auth_pack9 != NULL) + free_krb5_auth_pack_draft9(context, &auth_pack9); + + return retval; +} + +static krb5_error_code +pkinit_server_return_padata(krb5_context context, + krb5_pa_data * padata, + struct _krb5_db_entry_new * client, + krb5_data *req_pkt, + krb5_kdc_req * request, + krb5_kdc_rep * reply, + struct _krb5_key_data * client_key, + krb5_keyblock * encrypting_key, + krb5_pa_data ** send_pa, + preauth_get_entry_data_proc server_get_entry_data, + void *pa_plugin_context, + void **pa_request_context) +{ + krb5_error_code retval = 0; + krb5_data scratch = {0, 0, NULL}; + krb5_pa_pk_as_req *reqp = NULL; + krb5_pa_pk_as_req_draft9 *reqp9 = NULL; + int i = 0; + + unsigned char *subjectPublicKey = NULL; + unsigned char *dh_pubkey = NULL, *server_key = NULL; + unsigned int subjectPublicKey_len = 0; + unsigned int server_key_len = 0, dh_pubkey_len = 0; + + krb5_kdc_dh_key_info dhkey_info; + krb5_data *encoded_dhkey_info = NULL; + krb5_pa_pk_as_rep *rep = NULL; + krb5_pa_pk_as_rep_draft9 *rep9 = NULL; + krb5_data *out_data = NULL; + + krb5_enctype enctype = -1; + + krb5_reply_key_pack *key_pack = NULL; + krb5_reply_key_pack_draft9 *key_pack9 = NULL; + krb5_data *encoded_key_pack = NULL; + unsigned int num_types; + krb5_cksumtype *cksum_types = NULL; + + 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; + + if (pa_request_context == NULL || *pa_request_context == NULL) { + pkiDebug("missing request context \n"); + return EINVAL; + } + + plgctx = pkinit_find_realm_context(context, pa_plugin_context, + request->server); + if (plgctx == NULL) { + pkiDebug("Unable to locate correct realm context\n"); + return ENOENT; + } + + pkiDebug("pkinit_return_padata: entered!\n"); + reqctx = (pkinit_kdc_req_context)*pa_request_context; + + if (encrypting_key->contents) { + free(encrypting_key->contents); + encrypting_key->length = 0; + encrypting_key->contents = NULL; + } + + for(i = 0; i < request->nktypes; i++) { + enctype = request->ktype[i]; + if (!krb5_c_valid_enctype(enctype)) + continue; + else { + pkiDebug("KDC picked etype = %d\n", enctype); + break; + } + } + + if (i == request->nktypes) { + retval = KRB5KDC_ERR_ETYPE_NOSUPP; + goto cleanup; + } + + switch((int)reqctx->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + init_krb5_pa_pk_as_rep(&rep); + if (rep == NULL) { + retval = ENOMEM; + goto cleanup; + } + /* let's assume it's RSA. we'll reset it to DH if needed */ + rep->choice = choice_pa_pk_as_rep_encKeyPack; + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + init_krb5_pa_pk_as_rep_draft9(&rep9); + if (rep9 == NULL) { + retval = ENOMEM; + goto cleanup; + } + rep9->choice = choice_pa_pk_as_rep_draft9_encKeyPack; + break; + default: + retval = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + + if (reqctx->rcv_auth_pack != NULL && + reqctx->rcv_auth_pack->clientPublicValue != NULL) { + subjectPublicKey = + reqctx->rcv_auth_pack->clientPublicValue->subjectPublicKey.data; + subjectPublicKey_len = + reqctx->rcv_auth_pack->clientPublicValue->subjectPublicKey.length; + rep->choice = choice_pa_pk_as_rep_dhInfo; + } else if (reqctx->rcv_auth_pack9 != NULL && + reqctx->rcv_auth_pack9->clientPublicValue != NULL) { + subjectPublicKey = + reqctx->rcv_auth_pack9->clientPublicValue->subjectPublicKey.data; + subjectPublicKey_len = + reqctx->rcv_auth_pack9->clientPublicValue->subjectPublicKey.length; + rep9->choice = choice_pa_pk_as_rep_draft9_dhSignedData; + } + + /* if this DH, then process finish computing DH key */ + if (rep != NULL && (rep->choice == choice_pa_pk_as_rep_dhInfo || + rep->choice == choice_pa_pk_as_rep_draft9_dhSignedData)) { + pkiDebug("received DH key delivery AS REQ\n"); + retval = server_process_dh(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, subjectPublicKey, + subjectPublicKey_len, &dh_pubkey, &dh_pubkey_len, + &server_key, &server_key_len); + if (retval) { + pkiDebug("failed to process/create dh paramters\n"); + goto cleanup; + } + } + + if ((rep9 != NULL && + rep9->choice == choice_pa_pk_as_rep_draft9_dhSignedData) || + (rep != NULL && rep->choice == choice_pa_pk_as_rep_dhInfo)) { + retval = pkinit_octetstring2key(context, enctype, server_key, + server_key_len, encrypting_key); + if (retval) { + pkiDebug("pkinit_octetstring2key failed: %s\n", + error_message(retval)); + goto cleanup; + } + + dhkey_info.subjectPublicKey.length = dh_pubkey_len; + dhkey_info.subjectPublicKey.data = dh_pubkey; + dhkey_info.nonce = request->nonce; + dhkey_info.dhKeyExpiration = 0; + + retval = k5int_encode_krb5_kdc_dh_key_info(&dhkey_info, + &encoded_dhkey_info); + if (retval) { + pkiDebug("encode_krb5_kdc_dh_key_info failed\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)encoded_dhkey_info->data, + encoded_dhkey_info->length, + "/tmp/kdc_dh_key_info"); +#endif + + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + retval = cms_signeddata_create(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_SERVER, 1, + (unsigned char *)encoded_dhkey_info->data, + encoded_dhkey_info->length, + &rep->u.dh_Info.dhSignedData.data, + &rep->u.dh_Info.dhSignedData.length); + if (retval) { + pkiDebug("failed to create pkcs7 signed data\n"); + goto cleanup; + } + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + retval = cms_signeddata_create(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, CMS_SIGN_DRAFT9, 1, + (unsigned char *)encoded_dhkey_info->data, + encoded_dhkey_info->length, + &rep9->u.dhSignedData.data, + &rep9->u.dhSignedData.length); + if (retval) { + pkiDebug("failed to create pkcs7 signed data\n"); + goto cleanup; + } + break; + } + } else { + pkiDebug("received RSA key delivery AS REQ\n"); + + retval = krb5_c_make_random_key(context, enctype, encrypting_key); + if (retval) { + pkiDebug("unable to make a session key\n"); + 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) + 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], + 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; + } +#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); +#endif + + 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; + } + } + + 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, + (unsigned char *)encoded_key_pack->data, + encoded_key_pack->length, + &rep->u.encKeyPack.data, &rep->u.encKeyPack.length); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + /* 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; + } + } + + rep9->choice = choice_pa_pk_as_rep_draft9_encKeyPack; + retval = cms_envelopeddata_create(context, plgctx->cryptoctx, + reqctx->cryptoctx, plgctx->idctx, padata->pa_type, 1, + (unsigned char *)encoded_key_pack->data, + encoded_key_pack->length, + &rep9->u.encKeyPack.data, &rep9->u.encKeyPack.length); + break; + } + if (retval) { + pkiDebug("failed to create pkcs7 enveloped data: %s\n", + error_message(retval)); + goto cleanup; + } +#ifdef DEBUG_ASN1 + print_buffer_bin((unsigned char *)encoded_key_pack->data, + encoded_key_pack->length, + "/tmp/kdc_key_pack"); + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + print_buffer_bin(rep->u.encKeyPack.data, + rep->u.encKeyPack.length, + "/tmp/kdc_enc_key_pack"); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + print_buffer_bin(rep9->u.encKeyPack.data, + rep9->u.encKeyPack.length, + "/tmp/kdc_enc_key_pack"); + break; + } +#endif + } + + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + retval = k5int_encode_krb5_pa_pk_as_rep(rep, &out_data); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + retval = k5int_encode_krb5_pa_pk_as_rep_draft9(rep9, &out_data); + break; + } + if (retval) { + pkiDebug("failed to encode AS_REP\n"); + goto cleanup; + } +#ifdef DEBUG_ASN1 + if (out_data != NULL) + print_buffer_bin((unsigned char *)out_data->data, out_data->length, + "/tmp/kdc_as_rep"); +#endif + + *send_pa = (krb5_pa_data *) malloc(sizeof(krb5_pa_data)); + if (*send_pa == NULL) { + retval = ENOMEM; + free(out_data->data); + free(out_data); + out_data = NULL; + goto cleanup; + } + (*send_pa)->magic = KV5M_PA_DATA; + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + (*send_pa)->pa_type = KRB5_PADATA_PK_AS_REP; + break; + case KRB5_PADATA_PK_AS_REQ_OLD: + case KRB5_PADATA_PK_AS_REP_OLD: + (*send_pa)->pa_type = KRB5_PADATA_PK_AS_REP_OLD; + break; + } + (*send_pa)->length = out_data->length; + (*send_pa)->contents = (krb5_octet *) out_data->data; + + + cleanup: + pkinit_fini_kdc_req_context(context, reqctx); + if (scratch.data != NULL) + free(scratch.data); + if (out_data != NULL) + free(out_data); + if (encoded_dhkey_info != NULL) + krb5_free_data(context, encoded_dhkey_info); + if (encoded_key_pack != NULL) + krb5_free_data(context, encoded_key_pack); + if (dh_pubkey != NULL) + free(dh_pubkey); + if (server_key != NULL) + free(server_key); + if (cksum_types != NULL) + free(cksum_types); + + switch ((int)padata->pa_type) { + case KRB5_PADATA_PK_AS_REQ: + free_krb5_pa_pk_as_req(&reqp); + free_krb5_pa_pk_as_rep(&rep); + free_krb5_reply_key_pack(&key_pack); + break; + case KRB5_PADATA_PK_AS_REP_OLD: + case KRB5_PADATA_PK_AS_REQ_OLD: + free_krb5_pa_pk_as_req_draft9(&reqp9); + free_krb5_pa_pk_as_rep_draft9(&rep9); + if (!fixed_keypack) + free_krb5_reply_key_pack_draft9(&key_pack9); + else + free_krb5_reply_key_pack(&key_pack); + break; + } + + if (retval) + pkiDebug("pkinit_verify_padata failure"); + + return retval; +} + +static int +pkinit_server_get_flags(krb5_context kcontext, krb5_preauthtype patype) +{ + return PA_SUFFICIENT | PA_REPLACES_KEY; +} + +static krb5_preauthtype supported_server_pa_types[] = { + KRB5_PADATA_PK_AS_REQ, + KRB5_PADATA_PK_AS_REQ_OLD, + KRB5_PADATA_PK_AS_REP_OLD, + 0 +}; + +static void +pkinit_fini_kdc_profile(krb5_context context, pkinit_kdc_context plgctx) +{ + /* + * There is nothing currently allocated by pkinit_init_kdc_profile() + * which needs to be freed here. + */ +} + +static krb5_error_code +pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx) +{ + krb5_error_code retval; + char *eku_string = NULL; + + pkiDebug("%s: entered for realm %s\n", __FUNCTION__, plgctx->realmname); + retval = pkinit_kdcdefault_string(context, plgctx->realmname, + "pkinit_identity", + &plgctx->idopts->identity); + if (retval != 0 || NULL == plgctx->idopts->identity) { + retval = EINVAL; + krb5_set_error_message(context, retval, + "No pkinit_identity supplied for realm %s", + plgctx->realmname); + goto errout; + } + + retval = pkinit_kdcdefault_strings(context, plgctx->realmname, + "pkinit_anchors", + &plgctx->idopts->anchors); + if (retval != 0 || NULL == plgctx->idopts->anchors) { + retval = EINVAL; + krb5_set_error_message(context, retval, + "No pkinit_anchors supplied for realm %s", + plgctx->realmname); + goto errout; + } + + pkinit_kdcdefault_strings(context, plgctx->realmname, + "pkinit_pool", + &plgctx->idopts->intermediates); + + pkinit_kdcdefault_strings(context, plgctx->realmname, + "pkinit_revoke", + &plgctx->idopts->crls); + + pkinit_kdcdefault_string(context, plgctx->realmname, + "pkinit_kdc_ocsp", + &plgctx->idopts->ocsp); + + pkinit_kdcdefault_string(context, plgctx->realmname, + "pkinit_mappings_file", + &plgctx->idopts->dn_mapping_file); + + pkinit_kdcdefault_integer(context, plgctx->realmname, + "pkinit_dh_min_bits", + PKINIT_DEFAULT_DH_MIN_BITS, + &plgctx->opts->dh_min_bits); + if (plgctx->opts->dh_min_bits < 1024) { + pkiDebug("%s: invalid value (%d) for pkinit_dh_min_bits, " + "using default value (%d) instead\n", __FUNCTION__, + plgctx->opts->dh_min_bits, PKINIT_DEFAULT_DH_MIN_BITS); + plgctx->opts->dh_min_bits = PKINIT_DEFAULT_DH_MIN_BITS; + } + + pkinit_kdcdefault_boolean(context, plgctx->realmname, + "pkinit_allow_upn", + 0, &plgctx->opts->allow_upn); + + pkinit_kdcdefault_boolean(context, plgctx->realmname, + "pkinit_require_crl_checking", + 0, &plgctx->opts->require_crl_checking); + + pkinit_kdcdefault_string(context, plgctx->realmname, + "pkinit_eku_checking", + &eku_string); + if (eku_string != NULL) { + if (strcasecmp(eku_string, "kpClientAuth") == 0) { + plgctx->opts->require_eku = 1; + plgctx->opts->accept_secondary_eku = 0; + } else if (strcasecmp(eku_string, "scLogin") == 0) { + plgctx->opts->require_eku = 1; + plgctx->opts->accept_secondary_eku = 1; + } else if (strcasecmp(eku_string, "none") == 0) { + plgctx->opts->require_eku = 0; + plgctx->opts->accept_secondary_eku = 0; + } else { + pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n", + __FUNCTION__, eku_string); + } + free(eku_string); + } + + + return 0; +errout: + pkinit_fini_kdc_profile(context, plgctx); + return retval; +} + +static pkinit_kdc_context +pkinit_find_realm_context(krb5_context context, void *pa_plugin_context, + krb5_principal princ) +{ + int i; + pkinit_kdc_context *realm_contexts = pa_plugin_context; + + if (pa_plugin_context == NULL) + return NULL; + + for (i = 0; realm_contexts[i] != NULL; i++) { + pkinit_kdc_context p = realm_contexts[i]; + + if ((p->realmname_len == princ->realm.length) && + (strncmp(p->realmname, princ->realm.data, p->realmname_len) == 0)) { + pkiDebug("%s: returning context at %p for realm '%s'\n", + __FUNCTION__, p, p->realmname); + return p; + } + } + pkiDebug("%s: unable to find realm context for realm '%.*s'\n", + __FUNCTION__, princ->realm.length, princ->realm.data); + return NULL; +} + +static int +pkinit_server_plugin_init_realm(krb5_context context, const char *realmname, + pkinit_kdc_context *pplgctx) +{ + krb5_error_code retval = ENOMEM; + pkinit_kdc_context plgctx = NULL; + + *pplgctx = NULL; + + plgctx = (pkinit_kdc_context) calloc(1, sizeof(*plgctx)); + if (plgctx == NULL) + goto errout; + + pkiDebug("%s: initializing context at %p for realm '%s'\n", + __FUNCTION__, plgctx, realmname); + memset(plgctx, 0, sizeof(*plgctx)); + plgctx->magic = PKINIT_CTX_MAGIC; + + plgctx->realmname = strdup(realmname); + if (plgctx->realmname == NULL) + goto errout; + plgctx->realmname_len = strlen(plgctx->realmname); + + retval = pkinit_init_plg_crypto(&plgctx->cryptoctx); + if (retval) + goto errout; + + retval = pkinit_init_plg_opts(&plgctx->opts); + if (retval) + goto errout; + + retval = pkinit_init_identity_crypto(&plgctx->idctx); + if (retval) + goto errout; + + retval = pkinit_init_identity_opts(&plgctx->idopts); + if (retval) + goto errout; + + retval = pkinit_init_kdc_profile(context, plgctx); + if (retval) + goto errout; + + retval = pkinit_identity_initialize(context, plgctx->cryptoctx, NULL, + plgctx->idopts, plgctx->idctx, 0, NULL); + if (retval) + goto errout; + + pkiDebug("%s: returning context at %p for realm '%s'\n", + __FUNCTION__, plgctx, realmname); + *pplgctx = plgctx; + retval = 0; + +errout: + if (retval) + pkinit_server_plugin_fini_realm(context, plgctx); + + return retval; +} + +static int +pkinit_server_plugin_init(krb5_context context, void **blob, + const char **realmnames) +{ + krb5_error_code retval = ENOMEM; + pkinit_kdc_context plgctx, *realm_contexts = NULL; + int i, j; + size_t numrealms; + + retval = pkinit_accessor_init(); + if (retval) + return retval; + + /* Determine how many realms we may need to support */ + for (i = 0; realmnames[i] != NULL; i++) {}; + numrealms = i; + + realm_contexts = (pkinit_kdc_context *) + calloc(numrealms+1, sizeof(pkinit_kdc_context)); + if (realm_contexts == NULL) + return ENOMEM; + + for (i = 0, j = 0; i < numrealms; i++) { + pkiDebug("%s: processing realm '%s'\n", __FUNCTION__, realmnames[i]); + retval = pkinit_server_plugin_init_realm(context, realmnames[i], &plgctx); + if (retval == 0 && plgctx != NULL) + realm_contexts[j++] = plgctx; + } + + if (j == 0) { + retval = EINVAL; + krb5_set_error_message(context, retval, "No realms configured " + "correctly for pkinit support"); + goto errout; + } + + *blob = realm_contexts; + retval = 0; + pkiDebug("%s: returning context at %p\n", __FUNCTION__, realm_contexts); + +errout: + if (retval) + pkinit_server_plugin_fini(context, realm_contexts); + + return retval; +} + +static void +pkinit_server_plugin_fini_realm(krb5_context context, pkinit_kdc_context plgctx) +{ + if (plgctx == NULL) + return; + + pkinit_fini_kdc_profile(context, plgctx); + pkinit_fini_identity_opts(plgctx->idopts); + pkinit_fini_identity_crypto(plgctx->idctx); + pkinit_fini_plg_crypto(plgctx->cryptoctx); + pkinit_fini_plg_opts(plgctx->opts); + free(plgctx->realmname); + free(plgctx); +} + +static void +pkinit_server_plugin_fini(krb5_context context, void *blob) +{ + pkinit_kdc_context *realm_contexts = blob; + int i; + + if (realm_contexts == NULL) + return; + + for (i = 0; realm_contexts[i] != NULL; i++) { + pkinit_server_plugin_fini_realm(context, realm_contexts[i]); + } + pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts); + free(realm_contexts); +} + +static krb5_error_code +pkinit_init_kdc_req_context(krb5_context context, void **ctx) +{ + krb5_error_code retval = ENOMEM; + pkinit_kdc_req_context reqctx = NULL; + + reqctx = (pkinit_kdc_req_context)malloc(sizeof(*reqctx)); + if (reqctx == NULL) + return retval; + memset(reqctx, 0, sizeof(*reqctx)); + reqctx->magic = PKINIT_CTX_MAGIC; + + retval = pkinit_init_req_crypto(&reqctx->cryptoctx); + if (retval) + goto cleanup; + reqctx->rcv_auth_pack = NULL; + reqctx->rcv_auth_pack9 = NULL; + + pkiDebug("%s: returning reqctx at %p\n", __FUNCTION__, reqctx); + *ctx = reqctx; + retval = 0; +cleanup: + if (retval) + pkinit_fini_kdc_req_context(context, reqctx); + + return retval; +} + +static void +pkinit_fini_kdc_req_context(krb5_context context, void *ctx) +{ + pkinit_kdc_req_context reqctx = (pkinit_kdc_req_context)ctx; + + if (reqctx == NULL || reqctx->magic != PKINIT_CTX_MAGIC) { + pkiDebug("pkinit_fini_kdc_req_context: got bad reqctx (%p)!\n", reqctx); + return; + } + pkiDebug("%s: freeing reqctx at %p\n", __FUNCTION__, reqctx); + + pkinit_fini_req_crypto(reqctx->cryptoctx); + if (reqctx->rcv_auth_pack != NULL) + free_krb5_auth_pack(&reqctx->rcv_auth_pack); + if (reqctx->rcv_auth_pack9 != NULL) + free_krb5_auth_pack_draft9(context, &reqctx->rcv_auth_pack9); + + free(reqctx); +} + +struct krb5plugin_preauth_server_ftable_v1 preauthentication_server_1 = { + "pkinit", /* name */ + supported_server_pa_types, /* pa_type_list */ + pkinit_server_plugin_init, /* (*init_proc) */ + pkinit_server_plugin_fini, /* (*fini_proc) */ + pkinit_server_get_flags, /* (*flags_proc) */ + pkinit_server_get_edata, /* (*edata_proc) */ + pkinit_server_verify_padata,/* (*verify_proc) */ + pkinit_server_return_padata,/* (*return_proc) */ + NULL, /* (*freepa_reqcontext_proc) */ +}; diff --git a/src/plugins/preauth/wpse/wpse_main.c b/src/plugins/preauth/wpse/wpse_main.c index fd6ff92..22dfd13 100644 --- a/src/plugins/preauth/wpse/wpse_main.c +++ b/src/plugins/preauth/wpse/wpse_main.c @@ -30,7 +30,7 @@ /* Worst. Preauthentication. Scheme. Ever. */ -#ident "$Id$" +#ident "$Id: wpse_main.c,v 1.3 2007/01/02 22:33:51 kwc Exp $" #include "autoconf.h" @@ -101,9 +101,9 @@ client_process(krb5_context kcontext, void *gak_data, krb5_data *salt, krb5_data *s2kparams, krb5_keyblock *as_key, - krb5_pa_data **out_pa_data) + krb5_pa_data ***out_pa_data) { - krb5_pa_data *send_pa; + krb5_pa_data **send_pa; krb5_int32 nnonce, enctype; krb5_keyblock *kb; krb5_error_code status; @@ -121,19 +121,26 @@ client_process(krb5_context kcontext, if (pa_data->length == 0) { /* Create preauth data. */ - send_pa = malloc(sizeof(krb5_pa_data)); + send_pa = malloc(2 * sizeof(krb5_pa_data *)); if (send_pa == NULL) return ENOMEM; - send_pa->pa_type = KRB5_PADATA_WPSE_REQ; - send_pa->length = 4; - send_pa->contents = malloc(4); - if (send_pa->contents == NULL) { + send_pa[1] = NULL; /* Terminate list */ + send_pa[0] = malloc(sizeof(krb5_pa_data)); + if (send_pa[0] == NULL) { + free(send_pa); + return ENOMEM; + } + send_pa[0]->pa_type = KRB5_PADATA_WPSE_REQ; + send_pa[0]->length = 4; + send_pa[0]->contents = malloc(4); + if (send_pa[0]->contents == NULL) { + free(send_pa[0]); free(send_pa); return ENOMEM; } /* Store the preauth data. */ nnonce = htonl(request->nonce); - memcpy(send_pa->contents, &nnonce, 4); + memcpy(send_pa[0]->contents, &nnonce, 4); *out_pa_data = send_pa; } else { /* A reply from the KDC. Conventionally this would be @@ -262,11 +269,16 @@ server_verify(krb5_context kcontext, preauth_get_entry_data_proc server_get_entry_data, void *pa_module_context, void **pa_request_context, - krb5_data **e_data) + krb5_data **e_data, + krb5_authdata ***authz_data) { krb5_int32 nnonce; krb5_data *test_edata; + krb5_authdata **my_authz_data; +#ifdef DEBUG + fprintf(stderr, "wpse: server_verify()!\n"); +#endif /* Verify the preauth data. */ if (data->length != 4) return KRB5KDC_ERR_PREAUTH_FAILED; @@ -282,6 +294,54 @@ server_verify(krb5_context kcontext, if (*pa_request_context == NULL) *pa_request_context = malloc(4); + /* + * Return some junk authorization data just to exercise the + * code path handling the returned authorization data. + * + * NOTE that this is NOT VALID authorization data! + */ +#ifdef DEBUG + fprintf(stderr, "wpse: doing authorization data!\n"); +#endif +#if 1 /* USE_5000_AD */ +#define AD_ALLOC_SIZE 5000 + /* ad_header consists of a sequence tag (0x30) and length (0x82 0x1384) + * followed by octet string tag (0x04) and length (0x82 0x1380) */ + krb5_octet ad_header[] = {0x30, 0x82, 0x13, 0x84, 0x04, 0x82, 0x13, 0x80}; +#else +#define AD_ALLOC_SIZE 100 + /* ad_header consists of a sequence tag (0x30) and length (0x62) + * followed by octet string tag (0x04) and length (0x60) */ + krb5_octet ad_header[] = {0x30, 0x62, 0x04, 0x60}; +#endif + my_authz_data = malloc(2 * sizeof(*my_authz_data)); + if (my_authz_data != NULL) { + my_authz_data[1] = NULL; + my_authz_data[0] = malloc(sizeof(krb5_authdata)); + if (my_authz_data[0] == NULL) { + free(my_authz_data); + return ENOMEM; + } + my_authz_data[0]->contents = malloc(AD_ALLOC_SIZE); + if (my_authz_data[0]->contents == NULL) { + free(my_authz_data[0]); + free(my_authz_data); + return ENOMEM; + } + memset(my_authz_data[0]->contents, '\0', AD_ALLOC_SIZE); + my_authz_data[0]->magic = KV5M_AUTHDATA; + my_authz_data[0]->ad_type = 1; + my_authz_data[0]->length = AD_ALLOC_SIZE; + memcpy(my_authz_data[0]->contents, ad_header, sizeof(ad_header)); + sprintf(my_authz_data[0]->contents + sizeof(ad_header), + "wpse authorization data: %d bytes worth!\n", AD_ALLOC_SIZE); + *authz_data = my_authz_data; +#ifdef DEBUG + fprintf(stderr, "Returning %d bytes of authorization data\n", + AD_ALLOC_SIZE); +#endif + } + /* Return edata to exercise code that handles edata... */ test_edata = malloc(sizeof(*test_edata)); if (test_edata != NULL) { @@ -366,6 +426,7 @@ server_return(krb5_context kcontext, krb5_free_keyblock_contents(kcontext, encrypting_key); krb5_copy_keyblock_contents(kcontext, kb, encrypting_key); + /* Clean up. */ krb5_free_keyblock(kcontext, kb); @@ -375,13 +436,13 @@ server_return(krb5_context kcontext, static int server_get_flags(krb5_context kcontext, krb5_preauthtype pa_type) { - return PA_HARDWARE | PA_REPLACES_KEY; + return PA_HARDWARE | PA_REPLACES_KEY | PA_SUFFICIENT; } static krb5_preauthtype supported_client_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0}; static krb5_preauthtype supported_server_pa_types[] = {KRB5_PADATA_WPSE_REQ, 0}; -struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0 = { +struct krb5plugin_preauth_client_ftable_v1 preauthentication_client_1 = { "wpse", /* name */ &supported_client_pa_types[0], /* pa_type_list */ NULL, /* enctype_list */ @@ -395,7 +456,7 @@ struct krb5plugin_preauth_client_ftable_v0 preauthentication_client_0 = { client_gic_opt /* get init creds opts function */ }; -struct krb5plugin_preauth_server_ftable_v0 preauthentication_server_0 = { +struct krb5plugin_preauth_server_ftable_v1 preauthentication_server_1 = { "wpse", &supported_server_pa_types[0], NULL, |