From ff0091077ef03bf8010f61f4e1ee6190832448fb Mon Sep 17 00:00:00 2001 From: Kevin Coffman Date: Thu, 17 May 2007 18:15:14 +0000 Subject: - Add new config file option pkinit_longhorn which indicates we are talking to a Longhorn KDC and allows the necessary hacks to work with a Longhorn (beta 3) server - Add pkcs12 support (required for gssmonger testing) - Modify SAN and EKU processing so that it does not use pa_type and so that it allows checking with certificates containing more than one SAN - add new configuration file option, "pkinit_eku_checking" for the kdc, this can be specified as [kpClientAuth | scLogin | none] for the client, this can be specified as [kpKDC | kpServerAuth | none] - Restructure code to make DN and SAN matching possible. Still need the actual matching code. - Fix from Ken Renard re: using ENV: for identity processing - Create new header, pkinit_crypto.h separating out the crypto interface definition. git-svn-id: svn://anonsvn.mit.edu/krb5/users/coffman/pkinit@19549 dc483132-0cff-0310-8789-dd5450dbe970 --- src/plugins/preauth/pkinit/pkinit.h | 448 +------- src/plugins/preauth/pkinit/pkinit_clnt.c | 328 ++++-- src/plugins/preauth/pkinit/pkinit_crypto.h | 453 ++++++++ src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 1149 ++++++++++++-------- src/plugins/preauth/pkinit/pkinit_crypto_openssl.h | 57 +- src/plugins/preauth/pkinit/pkinit_lib.c | 26 +- src/plugins/preauth/pkinit/pkinit_profile.c | 46 +- src/plugins/preauth/pkinit/pkinit_srv.c | 216 +++- 8 files changed, 1654 insertions(+), 1069 deletions(-) create mode 100644 src/plugins/preauth/pkinit/pkinit_crypto.h diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index f3af97c..f8e52bb 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -53,6 +53,8 @@ #define pkiDebug(args...) #endif +extern int longhorn; /* XXX Talking to a Longhorn server? */ + /* Macros to deal with converting between various data types... */ #define PADATA_TO_KRB5DATA(pad, k5d) \ (k5d)->length = (pad)->length; (k5d)->data = (char *)(pad)->contents; @@ -102,13 +104,12 @@ typedef struct _pkinit_identity_crypto_context *pkinit_identity_crypto_context; */ 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 require_san; /* require SAN checking (default is true) */ 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 princ_in_cert; int dh_min_bits; /* minimum DH modulus size allowed */ - int allow_proxy_certs; } pkinit_plg_opts; /* @@ -116,6 +117,7 @@ typedef struct _pkinit_plg_opts { */ typedef struct _pkinit_req_opts { int require_eku; + int accept_secondary_eku; int require_san; int allow_upn; int dh_or_rsa; @@ -199,16 +201,8 @@ struct _pkinit_kdc_req_context { typedef struct _pkinit_kdc_req_context *pkinit_kdc_req_context; /* - * Functions to initialize and cleanup various context - */ -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); + * Functions in pkinit_lib.c + */ krb5_error_code pkinit_init_req_opts(pkinit_req_opts **); void pkinit_fini_req_opts(pkinit_req_opts *); @@ -221,418 +215,10 @@ 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); - -/* - * these describe the type of CMS message - */ -enum cms_msg_types { - CMS_SIGN_CLIENT, - CMS_SIGN_DRAFT9, - CMS_SIGN_SERVER, - CMS_ENVEL_SERVER -}; - -/* - * 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 looks for a SAN in the received certificate. - * if it finds one, it retrieves and returns Kerberos principal - * name encoded in the SAN - */ -krb5_error_code verify_id_pkinit_san - (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 allow_upn, /* IN - specifies if Windows SANs are allowed */ - krb5_principal *identity_in_san, /* OUT - receives Kerberos principal found in SAN */ - unsigned char **kdc_hostname, /* OUT - contains dNSName SAN (win2k KDC hostname) */ - int *san_valid); /* OUT - receives non-zero if a valid SAN was found */ - -/* - * this functions looks for an EKU in the received certificate. - * if config opts specifies that EKU check should be ignored, then - * the lack EKU in the received certificate is not treated as an error - */ -krb5_error_code verify_id_pkinit_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 */ - krb5_preauthtype pa_type, /* IN */ - int require_eku, /* IN - specifies if policy requires EKU checking */ - int *eku_valid); /* OUT - receives non-zero if a valid 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 - * 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 identity options specified via the command-line - * or config file and populate the crypto-specific identity - * information. - */ krb5_error_code pkinit_initialize_identity (krb5_context context, /* IN */ - pkinit_identity_opts *idopts, /* IN */ - pkinit_identity_crypto_context id_cryptoctx); /* IN/OUT */ - -krb5_error_code pkinit_process_identity_option - (krb5_context context, /* IN */ - int attr, /* IN */ - const char *value, /* IN */ - pkinit_identity_crypto_context id_cryptoctx); /* IN/OUT */ - -krb5_error_code pkinit_get_client_cert - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - const char *principal, krb5_get_init_creds_opt *opt); - -krb5_error_code pkinit_get_kdc_cert - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - const char *principal, krb5_get_init_creds_opt *opt); - -krb5_error_code pkinit_get_trusted_cacerts - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt); - -krb5_error_code pkinit_get_intermediate_cacerts - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt); - -krb5_error_code pkinit_get_crls - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt); - -/* - * 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_get_kdc_identity_crypto - (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx); - -krb5_error_code pkinit_identity_set_prompter - (pkinit_identity_crypto_context id_cryptoctx, - krb5_prompter_fct prompter, void *prompter_data); + pkinit_identity_opts *idopts, /* IN */ + pkinit_identity_crypto_context id_cryptoctx); /* IN/OUT */ /* * initialization and free functions @@ -685,15 +271,15 @@ krb5_error_code pkinit_kdcdefault_boolean 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); + 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); + 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); + 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); @@ -701,15 +287,15 @@ krb5_error_code pkinit_libdefault_integer (krb5_context context, const krb5_data *realm, const char *option, int default_value, int *ret_value); -krb5_error_code pkinit_get_kdc_hostnames - (krb5_context context, krb5_data *realm, char ***hostnames); - /* - * main api end + * debugging functions */ - -/* 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_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c index 2921fa1..5ce5aa9 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -43,6 +43,8 @@ /* #define DEBUG */ /* #define DEBUG_DH */ +int longhorn = 0; /* XXX Talking to a Longhorn server? */ + krb5_error_code pkinit_client_process (krb5_context context, void *plugin_context, void *request_context, krb5_get_init_creds_opt *opt, @@ -134,7 +136,8 @@ pa_pkinit_gen_req(krb5_context context, cksum.contents = NULL; reqctx->pa_type = in_padata->pa_type; - pkiDebug("option included = %d till=%d\n", request->kdc_options, request->till); + 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"); @@ -215,8 +218,8 @@ pa_pkinit_gen_req(krb5_context context, return_pa_data[0]->length = out_data->length; return_pa_data[0]->contents = (krb5_octet *) out_data->data; - if (return_pa_data[0]->pa_type == KRB5_PADATA_PK_AS_REP_OLD - && reqctx->opts->win2k_require_cksum) { + if ((return_pa_data[0]->pa_type == KRB5_PADATA_PK_AS_REP_OLD + && reqctx->opts->win2k_require_cksum) || (longhorn == 1)) { return_pa_data[1]->pa_type = 132; return_pa_data[1]->length = 0; return_pa_data[1]->contents = NULL; @@ -378,12 +381,10 @@ pkinit_as_req_create(krb5_context context, reqctx->cryptoctx, reqctx->idctx, CMS_SIGN_CLIENT, 1, (unsigned char *)coded_auth_pack->data, coded_auth_pack->length, &req->signedAuthPack.data, &req->signedAuthPack.length); -#if 0 /* VISTA HACK */ - if (retval == 0) { - pkiDebug("Hacking SignedAuthPack for VISTA!\n"); - req->signedAuthPack.data +=4; - req->signedAuthPack.length -= 4; - } +#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: @@ -397,6 +398,11 @@ pkinit_as_req_create(krb5_context context, (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) { @@ -411,7 +417,6 @@ pkinit_as_req_create(krb5_context context, 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); @@ -420,13 +425,6 @@ pkinit_as_req_create(krb5_context context, /* Encode the as-req */ retval = k5int_encode_krb5_pa_pk_as_req(req, as_req); -#if 0 /* VISTA HACK */ - if (retval == 0) { - pkiDebug("Hacking SignedAuthPack for VISTA!\n"); - req->signedAuthPack.data -=4; - req->signedAuthPack.length += 4; - } -#endif break; case KRB5_PADATA_PK_AS_REQ_OLD: #if 0 @@ -512,6 +510,152 @@ 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. @@ -536,9 +680,10 @@ pkinit_as_rep_parse(krb5_context context, unsigned char *client_key = NULL, *kdc_hostname = NULL; unsigned int client_key_len = 0; krb5_checksum cksum = {0, 0, 0, NULL}; - krb5_principal tmp_server; - int valid_eku = 0, valid_san = 0, i = 0; krb5_data k5data; + int valid_san = 0; + int valid_eku = 0; + int need_eku_checking = 1; assert((as_rep != NULL) && (key_block != NULL)); @@ -588,79 +733,30 @@ pkinit_as_rep_parse(krb5_context context, goto cleanup; } - if (reqctx->opts->require_san) { - retval = verify_id_pkinit_san(context, plgctx->cryptoctx, - reqctx->cryptoctx, reqctx->idctx, pa_type, - plgctx->opts->allow_upn, &tmp_server, &kdc_hostname, &valid_san); + 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_san) { - pkiDebug("failed to verify id-pkinit-san\n"); - retval = KRB5KDC_ERR_KDC_NOT_TRUSTED; + 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 { - if (pa_type == KRB5_PADATA_PK_AS_REP && - tmp_server != NULL) { - retval = krb5_principal_compare(context, request->server, - tmp_server); - krb5_free_principal(context, tmp_server); - if (!retval) { - pkiDebug("identity in the certificate does not match " - "the requested principal\n"); - retval = KRB5KDC_ERR_KDC_NAME_MISMATCH; - goto cleanup; - } - } else if (pa_type == KRB5_PADATA_PK_AS_REP_OLD) { - char **hostnames = NULL; - int name_matched = 0; - - if (reqctx->opts->require_hostname_match) { - retval = pkinit_libdefault_strings(context, - &request->server->realm, - "pkinit_win2k_hostname", - &hostnames); - if (retval) { - pkiDebug("No pkinit_win2k_hostname values found in " - "the config file.\n"); - retval = KRB5KDC_ERR_KDC_NOT_TRUSTED; - goto cleanup; - } - for (i = 0; hostnames != NULL && hostnames[i] != NULL; i++) { - pkiDebug("Comparing cert's dnsName '%s' with " - "trusted win2k hostname '%s'\n", - kdc_hostname, hostnames[i]); - if (strcmp((char *)kdc_hostname, hostnames[i]) == 0) { - pkiDebug("Cert's dnsName, '%s', matched\n", - kdc_hostname); - name_matched = 1; - break; - } - } - profile_free_list(hostnames); - if (!name_matched) { - pkiDebug("Cert's dnsName, '%s', NOT matched\n", - kdc_hostname); - retval = KRB5KDC_ERR_KDC_NOT_TRUSTED; - goto cleanup; - } - } else { - pkiDebug("config options says skip hostname check\n"); - } - } } - } else { - pkiDebug("config option says not to check for SAN\n"); - } - - retval = verify_id_pkinit_eku(context, plgctx->cryptoctx, reqctx->cryptoctx, - reqctx->idctx, pa_type, reqctx->opts->require_eku, &valid_eku); - if (retval) - goto cleanup; - if (!valid_eku) { - pkiDebug("failed to verify id-pkinit-KPKdc\n"); - retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; - goto cleanup; - } + } else + pkiDebug("%s: skipping EKU check\n", __FUNCTION__); OCTETDATA_TO_KRB5DATA(&dh_data, &k5data); @@ -703,7 +799,7 @@ pkinit_as_rep_parse(krb5_context context, if ((retval = k5int_decode_krb5_reply_key_pack(&k5data, &key_pack)) != 0) { pkiDebug("failed to decode reply_key_pack\n"); - if (pa_type == KRB5_PADATA_PK_AS_REP) + if (pa_type == KRB5_PADATA_PK_AS_REP && longhorn == 0) goto cleanup; else { if ((retval = @@ -729,9 +825,10 @@ pkinit_as_rep_parse(krb5_context context, 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); + 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; @@ -802,6 +899,8 @@ pkinit_client_profile(krb5_context context, 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); @@ -813,18 +912,39 @@ pkinit_client_profile(krb5_context context, "pkinit_win2k_require_binding", reqctx->opts->win2k_require_cksum, &reqctx->opts->win2k_require_cksum); + /* Temporarily just set global flag from config file */ pkinit_libdefault_boolean(context, &request->server->realm, - "pkinit_require_eku", - reqctx->opts->require_eku, - &reqctx->opts->require_eku); + "pkinit_longhorn", + 0, + &longhorn); pkinit_libdefault_boolean(context, &request->server->realm, "pkinit_require_krbtgt_otherName", reqctx->opts->require_san, &reqctx->opts->require_san); pkinit_libdefault_boolean(context, &request->server->realm, - "pkinit_require_hostname_match", - reqctx->opts->require_hostname_match, - &reqctx->opts->require_hostname_match); + "pkinit_allow_upn", + reqctx->opts->allow_upn, + &reqctx->opts->allow_upn); + 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); + } + /* Only process anchors here if they were not specified on command line */ if (reqctx->idopts->anchors == NULL) pkinit_libdefault_strings(context, &request->server->realm, @@ -905,8 +1025,11 @@ pkinit_client_process(krb5_context context, pkinit_identity_set_prompter(reqctx->idctx, prompter, prompter_data); retval = pkinit_initialize_identity(context, reqctx->idopts, reqctx->idctx); - if (retval) + if (retval) { + pkiDebug("pkinit_initialize_identity 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, opt); @@ -916,8 +1039,11 @@ pkinit_client_process(krb5_context context, */ retval = (*get_data_proc)(context, rock, krb5plugin_preauth_client_get_etype, &cdata); - if (retval) + 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); @@ -1077,11 +1203,11 @@ pkinit_client_req_init(krb5_context context, goto cleanup; reqctx->opts->require_eku = plgctx->opts->require_eku; + reqctx->opts->accept_secondary_eku = plgctx->opts->accept_secondary_eku; reqctx->opts->require_san = plgctx->opts->require_san; 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; - reqctx->opts->require_hostname_match = 0; retval = pkinit_init_req_crypto(&reqctx->cryptoctx); if (retval) diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h new file mode 100644 index 0000000..a753dd0 --- /dev/null +++ b/src/plugins/preauth/pkinit/pkinit_crypto.h @@ -0,0 +1,453 @@ +/* + * 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 +#include +#include +#include +#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 +}; + +/* + * 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 + * 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 identity options specified via the command-line + * or config file and populate the crypto-specific identity + * information. + */ +krb5_error_code pkinit_process_identity_option + (krb5_context context, /* IN */ + int attr, /* IN */ + const char *value, /* IN */ + pkinit_identity_crypto_context id_cryptoctx); /* IN/OUT */ + +krb5_error_code pkinit_get_kdc_cert + (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, krb5_get_init_creds_opt *opt); + +/* + * 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 index af26661..b51040b 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -164,12 +164,7 @@ unsigned char pkinit_4096_dhprime[4096/8] = { static int pkinit_oids_refs = 0; -const krb5_octet_data dh_oid = { 0, 7, - (unsigned char *)"\x2A\x86\x48\xce\x3e\x02\x01" }; - -static krb5_error_code -create_identifiers_from_stack(STACK_OF(X509) *sk, - krb5_external_principal_identifier *** ids); +#define MAX_CREDS_ALLOWED 20 krb5_error_code pkinit_init_plg_crypto(pkinit_plg_crypto_context *cryptoctx) { @@ -304,61 +299,57 @@ static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context ctx) { krb5_error_code retval = ENOMEM; - int tmp = 0; + int nid = 0; - tmp = OBJ_create("1.3.6.1.5.2.2", "id-pkinit-san", "KRB5PrincipalName"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_san = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - - tmp = OBJ_create("1.3.6.1.5.2.3.1", "id-pkinit-authdata", - "PKINIT signedAuthPack"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_authData = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - - tmp = OBJ_create("1.3.6.1.5.2.3.2", "id-pkinit-DHKeyData", - "PKINIT dhSignedData"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_DHKeyData = (ASN1_OBJECT *)OBJ_nid2obj(tmp); + /* + * 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 - tmp = OBJ_create("1.3.6.1.5.2.3.3", "id-pkinit-rkeyData", - "PKINIT encKeyPack"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_rkeyData = (ASN1_OBJECT *)OBJ_nid2obj(tmp); + 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"); - tmp = OBJ_create("1.3.6.1.5.2.3.4", "id-pkinit-KPClientAuth", - "PKINIT Client EKU"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_KPClientAuth = (ASN1_OBJECT *)OBJ_nid2obj(tmp); + 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"); - tmp = OBJ_create("1.3.6.1.5.2.3.5", "id-pkinit-KPKdc", "KDC EKU"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_KPKdc = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - tmp = OBJ_create("1.2.840.113549.1.7.1", "id-data", - "CMS id-data"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_authData9 = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - tmp = OBJ_create("1.3.6.1.4.1.311.20.2.3", "id-pkinit-san draft9", - "KRB5PrincipalName draft9"); - if (tmp == NID_undef) - goto out; - ctx->id_pkinit_san9 = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - tmp = OBJ_create("1.3.6.1.4.1.311.20.2.2", "id-ms-kp-sc-logon EKU", - "KDC/Client EKU draft9"); - if (tmp == NID_undef) - goto out; - ctx->id_ms_kp_sc_logon = (ASN1_OBJECT *)OBJ_nid2obj(tmp); - tmp = OBJ_create("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth EKU", - "KDC EKU draft9"); - if (tmp == NID_undef) - goto out; - ctx->id_kp_serverAuth = (ASN1_OBJECT *)OBJ_nid2obj(tmp); + 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; @@ -589,7 +580,7 @@ cms_signeddata_create(krb5_context context, if ((p7s = PKCS7_SIGNED_new()) == NULL) goto cleanup; p7->d.sign = p7s; - if (!ASN1_INTEGER_set(p7s->version, 1)) + if (!ASN1_INTEGER_set(p7s->version, 3)) goto cleanup; /* create a cert chain that has at least the signer's certificate */ @@ -605,7 +596,7 @@ cms_signeddata_create(krb5_context context, X509_STORE *certstore = NULL; X509_STORE_CTX certctx; STACK_OF(X509) *certstack = NULL; - char buf[256]; + char buf[DN_BUF_LEN]; int i = 0, size = 0; if ((certstore = X509_STORE_new()) == NULL) @@ -627,7 +618,7 @@ cms_signeddata_create(krb5_context context, 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, 256); + 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)); } @@ -709,7 +700,7 @@ cms_signeddata_create(krb5_context context, * digestAlgorithm AlgorithmIdentifier, * digest OCTET STRING } */ - if (id_cryptoctx->pkcs11_method && id_cryptoctx->mech == CKM_RSA_PKCS) { + if (id_cryptoctx->pkcs11_method == 1 && id_cryptoctx->mech == CKM_RSA_PKCS) { pkiDebug("mech = CKM_RSA_PKCS\n"); EVP_MD_CTX_init(&ctx2); EVP_DigestInit_ex(&ctx2, md_tmp, NULL); @@ -755,7 +746,7 @@ cms_signeddata_create(krb5_context context, #endif { pkiDebug("mech = %s\n", - id_cryptoctx->pkcs11_method ? "CKM_SHA1_RSA_PKCS" : "FS"); + id_cryptoctx->pkcs11_method == 1 ? "CKM_SHA1_RSA_PKCS" : "FS"); retval = pkinit_sign_data(context, id_cryptoctx, abuf, alen, &sig, &sig_len); } @@ -800,6 +791,7 @@ cms_signeddata_create(krb5_context context, 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; @@ -830,13 +822,13 @@ cms_signeddata_create(krb5_context context, retval = 0; #ifdef DEBUG_ASN1 - //print_buffer_bin(*signed_data, *signed_data_len, "/tmp/pkcs7_signeddata"); + print_buffer_bin(*signed_data, *signed_data_len, "/tmp/pkcs7_signeddata"); #endif cleanup2: EVP_MD_CTX_cleanup(&ctx); #ifndef WITHOUT_PKCS11 - if (id_cryptoctx->pkcs11_method && id_cryptoctx->mech == CKM_RSA_PKCS) + if (id_cryptoctx->pkcs11_method == 1 && id_cryptoctx->mech == CKM_RSA_PKCS) EVP_MD_CTX_cleanup(&ctx2); #endif if (alg != NULL) @@ -850,7 +842,7 @@ cms_signeddata_create(krb5_context context, if (digestInfo_buf != NULL) free(digestInfo_buf); cleanup: - if (p7 != NULL) + if (p7 != NULL) PKCS7_free(p7); if (sig != NULL) free(sig); @@ -886,14 +878,20 @@ cms_signeddata_verify(krb5_context context, 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[256]; + char buf[DN_BUF_LEN]; #ifdef DEBUG_ASN1 print_buffer_bin(signed_data, signed_data_len, "/tmp/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 messag */ if ((p7 = d2i_PKCS7(NULL, &p, (int)signed_data_len)) == NULL) { unsigned long err = ERR_peek_error(); @@ -988,7 +986,7 @@ cms_signeddata_verify(krb5_context context, 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, 256); + sk_X509_value(intermediateCAs, i)), buf, sizeof(buf)); pkiDebug("cert #%d: %s\n", i, buf); } } @@ -997,7 +995,7 @@ cms_signeddata_verify(krb5_context context, 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, 256); + sk_X509_value(idctx->trustedCAs, i)), buf, sizeof(buf)); pkiDebug("cert #%d: %s\n", i, buf); } } @@ -1006,7 +1004,7 @@ cms_signeddata_verify(krb5_context context, 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, 256); + X509_NAME_oneline(X509_CRL_get_issuer(crl), buf, sizeof(buf)); pkiDebug("crls by CA #%d: %s\n", i , buf); } } @@ -1032,7 +1030,7 @@ cms_signeddata_verify(krb5_context context, retval = KRB5KDC_ERR_INVALID_CERTIFICATE; } X509_NAME_oneline(X509_get_subject_name( - reqctx->received_cert), buf, 256); + 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", @@ -1042,7 +1040,7 @@ cms_signeddata_verify(krb5_context context, 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, 256); + X509_NAME_oneline(X509_get_subject_name(tmp_cert), buf, sizeof(buf)); pkiDebug("cert #%d: %s\n", j, buf); } #endif @@ -1057,10 +1055,6 @@ cms_signeddata_verify(krb5_context context, out = BIO_new(BIO_s_mem()); if (PKCS7_verify(p7, NULL, store, NULL, out, flags)) { - ASN1_OBJECT *oid = NULL; - oid = pkinit_pkcs7type2oid(plgctx, cms_msg_type); - if (oid == NULL) - goto cleanup; if (!OBJ_cmp(p7->d.sign->contents->type, oid)) pkiDebug("PKCS7 Verification successful\n"); else { @@ -1290,8 +1284,9 @@ cms_envelopeddata_verify(krb5_context context, int i = 0; unsigned int size = 0; const unsigned char *p = enveloped_data; - unsigned int tmp_buf_len = 0, tmp_buf2_len = 0; - unsigned char *tmp_buf = NULL, *tmp_buf2 = NULL; + 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; /* decode received PKCS7 message */ if ((p7 = d2i_PKCS7(NULL, &p, (int)enveloped_data_len)) == NULL) { @@ -1334,35 +1329,55 @@ cms_envelopeddata_verify(krb5_context context, 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: - retval = encode_signeddata(tmp_buf, tmp_buf_len, &tmp_buf2, - &tmp_buf2_len); - if (retval) { - pkiDebug("failed to encode signeddata\n"); - goto cleanup; - } -#ifdef DEBUG_ASN1 - print_buffer_bin(tmp_buf2, tmp_buf2_len, "/tmp/client_enc_keypack2"); -#endif - retval = cms_signeddata_verify(context, plg_cryptoctx, - req_cryptoctx, id_cryptoctx, CMS_ENVEL_SERVER, - require_crl_checking, tmp_buf2, tmp_buf2_len, data, - data_len, NULL, NULL); + msg_type = CMS_ENVEL_SERVER; + break; case KRB5_PADATA_PK_AS_REP_OLD: - retval = cms_signeddata_verify(context, plg_cryptoctx, - req_cryptoctx, id_cryptoctx, CMS_SIGN_DRAFT9, - require_crl_checking, tmp_buf, tmp_buf_len, data, - data_len, NULL, NULL); + msg_type = CMS_SIGN_DRAFT9; break; default: - pkiDebug("unrecognized pa_type = %d\n", pa_type); + 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 win2k-compatible, we don't do anything because it + * is already wrapped. (Except for longhorn which does + * things differently...) + */ + if (msg_type == CMS_ENVEL_SERVER || longhorn) { + 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; + } + +#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 { @@ -1386,44 +1401,78 @@ cms_envelopeddata_verify(krb5_context context, return retval; } -krb5_error_code -verify_id_pkinit_san(krb5_context context, - pkinit_plg_crypto_context plgctx, - pkinit_req_crypto_context reqctx, - pkinit_identity_crypto_context idctx, - krb5_preauthtype pa_type, - int allow_upn, - krb5_principal *out, - unsigned char **kdc_hostname, - int *valid_san) +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 = KRB5KDC_ERR_PREAUTH_FAILED; - int i = 0, ok = 0; - char buf[256]; + 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; + } - *valid_san = 0; - if (reqctx->received_cert == NULL) return retval; + if (cert == NULL) { + pkiDebug("%s: no certificate!\n", __FUNCTION__); + return retval; + } - /* now let's verify that KDC's certificate has id-pkinit-san */ - X509_NAME_oneline(X509_get_subject_name(reqctx->received_cert), buf, 256); - pkiDebug("looking for SANs in cert = %s\n", buf); + 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(reqctx->received_cert, - NID_subject_alt_name, -1)) >= 0) { + 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(reqctx->received_cert, i)) - || !(ialt = X509V3_EXT_d2i(ext))) { - pkiDebug("unable to retrieve subject alt name ext\n"); + if (!(ext = X509_get_ext(cert, i)) || !(ialt = X509V3_EXT_d2i(ext))) { + pkiDebug("%s: found no subject alt name extensions\n", + __FUNCTION__); goto cleanup; } - pkiDebug("found %d subject alt name extension(s)\n", - sk_GENERAL_NAME_num(ialt)); + 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 < sk_GENERAL_NAME_num(ialt); i++) { + for (i = 0; i < num_sans; i++) { krb5_data name = { 0, 0, NULL }; gen = sk_GENERAL_NAME_value(ialt, i); @@ -1431,143 +1480,188 @@ verify_id_pkinit_san(krb5_context context, case GEN_OTHERNAME: name.length = gen->d.otherName->value->value.sequence->length; name.data = (char *)gen->d.otherName->value->value.sequence->data; - if (!OBJ_cmp(plgctx->id_pkinit_san, gen->d.otherName->type_id)) { + 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, out); - } else if (allow_upn && !OBJ_cmp(plgctx->id_pkinit_san9, - gen->d.otherName->type_id)) { - ret = krb5_parse_name(context, name.data, out); + 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("unrecognized or pa_type incorrect oid in SAN\n"); + pkiDebug("%s: unrecognized othername oid in SAN\n", + __FUNCTION__); continue; } - if (ret) { - pkiDebug("failed to decode Krb5PrincipalName in SAN\n"); - break; - } - if (out != NULL) - ok = 1; break; case GEN_DNS: - if (pa_type == KRB5_PADATA_PK_AS_REP_OLD && - kdc_hostname != NULL) { - pkiDebug("Win2K KDC on host = %s\n", gen->d.dNSName->data); - *kdc_hostname = (unsigned char *) + 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); - ok = 1; + if (dnss[d] == NULL) { + pkiDebug("%s: failed to duplicate dns name\n", + __FUNCTION__); + } else { + d++; + num_found++; + } } break; default: - pkiDebug("SAN type = %d expecting %d\n", gen->type, - GEN_OTHERNAME); + pkiDebug("%s: SAN type = %d expecting %d\n", + __FUNCTION__, gen->type, GEN_OTHERNAME); } - if (ok) - break; } sk_GENERAL_NAME_pop_free(ialt, GENERAL_NAME_free); } - if (!ok) { - pkiDebug("didn't find id_pkinit_san\n"); - } retval = 0; + if (princs) + *princs_ret = princs; + if (upns) + *upn_ret = upns; + if (dnss) + *dns_ret = dnss; cleanup: - *valid_san = ok; + 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 -verify_id_pkinit_eku(krb5_context context, - pkinit_plg_crypto_context plgctx, - pkinit_req_crypto_context reqctx, - pkinit_identity_crypto_context idctx, - krb5_preauthtype pa_type, - int require_eku, - int *valid_eku) +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 = KRB5KDC_ERR_PREAUTH_FAILED; - int i = 0, ok = 0; - char buf[256]; - ASN1_OBJECT * oid_client = NULL, *oid_server = NULL; - ASN1_OBJECT *oid_logon = NULL, *oid_kp = NULL; + krb5_error_code retval = EINVAL; - *valid_eku = 0; - if (reqctx->received_cert == NULL) return retval; + if (reqctx->received_cert == NULL) { + pkiDebug("%s: No certificate!\n", __FUNCTION__); + return retval; + } - /* now let's verify that KDC's certificate has pkinit EKU */ - X509_NAME_oneline(X509_get_subject_name(reqctx->received_cert), buf, 256); - pkiDebug("looking for EKUs in cert = %s\n", buf); + return crypto_retrieve_X509_sans(context, plgctx, reqctx, + reqctx->received_cert, princs_ret, + upn_ret, dns_ret); +} - oid_client = plgctx->id_pkinit_KPClientAuth; - oid_server = plgctx->id_pkinit_KPKdc; - oid_logon = plgctx->id_ms_kp_sc_logon; - oid_kp = plgctx->id_kp_serverAuth; +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) { + NID_ext_key_usage, -1)) >= 0) { EXTENDED_KEY_USAGE *extusage; - if ((extusage = X509_get_ext_d2i(reqctx->received_cert, - NID_ext_key_usage, NULL, NULL))) { - for (i = 0; i < sk_ASN1_OBJECT_num(extusage); i++) { - ASN1_OBJECT *tmp_oid = NULL; - int flag = 0; + 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); - switch ((int)pa_type) { - case KRB5_PADATA_PK_AS_REQ_OLD: - case KRB5_PADATA_PK_AS_REQ: - if (!OBJ_cmp(oid_client, tmp_oid) || - !OBJ_cmp(oid_logon, tmp_oid)) - flag = 1; - break; - case KRB5_PADATA_PK_AS_REP_OLD: - case KRB5_PADATA_PK_AS_REP: - if (!OBJ_cmp(oid_server, tmp_oid) || - !OBJ_cmp(oid_kp, tmp_oid)) - flag = 1; - break; - default: - goto cleanup; - } - if (flag) { - ASN1_BIT_STRING *usage = NULL; - pkiDebug("found pa_type-specific EKU\n"); - - /* 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("found digitalSignature KU\n"); - ok = 1; - } else - pkiDebug("didn't find digitalSignature KU\n"); - } - - ASN1_BIT_STRING_free(usage); - break; + 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: - if (!ok) { - pkiDebug("didn't find extended key usage (EKU) for pkinit\n"); - if (0 == require_eku) { - pkiDebug("configuration says ignore missing EKU\n"); - ok = 1; - } - } - *valid_eku = ok; + pkiDebug("%s: returning retval %d, valid_eku %d\n", + __FUNCTION__, retval, *valid_eku); return retval; } @@ -2106,10 +2200,10 @@ pkinit_create_sequence_of_principal_identifiers( 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) { + 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; } @@ -2117,7 +2211,7 @@ pkinit_create_sequence_of_principal_identifiers( case TD_INVALID_CERTIFICATES: retval = create_krb5_invalidCertificates(context, plg_cryptoctx, req_cryptoctx, id_cryptoctx, &krb5_trusted_certifiers); - if (retval) { + if (retval) { pkiDebug("create_krb5_invalidCertificates failed\n"); goto cleanup; } @@ -2421,7 +2515,7 @@ pkinit_check_kdc_pkid(krb5_context context, 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, 0); + X509 *kdc_cert = sk_X509_value(id_cryptoctx->my_certs, id_cryptoctx->cert_index); *valid_kdcPkId = 0; pkiDebug("found kdcPkId in AS REQ\n"); @@ -2562,9 +2656,9 @@ openssl_callback(int ok, X509_STORE_CTX * ctx) { #ifdef DEBUG if (!ok) { - char buf[256]; + char buf[DN_BUF_LEN]; - X509_NAME_oneline(X509_get_subject_name(ctx->current_cert), buf, 256); + 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)); @@ -2590,10 +2684,30 @@ openssl_callback_ignore_crls(int ok, X509_STORE_CTX * ctx) 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; @@ -2606,29 +2720,42 @@ pkinit_pkcs7type2oid(pkinit_plg_crypto_context cryptoctx, int pkcs7_type) } static int -encode_signeddata(unsigned char *data, unsigned int data_len, - unsigned char **out, unsigned int *out_len) +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len, + int is_longhorn_server) { - unsigned int size = 0, r = 0; - ASN1_OBJECT *oid; + unsigned int orig_len = 0, oid_len = 0, tot_len = 0; + ASN1_OBJECT *oid = NULL; unsigned char *p = NULL; - r = ASN1_object_size(1, (int)data_len, V_ASN1_SEQUENCE); - oid = OBJ_nid2obj(NID_pkcs7_signed); - size = i2d_ASN1_OBJECT(oid, NULL); - size += r; + /* 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); + } - r = ASN1_object_size(1, (int)size, V_ASN1_SEQUENCE); - p = *out = (unsigned char *)malloc(r); + p = *out = (unsigned char *)malloc(tot_len); if (p == NULL) return -1; - ASN1_put_object(&p, 1, (int)size, V_ASN1_SEQUENCE, V_ASN1_UNIVERSAL); - i2d_ASN1_OBJECT(oid, &p); - ASN1_put_object(&p, 1, (int)data_len, 0, V_ASN1_CONTEXT_SPECIFIC); + 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 = r; + *out_len = tot_len; return 0; } @@ -2835,8 +2962,8 @@ pkinit_open_session(krb5_context context, krb5_error_code pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, - CK_ATTRIBUTE_TYPE usage, - CK_OBJECT_HANDLE *objp) + CK_ATTRIBUTE_TYPE usage, + CK_OBJECT_HANDLE *objp) { CK_OBJECT_CLASS cls; CK_ATTRIBUTE attrs[4]; @@ -2879,16 +3006,16 @@ pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx, nattrs++; if (id_cryptoctx->p11->C_FindObjectsInit(id_cryptoctx->session, - attrs, nattrs) != CKR_OK) { - pkiDebug("krb5_pkinit_sign_data: fail C_FindObjectsInit\n"); - return KRB5KDC_ERR_PREAUTH_FAILED; + attrs, nattrs) != CKR_OK) { + pkiDebug("krb5_pkinit_sign_data: fail C_FindObjectsInit\n"); + 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 %x\n", (int) count, (int) r); if (r != CKR_OK || count < 1) - return KRB5KDC_ERR_PREAUTH_FAILED; + return KRB5KDC_ERR_PREAUTH_FAILED; return 0; } #endif @@ -2998,7 +3125,7 @@ pkinit_decode_data(krb5_context context, { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - if (!id_cryptoctx->pkcs11_method) + 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 @@ -3098,7 +3225,7 @@ pkinit_sign_data(krb5_context context, { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - if (id_cryptoctx == NULL || !id_cryptoctx->pkcs11_method) + 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 @@ -3215,15 +3342,137 @@ pkinit_get_kdc_cert(krb5_context context, } static krb5_error_code +pkinit_get_client_cert_pkcs12(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds) +{ + krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + X509 *x = NULL; + PKCS12 *p12 = NULL; + int ret; + FILE *fp; + EVP_PKEY *y = NULL; + + if (id_cryptoctx->cert_filename == NULL) { + pkiDebug("failed to get user's cert location\n"); + goto cleanup; + } + + if (id_cryptoctx->key_filename == NULL) { + pkiDebug("failed to get user's private key location\n"); + goto cleanup; + } + + fp = fopen(id_cryptoctx->cert_filename, "rb"); + if (fp == NULL) { + pkiDebug("Failed to open PKCS12 file '%s', error %d\n", + id_cryptoctx->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", + id_cryptoctx->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, id_cryptoctx->cert_filename); + if (r >= sizeof(prompt_string)) { + pkiDebug("Prompt string, '%s %s', is too long!\n", + prompt_prefix, id_cryptoctx->cert_filename); + goto cleanup; + } + kprompt.prompt = prompt_string; + kprompt.hidden = 1; + kprompt.reply = &rdat; + prompt_type = KRB5_PROMPT_TYPE_PREAUTH; + + /* PROMPTER_INVOCATION */ + krb5int_set_prompt_types(context, &prompt_type); + r = (*id_cryptoctx->prompter)(context, id_cryptoctx->prompter_data, + NULL, NULL, 1, &kprompt); + krb5int_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; + } + } +#if 0 + id_cryptoctx->my_certs = sk_X509_new_null(); + sk_X509_push(id_cryptoctx->my_certs, x); + id_cryptoctx->cert_index = 0; +#else + creds[0] = malloc(sizeof(struct _pkinit_cred_info)); + if (creds[0] == NULL) + goto cleanup; + creds[0]->cert = x; + creds[0]->cert_label = NULL; + creds[0]->label_len = 0; + creds[0]->key = y; + creds[1] = NULL; +#endif + + retval = 0; + +cleanup: + if (p12) + PKCS12_free(p12); + if (retval) { +#if 0 + if (id_cryptoctx->my_certs != NULL) { + sk_X509_pop_free(id_cryptoctx->my_certs, X509_free); + id_cryptoctx->my_certs = NULL; + } +#else + if (x != NULL) + X509_free(x); + if (y != NULL) + EVP_PKEY_free(y); +#endif + } + return retval; +} + +static krb5_error_code pkinit_get_client_cert_fs(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, const char *principal, - krb5_get_init_creds_opt *opt) + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds) { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; X509 *x = NULL; + EVP_PKEY *y = NULL; if (id_cryptoctx->cert_filename == NULL) { pkiDebug("failed to get user's cert location\n"); @@ -3242,26 +3491,53 @@ pkinit_get_client_cert_fs(krb5_context context, goto cleanup; } else { +#if 0 /* add the certificate */ id_cryptoctx->my_certs = sk_X509_new_null(); sk_X509_push(id_cryptoctx->my_certs, x); id_cryptoctx->cert_index = 0; +#endif +#if 0 /* add the private key */ if ((id_cryptoctx->my_key = get_key(id_cryptoctx->key_filename)) == NULL) { pkiDebug("failed to load user's private key from '%s'\n", id_cryptoctx->key_filename); goto cleanup; } +#else + /* add the private key */ + if ((y = get_key(id_cryptoctx->key_filename)) == NULL) { + pkiDebug("failed to load user's private key from '%s'\n", + id_cryptoctx->key_filename); + goto cleanup; + } +#endif } - retval = 0; + + creds[0] = malloc(sizeof(struct _pkinit_cred_info)); + if (creds[0] == NULL) + goto cleanup; + creds[0]->cert = x; + creds[0]->cert_label = NULL; + creds[0]->label_len = 0; + creds[0]->key = y; + creds[1] = NULL; + +retval = 0; cleanup: if (retval) { +#if 0 if (id_cryptoctx->my_certs != NULL) { sk_X509_pop_free(id_cryptoctx->my_certs, X509_free); id_cryptoctx->my_certs = NULL; } +#endif + if (x != NULL) + X509_free(x); + if (y != NULL) + EVP_PKEY_free(y); } return retval; } @@ -3273,7 +3549,8 @@ pkinit_get_client_cert_pkcs11(krb5_context context, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, const char *principal, - krb5_get_init_creds_opt *opt) + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds) { #ifdef PKINIT_USE_MECH_LIST CK_MECHANISM_TYPE_PTR mechp; @@ -3374,9 +3651,13 @@ pkinit_get_client_cert_pkcs11(krb5_context context, } 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) { + creds[i] = NULL; break; } @@ -3416,6 +3697,7 @@ pkinit_get_client_cert_pkcs11(krb5_context context, pkiDebug("cert %d size %d id %d idlen %d\n", i, (int) attrs[0].ulValueLen, (int) cert_id[0], (int) attrs[1].ulValueLen); +#if 0 /* Just take the first one */ if (i == 0) { id_cryptoctx->cert_id = cert_id; @@ -3429,6 +3711,19 @@ pkinit_get_client_cert_pkcs11(krb5_context context, id_cryptoctx->cert_index = 0; } else free(cert_id); +#else + cp = (unsigned char *) cert; + x = d2i_X509(NULL, &cp, (int) attrs[0].ulValueLen); + if (x == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + creds[i] = malloc(sizeof(struct _pkinit_cred_info)); + if (creds[i] == NULL) + return KRB5KDC_ERR_PREAUTH_FAILED; + creds[i]->cert = x; + creds[i]->key = NULL; + creds[i]->cert_label = (char *)cert_id; + creds[i]->label_len = attrs[1].ulValueLen; +#endif free(cert); } id_cryptoctx->p11->C_FindObjectsFinal(id_cryptoctx->session); @@ -3438,7 +3733,7 @@ pkinit_get_client_cert_pkcs11(krb5_context context, } #endif -krb5_error_code +static krb5_error_code pkinit_get_client_cert(krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, @@ -3448,149 +3743,93 @@ pkinit_get_client_cert(krb5_context context, { krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; + pkinit_cred_info creds[MAX_CREDS_ALLOWED+1]; + + memset(creds, 0, sizeof(creds)); - if (!id_cryptoctx->pkcs11_method) - retval = pkinit_get_client_cert_fs(context, plg_cryptoctx, - req_cryptoctx, id_cryptoctx, - principal, opt); + switch(id_cryptoctx->pkcs11_method) { + case 0: + retval = pkinit_get_client_cert_fs(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, + principal, opt, creds); + break; #ifndef WITHOUT_PKCS11 - else - retval = pkinit_get_client_cert_pkcs11(context, plg_cryptoctx, - req_cryptoctx, id_cryptoctx, - principal, opt); + case 1: + retval = pkinit_get_client_cert_pkcs11(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, + principal, opt, creds); + break; #endif - return retval; -} - -krb5_error_code -pkinit_get_trusted_cacerts(krb5_context context, - pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt) -{ - krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - char *filename = NULL, *dirname = NULL; - - id_cryptoctx->trustedCAs = NULL; - - if (get_filename(&filename, "X509_CA_TRUSTED_BUNDLE", 1) != 0) { - pkiDebug("failed to get the name of the ca-bundle file of trusted CAs\n"); - } - - if (get_filename(&dirname, "X509_CA_TRUSTED_DIR", 1) != 0) { - pkiDebug("failed to get the dir of trusted CAs\n"); - } - - if (filename) { - retval = load_trusted_certifiers(&id_cryptoctx->trustedCAs, NULL, 0, - filename); - if (retval) - goto cleanup; + case 2: + retval = pkinit_get_client_cert_pkcs12(context, plg_cryptoctx, + req_cryptoctx, id_cryptoctx, + principal, opt, creds); + break; } + if (retval) + goto cleanup; - if (dirname) { - retval = load_trusted_certifiers_dir(&id_cryptoctx->trustedCAs, - NULL, 0, dirname); - if (retval) - goto cleanup; - } + /* pick the right certificate/key from the retrieved certs */ + retval = pkinit_match_cert(context, plg_cryptoctx, req_cryptoctx, + id_cryptoctx, principal, opt, creds); + if (retval) + goto cleanup; - retval = 0; cleanup: - if (filename != NULL) - free(filename); - if (dirname != NULL) - free(dirname); return retval; } -krb5_error_code -pkinit_get_intermediate_cacerts(krb5_context context, - pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt) +static krb5_error_code +pkinit_match_cert(krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds) { - krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - char *filename = NULL, *dirname = NULL; - - id_cryptoctx->intermediateCAs = NULL; - - if (get_filename(&filename, "X509_CA_INTERM_BUNDLE", 1) != 0) { - pkiDebug("failed to get the name of the ca-bundle file of intermediate CAs\n"); - } - if (get_filename(&dirname, "X509_CA_INTERM_DIR", 1) != 0) { - pkiDebug("failed to get the dir of intermediate CAs\n"); - } - - if (filename) { - retval = load_trusted_certifiers(&id_cryptoctx->intermediateCAs, NULL, - 0, filename); - if (retval) - goto cleanup; - } - - if (dirname) { - retval = load_trusted_certifiers_dir(&id_cryptoctx->intermediateCAs, - NULL, 0, dirname); - if (retval) - goto cleanup; - } - - retval = 0; -cleanup: - if (filename != NULL) - free(filename); - if (dirname != NULL) - free(dirname); - - return retval; -} - -krb5_error_code -pkinit_get_crls(krb5_context context, - pkinit_plg_crypto_context plg_cryptoctx, - pkinit_req_crypto_context req_cryptoctx, - pkinit_identity_crypto_context id_cryptoctx, - krb5_get_init_creds_opt *opt) -{ krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED; - char *filename = NULL, *dirname = NULL; + int i, creds_index = 0; - id_cryptoctx->revoked = NULL; + /* add code to find the right certificate */ - if (get_filename(&filename, "X509_CRL_BUNDLE", 1) != 0) { - pkiDebug("failed to get the name of the ca-bundle file of CRLs\n"); - } - - if (get_filename(&dirname, "X509_CRL_DIR", 1) != 0) { - pkiDebug("failed to get the dir of CRLs\n"); - } + /* copy the selected cert into our id_cryptoctx */ + id_cryptoctx->my_certs = sk_X509_new_null(); + sk_X509_push(id_cryptoctx->my_certs, creds[creds_index]->cert); + id_cryptoctx->cert_index = 0; - if (filename) { - retval = load_trusted_certifiers(NULL, &id_cryptoctx->revoked, - 1, filename); - if (retval) - goto cleanup; + if (id_cryptoctx->pkcs11_method != 1) { + id_cryptoctx->my_key = creds[creds_index]->key; + } +#ifndef WITHOUT_PKCS11 + else { + id_cryptoctx->cert_id = (CK_BYTE_PTR)creds[creds_index]->cert_label; + id_cryptoctx->cert_id_len = creds[creds_index]->label_len; } +#endif - if (dirname) { - retval = load_trusted_certifiers_dir(NULL, &id_cryptoctx->revoked, - 1, dirname); - if (retval) - goto cleanup; + /* free unused cred_infos */ + for (i = 0; ; i++) { + if (creds[i] == NULL) break; + else if (i == creds_index) { + free (creds[i]); + } else { + if (creds[i]->cert != NULL) + X509_free(creds[i]->cert); + if (creds[i]->key != NULL) + EVP_PKEY_free(creds[i]->key); +#ifndef WITHOUT_PKCS11 + if (creds[i]->cert_label != NULL) + free(creds[i]->cert_label); +#endif + free(creds[i]); + } } retval = 0; cleanup: - if (filename != NULL) - free(filename); - if (dirname != NULL) - free(dirname); - return retval; } @@ -3791,7 +4030,7 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, unsigned char *p = NULL; int len = 0; PKCS7_ISSUER_AND_SERIAL *is = NULL; - char buf[256]; + char buf[DN_BUF_LEN]; *ids = NULL; @@ -3806,7 +4045,7 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, x = sk_X509_value(sk, i); - X509_NAME_oneline(X509_get_subject_name(x), buf, 256); + X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf)); pkiDebug("#%d cert= %s\n", i, buf); /* fill-in subjectName */ @@ -3826,6 +4065,7 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, krb5_cas[i]->issuerAndSerialNumber.magic = 0; krb5_cas[i]->issuerAndSerialNumber.data = NULL; +if (longhorn == 0) { /* XXX Longhorn doesn't like this */ is = PKCS7_ISSUER_AND_SERIAL_new(); X509_NAME_set(&is->issuer, X509_get_issuer_name(x)); M_ASN1_INTEGER_free(is->serial); @@ -3836,12 +4076,14 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, goto cleanup; i2d_PKCS7_ISSUER_AND_SERIAL(is, &p); krb5_cas[i]->issuerAndSerialNumber.length = len; +} /* fill-in subjectKeyIdentifier */ krb5_cas[i]->subjectKeyIdentifier.length = 0; krb5_cas[i]->subjectKeyIdentifier.magic = 0; krb5_cas[i]->subjectKeyIdentifier.data = NULL; +if (longhorn == 0) { /* XXX Longhorn doesn't like this */ if (X509_get_ext_by_NID(x, NID_subject_key_identifier, -1) >= 0) { ASN1_OCTET_STRING *ikeyid = NULL; @@ -3857,6 +4099,7 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, if (ikeyid != NULL) ASN1_OCTET_STRING_free(ikeyid); } +} if (is != NULL) { if (is->issuer != NULL) X509_NAME_free(is->issuer); @@ -3878,10 +4121,10 @@ create_identifiers_from_stack(STACK_OF(X509) *sk, 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) + 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; @@ -3937,7 +4180,7 @@ create_krb5_trustedCas(krb5_context context, int i = 0, len = 0, sk_size = sk_X509_num(sk); krb5_trusted_ca **krb5_cas = NULL; X509 *x = NULL; - char buf[256]; + char buf[DN_BUF_LEN]; X509_NAME *xn = NULL; unsigned char *p = NULL; PKCS7_ISSUER_AND_SERIAL *is = NULL; @@ -3957,7 +4200,7 @@ create_krb5_trustedCas(krb5_context context, goto cleanup; x = sk_X509_value(sk, i); - X509_NAME_oneline(X509_get_subject_name(x), buf, 256); + X509_NAME_oneline(X509_get_subject_name(x), buf, sizeof(buf)); pkiDebug("#%d cert= %s\n", i, buf); switch (flag) { @@ -4014,6 +4257,7 @@ cleanup: #define IDTYPE_DIR 2 #define IDTYPE_PKCS11 3 #define IDTYPE_ENVVAR 4 +#define IDTYPE_PKCS12 5 #define CATYPE_ANCHORS 1 #define CATYPE_INTERMEDIATES 2 @@ -4037,25 +4281,25 @@ parse_pkcs11_options(krb5_context context, return retval; for ((cp = strtok(s, ":")); cp; (cp = strtok(NULL, ":"))) { - vp = strchr(cp, '='); + vp = strchr(cp, '='); - /* If there is no "=", this is a pkcs11 module name */ - if (vp == NULL) { + /* If there is no "=", this is a pkcs11 module name */ + if (vp == NULL) { if (id_cryptoctx->p11_module_name != NULL) free(id_cryptoctx->p11_module_name); - id_cryptoctx->p11_module_name = strdup(cp); + id_cryptoctx->p11_module_name = strdup(cp); if (id_cryptoctx->p11_module_name == NULL) goto cleanup; - continue; - } - *vp++ = '\0'; - if (!strcmp(cp, "module_name")) { + continue; + } + *vp++ = '\0'; + if (!strcmp(cp, "module_name")) { if (id_cryptoctx->p11_module_name != NULL) free(id_cryptoctx->p11_module_name); - id_cryptoctx->p11_module_name = strdup(vp); + id_cryptoctx->p11_module_name = strdup(vp); if (id_cryptoctx->p11_module_name == NULL) goto cleanup; - } else if (!strcmp(cp, "slotid")) { + } else if (!strcmp(cp, "slotid")) { long slotid = strtol(vp, NULL, 10); if ((slotid == LONG_MIN || slotid == LONG_MAX) && errno != 0) { retval = EINVAL; @@ -4065,32 +4309,32 @@ parse_pkcs11_options(krb5_context context, retval = EINVAL; goto cleanup; } - id_cryptoctx->slotid = slotid; - } else if (!strcmp(cp, "token")) { - if (id_cryptoctx->token_label != NULL) - free(id_cryptoctx->token_label); - id_cryptoctx->token_label = strdup(vp); + id_cryptoctx->slotid = slotid; + } else if (!strcmp(cp, "token")) { + if (id_cryptoctx->token_label != NULL) + free(id_cryptoctx->token_label); + id_cryptoctx->token_label = strdup(vp); if (id_cryptoctx->token_label == NULL) goto cleanup; - } else if (!strcmp(cp, "certid")) { - if (id_cryptoctx->cert_id != NULL) - free(id_cryptoctx->cert_id); - bn = NULL; + } else if (!strcmp(cp, "certid")) { + if (id_cryptoctx->cert_id != NULL) + free(id_cryptoctx->cert_id); + bn = NULL; /* XXX do we need BN stuff at this point? */ - BN_hex2bn(&bn, vp); - id_cryptoctx->cert_id_len = BN_num_bytes(bn); - id_cryptoctx->cert_id = (unsigned char *)malloc((size_t) id_cryptoctx->cert_id_len); - if (id_cryptoctx->cert_id == NULL) + BN_hex2bn(&bn, vp); + id_cryptoctx->cert_id_len = BN_num_bytes(bn); + id_cryptoctx->cert_id = (unsigned char *)malloc((size_t) id_cryptoctx->cert_id_len); + if (id_cryptoctx->cert_id == NULL) goto cleanup; - BN_bn2bin(bn, id_cryptoctx->cert_id); - BN_free(bn); - } else if (!strcmp(cp, "certlabel")) { - if (id_cryptoctx->cert_label != NULL) - free(id_cryptoctx->cert_label); - id_cryptoctx->cert_label = strdup(vp); + BN_bn2bin(bn, id_cryptoctx->cert_id); + BN_free(bn); + } else if (!strcmp(cp, "certlabel")) { + if (id_cryptoctx->cert_label != NULL) + free(id_cryptoctx->cert_label); + id_cryptoctx->cert_label = strdup(vp); if (id_cryptoctx->cert_label == NULL) goto cleanup; - } + } } retval = 0; cleanup: @@ -4132,6 +4376,32 @@ cleanup: return retval; } +static krb5_error_code +parse_pkcs12_options(krb5_context context, + pkinit_identity_crypto_context id_cryptoctx, + const char *residual) +{ + krb5_error_code retval = ENOMEM; + + if (residual == NULL || residual[0] == '\0') + return 0; + + id_cryptoctx->cert_filename = strdup(residual); + if (id_cryptoctx->cert_filename == NULL) + goto cleanup; + + id_cryptoctx->key_filename = strdup(residual); + if (id_cryptoctx->key_filename == NULL) + goto cleanup; + + pkiDebug("%s: cert_filename '%s' key_filename '%s'\n", + __FUNCTION__, id_cryptoctx->cert_filename, + id_cryptoctx->key_filename); + retval = 0; +cleanup: + return retval; +} + /* * XXX * This should be moved to the common code (pkinit_lib.c?) @@ -4145,6 +4415,8 @@ process_option_identity(krb5_context context, const char *value, int idtype; krb5_error_code retval = 0; + pkiDebug("%s: processing value '%s'\n", + __FUNCTION__, value ? value : "NULL"); if (value == NULL) return EINVAL; @@ -4157,11 +4429,15 @@ process_option_identity(krb5_context context, const char *value, idtype = IDTYPE_FILE; } else if (strncmp(value, "PKCS11:", typelen) == 0) { idtype = IDTYPE_PKCS11; + } 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); @@ -4174,12 +4450,16 @@ process_option_identity(krb5_context context, const char *value, switch (idtype) { case IDTYPE_ENVVAR: - retval = process_option_identity(context, getenv(residual), - id_cryptoctx); + return process_option_identity(context, getenv(residual), + id_cryptoctx); break; case IDTYPE_FILE: retval = parse_fs_options(context, id_cryptoctx, residual); break; + case IDTYPE_PKCS12: + id_cryptoctx->pkcs11_method = 2; + retval = parse_pkcs12_options(context, id_cryptoctx, residual); + break; #ifndef WITHOUT_PKCS11 case IDTYPE_PKCS11: id_cryptoctx->pkcs11_method = 1; @@ -4378,7 +4658,7 @@ pkinit_process_td_trusted_certifiers( PKCS7_ISSUER_AND_SERIAL *is = NULL; ASN1_OCTET_STRING *id = NULL; const unsigned char *p = NULL; - char buf[256]; + char buf[DN_BUF_LEN]; int i = 0; if (td_type == TD_TRUSTED_CERTIFIERS) @@ -4394,7 +4674,7 @@ pkinit_process_td_trusted_certifiers( (int)krb5_trusted_certifiers[i]->subjectName.length); if (xn == NULL) goto cleanup; - X509_NAME_oneline(xn, buf, 256); + 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 @@ -4408,7 +4688,7 @@ pkinit_process_td_trusted_certifiers( (int)krb5_trusted_certifiers[i]->issuerAndSerialNumber.length); if (is == NULL) goto cleanup; - X509_NAME_oneline(is->issuer, buf, 256); + 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)); @@ -4477,12 +4757,12 @@ pkcs7_dataDecode(krb5_context context, goto cleanup; } -/* It was encrypted, we need to decrypt the secret key - * with the private key */ + /* It was encrypted, we need to decrypt the secret key + * with the private key */ -/* Find the recipientInfo which matches the passed certificate - * (if any) - */ + /* Find the recipientInfo which matches the passed certificate + * (if any) + */ if (cert) { for (i=0; i #include #include +#include #include #include #include @@ -50,6 +51,8 @@ #include #endif +#define DN_BUF_LEN 256 + #define PKCS11_MODNAME "opensc-pkcs11.so" #define PK_SIGLEN_GUESS 1000 #define PK_NOSLOT 999999 @@ -89,7 +92,7 @@ struct _pkinit_plg_crypto_context { ASN1_OBJECT *id_pkinit_DHKeyData; ASN1_OBJECT *id_pkinit_rkeyData; ASN1_OBJECT *id_pkinit_san; - ASN1_OBJECT *id_pkinit_san9; + ASN1_OBJECT *id_ms_san_upn; ASN1_OBJECT *id_pkinit_KPClientAuth; ASN1_OBJECT *id_pkinit_KPKdc; ASN1_OBJECT *id_ms_kp_sc_logon; @@ -101,6 +104,14 @@ struct _pkinit_req_crypto_context { DH *dh; }; +struct _pkinit_cred_info { + X509 *cert; + EVP_PKEY *key; + char *cert_label; + int label_len; +}; +typedef struct _pkinit_cred_info * pkinit_cred_info; + static void openssl_init(void); static krb5_error_code pkinit_init_pkinit_oids(pkinit_plg_crypto_context ); @@ -145,7 +156,6 @@ static void print_dh(DH *, char *); static void print_pubkey(BIGNUM *, char *); #endif -static krb5_error_code get_filename(char **, char *, int); static X509 *get_cert(char *filename); static EVP_PKEY *get_key(char *filename); @@ -190,12 +200,22 @@ CK_RV pkinit_C_Decrypt CK_BYTE_PTR pEncryptedData, CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen); #endif + +static krb5_error_code pkinit_get_client_cert + (krb5_context context, + pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, + krb5_get_init_creds_opt *opt); + static krb5_error_code pkinit_get_client_cert_pkcs11 (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, const char *principal, - krb5_get_init_creds_opt *opt); + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds); static krb5_error_code pkinit_sign_data_pkcs11 (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, unsigned char *data, unsigned int data_len, @@ -206,12 +226,29 @@ static krb5_error_code pkinit_decode_data_pkcs11 unsigned char **decoded_data, unsigned int *decoded_data_len); #endif /* WITHOUT_PKCS11 */ +static krb5_error_code pkinit_get_client_cert_pkcs12 + (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds); + static krb5_error_code pkinit_get_client_cert_fs (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, pkinit_req_crypto_context req_cryptoctx, pkinit_identity_crypto_context id_cryptoctx, const char *principal, - krb5_get_init_creds_opt *opt); + krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds); + +static krb5_error_code pkinit_match_cert + (krb5_context context, pkinit_plg_crypto_context plg_cryptoctx, + pkinit_req_crypto_context req_cryptoctx, + pkinit_identity_crypto_context id_cryptoctx, + const char *principal, krb5_get_init_creds_opt *opt, + pkinit_cred_info *creds); + static krb5_error_code pkinit_sign_data_fs (krb5_context context, pkinit_identity_crypto_context id_cryptoctx, unsigned char *data, unsigned int data_len, @@ -224,10 +261,6 @@ static krb5_error_code pkinit_decode_data_fs static krb5_error_code der_decode_data (unsigned char *, long, unsigned char **, long *); -static int encode_signeddata - (unsigned char *data, unsigned int data_len, - unsigned char **out, unsigned int *out_len); - static krb5_error_code load_trusted_certifiers (STACK_OF(X509) **trusted_CAs, STACK_OF(X509_CRL) **crls, int return_crls, char *filename); @@ -243,6 +276,14 @@ create_krb5_invalidCertificates(krb5_context context, 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); +static int +wrap_signeddata(unsigned char *data, unsigned int data_len, + unsigned char **out, unsigned int *out_len, + int is_longhorn_server); + /* This handy macro borrowed from crypto/x509v3/v3_purp.c */ #define ku_reject(x, usage) \ (((x)->ex_flags & EXFLAG_KUSAGE) && !((x)->ex_kusage & (usage))) diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c index d5bffaf..76b703e 100644 --- a/src/plugins/preauth/pkinit/pkinit_lib.c +++ b/src/plugins/preauth/pkinit/pkinit_lib.c @@ -40,6 +40,10 @@ #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; @@ -51,11 +55,11 @@ krb5_error_code pkinit_init_req_opts(pkinit_req_opts **reqopts) return retval; opts->require_eku = 1; + opts->accept_secondary_eku = 0; opts->require_san = 1; opts->allow_upn = 0; opts->dh_or_rsa = DH_PROTOCOL; opts->require_crl_checking = 0; - opts->require_hostname_match = 0; opts->dh_size = 1024; opts->win2k_target = 0; opts->win2k_require_cksum = 0; @@ -83,14 +87,13 @@ krb5_error_code pkinit_init_plg_opts(pkinit_plg_opts **plgopts) return retval; opts->require_eku = 1; + opts->accept_secondary_eku = 0; opts->require_san = 1; opts->dh_or_rsa = DH_PROTOCOL; opts->allow_upn = 0; opts->require_crl_checking = 0; - opts->princ_in_cert = 0; opts->dh_min_bits = 0; - opts->allow_proxy_certs = 0; *plgopts = opts; @@ -226,6 +229,7 @@ pkinit_fini_identity_opts(pkinit_identity_opts *idopts) free_list(idopts->anchors); free_list(idopts->intermediates); free_list(idopts->crls); + free_list(idopts->identity_alt); free(idopts); } @@ -238,8 +242,7 @@ pkinit_initialize_identity(krb5_context context, krb5_error_code retval = EINVAL; int i; - pkiDebug("pkinit_initialize_identity %p %p %p\n", - context, idopts, id_cryptoctx); + pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx); if (idopts == NULL || id_cryptoctx == NULL) goto errout; @@ -251,20 +254,19 @@ pkinit_initialize_identity(krb5_context context, * then we will try alternatives which may have been specified * in the config file. */ - if (idopts->identity != NULL) + if (idopts->identity != NULL) { retval = pkinit_process_identity_option(context, PKINIT_ID_OPT_USER_IDENTITY, idopts->identity, id_cryptoctx); - else { - for (i = 0; - retval != 0 - && idopts->identity_alt != NULL - && idopts->identity_alt[i] != NULL; i++) { + } else if (idopts->identity_alt != NULL) { + for (i = 0; retval != 0 && idopts->identity_alt[i] != NULL; i++) retval = pkinit_process_identity_option(context, PKINIT_ID_OPT_USER_IDENTITY, idopts->identity_alt[i], id_cryptoctx); - } + } else { + pkiDebug("%s: no user identity options specified\n", __FUNCTION__); + goto errout; } if (retval) goto errout; diff --git a/src/plugins/preauth/pkinit/pkinit_profile.c b/src/plugins/preauth/pkinit/pkinit_profile.c index 9815d63..abdd9cc 100644 --- a/src/plugins/preauth/pkinit/pkinit_profile.c +++ b/src/plugins/preauth/pkinit/pkinit_profile.c @@ -200,13 +200,13 @@ pkinit_kdcdefault_integer(krb5_context context, const char *realmname, retval = pkinit_kdcdefault_string(context, realmname, option, &string); if (retval == 0) { - char *endptr; - long l; - l = strtol(string, &endptr, 0); - if (endptr == string) + char *endptr; + long l; + l = strtol(string, &endptr, 0); + if (endptr == string) *ret_value = default_value; - else - *ret_value = l; + else + *ret_value = l; free(string); } else *ret_value = default_value; @@ -235,7 +235,7 @@ pkinit_libdefault_strings(krb5_context context, const krb5_data *realm, char realmstr[1024]; if (realm != NULL && realm->length > sizeof(realmstr)-1) - return EINVAL; + return EINVAL; if (realm != NULL) { strncpy(realmstr, realm->data, realm->length); @@ -243,7 +243,7 @@ pkinit_libdefault_strings(krb5_context context, const krb5_data *realm, } if (!context || (context->magic != KV5M_CONTEXT)) - return KV5M_CONTEXT; + return KV5M_CONTEXT; profile = context->profile; @@ -254,9 +254,9 @@ pkinit_libdefault_strings(krb5_context context, const krb5_data *realm, * Try number one: * * [libdefaults] - * REALM = { - * option = - * } + * REALM = { + * option = + * } */ names[1] = realmstr; @@ -270,18 +270,18 @@ pkinit_libdefault_strings(krb5_context context, const krb5_data *realm, * Try number two: * * [libdefaults] - * option = + * option = */ names[1] = option; names[2] = 0; retval = profile_get_values(profile, names, &values); if (retval == 0 && values != NULL && values[0] != NULL) - goto goodbye; + goto goodbye; goodbye: if (values == NULL) - return ENOENT; + return ENOENT; *ret_value = values; @@ -324,10 +324,10 @@ pkinit_libdefault_boolean(krb5_context context, const krb5_data *realm, retval = pkinit_libdefault_string(context, realm, option, &string); if (retval == 0) { - *ret_value = _krb5_conf_boolean(string); - free(string); + *ret_value = _krb5_conf_boolean(string); + free(string); } else - *ret_value = default_value; + *ret_value = default_value; return 0; } @@ -343,13 +343,13 @@ pkinit_libdefault_integer(krb5_context context, const krb5_data *realm, retval = pkinit_libdefault_string(context, realm, option, &string); if (retval == 0) { - char *endptr; - long l; - l = strtol(string, &endptr, 0); - if (endptr == string) + char *endptr; + long l; + l = strtol(string, &endptr, 0); + if (endptr == string) *ret_value = default_value; - else - *ret_value = l; + else + *ret_value = l; free(string); } diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c index c4a67aa..f260096 100644 --- a/src/plugins/preauth/pkinit/pkinit_srv.c +++ b/src/plugins/preauth/pkinit/pkinit_srv.c @@ -147,7 +147,7 @@ pkinit_server_get_edata(krb5_context context, krb5_error_code retval = 0; pkinit_kdc_context plgctx = NULL; - pkiDebug("pkinit_get_edata: entered!\n"); + pkiDebug("pkinit_server_get_edata: entered!\n"); /* * If we don't have a realm context for the given realm, @@ -162,6 +162,138 @@ pkinit_server_get_edata(krb5_context context, } 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; + + 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 + + pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__); + for (i = 0; princs != NULL && princs[i] != NULL; i++) { + 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++) { + 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); + } + 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, @@ -183,7 +315,6 @@ pkinit_server_verify_padata(krb5_context context, pkinit_kdc_context plgctx = NULL; pkinit_kdc_req_context reqctx; krb5_preauthtype pa_type; - krb5_principal tmp_client; krb5_checksum cksum = {0, 0, 0, NULL}; krb5_data *der_req = NULL; int valid_eku = 0, valid_san = 0; @@ -259,39 +390,25 @@ pkinit_server_verify_padata(krb5_context context, goto cleanup; } - retval = verify_id_pkinit_san(context, plgctx->cryptoctx, reqctx->cryptoctx, - plgctx->idctx, data->pa_type, plgctx->opts->allow_upn, &tmp_client, - NULL, &valid_san); + retval = verify_client_san(context, plgctx, reqctx, request->client, + &valid_san); if (retval) goto cleanup; + if (!valid_san) { - pkiDebug("failed to verify id-pkinit-san\n"); - retval = KRB5KDC_ERR_CLIENT_NOT_TRUSTED; + pkiDebug("%s: did not find an acceptable SAN in user certificate\n", + __FUNCTION__); + retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; goto cleanup; - } else { - if (tmp_client != NULL) { - retval = krb5_principal_compare(context, request->client, - tmp_client); - krb5_free_principal(context, tmp_client); - if (!retval) { - pkiDebug("identity in the certificate does not match " - "the requested principal\n"); - retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH; - goto cleanup; - } - } else { - pkiDebug("didn't find Kerberos identity in certificate\n"); - retval = KRB5KDC_ERR_CLIENT_NOT_TRUSTED; - goto cleanup; - } } - retval = verify_id_pkinit_eku(context, plgctx->cryptoctx, reqctx->cryptoctx, - plgctx->idctx, data->pa_type, plgctx->opts->require_eku, &valid_eku); + retval = verify_client_eku(context, plgctx, reqctx, &valid_eku); if (retval) goto cleanup; + if (!valid_eku) { - pkiDebug("failed to verify id-pkinit-KPClientAuth\n"); + pkiDebug("%s: did not find an acceptable EKU in user certificate\n", + __FUNCTION__); retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE; goto cleanup; } @@ -718,11 +835,6 @@ pkinit_server_return_padata(krb5_context context, goto cleanup; } - init_krb5_pa_pk_as_rep(&rep); - if (rep == NULL) { - retval = ENOMEM; - goto cleanup; - } rep->choice = choice_pa_pk_as_rep_encKeyPack; retval = cms_envelopeddata_create(context, plgctx->cryptoctx, reqctx->cryptoctx, plgctx->idctx, padata->pa_type, 1, @@ -747,11 +859,7 @@ pkinit_server_return_padata(krb5_context context, pkiDebug("failed to encode reply_key_pack\n"); goto cleanup; } - init_krb5_pa_pk_as_rep_draft9(&rep9); - if (rep9 == NULL) { - retval = ENOMEM; - 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, @@ -888,6 +996,7 @@ 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, @@ -928,18 +1037,31 @@ pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx) "pkinit_mappings_file", &plgctx->idopts->dn_mapping_file); - pkinit_kdcdefault_boolean(context, plgctx->realmname, - "pkinit_principal_in_certificate", - 1, &plgctx->opts->princ_in_cert); - - pkinit_kdcdefault_boolean(context, plgctx->realmname, - "pkinit_allow_proxy_certificate", - 0, &plgctx->opts->allow_proxy_certs); - pkinit_kdcdefault_integer(context, plgctx->realmname, "pkinit_dh_min_bits", 0, &plgctx->opts->dh_min_bits); + 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); @@ -1041,7 +1163,7 @@ pkinit_server_plugin_init(krb5_context context, void **blob, retval = pkinit_accessor_init(); if (retval) - return retval; + return retval; /* Determine how many realms we may need to support */ for (i = 0; realmnames[i] != NULL; i++) {}; @@ -1141,8 +1263,8 @@ 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("pkinit_fini_kdc_req_context: got bad reqctx (%p)!\n", reqctx); + return; } pkiDebug("%s: freeing reqctx at %p\n", __FUNCTION__, reqctx); -- cgit v1.1