diff options
author | Alexandra Ellwood <lxs@mit.edu> | 2008-05-30 20:47:03 +0000 |
---|---|---|
committer | Alexandra Ellwood <lxs@mit.edu> | 2008-05-30 20:47:03 +0000 |
commit | 7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a (patch) | |
tree | 7cbcc11e0de0af794f9c2f16d03a6b1505e20a1d /src/kdc/kdc_preauth.c | |
parent | 8505824cad8ed0b6e8b96a5103cd43373c266996 (diff) | |
download | krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.gz krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.xz krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.zip |
Apple PKINIT patch commit
Commit of Apple PKINIT patches under "APPLE_PKINIT" preprocessor symbol.
Long term goal is to merge these patches with the pkinit preauth plugin which
does not currently have support for Mac OS X crypto libraries or the exported
functions used by Back To My Mac.
ticket: new
status: open
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20346 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/kdc/kdc_preauth.c')
-rw-r--r-- | src/kdc/kdc_preauth.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index 67764b22e..a250cf4ec 100644 --- a/src/kdc/kdc_preauth.c +++ b/src/kdc/kdc_preauth.c @@ -57,6 +57,11 @@ #include "extern.h" #include <stdio.h> #include "adm_proto.h" +#if APPLE_PKINIT +#include "pkinit_server.h" +#include "pkinit_cert_store.h" +#endif /* APPLE_PKINIT */ + #include <syslog.h> #include <assert.h> @@ -188,7 +193,58 @@ static krb5_error_code return_sam_data void *pa_module_context, void **pa_request_context); +#if APPLE_PKINIT +/* PKINIT preauth support */ +static krb5_error_code get_pkinit_edata( + krb5_context context, + krb5_kdc_req *request, + krb5_db_entry *client, + krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + krb5_pa_data *pa_data); +static krb5_error_code verify_pkinit_request( + krb5_context context, + krb5_db_entry *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data, + krb5_authdata ***authz_data); +static krb5_error_code return_pkinit_response( + krb5_context context, + krb5_pa_data * padata, + krb5_db_entry *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_data, + void *pa_module_context, + void **pa_request_context); +#endif /* APPLE_PKINIT */ + static krb5_preauth_systems static_preauth_systems[] = { +#if APPLE_PKINIT + { + "pkinit", + KRB5_PADATA_PK_AS_REQ, + PA_SUFFICIENT, + NULL, // pa_sys_context + NULL, // init + NULL, // fini + get_pkinit_edata, + verify_pkinit_request, + return_pkinit_response, + NULL // free_pa_request_context + }, +#endif /* APPLE_PKINIT */ { "timestamp", KRB5_PADATA_ENC_TIMESTAMP, @@ -2350,3 +2406,449 @@ verify_sam_response(krb5_context context, krb5_db_entry *client, return retval; } + +#if APPLE_PKINIT +/* PKINIT preauth support */ +#define PKINIT_DEBUG 0 +#if PKINIT_DEBUG +#define kdcPkinitDebug(args...) printf(args) +#else +#define kdcPkinitDebug(args...) +#endif + +/* + * get_edata() - our only job is to determine whether this KDC is capable of + * performing PKINIT. We infer that from the presence or absence of any + * KDC signing cert. + */ +static krb5_error_code get_pkinit_edata( + krb5_context context, + krb5_kdc_req *request, + krb5_db_entry *client, + krb5_db_entry *server, + preauth_get_entry_data_proc pkinit_get_entry_data, + void *pa_module_context, + krb5_pa_data *pa_data) +{ + krb5_pkinit_signing_cert_t cert = NULL; + krb5_error_code err = krb5_pkinit_get_kdc_cert(0, NULL, NULL, &cert); + + kdcPkinitDebug("get_pkinit_edata: kdc cert %s\n", err ? "NOT FOUND" : "FOUND"); + if(cert) { + krb5_pkinit_release_cert(cert); + } + return err; +} + +/* + * This is 0 only for testing until the KDC DB contains + * the hash of the client cert + */ +#define REQUIRE_CLIENT_CERT_MATCH 1 + +static krb5_error_code verify_pkinit_request( + krb5_context context, + krb5_db_entry *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc pkinit_get_entry_data, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data, + krb5_authdata ***authz_data) +{ + krb5_error_code krtn; + krb5_data pa_data; + krb5_data *der_req = NULL; + krb5_boolean valid_cksum; + char *cert_hash = NULL; + unsigned cert_hash_len; + unsigned key_dex; + unsigned cert_match = 0; + krb5_keyblock decrypted_key; + + /* the data we get from the AS-REQ */ + krb5_timestamp client_ctime = 0; + krb5_ui_4 client_cusec = 0; + krb5_timestamp kdc_ctime = 0; + krb5_int32 kdc_cusec = 0; + krb5_ui_4 nonce = 0; + krb5_checksum pa_cksum; + krb5int_cert_sig_status cert_sig_status; + krb5_data client_cert = {0, 0, NULL}; + + krb5_kdc_req *tmp_as_req = NULL; + + kdcPkinitDebug("verify_pkinit_request\n"); + + decrypted_key.contents = NULL; + pa_data.data = (char *)data->contents; + pa_data.length = data->length; + krtn = krb5int_pkinit_as_req_parse(context, &pa_data, + &client_ctime, &client_cusec, + &nonce, &pa_cksum, + &cert_sig_status, + NULL, NULL, /* num_cms_types, cms_types */ + &client_cert, /* signer_cert */ + /* remaining fields unused (for now) */ + NULL, NULL, /* num_all_certs, all_certs */ + NULL, NULL, /* num_trusted_CAs, trusted_CAs */ + NULL); /* kdc_cert */ + if(krtn) { + kdcPkinitDebug("pa_pk_as_req_parse returned %d; PKINIT aborting.\n", + (int)krtn); + return krtn; + } + #if PKINIT_DEBUG + if(cert_sig_status != pki_cs_good) { + kdcPkinitDebug("verify_pkinit_request: cert_sig_status %d\n", + (int)cert_sig_status); + } + #endif /* PKINIT_DEBUG */ + + /* + * Verify signature and cert. + * FIXME: The spec calls for an e-data with error-specific type to be + * returned on error here. TD_TRUSTED_CERTIFIERS + * to be returned to the client here. There is no way for a preauth + * module to pass back e-data to process_as_req at this time. We + * might want to add such capability via an out param to check_padata + * and to its callees. + */ + switch(cert_sig_status) { + case pki_cs_good: + break; + case pki_cs_sig_verify_fail: + /* no e-data */ + krtn = KDC_ERR_INVALID_SIG; + goto cleanup; + case pki_cs_no_root: + case pki_cs_unknown_root: + case pki_cs_untrusted: + /* + * Can't verify to known root. + * e-data TD_TRUSTED_CERTIFIERS + */ + kdcPkinitDebug("verify_pkinit_request: KDC_ERR_CANT_VERIFY_CERTIFICATE\n"); + krtn = KDC_ERR_CANT_VERIFY_CERTIFICATE; + goto cleanup; + case pki_cs_bad_leaf: + case pki_cs_expired: + case pki_cs_not_valid_yet: + /* + * Problems with client cert itself. + * e-data type TD_INVALID_CERTIFICATES + */ + krtn = KDC_ERR_INVALID_CERTIFICATE; + goto cleanup; + case pki_cs_revoked: + /* e-data type TD-INVALID-CERTIFICATES */ + krtn = KDC_ERR_REVOKED_CERTIFICATE; + goto cleanup; + case pki_bad_key_use: + krtn = KDC_ERR_INCONSISTENT_KEY_PURPOSE; + /* no e-data */ + goto cleanup; + case pki_bad_digest: + /* undefined (explicitly!) e-data */ + krtn = KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED; + goto cleanup; + case pki_bad_cms: + case pki_cs_other_err: + default: + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + + krtn = krb5_us_timeofday(context, &kdc_ctime, &kdc_cusec); + if(krtn) { + goto cleanup; + } + if (labs(kdc_ctime - client_ctime) > context->clockskew) { + kdcPkinitDebug("verify_pkinit_request: clock skew violation client %d svr %d\n", + (int)client_ctime, (int)kdc_ctime); + krtn = KRB5KRB_AP_ERR_SKEW; + goto cleanup; + } + + /* + * The KDC may have modified the request after decoding it. + * We need to compute the checksum on the data that + * came from the client. Therefore, we use the original + * packet contents. + */ + krtn = decode_krb5_as_req(req_pkt, &tmp_as_req); + if(krtn) { + kdcPkinitDebug("decode_krb5_as_req returned %d\n", (int)krtn); + goto cleanup; + } + + /* calculate and compare checksum */ + krtn = encode_krb5_kdc_req_body(tmp_as_req, &der_req); + if(krtn) { + kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn); + goto cleanup; + } + krtn = krb5_c_verify_checksum(context, NULL, 0, der_req, + &pa_cksum, &valid_cksum); + if(krtn) { + kdcPkinitDebug("krb5_c_verify_checksum returned %d\n", (int)krtn); + goto cleanup; + } + if(!valid_cksum) { + kdcPkinitDebug("verify_pkinit_request: checksum error\n"); + krtn = KRB5KRB_AP_ERR_BAD_INTEGRITY; + goto cleanup; + } + + #if REQUIRE_CLIENT_CERT_MATCH + /* look up in the KDB to ensure correct client/cert binding */ + cert_hash = krb5_pkinit_cert_hash_str(&client_cert); + if(cert_hash == NULL) { + krtn = ENOMEM; + goto cleanup; + } + cert_hash_len = strlen(cert_hash); + for(key_dex=0; key_dex<client->n_key_data; key_dex++) { + krb5_key_data *key_data = &client->key_data[key_dex]; + kdcPkinitDebug("--- key %u type[0] %u length[0] %u type[1] %u length[1] %u\n", + key_dex, + key_data->key_data_type[0], key_data->key_data_length[0], + key_data->key_data_type[1], key_data->key_data_length[1]); + if(key_data->key_data_type[1] != KRB5_KDB_SALTTYPE_CERTHASH) { + continue; + } + + /* + * Unfortunately this key is stored encrypted even though it's + * not sensitive... + */ + krtn = krb5_dbekd_decrypt_key_data(context, &master_keyblock, + key_data, &decrypted_key, NULL); + if(krtn) { + kdcPkinitDebug("verify_pkinit_request: error decrypting cert hash block\n"); + break; + } + if((decrypted_key.contents != NULL) && + (cert_hash_len == decrypted_key.length) && + !memcmp(decrypted_key.contents, cert_hash, cert_hash_len)) { + cert_match = 1; + break; + } + } + if(decrypted_key.contents) { + krb5_free_keyblock_contents(context, &decrypted_key); + } + if(!cert_match) { + kdcPkinitDebug("verify_pkinit_request: client cert does not match\n"); + krtn = KDC_ERR_CLIENT_NOT_TRUSTED; + goto cleanup; + } + #endif /* REQUIRE_CLIENT_CERT_MATCH */ + krtn = 0; + setflag(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH); + +cleanup: + if(pa_cksum.contents) { + free(pa_cksum.contents); + } + if (tmp_as_req) { + krb5_free_kdc_req(context, tmp_as_req); + } + if (der_req) { + krb5_free_data(context, der_req); + } + if(cert_hash) { + free(cert_hash); + } + if(client_cert.data) { + free(client_cert.data); + } + kdcPkinitDebug("verify_pkinit_request: returning %d\n", (int)krtn); + return krtn; +} + +static krb5_error_code return_pkinit_response( + krb5_context context, + krb5_pa_data * padata, + krb5_db_entry *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + krb5_key_data *client_key, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc pkinit_get_entry_data, + void *pa_module_context, + void **pa_request_context) +{ + krb5_error_code krtn; + krb5_data pa_data; + krb5_pkinit_signing_cert_t signing_cert = NULL; + krb5_checksum as_req_checksum = {0}; + krb5_data *encoded_as_req = NULL; + krb5int_algorithm_id *cms_types = NULL; + krb5_ui_4 num_cms_types = 0; + + /* the data we get from the AS-REQ */ + krb5_ui_4 nonce = 0; + krb5_data client_cert = {0}; + + /* + * Trusted CA list and specific KC cert optionally obtained via + * krb5int_pkinit_as_req_parse(). All are DER-encoded + * issuerAndSerialNumbers. + */ + krb5_data *trusted_CAs = NULL; + krb5_ui_4 num_trusted_CAs; + krb5_data kdc_cert = {0}; + + if (padata == NULL) { + /* Client has to send us something */ + return 0; + } + + kdcPkinitDebug("return_pkinit_response\n"); + pa_data.data = (char *)padata->contents; + pa_data.length = padata->length; + + /* + * We've already verified; just obtain the fields we need to create a response + */ + krtn = krb5int_pkinit_as_req_parse(context, + &pa_data, + NULL, NULL, &nonce, /* ctime, cusec, nonce */ + NULL, NULL, /* pa_cksum, cert_status */ + &num_cms_types, &cms_types, + &client_cert, /* signer_cert: we encrypt for this */ + /* remaining fields unused (for now) */ + NULL, NULL, /* num_all_certs, all_certs */ + &num_trusted_CAs, &trusted_CAs, + &kdc_cert); + if(krtn) { + kdcPkinitDebug("pa_pk_as_req_parse returned %d; PKINIT aborting.\n", (int)krtn); + goto cleanup; + } + if(client_cert.data == NULL) { + kdcPkinitDebug("pa_pk_as_req_parse failed to give a client_cert; aborting.\n"); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + + if(krb5_pkinit_get_kdc_cert(num_trusted_CAs, trusted_CAs, + (kdc_cert.data ? &kdc_cert : NULL), + &signing_cert)) { + /* + * Since get_pkinit_edata was able to obtain *some* KDC cert, + * this means that we can't satisfy the client's requirement. + * FIXME - particular error status for this? + */ + kdcPkinitDebug("return_pkinit_response: NO appropriate signing cert!\n"); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto cleanup; + } + + /* + * Cook up keyblock for caller and for outgoing AS-REP. + * FIXME how much is known to be valid about encrypting_key? + * Will encrypting_key->enctype always be valid here? Seems that + * if we allow for clients without a shared secret (i.e. preauth + * by PKINIT only) there won't be a valid encrypting_key set up + * here for us. + */ + krb5_free_keyblock_contents(context, encrypting_key); + krb5_c_make_random_key(context, encrypting_key->enctype, encrypting_key); + + /* calculate checksum of incoming AS-REQ */ + krtn = encode_krb5_as_req(request, &encoded_as_req); + if(krtn) { + kdcPkinitDebug("encode_krb5_as_req returned %d; PKINIT aborting.\n", (int)krtn); + goto cleanup; + } + krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype, + encrypting_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + encoded_as_req, &as_req_checksum); + if(krtn) { + goto cleanup; + } + + /* + * FIXME: here we assume that the client has one cert - the one that + * signed the AuthPack in the request (and that we therefore obtained from + * krb5int_pkinit_as_req_parse()), and the one we're using to encrypt the + * ReplyKeyPack with here. This may need rethinking. + */ + krtn = krb5int_pkinit_as_rep_create(context, + encrypting_key, &as_req_checksum, signing_cert, TRUE, + &client_cert, + num_cms_types, cms_types, + num_trusted_CAs, trusted_CAs, + (kdc_cert.data ? &kdc_cert : NULL), + &pa_data); + if(krtn) { + kdcPkinitDebug("pa_pk_as_rep_create returned %d; PKINIT aborting.\n", (int)krtn); + goto cleanup; + } + + *send_pa = (krb5_pa_data *)malloc(sizeof(krb5_pa_data)); + if(*send_pa == NULL) { + krtn = ENOMEM; + free(pa_data.data); + goto cleanup; + } + (*send_pa)->magic = KV5M_PA_DATA; + (*send_pa)->pa_type = KRB5_PADATA_PK_AS_REP; + (*send_pa)->length = pa_data.length; + (*send_pa)->contents = (krb5_octet *)pa_data.data; + krtn = 0; + + #if PKINIT_DEBUG + fprintf(stderr, "return_pkinit_response: SUCCESS\n"); + fprintf(stderr, "nonce 0x%x enctype %d keydata %02x %02x %02x %02x...\n", + (int)nonce, (int)encrypting_key->enctype, + encrypting_key->contents[0], encrypting_key->contents[1], + encrypting_key->contents[2], encrypting_key->contents[3]); + #endif + +cleanup: + /* all of this was allocd by krb5int_pkinit_as_req_parse() */ + if(signing_cert) { + krb5_pkinit_release_cert(signing_cert); + } + if(cms_types) { + unsigned dex; + krb5int_algorithm_id *alg_id; + + for(dex=0; dex<num_cms_types; dex++) { + alg_id = &cms_types[dex]; + if(alg_id->algorithm.data) { + free(alg_id->algorithm.data); + } + if(alg_id->parameters.data) { + free(alg_id->parameters.data); + } + } + free(cms_types); + } + if(trusted_CAs) { + unsigned dex; + for(dex=0; dex<num_trusted_CAs; dex++) { + free(trusted_CAs[dex].data); + } + free(trusted_CAs); + } + if(kdc_cert.data) { + free(kdc_cert.data); + } + if(client_cert.data) { + free(client_cert.data); + } + if(encoded_as_req) { + krb5_free_data(context, encoded_as_req); + } + return krtn; +} + +#endif /* APPLE_PKINIT */ |