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 | |
parent | 8505824cad8ed0b6e8b96a5103cd43373c266996 (diff) | |
download | krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.zip krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.gz krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.bz2 |
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
-rw-r--r-- | src/include/kdb.h | 1 | ||||
-rw-r--r-- | src/include/krb5/krb5.hin | 9 | ||||
-rw-r--r-- | src/include/pkinit_apple_utils.h | 151 | ||||
-rw-r--r-- | src/include/pkinit_asn1.h | 172 | ||||
-rw-r--r-- | src/include/pkinit_cert_store.h | 154 | ||||
-rw-r--r-- | src/include/pkinit_client.h | 94 | ||||
-rw-r--r-- | src/include/pkinit_cms.h | 179 | ||||
-rw-r--r-- | src/kadmin/cli/kadmin.c | 95 | ||||
-rw-r--r-- | src/kdc/do_as_req.c | 19 | ||||
-rw-r--r-- | src/kdc/kdc_preauth.c | 502 | ||||
-rw-r--r-- | src/kdc/pkinit_apple_server.c | 241 | ||||
-rw-r--r-- | src/kdc/pkinit_server.h | 112 | ||||
-rw-r--r-- | src/lib/krb5/krb/get_in_tkt.c | 76 | ||||
-rw-r--r-- | src/lib/krb5/krb/pkinit_apple_asn1.c | 957 | ||||
-rw-r--r-- | src/lib/krb5/krb/pkinit_apple_cert_store.c | 599 | ||||
-rw-r--r-- | src/lib/krb5/krb/pkinit_apple_client.c | 263 | ||||
-rw-r--r-- | src/lib/krb5/krb/pkinit_apple_cms.c | 559 | ||||
-rw-r--r-- | src/lib/krb5/krb/pkinit_apple_utils.c | 433 | ||||
-rw-r--r-- | src/lib/krb5/krb/preauth2.c | 301 | ||||
-rw-r--r-- | src/lib/krb5/krb/str_conv.c | 5 |
20 files changed, 4912 insertions, 10 deletions
diff --git a/src/include/kdb.h b/src/include/kdb.h index e8a5878..66e8d06 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -63,6 +63,7 @@ #define KRB5_KDB_SALTTYPE_ONLYREALM 3 #define KRB5_KDB_SALTTYPE_SPECIAL 4 #define KRB5_KDB_SALTTYPE_AFS3 5 +#define KRB5_KDB_SALTTYPE_CERTHASH 6 /* Attributes */ #define KRB5_KDB_DISALLOW_POSTDATED 0x00000001 diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 279304e..4192362 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -368,6 +368,15 @@ typedef struct _krb5_enc_data { #define ENCTYPE_DES3_CBC_SHA 0x0005 /* DES-3 cbc mode with NIST-SHA */ #define ENCTYPE_DES3_CBC_RAW 0x0006 /* DES-3 cbc mode raw */ #define ENCTYPE_DES_HMAC_SHA1 0x0008 +/* PKINIT */ +#define ENCTYPE_DSA_SHA1_CMS 0x0009 /* DSA with SHA1, CMS signature */ +#define ENCTYPE_MD5_RSA_CMS 0x000a /* MD5 with RSA, CMS signature */ +#define ENCTYPE_SHA1_RSA_CMS 0x000b /* SHA1 with RSA, CMS signature */ +#define ENCTYPE_RC2_CBC_ENV 0x000c /* RC2 cbc mode, CMS enveloped data */ +#define ENCTYPE_RSA_ENV 0x000d /* RSA encryption, CMS enveloped data */ +#define ENCTYPE_RSA_ES_OAEP_ENV 0x000e /* RSA w/OEAP encryption, CMS enveloped data */ +#define ENCTYPE_DES3_CBC_ENV 0x000f /* DES-3 cbc mode, CMS enveloped data */ + #define ENCTYPE_DES3_CBC_SHA1 0x0010 #define ENCTYPE_AES128_CTS_HMAC_SHA1_96 0x0011 #define ENCTYPE_AES256_CTS_HMAC_SHA1_96 0x0012 diff --git a/src/include/pkinit_apple_utils.h b/src/include/pkinit_apple_utils.h new file mode 100644 index 0000000..313955f --- /dev/null +++ b/src/include/pkinit_apple_utils.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_utils.h - PKINIT utilities, Mac OS X version + * + * Created 19 May 2004 by Doug Mitchell. + */ + +#ifndef _PKINIT_APPLE_UTILS_H_ +#define _PKINIT_APPLE_UTILS_H_ + +#include <krb5/krb5.h> +#include <Security/SecAsn1Coder.h> +#include <Security/cssmapple.h> +#include <CoreFoundation/CoreFoundation.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PKI_DEBUG +#define PKI_DEBUG 0 +#endif + +#if PKI_DEBUG + +#include <stdio.h> + +#define pkiDebug(args...) printf(args) +#define pkiCssmErr(str, rtn) cssmPerror(str, rtn) +#else +#define pkiDebug(args...) +#define pkiCssmErr(str, rtn) +#endif /* PKI_DEBUG */ + +/* + * Macros used to initialize a declared CSSM_DATA and krb5_data to zero/NULL values. + */ +#define INIT_CDATA(cd) cd = {0, NULL} +#define INIT_KDATA(kd) kd = {0, 0, NULL} + +/* attach/detach to/from CL */ +CSSM_RETURN pkiClDetachUnload(CSSM_CL_HANDLE clHand); +CSSM_CL_HANDLE pkiClStartup(void); + +/* + * CSSM_DATA <--> krb5_ui_4 + */ +krb5_error_code pkiDataToInt( + const CSSM_DATA *cdata, + krb5_int32 *i); /* RETURNED */ + +krb5_error_code pkiIntToData( + krb5_int32 num, + CSSM_DATA *cdata, /* allocated in coder space and RETURNED */ + SecAsn1CoderRef coder); + +/* + * raw data --> krb5_data + */ +krb5_error_code pkiDataToKrb5Data( + const void *data, + unsigned dataLen, + krb5_data *kd); /* content mallocd and RETURNED */ + +/* + * CSSM_DATA <--> krb5_data + * + * CSSM_DATA data is managed by a SecAsn1CoderRef; krb5_data.data is mallocd. + */ +krb5_error_code pkiCssmDataToKrb5Data( + const CSSM_DATA *cd, + krb5_data *kd); /* content mallocd and RETURNED */ + + +krb5_error_code pkiKrb5DataToCssm( + const krb5_data *kd, + CSSM_DATA *cdata, /* allocated in coder space and RETURNED */ + SecAsn1CoderRef coder); + +/* + * CFDataRef --> krb5_data, mallocing the destination contents. + */ +krb5_error_code pkiCfDataToKrb5Data( + CFDataRef cfData, + krb5_data *kd); /* content mallocd and RETURNED */ + +/* + * Non-mallocing conversion between CSSM_DATA and krb5_data + */ +#define PKI_CSSM_TO_KRB_DATA(cd, kd) \ + (kd)->data = (char *)(cd)->Data; \ + (kd)->length = (cd)->Length; + +#define PKI_KRB_TO_CSSM_DATA(kd, cd) \ + (cd)->Data = (uint8 *)(kd)->data; \ + (cd)->Length = (kd)->length; + +/* + * Compare to CSSM_DATAs. Return TRUE if they're the same else FALSE. + */ +krb5_boolean pkiCompareCssmData( + const CSSM_DATA *d1, + const CSSM_DATA *d2); + +/* + * krb5_timestamp <--> a mallocd string in generalized format + */ +krb5_error_code pkiKrbTimestampToStr( + krb5_timestamp kts, + char **str); /* mallocd and RETURNED */ + +krb5_error_code pkiTimeStrToKrbTimestamp( + const char *str, + unsigned len, + krb5_timestamp *kts); /* RETURNED */ + +/* + * How many items in a NULL-terminated array of pointers? + */ +unsigned pkiNssArraySize( + const void **array); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_APPLE_UTILS_H_ */ diff --git a/src/include/pkinit_asn1.h b/src/include/pkinit_asn1.h new file mode 100644 index 0000000..b90ae59 --- /dev/null +++ b/src/include/pkinit_asn1.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_asn1.h - ASN.1 encode/decode routines for PKINIT + * + * Created 18 May 2004 by Doug Mitchell. + */ + +#ifndef _PKINIT_ASN1_H_ +#define _PKINIT_ASN1_H_ + +#include <krb5/krb5.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* RFC 3280 AlgorithmIdentifier */ +typedef struct { + krb5_data algorithm; /* OID */ + krb5_data parameters; /* ASN_ANY, defined by algorithm */ +} krb5int_algorithm_id; + +/* + * Encode and decode AuthPack, public key version (no Diffie-Hellman components). + */ +krb5_error_code krb5int_pkinit_auth_pack_encode( + krb5_timestamp kctime, + krb5_int32 cusec, /* microseconds */ + krb5_ui_4 nonce, + const krb5_checksum *pa_checksum, + const krb5int_algorithm_id *cms_types, /* optional */ + krb5_ui_4 num_cms_types, + krb5_data *auth_pack); /* mallocd and RETURNED */ + +/* all returned values are optional - pass NULL if you don't want them */ +krb5_error_code krb5int_pkinit_auth_pack_decode( + const krb5_data *auth_pack, /* DER encoded */ + krb5_timestamp *kctime, /* RETURNED */ + krb5_ui_4 *cusec, /* microseconds, RETURNED */ + krb5_ui_4 *nonce, /* RETURNED */ + krb5_checksum *pa_checksum, /* contents mallocd and RETURNED */ + krb5int_algorithm_id **cms_types, /* mallocd and RETURNED */ + krb5_ui_4 *num_cms_types); /* RETURNED */ + + +/* + * Given DER-encoded issuer and serial number, create an encoded + * IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_issuer_serial_encode( + const krb5_data *issuer, /* DER encoded */ + const krb5_data *serial_num, + krb5_data *issuer_and_serial); /* content mallocd and RETURNED */ + +/* + * Decode IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_issuer_serial_decode( + const krb5_data *issuer_and_serial, /* DER encoded */ + krb5_data *issuer, /* DER encoded, RETURNED */ + krb5_data *serial_num); /* RETURNED */ + +/* + * Top-level encode for PA-PK-AS-REQ. + * The signed_auth_pack field is wrapped in an OCTET STRING, content + * specific tag 0, during encode. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_req_encode( + const krb5_data *signed_auth_pack, /* DER encoded ContentInfo */ + const krb5_data *trusted_CAs, /* optional: trustedCertifiers. Contents are + * DER-encoded issuer/serialNumbers. */ + krb5_ui_4 num_trusted_CAs, + const krb5_data *kdc_cert, /* optional kdcPkId, DER encoded issuer/serial */ + krb5_data *pa_pk_as_req); /* mallocd and RETURNED */ + +/* + * Top-level decode for PA-PK-AS-REQ. Does not perform cert verification on the + * ContentInfo; that is returned in BER-encoded form and processed elsewhere. + * The OCTET STRING wrapping the signed_auth_pack field is removed during the + * decode. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_req_decode( + const krb5_data *pa_pk_as_req, + krb5_data *signed_auth_pack, /* DER encoded ContentInfo, RETURNED */ + /* + * Remainder are optionally RETURNED (specify NULL for pointers to + * items you're not interested in). + */ + krb5_ui_4 *num_trusted_CAs, /* sizeof trusted_CAs */ + krb5_data **trusted_CAs, /* mallocd array of DER-encoded TrustedCAs + * issuer/serial */ + krb5_data *kdc_cert); /* DER encoded issuer/serial */ + +/* + * Encode a ReplyKeyPack. The result is used as the Content of a SignedData. + */ +krb5_error_code krb5int_pkinit_reply_key_pack_encode( + const krb5_keyblock *key_block, + const krb5_checksum *checksum, + krb5_data *reply_key_pack); /* mallocd and RETURNED */ + +/* + * Decode a ReplyKeyPack. + */ +krb5_error_code krb5int_pkinit_reply_key_pack_decode( + const krb5_data *reply_key_pack, + krb5_keyblock *key_block, /* RETURNED */ + krb5_checksum *checksum); /* contents mallocd and RETURNED */ + +/* + * Encode a PA-PK-AS-REP. + * Exactly one of {dh_signed_data, enc_key_pack} is non-NULL on entry; + * each is a previously encoded item. + * + * dh_signed_data, if specified, is an encoded DHRepInfo. + * enc_key_pack, if specified, is EnvelopedData(signedData(ReplyKeyPack) + */ +krb5_error_code krb5int_pkinit_pa_pk_as_rep_encode( + const krb5_data *dh_signed_data, + const krb5_data *enc_key_pack, /* EnvelopedData(signedData(ReplyKeyPack) */ + krb5_data *pa_pk_as_rep); /* mallocd and RETURNED */ + +/* + * Decode a PA-PK-AS-REP. + * On successful return, exactly one of {dh_signed_data, enc_key_pack} + * will be non-NULL, each of which is mallocd and must be freed by + * caller. + * + * dh_signed_data, if returned, is an encoded DHRepInfo. + * enc_key_pack, if specified, is EnvelopedData(signedData(ReplyKeyPack) + */ +krb5_error_code krb5int_pkinit_pa_pk_as_rep_decode( + const krb5_data *pa_pk_as_rep, + krb5_data *dh_signed_data, + krb5_data *enc_key_pack); + +/* + * Given a DER encoded certificate, obtain the associated IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_get_issuer_serial( + const krb5_data *cert, + krb5_data *issuer_and_serial); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_ASN1_H_ */ diff --git a/src/include/pkinit_cert_store.h b/src/include/pkinit_cert_store.h new file mode 100644 index 0000000..1e848d7 --- /dev/null +++ b/src/include/pkinit_cert_store.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_cert_store.h - PKINIT certificate storage/retrieval utilities + * + * Created 26 May 2004 by Doug Mitchell at Apple. + */ + +#ifndef _PKINIT_CERT_STORE_H_ +#define _PKINIT_CERT_STORE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <krb5/krb5.h> + +/* + * Opaque reference to a machine-dependent representation of a certificate + * which is capable of signing. On Mac OS X this is actually a SecIdentityRef. + */ +typedef void *krb5_pkinit_signing_cert_t; + +/* + * Opaque reference to a database in which PKINIT-related certificates are stored. + */ +typedef void *krb5_pkinit_cert_db_t; + +/* + * Obtain signing cert for specified principal. On successful return, + * caller must eventually release the cert with krb5_pkinit_release_cert(). + * + * Returns KRB5_PRINC_NOMATCH if client cert not found. + */ +krb5_error_code krb5_pkinit_get_client_cert( + const char *principal, /* full principal string */ + krb5_pkinit_signing_cert_t *client_cert); /* RETURNED */ + +/* + * Determine if the specified client has a signing cert. Returns TRUE + * if so, else returns FALSE. + */ +krb5_boolean krb5_pkinit_have_client_cert( + const char *principal); /* full principal string */ + +/* + * Store the specified certificate (or, more likely, some platform-dependent + * reference to it) as the specified principal's signing cert. Passing + * in NULL for the client_cert has the effect of deleting the relevant entry + * in the cert storage. + */ +krb5_error_code krb5_pkinit_set_client_cert( + const char *principal, /* full principal string */ + krb5_pkinit_signing_cert_t client_cert); + +/* + * Obtain a reference to the client's cert database. Specify either principal + * name or client_cert as obtained from krb5_pkinit_get_client_cert(). + */ +krb5_error_code krb5_pkinit_get_client_cert_db( + const char *principal, /* optional, full principal string */ + krb5_pkinit_signing_cert_t client_cert, /* optional, from krb5_pkinit_get_client_cert() */ + krb5_pkinit_cert_db_t *client_cert_db); /* RETURNED */ + +/* + * Obtain the KDC signing cert, with optional CA and specific cert specifiers. + * CAs and cert specifiers are in the form of DER-encoded issuerAndSerialNumbers. + * + * The client_spec argument is typically provided by the client as kdcPkId. + * + * If trusted_CAs and client_spec are NULL, a platform-dependent preferred + * KDC signing cert is returned, if one exists. + * + * On successful return, caller must eventually release the cert with + * krb5_pkinit_release_cert(). Outside of an unusual test configuration this = + * + * Returns KRB5_PRINC_NOMATCH if KDC cert not found. + * + */ +krb5_error_code krb5_pkinit_get_kdc_cert( + krb5_ui_4 num_trusted_CAs, /* sizeof *trusted_CAs */ + krb5_data *trusted_CAs, /* optional */ + krb5_data *client_spec, /* optional */ + krb5_pkinit_signing_cert_t *kdc_cert); /* RETURNED */ + +/* + * Obtain a reference to the KDC's cert database. + */ +krb5_error_code krb5_pkinit_get_kdc_cert_db( + krb5_pkinit_cert_db_t *kdc_cert_db); /* RETURNED */ + +/* + * Release certificate references obtained via krb5_pkinit_get_client_cert() and + * krb5_pkinit_get_kdc_cert(). + */ +extern void krb5_pkinit_release_cert( + krb5_pkinit_signing_cert_t cert); + +/* + * Release database references obtained via krb5_pkinit_get_client_cert_db() and + * krb5_pkinit_get_kdc_cert_db(). + */ +extern void krb5_pkinit_release_cert_db( + krb5_pkinit_cert_db_t cert_db); + +/* + * Obtain a mallocd C-string representation of a certificate's SHA1 digest. + * Only error is a NULL return indicating memory failure. + * Caller must free the returned string. + */ +char *krb5_pkinit_cert_hash_str( + const krb5_data *cert); + +/* + * Obtain a client's optional list of trusted KDC CA certs (trustedCertifiers) + * and/or trusted KDC cert (kdcPkId) for a given client and server. + * All returned values are mallocd and must be freed by caller; the contents + * of the krb5_datas are DER-encoded certificates. + */ +krb5_error_code krb5_pkinit_get_server_certs( + const char *client_principal, + const char *server_principal, + krb5_data **trusted_CAs, /* RETURNED, though return value may be NULL */ + krb5_ui_4 *num_trusted_CAs, /* RETURNED */ + krb5_data *kdc_cert); /* RETURNED, though may be 0/NULL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_CERT_STORE_H_ */ diff --git a/src/include/pkinit_client.h b/src/include/pkinit_client.h new file mode 100644 index 0000000..31951ca --- /dev/null +++ b/src/include/pkinit_client.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_client.h - Client side routines for PKINIT + * + * Created 20 May 2004 by Doug Mitchell at Apple. + */ + +#ifndef _PKINIT_CLIENT_H_ +#define _PKINIT_CLIENT_H_ + +#include <krb5/krb5.h> +#include "pkinit_cms.h" +#include "pkinit_cert_store.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a PA-PK-AS-REQ message. + */ +krb5_error_code krb5int_pkinit_as_req_create( + krb5_context context, + krb5_timestamp kctime, + krb5_int32 cusec, /* microseconds */ + krb5_ui_4 nonce, + const krb5_checksum *cksum, + krb5_pkinit_signing_cert_t client_cert, /* required! */ + + /* + * trusted_CAs correponds to PA-PK-AS-REQ.trustedCertifiers. + * Expressed here as an optional list of DER-encoded certs. + */ + const krb5_data *trusted_CAs, + krb5_ui_4 num_trusted_CAs, + + /* optional PA-PK-AS-REQ.kdcPkId, expressed here as a + * DER-encoded cert */ + const krb5_data *kdc_cert, + krb5_data *as_req); /* mallocd and RETURNED */ + +/* + * Parse PA-PK-AS-REP message. Optionally evaluates the message's certificate chain. + * Optionally returns various components. + */ +krb5_error_code krb5int_pkinit_as_rep_parse( + krb5_context context, + const krb5_data *as_rep, + krb5_pkinit_signing_cert_t client_cert, /* required for decryption */ + krb5_keyblock *key_block, /* RETURNED */ + krb5_checksum *checksum, /* checksum of corresponding AS-REQ */ + /* contents mallocd and RETURNED */ + krb5int_cert_sig_status *cert_status, /* RETURNED */ + + /* + * Cert fields, all optionally RETURNED. + * + * signer_cert is the DER-encoded leaf cert from the incoming SignedData. + * all_certs is an array of all of the certs in the incoming SignedData, + * in full DER-encoded form. + */ + krb5_data *signer_cert, /* content mallocd */ + unsigned *num_all_certs, /* sizeof *all_certs */ + krb5_data **all_certs); /* krb5_data's and their content mallocd */ + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_CLIENT_H_ */ diff --git a/src/include/pkinit_cms.h b/src/include/pkinit_cms.h new file mode 100644 index 0000000..6e5fb96 --- /dev/null +++ b/src/include/pkinit_cms.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_cms.h - CMS encode/decode routines, Mac OS X version + * + * Created 19 May 2004 by Doug Mitchell at Apple. + */ + +#ifndef _PKINIT_CMS_H_ +#define _PKINIT_CMS_H_ + +#include <krb5/krb5.h> +#include "pkinit_cert_store.h" /* for krb5_pkinit_signing_cert_t */ +#include "pkinit_asn1.h" /* for krb5int_algorithm_id */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Define ContentType for a SignedData and EnvelopedData. + */ +enum { + /* normal CMS ContentTypes */ + ECT_Data, + ECT_SignedData, + ECT_EnvelopedData, + ECT_EncryptedData, + + /* + * For SignedAuthPack + * pkauthdata: { iso (1) org (3) dod (6) internet (1) + * security (5) kerberosv5 (2) pkinit (3) pkauthdata (1)} + */ + ECT_PkAuthData, + + /* + * For ReplyKeyPack + * pkrkeydata: { iso (1) org (3) dod (6) internet (1) + * security (5) kerberosv5 (2) pkinit (3) pkrkeydata (3) } + */ + ECT_PkReplyKeyKata, + + /* + * Other - i.e., unrecognized ContentType on decode. + */ + ECT_Other +}; +typedef krb5_int32 krb5int_cms_content_type; + +/* + * Result of certificate and signature verification. + */ +enum { + pki_cs_good = 0, + pki_not_signed, /* message not signed */ + pki_not_evaluated, /* signed, but not evaluated per caller request */ + /* remainder imply good signature on the message proper, i.e., these + * are all certificate errors. */ + pki_cs_sig_verify_fail, /* signature verification failed */ + pki_cs_bad_leaf, /* leaf/subject cert itself is plain bad */ + pki_cs_no_root, /* looks good but not verifiable to any root */ + pki_cs_unknown_root, /* verified to root we don't recognize */ + pki_cs_expired, /* expired */ + pki_cs_not_valid_yet, /* cert not valid yet */ + pki_cs_revoked, /* revoked via CRL or OCSP */ + pki_cs_untrusted, /* marked by user as untrusted */ + pki_bad_cms, /* CMS Format precluded verification */ + pki_bad_key_use, /* Bad ExtendedKeyUse or KeyUsage extension */ + pki_bad_digest, /* unacceptable CMS digest algorithm */ + pki_cs_other_err /* other cert verify error */ +}; +typedef krb5_int32 krb5int_cert_sig_status; + +/* + * Create a CMS message: either encrypted (EnvelopedData), signed + * (SignedData), or both (EnvelopedData(SignedData(content)). + * + * The message is signed iff signing_cert is non-NULL. + * The message is encrypted iff recip_cert is non-NULL. + * + * The content_type argument specifies to the eContentType + * for a SignedData's EncapsulatedContentInfo; it's ignored + * if the message is not to be signed. + * + * The cms_types argument optionally specifies a list, in order + * of decreasing preference, of CMS algorithms to use in the + * creation of the CMS message. + */ +krb5_error_code krb5int_pkinit_create_cms_msg( + const krb5_data *content, /* Content */ + krb5_pkinit_signing_cert_t signing_cert, /* optional: signed by this cert */ + const krb5_data *recip_cert, /* optional: encrypted with this cert */ + krb5int_cms_content_type content_type, /* OID for EncapsulatedData */ + krb5_ui_4 num_cms_types, /* optional */ + const krb5int_algorithm_id *cms_types, /* optional */ + krb5_data *content_info); /* contents mallocd and RETURNED */ + +/* + * Parse a ContentInfo as best we can. All returned fields are optional - + * pass NULL for values you don't need. + * + * If signer_cert_status is NULL on entry, NO signature or cert evaluation + * will be performed. + * + * The is_client_msg argument indicates whether the CMS message originated + * from the client (TRUE) or server (FALSE) and may be used in platform- + * dependent certificate evaluation. + * + * Note that signature and certificate verification errors do NOT cause + * this routine itself to return an error; caller is reponsible for + * handling such errors per the signer_cert_status out parameter. + */ +krb5_error_code krb5int_pkinit_parse_cms_msg( + const krb5_data *content_info, + krb5_pkinit_cert_db_t cert_db, /* may be required for SignedData */ + krb5_boolean is_client_msg, /* TRUE : msg is from client */ + krb5_boolean *is_signed, /* RETURNED */ + krb5_boolean *is_encrypted, /* RETURNED */ + krb5_data *raw_data, /* RETURNED */ + krb5int_cms_content_type *inner_content_type,/* Returned, ContentType of */ + /* EncapsulatedData if */ + /* *is_signed true */ + /* returned for type SignedData only */ + krb5_data *signer_cert, /* RETURNED */ + krb5int_cert_sig_status *signer_cert_status,/* RETURNED */ + unsigned *num_all_certs, /* size of *all_certs RETURNED */ + krb5_data **all_certs); /* entire cert chain RETURNED */ + +/* + * An AuthPack contains an optional set of AlgorithmIdentifiers + * which define the CMS algorithms supported by the client, in + * order of decreasing preference. + * + * krb5int_pkinit_get_cms_types() is a CMS-implementation-dependent + * function returning supported CMS algorithms in the form of a + * pointer and a length suitable for passing to + * krb5int_pkinit_auth_pack_encode. If no preference is to be expressed, + * this function returns NULL/0 (without returning a nonzero krb5_error_code). + * + * krb5int_pkinit_free_cms_types() frees the pointer obtained + * from krb5int_pkinit_get_cms_types() as necessary. + */ +krb5_error_code krb5int_pkinit_get_cms_types( + krb5int_algorithm_id **supported_cms_types, /* RETURNED */ + krb5_ui_4 *num_supported_cms_types); /* RETURNED */ + +krb5_error_code krb5int_pkinit_free_cms_types( + krb5int_algorithm_id *supported_cms_types, + krb5_ui_4 num_supported_cms_types); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_CMS_H_ */ diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c index 76f7c3e..98ff995 100644 --- a/src/kadmin/cli/kadmin.c +++ b/src/kadmin/cli/kadmin.c @@ -880,7 +880,11 @@ kadmin_free_tl_data(kadm5_principal_ent_t princ) #define KRB5_TL_DB_ARGS 0x7fff static int kadmin_parse_princ_args(argc, argv, oprinc, mask, pass, randkey, - ks_tuple, n_ks_tuple, caller) + ks_tuple, n_ks_tuple, +#if APPLE_PKINIT + cert_hash, +#endif /* APPLE_PKINIT */ + caller) int argc; char *argv[]; kadm5_principal_ent_t oprinc; @@ -889,6 +893,9 @@ kadmin_parse_princ_args(argc, argv, oprinc, mask, pass, randkey, int *randkey; krb5_key_salt_tuple **ks_tuple; int *n_ks_tuple; +#if APPLE_PKINIT + char **cert_hash; +#endif /* APPLE_PKINIT */ char *caller; { int i, j, attrib_set; @@ -901,6 +908,9 @@ kadmin_parse_princ_args(argc, argv, oprinc, mask, pass, randkey, *pass = NULL; *n_ks_tuple = 0; *ks_tuple = NULL; +#if APPLE_PKINIT + *cert_hash = NULL; +#endif /* APPLE_PKINIT */ time(&now); *randkey = 0; for (i = 1; i < argc - 1; i++) { @@ -1040,6 +1050,17 @@ kadmin_parse_princ_args(argc, argv, oprinc, mask, pass, randkey, ++*randkey; continue; } +#if APPLE_PKINIT + if (strlen(argv[i]) == 9 && + !strcmp("-certhash", argv[i])) { + if (++i > argc - 2) + return -1; + else { + *cert_hash = argv[i]; + continue; + } + } +#endif /* APPLE_PKINIT */ if (!strcmp("-e", argv[i])) { if (++i > argc - 2) return -1; @@ -1095,7 +1116,11 @@ kadmin_addprinc_usage(func) { fprintf(stderr, "usage: %s [options] principal\n", func); fprintf(stderr, "\toptions are:\n"); - fprintf(stderr, "\t\t[-x db_princ_args]* [-expire expdate] [-pwexpire pwexpdate] [-maxlife maxtixlife]\n\t\t[-kvno kvno] [-policy policy] [-clearpolicy] [-randkey]\n\t\t[-pw password] [-maxrenewlife maxrenewlife]\n\t\t[-e keysaltlist]\n\t\t[{+|-}attribute]\n"); + fprintf(stderr, "\t\t[-x db_princ_args]* [-expire expdate] [-pwexpire pwexpdate] [-maxlife maxtixlife]\n\t\t[-kvno kvno] [-policy policy] [-clearpolicy] [-randkey]\n\t\t[-pw password] [-maxrenewlife maxrenewlife]\n\t\t[-e keysaltlist]\n\t\t[{+|-}attribute]\n" +#if APPLE_PKINIT + "\t\t[-certhash hash_string]\n" +#endif /* APPLE_PKINIT */ + ); fprintf(stderr, "\tattributes are:\n"); fprintf(stderr, "%s%s%s", "\t\tallow_postdated allow_forwardable allow_tgs_req allow_renewable\n", @@ -1136,6 +1161,9 @@ void kadmin_addprinc(argc, argv) krb5_error_code retval; static char newpw[1024], dummybuf[256]; static char prompt1[1024], prompt2[1024]; +#if APPLE_PKINIT + char *cert_hash = NULL; +#endif /* APPLE_PKINIT */ if (dummybuf[0] == 0) { for (i = 0; i < 256; i++) @@ -1149,12 +1177,23 @@ void kadmin_addprinc(argc, argv) if (kadmin_parse_princ_args(argc, argv, &princ, &mask, &pass, &randkey, &ks_tuple, &n_ks_tuple, +#if APPLE_PKINIT + &cert_hash, +#endif /* APPLE_PKINIT */ "add_principal")) { kadmin_addprinc_usage("add_principal"); kadmin_free_tl_data(&princ); /* need to free ks_tuple also??? */ return; } +#if APPLE_PKINIT + if(cert_hash != NULL) { + fprintf(stderr, + "add_principal: -certhash not allowed; use modify_principal\n"); + return; + } +#endif /* APPLE_PKINIT */ + retval = krb5_unparse_name(context, princ.principal, &canon); if (retval) { com_err("add_principal", @@ -1284,6 +1323,9 @@ void kadmin_modprinc(argc, argv) int randkey = 0; int n_ks_tuple = 0; krb5_key_salt_tuple *ks_tuple; +#if APPLE_PKINIT + char *cert_hash = NULL; +#endif /* APPLE_PKINIT */ if (argc < 2) { kadmin_modprinc_usage("modify_principal"); @@ -1307,10 +1349,10 @@ void kadmin_modprinc(argc, argv) } retval = kadm5_get_principal(handle, kprinc, &oldprinc, KADM5_PRINCIPAL_NORMAL_MASK); - krb5_free_principal(context, kprinc); if (retval) { com_err("modify_principal", retval, "while getting \"%s\".", canon); + krb5_free_principal(context, kprinc); free(canon); return; } @@ -1320,24 +1362,30 @@ void kadmin_modprinc(argc, argv) &princ, &mask, &pass, &randkey, &ks_tuple, &n_ks_tuple, +#if APPLE_PKINIT + &cert_hash, +#endif /* APPLE_PKINIT */ "modify_principal"); if (ks_tuple != NULL) { free(ks_tuple); kadmin_modprinc_usage("modify_principal"); free(canon); - kadmin_free_tl_data(&princ); + krb5_free_principal(context, kprinc); + kadmin_free_tl_data(&princ); /* Apple had this commented out. Why? */ return; } if (retval) { kadmin_modprinc_usage("modify_principal"); free(canon); - kadmin_free_tl_data(&princ); + krb5_free_principal(context, kprinc); + kadmin_free_tl_data(&princ); /* Apple had this commented out. Why? */ return; } if (randkey) { fprintf(stderr, "modify_principal: -randkey not allowed\n"); krb5_free_principal(context, princ.principal); free(canon); + krb5_free_principal(context, kprinc); kadmin_free_tl_data(&princ); return; } @@ -1346,10 +1394,45 @@ void kadmin_modprinc(argc, argv) "modify_principal: -pw not allowed; use change_password\n"); krb5_free_principal(context, princ.principal); free(canon); + krb5_free_principal(context, kprinc); kadmin_free_tl_data(&princ); return; } - retval = kadm5_modify_principal(handle, &princ, mask); +#if APPLE_PKINIT + if (cert_hash) { + /* + * Use something other than the 1st preferred enctype here for fallback + * to pwd authentication + */ + krb5_key_salt_tuple key_salt = {ENCTYPE_ARCFOUR_HMAC, KRB5_KDB_SALTTYPE_CERTHASH}; + krb5_keyblock keyblock; + kadm5_ret_t kadmin_rtn; + + keyblock.magic = KV5M_KEYBLOCK; + keyblock.enctype = ENCTYPE_ARCFOUR_HMAC; + keyblock.length = strlen(cert_hash); + keyblock.contents = (krb5_octet *)cert_hash; + kadmin_rtn = kadm5_setkey_principal_3(handle, kprinc, + TRUE, /* keepold - we're appending */ + 1, &key_salt, + &keyblock, 1); + if (kadmin_rtn) { + com_err("modify_principal", kadmin_rtn, + "while adding certhash for \"%s\".", canon); + printf("realm %s data %s\n", (char *)kprinc->realm.data, (char *)kprinc->data->data); + free(canon); + krb5_free_principal(context, princ.principal); + krb5_free_principal(context, kprinc); + return; + } + retval = 0; + } +#endif /* APPLE_PKINIT */ + if (mask) { + /* skip this if all we're doing is setting certhash */ + retval = kadm5_modify_principal(handle, &princ, mask); + } + krb5_free_principal(context, kprinc); krb5_free_principal(context, princ.principal); if (retval) { com_err("modify_principal", retval, diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c index 4c2a09b..5dbf7ac 100644 --- a/src/kdc/do_as_req.c +++ b/src/kdc/do_as_req.c @@ -45,6 +45,15 @@ #include "adm_proto.h" #include "extern.h" +#if APPLE_PKINIT +#define AS_REQ_DEBUG 0 +#if AS_REQ_DEBUG +#define asReqDebug(args...) printf(args) +#else +#define asReqDebug(args...) +#endif +#endif /* APPLE_PKINIT */ + static krb5_error_code prepare_error_as (krb5_kdc_req *, int, krb5_data *, krb5_data **, const char *); @@ -80,6 +89,11 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, char fromstringbuf[70]; void *pa_context = NULL; +#if APPLE_PKINIT + asReqDebug("process_as_req top realm %s name %s\n", + request->client->realm.data, request->client->data->data); +#endif /* APPLE_PKINIT */ + ticket_reply.enc_part.ciphertext.data = 0; e_data.data = 0; encrypting_key.contents = 0; @@ -391,6 +405,11 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, goto errout; } +#if APPLE_PKINIT + asReqDebug("process_as_req reply realm %s name %s\n", + reply.client->realm.data, reply.client->data->data); +#endif /* APPLE_PKINIT */ + /* now encode/encrypt the response */ reply.enc_part.enctype = encrypting_key.enctype; diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index 67764b2..a250cf4 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 */ diff --git a/src/kdc/pkinit_apple_server.c b/src/kdc/pkinit_apple_server.c new file mode 100644 index 0000000..b86c634 --- /dev/null +++ b/src/kdc/pkinit_apple_server.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_server.c - Server side routines for PKINIT, Mac OS X version + * + * Created 21 May 2004 by Doug Mitchell at Apple. + */ + +#if APPLE_PKINIT + +#include "pkinit_server.h" +#include "pkinit_asn1.h" +#include "pkinit_cms.h" +#include <assert.h> + +#define PKINIT_DEBUG 0 +#if PKINIT_DEBUG +#define pkiDebug(args...) printf(args) +#else +#define pkiDebug(args...) +#endif + +/* + * Parse PA-PK-AS-REQ message. Optionally evaluates the message's certificate chain. + * Optionally returns various components. + */ +krb5_error_code krb5int_pkinit_as_req_parse( + krb5_context context, + const krb5_data *as_req, + krb5_timestamp *kctime, /* optionally RETURNED */ + krb5_ui_4 *cusec, /* microseconds, optionally RETURNED */ + krb5_ui_4 *nonce, /* optionally RETURNED */ + krb5_checksum *pa_cksum, /* optional, contents mallocd and RETURNED */ + krb5int_cert_sig_status *cert_status,/* optionally RETURNED */ + krb5_ui_4 *num_cms_types, /* optionally RETURNED */ + krb5int_algorithm_id **cms_types, /* optionally mallocd and RETURNED */ + + /* + * Cert fields, all optionally RETURNED. + * + * signer_cert is the full X.509 leaf cert from the incoming SignedData. + * all_certs is an array of all of the certs in the incoming SignedData, + * in full X.509 form. + */ + krb5_data *signer_cert, /* content mallocd */ + krb5_ui_4 *num_all_certs, /* sizeof *all_certs */ + krb5_data **all_certs, /* krb5_data's and their content mallocd */ + + /* + * Array of trustedCertifiers, optionally RETURNED. These are DER-encoded + * issuer/serial numbers. + */ + krb5_ui_4 *num_trusted_CAs, /* sizeof *trusted_CAs */ + krb5_data **trusted_CAs, /* krb5_data's and their content mallocd */ + + /* KDC cert specified by client as kdcPkId. DER-encoded issuer/serial number. */ + krb5_data *kdc_cert) +{ + krb5_error_code krtn; + krb5_data signed_auth_pack = {0, 0, NULL}; + krb5_data raw_auth_pack = {0, 0, NULL}; + krb5_data *raw_auth_pack_p = NULL; + krb5_boolean proceed = FALSE; + krb5_boolean need_auth_pack = FALSE; + krb5int_cms_content_type content_type; + krb5_pkinit_cert_db_t cert_db = NULL; + krb5_boolean is_signed; + krb5_boolean is_encrypted; + + assert(as_req != NULL); + + /* + * We always have to decode the top-level AS-REQ... + */ + krtn = krb5int_pkinit_pa_pk_as_req_decode(as_req, &signed_auth_pack, + num_trusted_CAs, trusted_CAs, /* optional */ + kdc_cert); /* optional */ + if (krtn) { + pkiDebug("krb5int_pkinit_pa_pk_as_req_decode returned %d\n", (int)krtn); + return krtn; + } + + /* Do we need info about or from the ContentInto or AuthPack? */ + if ((kctime != NULL) || (cusec != NULL) || (nonce != NULL) || + (pa_cksum != NULL) || (cms_types != NULL)) { + need_auth_pack = TRUE; + raw_auth_pack_p = &raw_auth_pack; + } + if (need_auth_pack || (cert_status != NULL) || + (signer_cert != NULL) || (all_certs != NULL)) { + proceed = TRUE; + } + if (!proceed) { + krtn = 0; + goto err_out; + } + + /* Parse and possibly verify the ContentInfo */ + krtn = krb5_pkinit_get_kdc_cert_db(&cert_db); + if (krtn) { + pkiDebug("pa_pk_as_req_parse: error in krb5_pkinit_get_kdc_cert_db\n"); + goto err_out; + } + krtn = krb5int_pkinit_parse_cms_msg(&signed_auth_pack, cert_db, TRUE, + &is_signed, &is_encrypted, + raw_auth_pack_p, &content_type, signer_cert, cert_status, + num_all_certs, all_certs); + if (krtn) { + pkiDebug("krb5int_pkinit_parse_content_info returned %d\n", (int)krtn); + goto err_out; + } + + if (is_encrypted || !is_signed) { + pkiDebug("pkinit_parse_content_info: is_encrypted %s is_signed %s!\n", + is_encrypted ? "true" :"false", + is_signed ? "true" : "false"); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto err_out; + } + if (content_type != ECT_PkAuthData) { + pkiDebug("authPack eContentType %d!\n", (int)content_type); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto err_out; + } + + /* optionally parse contents of authPack */ + if (need_auth_pack) { + krtn = krb5int_pkinit_auth_pack_decode(&raw_auth_pack, kctime, + cusec, nonce, pa_cksum, + cms_types, num_cms_types); + if(krtn) { + pkiDebug("krb5int_pkinit_auth_pack_decode returned %d\n", (int)krtn); + goto err_out; + } + } + +err_out: + /* free temp mallocd data that we didn't pass back to caller */ + if(signed_auth_pack.data) { + free(signed_auth_pack.data); + } + if(raw_auth_pack.data) { + free(raw_auth_pack.data); + } + if(cert_db) { + krb5_pkinit_release_cert_db(cert_db); + } + return krtn; +} + +/* + * Create a PA-PK-AS-REP message, public key (no Diffie Hellman) version. + * + * PA-PK-AS-REP is based on ReplyKeyPack like so: + * + * PA-PK-AS-REP ::= EnvelopedData(SignedData(ReplyKeyPack)) + */ +krb5_error_code krb5int_pkinit_as_rep_create( + krb5_context context, + const krb5_keyblock *key_block, + const krb5_checksum *checksum, /* checksum of corresponding AS-REQ */ + krb5_pkinit_signing_cert_t signer_cert, /* server's cert */ + krb5_boolean include_server_cert,/* include signer_cert in SignerInfo */ + const krb5_data *recipient_cert, /* client's cert */ + + /* + * These correspond to the same out-parameters from + * krb5int_pkinit_as_req_parse(). All are optional. + */ + krb5_ui_4 num_cms_types, + const krb5int_algorithm_id *cms_types, + krb5_ui_4 num_trusted_CAs, + krb5_data *trusted_CAs, + krb5_data *kdc_cert, + + krb5_data *as_rep) /* mallocd and RETURNED */ +{ + krb5_data reply_key_pack = {0, 0, NULL}; + krb5_error_code krtn; + krb5_data enc_key_pack = {0, 0, NULL}; + + /* innermost content = ReplyKeyPack */ + krtn = krb5int_pkinit_reply_key_pack_encode(key_block, checksum, + &reply_key_pack); + if (krtn) { + return krtn; + } + + /* + * Put that in an EnvelopedData(SignedData) + * -- SignedData.EncapsulatedData.ContentType = id-pkinit-rkeyData + */ + krtn = krb5int_pkinit_create_cms_msg(&reply_key_pack, + signer_cert, + recipient_cert, + ECT_PkReplyKeyKata, + num_cms_types, cms_types, + &enc_key_pack); + if (krtn) { + goto err_out; + } + + /* + * Finally, wrap that inside of PA-PK-AS-REP + */ + krtn = krb5int_pkinit_pa_pk_as_rep_encode(NULL, &enc_key_pack, as_rep); + +err_out: + if (reply_key_pack.data) { + free(reply_key_pack.data); + } + if (enc_key_pack.data) { + free(enc_key_pack.data); + } + return krtn; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/kdc/pkinit_server.h b/src/kdc/pkinit_server.h new file mode 100644 index 0000000..773b497 --- /dev/null +++ b/src/kdc/pkinit_server.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_server.h - Server side routines for PKINIT + * + * Created 21 May 2004 by Doug Mitchell at Apple. + */ + +#ifndef _PKINIT_SERVER_H_ +#define _PKINIT_SERVER_H_ + +#include "krb5.h" +#include "pkinit_cms.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * Parse PA-PK-AS-REQ message. Optionally evaluates the message's certificate chain + * if cert_status is non-NULL. Optionally returns various components. + */ +krb5_error_code krb5int_pkinit_as_req_parse( + krb5_context context, + const krb5_data *as_req, + krb5_timestamp *kctime, /* optionally RETURNED */ + krb5_ui_4 *cusec, /* microseconds, optionally RETURNED */ + krb5_ui_4 *nonce, /* optionally RETURNED */ + krb5_checksum *pa_cksum, /* optional, contents mallocd and RETURNED */ + krb5int_cert_sig_status *cert_status, /* optionally RETURNED */ + krb5_ui_4 *num_cms_types, /* optionally RETURNED */ + krb5int_algorithm_id **cms_types, /* optionally mallocd and RETURNED */ + + /* + * Cert fields, all optionally RETURNED. + * + * signer_cert is the full X.509 leaf cert from the incoming SignedData. + * all_certs is an array of all of the certs in the incoming SignedData, + * in full X.509 form. + */ + krb5_data *signer_cert, /* content mallocd */ + krb5_ui_4 *num_all_certs, /* sizeof *all_certs */ + krb5_data **all_certs, /* krb5_data's and their content mallocd */ + + /* + * Array of trustedCertifiers, optionally RETURNED. These are DER-encoded + * issuer/serial numbers. + */ + krb5_ui_4 *num_trusted_CAs, /* sizeof *trustedCAs */ + krb5_data **trusted_CAs, /* krb5_data's and their content mallocd */ + + /* KDC cert specified by client as kdcPkId. DER-encoded issuer/serial number. */ + krb5_data *kdc_cert); + + +/* + * Create a PA-PK-AS-REP message, public key (no Diffie Hellman) version. + * + * PA-PK-AS-REP is based on ReplyKeyPack like so: + * + * PA-PK-AS-REP ::= EnvelopedData(SignedData(ReplyKeyPack)) + */ +krb5_error_code krb5int_pkinit_as_rep_create( + krb5_context context, + const krb5_keyblock *key_block, + const krb5_checksum *checksum, /* checksum of corresponding AS-REQ */ + krb5_pkinit_signing_cert_t signer_cert, /* server's cert */ + krb5_boolean include_server_cert, /* include signer_cert in SignerInfo */ + const krb5_data *recipient_cert, /* client's cert */ + + /* + * These correspond to the same out-parameters from + * krb5int_pkinit_as_req_parse(). All are optional. + */ + krb5_ui_4 num_cms_types, + const krb5int_algorithm_id *cms_types, + krb5_ui_4 num_trusted_CAs, + krb5_data *trusted_CAs, + krb5_data *kdc_cert, + + /* result here, mallocd and RETURNED */ + krb5_data *as_rep); + +#ifdef __cplusplus +} +#endif + +#endif /* _PKINIT_SERVER_H_ */ diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index a4e2b01..5edbc26 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -33,6 +33,15 @@ #include "int-proto.h" #include "os-proto.h" +#if APPLE_PKINIT +#define IN_TKT_DEBUG 0 +#if IN_TKT_DEBUG +#define inTktDebug(args...) printf(args) +#else +#define inTktDebug(args...) +#endif +#endif /* APPLE_PKINIT */ + /* All-purpose initial ticket routine, usually called via krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey. @@ -99,6 +108,26 @@ static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y) return x + y; } +#if APPLE_PKINIT +/* + * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this + * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this + * value is used interchangeably with an explicit now_time throughout this module... + */ +static krb5_error_code +gen_nonce(krb5_context context, + krb5_int32 *nonce) +{ + krb5_int32 time_now; + krb5_error_code retval = krb5_timeofday(context, &time_now); + if(retval) { + return retval; + } + *nonce = time_now; + return 0; +} +#endif /* APPLE_PKINIT */ + /* * This function sends a request to the KDC, and gets back a response; * the response is parsed into ret_err_reply or ret_as_reply if the @@ -138,6 +167,10 @@ send_again: retval = krb5_sendto_kdc(context, packet, krb5_princ_realm(context, request->client), &reply, use_master, tcp_only); +#if APPLE_PKINIT + inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval); +#endif /* APPLE_PKINIT */ + if (retval) goto cleanup; @@ -285,8 +318,20 @@ verify_as_reply(krb5_context context, (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) && (request->till != 0) && (as_reply->enc_part2->times.renew_till > request->till)) - ) + ) { +#if APPLE_PKINIT + inTktDebug("verify_as_reply: KDCREP_MODIFIED\n"); + #if IN_TKT_DEBUG + if(request->client->realm.length && request->client->data->length) + inTktDebug("request: name %s realm %s\n", + request->client->realm.data, request->client->data->data); + if(as_reply->client->realm.length && as_reply->client->data->length) + inTktDebug("reply : name %s realm %s\n", + as_reply->client->realm.data, as_reply->client->data->data); + #endif +#endif /* APPLE_PKINIT */ return KRB5_KDCREP_MODIFIED; + } if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) { retval = krb5_set_real_time(context, @@ -464,6 +509,10 @@ krb5_get_in_tkt(krb5_context context, krb5_int32 do_more = 0; int use_master = 0; +#if APPLE_PKINIT + inTktDebug("krb5_get_in_tkt top\n"); +#endif /* APPLE_PKINIT */ + if (! krb5_realm_compare(context, creds->client, creds->server)) return KRB5_IN_TKT_REALM_MISMATCH; @@ -490,6 +539,13 @@ krb5_get_in_tkt(krb5_context context, request.from = creds->times.starttime; request.till = creds->times.endtime; request.rtime = creds->times.renew_till; +#if APPLE_PKINIT + retval = gen_nonce(context, (krb5_int32 *)&time_now); + if(retval) { + goto cleanup; + } + request.nonce = time_now; +#endif /* APPLE_PKINIT */ request.ktype = malloc (sizeof(get_in_tkt_enctypes)); if (request.ktype == NULL) { @@ -545,6 +601,9 @@ krb5_get_in_tkt(krb5_context context, goto cleanup; } +#if APPLE_PKINIT + inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n"); +#endif /* APPLE_PKINIT */ if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc, keyseed, creds, &request)) != 0) goto cleanup; @@ -884,7 +943,10 @@ krb5_get_init_creds(krb5_context context, salt.data = NULL; local_as_reply = 0; - +#if APPLE_PKINIT + inTktDebug("krb5_get_init_creds top\n"); +#endif /* APPLE_PKINIT */ + err_reply = NULL; /* @@ -1210,6 +1272,10 @@ krb5_get_init_creds(krb5_context context, } } +#if APPLE_PKINIT + inTktDebug("krb5_get_init_creds done with send_as_request loop lc %d\n", + (int)loopcount); +#endif /* APPLE_PKINIT */ if (loopcount == MAX_IN_TKT_LOOPS) { ret = KRB5_GET_IN_TKT_LOOP; goto cleanup; @@ -1227,8 +1293,12 @@ krb5_get_init_creds(krb5_context context, local_as_reply->padata, &kdc_padata, &salt, &s2kparams, &etype, &as_key, prompter, prompter_data, gak_fct, gak_data, - &get_data_rock, options))) + &get_data_rock, options))) { +#if APPLE_PKINIT + inTktDebug("krb5_get_init_creds krb5_do_preauth returned %d\n", (int)ret); +#endif /* APPLE_PKINIT */ goto cleanup; + } /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY, the AS_REP comes back encrypted in the user's longterm key diff --git a/src/lib/krb5/krb/pkinit_apple_asn1.c b/src/lib/krb5/krb/pkinit_apple_asn1.c new file mode 100644 index 0000000..52ae1b0 --- /dev/null +++ b/src/lib/krb5/krb/pkinit_apple_asn1.c @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_asn1.c - ASN.1 encode/decode routines for PKINIT, Mac OS X version + * + * Created 19 May 2004 by Doug Mitchell. + */ + +#if APPLE_PKINIT + +#include "k5-int.h" +#include "pkinit_asn1.h" +#include "pkinit_apple_utils.h" +#include <stddef.h> +#include <Security/SecAsn1Types.h> +#include <Security/SecAsn1Templates.h> +#include <Security/SecAsn1Coder.h> +#include <Security/Security.h> +#include <sys/errno.h> +#include <assert.h> +#include <strings.h> + +#pragma mark ----- utility routines ----- + +/* malloc a NULL-ed array of pointers of size num+1 */ +static void **pkiNssNullArray( + uint32 num, + SecAsn1CoderRef coder) +{ + unsigned len = (num + 1) * sizeof(void *); + void **p = (void **)SecAsn1Malloc(coder, len); + memset(p, 0, len); + return p; +} + +#pragma mark ====== begin PA-PK-AS-REQ components ====== + +#pragma mark ----- pkAuthenticator ----- + +/* + * There is a unique error code for "missing paChecksum", so we mark it here + * as optional so the decoder can process a pkAuthenticator without the + * checksum; caller must verify that paChecksum.Data != NULL. + */ +typedef struct { + CSSM_DATA cusec; /* INTEGER, microseconds */ + CSSM_DATA kctime; /* UTC time (with trailing 'Z') */ + CSSM_DATA nonce; /* INTEGER */ + CSSM_DATA paChecksum; /* OCTET STRING */ +} KRB5_PKAuthenticator; + +static const SecAsn1Template KRB5_PKAuthenticatorTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PKAuthenticator) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_PKAuthenticator,cusec), + kSecAsn1IntegerTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_PKAuthenticator,kctime), + kSecAsn1GeneralizedTimeTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 2, + offsetof(KRB5_PKAuthenticator,nonce), + kSecAsn1IntegerTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | + SEC_ASN1_OPTIONAL | 3, + offsetof(KRB5_PKAuthenticator,paChecksum), + &kSecAsn1OctetStringTemplate }, + { 0 } +}; + +#pragma mark ----- AuthPack ----- + +typedef struct { + KRB5_PKAuthenticator pkAuth; + CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *pubKeyInfo; /* OPTIONAL */ + CSSM_X509_ALGORITHM_IDENTIFIER **supportedCMSTypes;/* OPTIONAL */ + CSSM_DATA *clientDHNonce; /* OPTIONAL */ +} KRB5_AuthPack; + +/* + * These are copied from keyTemplates.c in the libsecurity_asn1 project; + * they aren't public API. + */ + +/* AlgorithmIdentifier : CSSM_X509_ALGORITHM_IDENTIFIER */ +static const SecAsn1Template AlgorithmIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CSSM_X509_ALGORITHM_IDENTIFIER) }, + { SEC_ASN1_OBJECT_ID, + offsetof(CSSM_X509_ALGORITHM_IDENTIFIER,algorithm), }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(CSSM_X509_ALGORITHM_IDENTIFIER,parameters), }, + { 0, } +}; + + +/* SubjectPublicKeyInfo : CSSM_X509_SUBJECT_PUBLIC_KEY_INFO */ +static const SecAsn1Template SubjectPublicKeyInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO) }, + { SEC_ASN1_INLINE, + offsetof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO,algorithm), + AlgorithmIDTemplate }, + { SEC_ASN1_BIT_STRING, + offsetof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO,subjectPublicKey), }, + { 0, } +}; + +/* end of copied templates */ + +static const SecAsn1Template kSecAsn1SequenceOfAlgIdTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, AlgorithmIDTemplate } +}; + +static const SecAsn1Template KRB5_AuthPackTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_AuthPack) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_AuthPack,pkAuth), + KRB5_PKAuthenticatorTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | + SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 1, + offsetof(KRB5_AuthPack,pubKeyInfo), + SubjectPublicKeyInfoTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | + SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 2, + offsetof(KRB5_AuthPack,supportedCMSTypes), + kSecAsn1SequenceOfAlgIdTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | + SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 3, + offsetof(KRB5_AuthPack,clientDHNonce), + kSecAsn1OctetStringTemplate }, + { 0 } +}; + +/* + * Encode AuthPack, public key version (no Diffie-Hellman components). + */ +krb5_error_code krb5int_pkinit_auth_pack_encode( + krb5_timestamp kctime, + krb5_int32 cusec, /* microseconds */ + krb5_ui_4 nonce, + const krb5_checksum *pa_checksum, + const krb5int_algorithm_id *cms_types, /* optional */ + krb5_ui_4 num_cms_types, + krb5_data *auth_pack) /* mallocd and RETURNED */ +{ + KRB5_AuthPack localAuthPack; + SecAsn1CoderRef coder; + CSSM_DATA *cksum = &localAuthPack.pkAuth.paChecksum; + krb5_error_code ourRtn = 0; + CSSM_DATA ber = {0, NULL}; + OSStatus ortn; + char *timeStr = NULL; + + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&localAuthPack, 0, sizeof(localAuthPack)); + if(pkiKrbTimestampToStr(kctime, &timeStr)) { + ourRtn = -1; + goto errOut; + } + localAuthPack.pkAuth.kctime.Data = (uint8 *)timeStr; + localAuthPack.pkAuth.kctime.Length = strlen(timeStr); + if(pkiIntToData(cusec, &localAuthPack.pkAuth.cusec, coder)) { + ourRtn = ENOMEM; + goto errOut; + } + if(pkiIntToData(nonce, &localAuthPack.pkAuth.nonce, coder)) { + ourRtn = ENOMEM; + goto errOut; + } + cksum->Data = (uint8 *)pa_checksum->contents; + cksum->Length = pa_checksum->length; + + if((cms_types != NULL) && (num_cms_types != 0)) { + unsigned dex; + CSSM_X509_ALGORITHM_IDENTIFIER **algIds; + + /* build a NULL_terminated array of CSSM_X509_ALGORITHM_IDENTIFIERs */ + localAuthPack.supportedCMSTypes = (CSSM_X509_ALGORITHM_IDENTIFIER **) + SecAsn1Malloc(coder, + (num_cms_types + 1) * sizeof(CSSM_X509_ALGORITHM_IDENTIFIER *)); + algIds = localAuthPack.supportedCMSTypes; + for(dex=0; dex<num_cms_types; dex++) { + algIds[dex] = (CSSM_X509_ALGORITHM_IDENTIFIER *) + SecAsn1Malloc(coder, sizeof(CSSM_X509_ALGORITHM_IDENTIFIER)); + pkiKrb5DataToCssm(&cms_types[dex].algorithm, + &algIds[dex]->algorithm, coder); + if(cms_types[dex].parameters.data != NULL) { + pkiKrb5DataToCssm(&cms_types[dex].parameters, + &algIds[dex]->parameters, coder); + } + else { + algIds[dex]->parameters.Data = NULL; + algIds[dex]->parameters.Length = 0; + } + } + algIds[num_cms_types] = NULL; + } + ortn = SecAsn1EncodeItem(coder, &localAuthPack, KRB5_AuthPackTemplate, &ber); + if(ortn) { + ourRtn = ENOMEM; + goto errOut; + } + + if(pkiCssmDataToKrb5Data(&ber, auth_pack)) { + ourRtn = ENOMEM; + } + else { + auth_pack->magic = KV5M_AUTHENTICATOR; + ourRtn = 0; + } +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +/* + * Decode AuthPack, public key version (no Diffie-Hellman components). + */ +krb5_error_code krb5int_pkinit_auth_pack_decode( + const krb5_data *auth_pack, /* DER encoded */ + krb5_timestamp *kctime, /* RETURNED */ + krb5_ui_4 *cusec, /* microseconds, RETURNED */ + krb5_ui_4 *nonce, /* RETURNED */ + krb5_checksum *pa_checksum, /* contents mallocd and RETURNED */ + krb5int_algorithm_id **cms_types, /* optionally mallocd and RETURNED */ + krb5_ui_4 *num_cms_types) /* optionally RETURNED */ +{ + KRB5_AuthPack localAuthPack; + SecAsn1CoderRef coder; + CSSM_DATA der = {0, NULL}; + krb5_error_code ourRtn = 0; + CSSM_DATA *cksum = &localAuthPack.pkAuth.paChecksum; + + /* Decode --> localAuthPack */ + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + PKI_KRB_TO_CSSM_DATA(auth_pack, &der); + memset(&localAuthPack, 0, sizeof(localAuthPack)); + if(SecAsn1DecodeData(coder, &der, KRB5_AuthPackTemplate, &localAuthPack)) { + ourRtn = ASN1_BAD_FORMAT; + goto errOut; + } + + /* optionally Convert KRB5_AuthPack to caller's params */ + if(kctime) { + if((ourRtn = pkiTimeStrToKrbTimestamp((char *)localAuthPack.pkAuth.kctime.Data, + localAuthPack.pkAuth.kctime.Length, kctime))) { + goto errOut; + } + } + if(cusec) { + if((ourRtn = pkiDataToInt(&localAuthPack.pkAuth.cusec, (krb5_int32 *)cusec))) { + goto errOut; + } + } + if(nonce) { + if((ourRtn = pkiDataToInt(&localAuthPack.pkAuth.nonce, (krb5_int32 *)nonce))) { + goto errOut; + } + } + if(pa_checksum) { + if(cksum->Length == 0) { + /* This is the unique error for "no paChecksum" */ + ourRtn = KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED; + goto errOut; + } + else { + pa_checksum->contents = (krb5_octet *)malloc(cksum->Length); + if(pa_checksum->contents == NULL) { + ourRtn = ENOMEM; + goto errOut; + } + pa_checksum->length = cksum->Length; + memmove(pa_checksum->contents, cksum->Data, pa_checksum->length); + pa_checksum->magic = KV5M_CHECKSUM; + /* This used to be encoded with the checksum but no more... */ + pa_checksum->checksum_type = CKSUMTYPE_NIST_SHA; + } + } + if(cms_types) { + if(localAuthPack.supportedCMSTypes == NULL) { + *cms_types = NULL; + *num_cms_types = 0; + } + else { + /* + * Convert NULL-terminated array of CSSM-style algIds to + * krb5int_algorithm_ids. + */ + unsigned dex; + unsigned num_types = 0; + CSSM_X509_ALGORITHM_IDENTIFIER **alg_ids; + krb5int_algorithm_id *kalg_ids; + + for(alg_ids=localAuthPack.supportedCMSTypes; + *alg_ids; + alg_ids++) { + num_types++; + } + *cms_types = kalg_ids = (krb5int_algorithm_id *)malloc( + sizeof(krb5int_algorithm_id) * num_types); + *num_cms_types = num_types; + memset(kalg_ids, 0, sizeof(krb5int_algorithm_id) * num_types); + alg_ids = localAuthPack.supportedCMSTypes; + for(dex=0; dex<num_types; dex++) { + if(alg_ids[dex]->algorithm.Data) { + pkiCssmDataToKrb5Data(&alg_ids[dex]->algorithm, + &kalg_ids[dex].algorithm); + } + if(alg_ids[dex]->parameters.Data) { + pkiCssmDataToKrb5Data(&alg_ids[dex]->parameters, + &kalg_ids[dex].parameters); + } + } + } + } + ourRtn = 0; +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +#pragma mark ----- IssuerAndSerialNumber ----- + +/* + * Issuer/serial number - specify issuer as ASN_ANY because we can get it from + * CL in DER-encoded state. + */ +typedef struct { + CSSM_DATA derIssuer; + CSSM_DATA serialNumber; +} KRB5_IssuerAndSerial; + +static const SecAsn1Template KRB5_IssuerAndSerialTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_IssuerAndSerial) }, + { SEC_ASN1_ANY, offsetof(KRB5_IssuerAndSerial, derIssuer) }, + { SEC_ASN1_INTEGER, offsetof(KRB5_IssuerAndSerial, serialNumber) }, + { 0 } +}; + +/* + * Given DER-encoded issuer and serial number, create an encoded + * IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_issuer_serial_encode( + const krb5_data *issuer, /* DER encoded */ + const krb5_data *serial_num, + krb5_data *issuer_and_serial) /* content mallocd and RETURNED */ +{ + KRB5_IssuerAndSerial issuerSerial; + SecAsn1CoderRef coder; + CSSM_DATA ber = {0, NULL}; + OSStatus ortn; + + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + PKI_KRB_TO_CSSM_DATA(issuer, &issuerSerial.derIssuer); + PKI_KRB_TO_CSSM_DATA(serial_num, &issuerSerial.serialNumber); + ortn = SecAsn1EncodeItem(coder, &issuerSerial, KRB5_IssuerAndSerialTemplate, &ber); + if(ortn) { + ortn = ENOMEM; + goto errOut; + } + ortn = pkiCssmDataToKrb5Data(&ber, issuer_and_serial); +errOut: + SecAsn1CoderRelease(coder); + return ortn; +} + +/* + * Decode IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_issuer_serial_decode( + const krb5_data *issuer_and_serial, /* DER encoded */ + krb5_data *issuer, /* DER encoded, RETURNED */ + krb5_data *serial_num) /* RETURNED */ +{ + KRB5_IssuerAndSerial issuerSerial; + SecAsn1CoderRef coder; + CSSM_DATA der = {issuer_and_serial->length, (uint8 *)issuer_and_serial->data}; + krb5_error_code ourRtn = 0; + + /* Decode --> issuerSerial */ + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&issuerSerial, 0, sizeof(issuerSerial)); + if(SecAsn1DecodeData(coder, &der, KRB5_IssuerAndSerialTemplate, &issuerSerial)) { + ourRtn = ASN1_BAD_FORMAT; + goto errOut; + } + + /* Convert KRB5_IssuerAndSerial to caller's params */ + if((ourRtn = pkiCssmDataToKrb5Data(&issuerSerial.derIssuer, issuer))) { + goto errOut; + } + if((ourRtn = pkiCssmDataToKrb5Data(&issuerSerial.serialNumber, serial_num))) { + ourRtn = ENOMEM; + goto errOut; + } + +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +#pragma mark ----- ExternalPrincipalIdentifier ----- + +/* + * Shown here for completeness; this module only implements the + * issuerAndSerialNumber option. + */ +typedef struct { + CSSM_DATA subjectName; /* [0] IMPLICIT OCTET STRING OPTIONAL */ + /* contents = encoded Name */ + CSSM_DATA issuerAndSerialNumber; /* [1] IMPLICIT OCTET STRING OPTIONAL */ + /* contents = encoded Issuer&Serial */ + CSSM_DATA subjectKeyIdentifier; /* [2] IMPLICIT OCTET STRING OPTIONAL */ + /* contents = encoded subjectKeyIdentifier extension */ +} KRB5_ExternalPrincipalIdentifier; + +static const SecAsn1Template KRB5_ExternalPrincipalIdentifierTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_ExternalPrincipalIdentifier) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 0, + offsetof(KRB5_ExternalPrincipalIdentifier, subjectName), + kSecAsn1OctetStringTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 1, + offsetof(KRB5_ExternalPrincipalIdentifier, issuerAndSerialNumber), + kSecAsn1OctetStringTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 2, + offsetof(KRB5_ExternalPrincipalIdentifier, subjectKeyIdentifier), + kSecAsn1OctetStringTemplate }, + { 0 } +}; + +static const SecAsn1Template KRB5_SequenceOfExternalPrincipalIdentifierTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, KRB5_ExternalPrincipalIdentifierTemplate } +}; + +#pragma mark ----- PA-PK-AS-REQ ----- + +/* + * Top-level PA-PK-AS-REQ. All fields except for trusted_CAs are pre-encoded + * before we encode this and are still DER-encoded after we decode. + * The signedAuthPack and kdcPkId fields are wrapped in OCTET STRINGs + * during encode; we strip off the OCTET STRING wrappers during decode. + */ +typedef struct { + CSSM_DATA signedAuthPack; /* ContentInfo, SignedData */ + /* Content is KRB5_AuthPack */ + KRB5_ExternalPrincipalIdentifier + **trusted_CAs; /* optional */ + CSSM_DATA kdcPkId; /* optional */ +} KRB5_PA_PK_AS_REQ; + +static const SecAsn1Template KRB5_PA_PK_AS_REQTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PA_PK_AS_REQ) }, + { SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(KRB5_PA_PK_AS_REQ, signedAuthPack), + kSecAsn1OctetStringTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_PA_PK_AS_REQ, trusted_CAs), + KRB5_SequenceOfExternalPrincipalIdentifierTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(KRB5_PA_PK_AS_REQ, kdcPkId), + kSecAsn1AnyTemplate }, + { 0 } +}; + +/* + * Top-level encode for PA-PK-AS-REQ. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_req_encode( + const krb5_data *signed_auth_pack, /* DER encoded ContentInfo */ + const krb5_data *trusted_CAs, /* optional: trustedCertifiers. Contents are + * DER-encoded issuer/serialNumbers. */ + krb5_ui_4 num_trusted_CAs, + const krb5_data *kdc_cert, /* optional kdcPkId, DER encoded issuer/serial */ + krb5_data *pa_pk_as_req) /* mallocd and RETURNED */ +{ + KRB5_PA_PK_AS_REQ req; + SecAsn1CoderRef coder; + CSSM_DATA ber = {0, NULL}; + OSStatus ortn; + unsigned dex; + + assert(signed_auth_pack != NULL); + assert(pa_pk_as_req != NULL); + + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + + /* krb5_data ==> CSSM format */ + + memset(&req, 0, sizeof(req)); + PKI_KRB_TO_CSSM_DATA(signed_auth_pack, &req.signedAuthPack); + if(num_trusted_CAs) { + /* + * Set up a NULL-terminated array of KRB5_ExternalPrincipalIdentifier + * pointers. We malloc the actual KRB5_ExternalPrincipalIdentifiers as + * a contiguous array; it's in temp SecAsn1CoderRef memory. The referents + * are just dropped in from the caller's krb5_datas. + */ + KRB5_ExternalPrincipalIdentifier *cas = + (KRB5_ExternalPrincipalIdentifier *)SecAsn1Malloc(coder, + num_trusted_CAs * sizeof(KRB5_ExternalPrincipalIdentifier)); + req.trusted_CAs = + (KRB5_ExternalPrincipalIdentifier **) + pkiNssNullArray(num_trusted_CAs, coder); + for(dex=0; dex<num_trusted_CAs; dex++) { + req.trusted_CAs[dex] = &cas[dex]; + memset(&cas[dex], 0, sizeof(KRB5_ExternalPrincipalIdentifier)); + PKI_KRB_TO_CSSM_DATA(&trusted_CAs[dex], + &cas[dex].issuerAndSerialNumber); + } + } + if(kdc_cert) { + PKI_KRB_TO_CSSM_DATA(kdc_cert, &req.kdcPkId); + } + + /* encode */ + ortn = SecAsn1EncodeItem(coder, &req, KRB5_PA_PK_AS_REQTemplate, &ber); + if(ortn) { + ortn = ENOMEM; + goto errOut; + } + ortn = pkiCssmDataToKrb5Data(&ber, pa_pk_as_req); + +errOut: + SecAsn1CoderRelease(coder); + return ortn; +} + +/* + * Top-level decode for PA-PK-AS-REQ. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_req_decode( + const krb5_data *pa_pk_as_req, + krb5_data *signed_auth_pack, /* DER encoded ContentInfo, RETURNED */ + /* + * Remainder are optionally RETURNED (specify NULL for pointers to + * items you're not interested in). + */ + krb5_ui_4 *num_trusted_CAs, /* sizeof trusted_CAs */ + krb5_data **trusted_CAs, /* mallocd array of DER-encoded TrustedCAs issuer/serial */ + krb5_data *kdc_cert) /* DER encoded issuer/serial */ +{ + KRB5_PA_PK_AS_REQ asReq; + SecAsn1CoderRef coder; + CSSM_DATA der; + krb5_error_code ourRtn = 0; + + assert(pa_pk_as_req != NULL); + + /* Decode --> KRB5_PA_PK_AS_REQ */ + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + PKI_KRB_TO_CSSM_DATA(pa_pk_as_req, &der); + memset(&asReq, 0, sizeof(asReq)); + if(SecAsn1DecodeData(coder, &der, KRB5_PA_PK_AS_REQTemplate, &asReq)) { + ourRtn = ASN1_BAD_FORMAT; + goto errOut; + } + + /* Convert decoded results to caller's args; each is optional */ + if(signed_auth_pack != NULL) { + if((ourRtn = pkiCssmDataToKrb5Data(&asReq.signedAuthPack, signed_auth_pack))) { + goto errOut; + } + } + if(asReq.trusted_CAs && (trusted_CAs != NULL)) { + /* NULL-terminated array of CSSM_DATA ptrs */ + unsigned numCas = pkiNssArraySize((const void **)asReq.trusted_CAs); + unsigned dex; + krb5_data *kdcCas; + + kdcCas = (krb5_data *)malloc(sizeof(krb5_data) * numCas); + if(kdcCas == NULL) { + ourRtn = ENOMEM; + goto errOut; + } + for(dex=0; dex<numCas; dex++) { + KRB5_ExternalPrincipalIdentifier *epi = asReq.trusted_CAs[dex]; + if(epi->issuerAndSerialNumber.Data) { + /* the only variant we support */ + pkiCssmDataToKrb5Data(&epi->issuerAndSerialNumber, &kdcCas[dex]); + } + } + *trusted_CAs = kdcCas; + *num_trusted_CAs = numCas; + } + if(asReq.kdcPkId.Data && kdc_cert) { + if((ourRtn = pkiCssmDataToKrb5Data(&asReq.kdcPkId, kdc_cert))) { + goto errOut; + } + } +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +#pragma mark ====== begin PA-PK-AS-REP components ====== + +typedef struct { + CSSM_DATA subjectPublicKey; /* BIT STRING */ + CSSM_DATA nonce; /* from KRB5_PKAuthenticator.nonce */ + CSSM_DATA *expiration; /* optional UTC time */ +} KRB5_KDC_DHKeyInfo; + +typedef struct { + CSSM_DATA keyType; + CSSM_DATA keyValue; +} KRB5_EncryptionKey; + +static const SecAsn1Template KRB5_EncryptionKeyTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_EncryptionKey) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_EncryptionKey, keyType), + kSecAsn1IntegerTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_EncryptionKey, keyValue), + kSecAsn1OctetStringTemplate }, + { 0 } +}; + +#pragma mark ----- Checksum ----- + +typedef struct { + CSSM_DATA checksumType; + CSSM_DATA checksum; +} KRB5_Checksum; + +static const SecAsn1Template KRB5_ChecksumTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_Checksum) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_Checksum,checksumType), + kSecAsn1IntegerTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_Checksum,checksum), + kSecAsn1OctetStringTemplate }, + { 0 } +}; + +typedef struct { + KRB5_EncryptionKey encryptionKey; + KRB5_Checksum asChecksum; +} KRB5_ReplyKeyPack; + +static const SecAsn1Template KRB5_ReplyKeyPackTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_ReplyKeyPack) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_ReplyKeyPack, encryptionKey), + KRB5_EncryptionKeyTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_ReplyKeyPack,asChecksum), + KRB5_ChecksumTemplate }, + { 0 } +}; + +/* + * Encode a ReplyKeyPack. The result is used as the Content of a SignedData. + */ +krb5_error_code krb5int_pkinit_reply_key_pack_encode( + const krb5_keyblock *key_block, + const krb5_checksum *checksum, + krb5_data *reply_key_pack) /* mallocd and RETURNED */ +{ + KRB5_ReplyKeyPack repKeyPack; + SecAsn1CoderRef coder; + krb5_error_code ourRtn = 0; + CSSM_DATA der = {0, NULL}; + OSStatus ortn; + KRB5_EncryptionKey *encryptKey = &repKeyPack.encryptionKey; + KRB5_Checksum *cksum = &repKeyPack.asChecksum; + + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&repKeyPack, 0, sizeof(repKeyPack)); + + if((ourRtn = pkiIntToData(key_block->enctype, &encryptKey->keyType, coder))) { + goto errOut; + } + encryptKey->keyValue.Length = key_block->length, + encryptKey->keyValue.Data = (uint8 *)key_block->contents; + + if((ourRtn = pkiIntToData(checksum->checksum_type, &cksum->checksumType, coder))) { + goto errOut; + } + cksum->checksum.Data = (uint8 *)checksum->contents; + cksum->checksum.Length = checksum->length; + + ortn = SecAsn1EncodeItem(coder, &repKeyPack, KRB5_ReplyKeyPackTemplate, &der); + if(ortn) { + ourRtn = ENOMEM; + goto errOut; + } + ourRtn = pkiCssmDataToKrb5Data(&der, reply_key_pack); +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +/* + * Decode a ReplyKeyPack. + */ +krb5_error_code krb5int_pkinit_reply_key_pack_decode( + const krb5_data *reply_key_pack, + krb5_keyblock *key_block, /* RETURNED */ + krb5_checksum *checksum) /* contents mallocd and RETURNED */ +{ + KRB5_ReplyKeyPack repKeyPack; + SecAsn1CoderRef coder; + krb5_error_code ourRtn = 0; + KRB5_EncryptionKey *encryptKey = &repKeyPack.encryptionKey; + CSSM_DATA der = {reply_key_pack->length, (uint8 *)reply_key_pack->data}; + krb5_data tmpData; + KRB5_Checksum *cksum = &repKeyPack.asChecksum; + + /* Decode --> KRB5_ReplyKeyPack */ + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&repKeyPack, 0, sizeof(repKeyPack)); + if(SecAsn1DecodeData(coder, &der, KRB5_ReplyKeyPackTemplate, &repKeyPack)) { + ourRtn = ASN1_BAD_FORMAT; + goto errOut; + } + + if((ourRtn = pkiDataToInt(&encryptKey->keyType, (krb5_int32 *)&key_block->enctype))) { + goto errOut; + } + if((ourRtn = pkiCssmDataToKrb5Data(&encryptKey->keyValue, &tmpData))) { + goto errOut; + } + key_block->contents = (krb5_octet *)tmpData.data; + key_block->length = tmpData.length; + + if((ourRtn = pkiDataToInt(&cksum->checksumType, &checksum->checksum_type))) { + goto errOut; + } + checksum->contents = (krb5_octet *)malloc(cksum->checksum.Length); + if(checksum->contents == NULL) { + ourRtn = ENOMEM; + goto errOut; + } + checksum->length = cksum->checksum.Length; + memmove(checksum->contents, cksum->checksum.Data, checksum->length); + checksum->magic = KV5M_CHECKSUM; + +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + + +#pragma mark ----- KRB5_PA_PK_AS_REP ----- +/* + * Top-level PA-PK-AS-REP. Exactly one of the optional fields must be present. + */ +typedef struct { + CSSM_DATA *dhSignedData; /* ContentInfo, SignedData */ + /* Content is KRB5_KDC_DHKeyInfo */ + CSSM_DATA *encKeyPack; /* ContentInfo, SignedData */ + /* Content is ReplyKeyPack */ +} KRB5_PA_PK_AS_REP; + +static const SecAsn1Template KRB5_PA_PK_AS_REPTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PA_PK_AS_REP) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | + SEC_ASN1_EXPLICIT | 0, + offsetof(KRB5_PA_PK_AS_REP, dhSignedData), + kSecAsn1PointerToAnyTemplate }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL | + SEC_ASN1_EXPLICIT | 1, + offsetof(KRB5_PA_PK_AS_REP, encKeyPack), + kSecAsn1PointerToAnyTemplate }, + { 0 } +}; + +/* + * Encode a KRB5_PA_PK_AS_REP. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_rep_encode( + const krb5_data *dh_signed_data, + const krb5_data *enc_key_pack, + krb5_data *pa_pk_as_rep) /* mallocd and RETURNED */ +{ + KRB5_PA_PK_AS_REP asRep; + SecAsn1CoderRef coder; + krb5_error_code ourRtn = 0; + CSSM_DATA der = {0, NULL}; + OSStatus ortn; + CSSM_DATA dhSignedData; + CSSM_DATA encKeyPack; + + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&asRep, 0, sizeof(asRep)); + if(dh_signed_data) { + PKI_KRB_TO_CSSM_DATA(dh_signed_data, &dhSignedData); + asRep.dhSignedData = &dhSignedData; + } + if(enc_key_pack) { + PKI_KRB_TO_CSSM_DATA(enc_key_pack, &encKeyPack); + asRep.encKeyPack = &encKeyPack; + } + + ortn = SecAsn1EncodeItem(coder, &asRep, KRB5_PA_PK_AS_REPTemplate, &der); + if(ortn) { + ourRtn = ENOMEM; + goto errOut; + } + ourRtn = pkiCssmDataToKrb5Data(&der, pa_pk_as_rep); + +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +/* + * Decode a KRB5_PA_PK_AS_REP. + */ +krb5_error_code krb5int_pkinit_pa_pk_as_rep_decode( + const krb5_data *pa_pk_as_rep, + krb5_data *dh_signed_data, + krb5_data *enc_key_pack) +{ + KRB5_PA_PK_AS_REP asRep; + SecAsn1CoderRef coder; + CSSM_DATA der = {pa_pk_as_rep->length, (uint8 *)pa_pk_as_rep->data}; + krb5_error_code ourRtn = 0; + + /* Decode --> KRB5_PA_PK_AS_REP */ + if(SecAsn1CoderCreate(&coder)) { + return ENOMEM; + } + memset(&asRep, 0, sizeof(asRep)); + if(SecAsn1DecodeData(coder, &der, KRB5_PA_PK_AS_REPTemplate, &asRep)) { + ourRtn = ASN1_BAD_FORMAT; + goto errOut; + } + + if(asRep.dhSignedData) { + if((ourRtn = pkiCssmDataToKrb5Data(asRep.dhSignedData, dh_signed_data))) { + goto errOut; + } + } + if(asRep.encKeyPack) { + ourRtn = pkiCssmDataToKrb5Data(asRep.encKeyPack, enc_key_pack); + } + +errOut: + SecAsn1CoderRelease(coder); + return ourRtn; +} + +#pragma mark ====== General utilities ====== + +/* + * Given a DER encoded certificate, obtain the associated IssuerAndSerialNumber. + */ +krb5_error_code krb5int_pkinit_get_issuer_serial( + const krb5_data *cert, + krb5_data *issuer_and_serial) +{ + CSSM_HANDLE cacheHand = 0; + CSSM_RETURN crtn = CSSM_OK; + CSSM_DATA certData = { cert->length, (uint8 *)cert->data }; + CSSM_HANDLE resultHand = 0; + CSSM_DATA_PTR derIssuer = NULL; + CSSM_DATA_PTR serial; + krb5_data krb_serial; + krb5_data krb_issuer; + uint32 numFields; + krb5_error_code ourRtn = 0; + + CSSM_CL_HANDLE clHand = pkiClStartup(); + if(clHand == 0) { + return CSSMERR_CSSM_ADDIN_LOAD_FAILED; + } + /* subsequent errors to errOut: */ + + crtn = CSSM_CL_CertCache(clHand, &certData, &cacheHand); + if(crtn) { + pkiCssmErr("CSSM_CL_CertCache", crtn); + ourRtn = ASN1_PARSE_ERROR; + goto errOut; + } + + /* obtain the two fields; issuer is DER encoded */ + crtn = CSSM_CL_CertGetFirstCachedFieldValue(clHand, cacheHand, + &CSSMOID_X509V1IssuerNameStd, &resultHand, &numFields, &derIssuer); + if(crtn) { + pkiCssmErr("CSSM_CL_CertGetFirstCachedFieldValue(issuer)", crtn); + ourRtn = ASN1_PARSE_ERROR; + goto errOut; + } + crtn = CSSM_CL_CertGetFirstCachedFieldValue(clHand, cacheHand, + &CSSMOID_X509V1SerialNumber, &resultHand, &numFields, &serial); + if(crtn) { + pkiCssmErr("CSSM_CL_CertGetFirstCachedFieldValue(serial)", crtn); + ourRtn = ASN1_PARSE_ERROR; + goto errOut; + } + PKI_CSSM_TO_KRB_DATA(derIssuer, &krb_issuer); + PKI_CSSM_TO_KRB_DATA(serial, &krb_serial); + ourRtn = krb5int_pkinit_issuer_serial_encode(&krb_issuer, &krb_serial, issuer_and_serial); + +errOut: + if(derIssuer) { + CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerNameStd, derIssuer); + } + if(serial) { + CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SerialNumber, serial); + } + if(cacheHand) { + CSSM_CL_CertAbortCache(clHand, cacheHand); + } + if(clHand) { + pkiClDetachUnload(clHand); + } + return ourRtn; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/lib/krb5/krb/pkinit_apple_cert_store.c b/src/lib/krb5/krb/pkinit_apple_cert_store.c new file mode 100644 index 0000000..06ac37d --- /dev/null +++ b/src/lib/krb5/krb/pkinit_apple_cert_store.c @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_cert_store.c - PKINIT certificate storage/retrieval utilities, + * MAC OS X version + * + * Created 26 May 2004 by Doug Mitchell at Apple. + */ + +#if APPLE_PKINIT + +#include "pkinit_cert_store.h" +#include "pkinit_asn1.h" +#include "pkinit_apple_utils.h" +#include <CoreFoundation/CFString.h> +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> +#include <assert.h> +#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> +#include <CommonCrypto/CommonDigest.h> +#include <sys/errno.h> + +/* + * Client cert info is stored in preferences with this following parameters: + * + * key = kPkinitClientCertKey + * appID = kPkinitClientCertApp + * username = kCFPreferencesCurrentUser + * hostname = kCFPreferencesAnyHost + * + * The stored property list is a CFDictionary. Keys in the dictionary are + * principal names (e.g. foobar@REALM.LOCAL). + * + * Values in the dictionary are raw data containing the DER-encoded issuer and + * serial number of the certificate. + * + * When obtaining a PKINIT cert, if an entry in the CFDictionary for the specified + * principal is not found, the entry for the default will be used if it's there. + */ + +/* + * NOTE: ANSI C code requires an Apple-Custom -fconstant-cfstrings CFLAGS to + * use CFSTR in a const declaration so we just declare the C strings here. + */ +#define kPkinitClientCertKey "KRBClientCert" +#define kPkinitClientCertApp "edu.mit.Kerberos.pkinit" + +/* + * KDC cert stored in this keychain. It's linked to systemkeychain so that if + * a root process tries to unlock it, it auto-unlocks. + */ +#define KDC_KEYCHAIN "/var/db/krb5kdc/kdc.keychain" + +/* + * Given a certificate, obtain the DER-encoded issuer and serial number. Result + * is mallocd and must be freed by caller. + */ +static OSStatus pkinit_get_cert_issuer_sn( + SecCertificateRef certRef, + CSSM_DATA *issuerSerial) /* mallocd and RETURNED */ +{ + OSStatus ortn; + CSSM_DATA certData; + krb5_data INIT_KDATA(issuerSerialKrb); + krb5_data certDataKrb; + krb5_error_code krtn; + + assert(certRef != NULL); + assert(issuerSerial != NULL); + + ortn = SecCertificateGetData(certRef, &certData); + if(ortn) { + pkiCssmErr("SecCertificateGetData", ortn); + return ortn; + } + PKI_CSSM_TO_KRB_DATA(&certData, &certDataKrb); + krtn = krb5int_pkinit_get_issuer_serial(&certDataKrb, &issuerSerialKrb); + if(krtn) { + return CSSMERR_CL_INVALID_DATA; + } + PKI_KRB_TO_CSSM_DATA(&issuerSerialKrb, issuerSerial); + return noErr; +} + +/* + * Determine if specified identity's cert's issuer and serial number match the + * provided issuer and serial number. Returns nonzero on match, else returns zero. + */ +static int pkinit_issuer_sn_match( + SecIdentityRef idRef, + const CSSM_DATA *matchIssuerSerial) +{ + OSStatus ortn; + SecCertificateRef certRef = NULL; + CSSM_DATA INIT_CDATA(certIssuerSerial); + int ourRtn = 0; + + assert(idRef != NULL); + assert(matchIssuerSerial != NULL); + + /* Get this cert's issuer/serial number */ + ortn = SecIdentityCopyCertificate(idRef, &certRef); + if(ortn) { + pkiCssmErr("SecIdentityCopyCertificate", ortn); + return 0; + } + /* subsequent errors to errOut: */ + ortn = pkinit_get_cert_issuer_sn(certRef, &certIssuerSerial); + if(ortn) { + pkiCssmErr("SecIdentityCopyCertificate", ortn); + goto errOut; + } + ourRtn = pkiCompareCssmData(matchIssuerSerial, &certIssuerSerial) ? 1 : 0; +errOut: + if(certRef != NULL) { + CFRelease(certRef); + } + if(certIssuerSerial.Data != NULL) { + free(certIssuerSerial.Data); + } + return ourRtn; +} + +/* + * Search specified keychain/array/NULL (NULL meaning the default search list) for + * an Identity matching specified key usage and optional Issuer/Serial number. + * If issuer/serial is specified and no identities match, or if no identities found + * matching specified Key usage, errSecItemNotFound is returned. + * + * Caller must CFRelease a non-NULL returned idRef. + */ +static OSStatus pkinit_search_ident( + CFTypeRef keychainOrArray, + CSSM_KEYUSE keyUsage, + const CSSM_DATA *issuerSerial, /* optional */ + SecIdentityRef *foundId) /* RETURNED */ +{ + OSStatus ortn; + SecIdentityRef idRef = NULL; + SecIdentitySearchRef srchRef = NULL; + + ortn = SecIdentitySearchCreate(keychainOrArray, keyUsage, &srchRef); + if(ortn) { + pkiCssmErr("SecIdentitySearchCreate", ortn); + return ortn; + } + do { + ortn = SecIdentitySearchCopyNext(srchRef, &idRef); + if(ortn != noErr) { + break; + } + if(issuerSerial == NULL) { + /* no match needed, we're done - this is the KDC cert case */ + break; + } + else if(pkinit_issuer_sn_match(idRef, issuerSerial)) { + /* match, we're done */ + break; + } + /* finished with this one */ + CFRelease(idRef); + idRef = NULL; + } while(ortn == noErr); + + CFRelease(srchRef); + if(idRef == NULL) { + return errSecItemNotFound; + } + else { + *foundId = idRef; + return noErr; + } +} + +/* + * In Mac OS terms, get the keychain on which a given identity resides. + */ +static krb5_error_code pkinit_cert_to_db( + krb5_pkinit_signing_cert_t idRef, + krb5_pkinit_cert_db_t *dbRef) +{ + SecKeychainRef kcRef = NULL; + SecKeyRef keyRef = NULL; + OSStatus ortn; + + /* that's an identity - get the associated key's keychain */ + ortn = SecIdentityCopyPrivateKey((SecIdentityRef)idRef, &keyRef); + if(ortn) { + pkiCssmErr("SecIdentityCopyPrivateKey", ortn); + return ortn; + } + ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef); + if(ortn) { + pkiCssmErr("SecKeychainItemCopyKeychain", ortn); + } + else { + *dbRef = (krb5_pkinit_cert_db_t)kcRef; + } + CFRelease(keyRef); + return ortn; +} + +/* + * Obtain the CFDictionary representing this user's PKINIT client cert prefs, if it + * exists. Returns noErr or errSecItemNotFound as appropriate. + */ +static OSStatus pkinit_get_pref_dict( + CFDictionaryRef *dict) +{ + CFDictionaryRef theDict; + theDict = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR(kPkinitClientCertKey), + CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if(theDict == NULL) { + pkiDebug("pkinit_get_pref_dict: no kPkinitClientCertKey\n"); + return errSecItemNotFound; + } + if(CFGetTypeID(theDict) != CFDictionaryGetTypeID()) { + pkiDebug("pkinit_get_pref_dict: bad kPkinitClientCertKey pref\n"); + CFRelease(theDict); + return errSecItemNotFound; + } + *dict = theDict; + return noErr; +} + +#pragma mark --- Public client side functions --- + +/* + * Obtain signing cert for specified principal. On successful return, + * caller must eventually release the cert with krb5_pkinit_release_cert(). + */ +krb5_error_code krb5_pkinit_get_client_cert( + const char *principal, /* full principal string */ + krb5_pkinit_signing_cert_t *client_cert) +{ + CFDataRef issuerSerial = NULL; + CSSM_DATA issuerSerialData; + SecIdentityRef idRef = NULL; + OSStatus ortn; + CFDictionaryRef theDict = NULL; + krb5_error_code ourRtn = 0; + + if(principal == NULL) { + return KRB5_PRINC_NOMATCH; + } + + /* Is there a stored preference for PKINIT certs for this user? */ + ortn = pkinit_get_pref_dict(&theDict); + if(ortn) { + return KRB5_PRINC_NOMATCH; + } + + /* Entry in the dictionary for specified principal? */ + CFStringRef cfPrinc = CFStringCreateWithCString(NULL, principal, + kCFStringEncodingASCII); + issuerSerial = (CFDataRef)CFDictionaryGetValue(theDict, cfPrinc); + CFRelease(cfPrinc); + if(issuerSerial == NULL) { + pkiDebug("krb5_pkinit_get_client_cert: no identity found\n"); + ourRtn = KRB5_PRINC_NOMATCH; + goto errOut; + } + if(CFGetTypeID(issuerSerial) != CFDataGetTypeID()) { + pkiDebug("krb5_pkinit_get_client_cert: bad kPkinitClientCertKey value\n"); + ourRtn = KRB5_PRINC_NOMATCH; + goto errOut; + } + + issuerSerialData.Data = (uint8 *)CFDataGetBytePtr(issuerSerial); + issuerSerialData.Length = CFDataGetLength(issuerSerial); + + /* find a cert with that issuer/serial number in default search list */ + ortn = pkinit_search_ident(NULL, CSSM_KEYUSE_SIGN | CSSM_KEYUSE_ENCRYPT, + &issuerSerialData, &idRef); + if(ortn) { + pkiDebug("krb5_pkinit_get_client_cert: no identity found!\n"); + pkiCssmErr("pkinit_search_ident", ortn); + ourRtn = KRB5_PRINC_NOMATCH; + } + else { + *client_cert = (krb5_pkinit_signing_cert_t)idRef; + } +errOut: + if(theDict) { + CFRelease(theDict); + } + return ourRtn; +} + +/* + * Determine if the specified client has a signing cert. Returns TRUE + * if so, else returns FALSE. + */ +krb5_boolean krb5_pkinit_have_client_cert( + const char *principal) /* full principal string */ +{ + krb5_pkinit_signing_cert_t signing_cert = NULL; + krb5_error_code krtn; + + krtn = krb5_pkinit_get_client_cert(principal, &signing_cert); + if(krtn) { + return FALSE; + } + if(signing_cert != NULL) { + krb5_pkinit_release_cert(signing_cert); + return TRUE; + } + else { + return FALSE; + } +} + +/* + * Store the specified certificate (or, more likely, some platform-dependent + * reference to it) as the specified principal's signing cert. Passing + * in NULL for the client_cert has the effect of deleting the relevant entry + * in the cert storage. + */ +krb5_error_code krb5_pkinit_set_client_cert( + const char *principal, /* full principal string */ + krb5_pkinit_signing_cert_t client_cert) +{ + SecIdentityRef idRef = (SecIdentityRef)client_cert; + OSStatus ortn; + CSSM_DATA issuerSerial = {0, NULL}; + CFDataRef cfIssuerSerial = NULL; + CFDictionaryRef existDict = NULL; + CFMutableDictionaryRef newDict = NULL; + SecCertificateRef certRef = NULL; + CFStringRef keyStr = NULL; + krb5_error_code ourRtn = 0; + + if(idRef != NULL) { + if(CFGetTypeID(idRef) != SecIdentityGetTypeID()) { + return KRB5KRB_ERR_GENERIC; + } + + /* Get the cert */ + ortn = SecIdentityCopyCertificate(idRef, &certRef); + if(ortn) { + pkiCssmErr("SecIdentityCopyCertificate", ortn); + return KRB5KRB_ERR_GENERIC; + } + + /* Cook up DER-encoded issuer/serial number */ + ortn = pkinit_get_cert_issuer_sn(certRef, &issuerSerial); + if(ortn) { + ourRtn = KRB5KRB_ERR_GENERIC; + goto errOut; + } + } + + /* + * Obtain the existing pref for kPkinitClientCertKey as a CFDictionary, or + * cook up a new one. + */ + ortn = pkinit_get_pref_dict(&existDict); + if(ortn == noErr) { + /* dup to a mutable dictionary */ + newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict); + } + else { + if(idRef == NULL) { + /* no existing entry, nothing to delete, we're done */ + return 0; + } + newDict = CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + if(newDict == NULL) { + ourRtn = ENOMEM; + goto errOut; + } + + /* issuer / serial number ==> that dictionary */ + keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII); + if(idRef == NULL) { + CFDictionaryRemoveValue(newDict, keyStr); + } + else { + cfIssuerSerial = CFDataCreate(NULL, issuerSerial.Data, issuerSerial.Length); + CFDictionarySetValue(newDict, keyStr, cfIssuerSerial); + } + + /* dictionary ==> prefs */ + CFPreferencesSetValue(CFSTR(kPkinitClientCertKey), newDict, + CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if(CFPreferencesSynchronize(CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, + kCFPreferencesAnyHost)) { + ourRtn = 0; + } + else { + ourRtn = EACCES; /* any better ideas? */ + } +errOut: + if(certRef) { + CFRelease(certRef); + } + if(cfIssuerSerial) { + CFRelease(cfIssuerSerial); + } + if(issuerSerial.Data) { + free(issuerSerial.Data); + } + if(existDict) { + CFRelease(existDict); + } + if(newDict) { + CFRelease(newDict); + } + if(keyStr) { + CFRelease(keyStr); + } + return ourRtn; +} + +/* + * Obtain a reference to the client's cert database. Specify either principal + * name or client_cert as obtained from krb5_pkinit_get_client_cert(). + */ +krb5_error_code krb5_pkinit_get_client_cert_db( + const char *principal, /* full principal string */ + krb5_pkinit_signing_cert_t client_cert, /* optional, from krb5_pkinit_get_client_cert() */ + krb5_pkinit_cert_db_t *client_cert_db)/* RETURNED */ +{ + krb5_error_code krtn; + krb5_pkinit_signing_cert_t local_cert; + + assert((client_cert != NULL) || (principal != NULL)); + if(client_cert == NULL) { + /* caller didn't provide, look it up */ + krtn = krb5_pkinit_get_client_cert(principal, &local_cert); + if(krtn) { + return krtn; + } + } + else { + /* easy case */ + local_cert = client_cert; + } + krtn = pkinit_cert_to_db(local_cert, client_cert_db); + if(client_cert == NULL) { + krb5_pkinit_release_cert(local_cert); + } + return krtn; +} + +#pragma mark --- Public server side functions --- + +/* + * Obtain the KDC signing cert, with optional CA and specific cert specifiers. + * CAs and cert specifiers are in the form of DER-encoded issuerAndSerialNumbers. + * + * The client_spec argument is typically provided by the client as kdcPkId. + */ +krb5_error_code krb5_pkinit_get_kdc_cert( + krb5_ui_4 num_trusted_CAs, /* sizeof *trusted_CAs */ + krb5_data *trusted_CAs, /* optional */ + krb5_data *client_spec, /* optional */ + krb5_pkinit_signing_cert_t *kdc_cert) +{ + SecIdentityRef idRef = NULL; + OSStatus ortn; + krb5_error_code ourRtn = 0; + + /* OS X: trusted_CAs and client_spec ignored */ + + ortn = SecIdentityCopySystemIdentity(kSecIdentityDomainKerberosKDC, + &idRef, NULL); + if(ortn) { + pkiCssmErr("SecIdentityCopySystemIdentity", ortn); + return KRB5_PRINC_NOMATCH; + } + *kdc_cert = (krb5_pkinit_signing_cert_t)idRef; + return ourRtn; +} + +/* + * Obtain a reference to the KDC's cert database. + */ +krb5_error_code krb5_pkinit_get_kdc_cert_db( + krb5_pkinit_cert_db_t *kdc_cert_db) +{ + krb5_pkinit_signing_cert_t kdcCert = NULL; + krb5_error_code krtn; + + krtn = krb5_pkinit_get_kdc_cert(0, NULL, NULL, &kdcCert); + if(krtn) { + return krtn; + } + krtn = pkinit_cert_to_db(kdcCert, kdc_cert_db); + krb5_pkinit_release_cert(kdcCert); + return krtn; +} + +/* + * Release certificate references obtained via krb5_pkinit_get_client_cert() and + * krb5_pkinit_get_kdc_cert(). + */ +void krb5_pkinit_release_cert( + krb5_pkinit_signing_cert_t cert) +{ + if(cert == NULL) { + return; + } + CFRelease((CFTypeRef)cert); +} + +/* + * Release database references obtained via krb5_pkinit_get_client_cert_db() and + * krb5_pkinit_get_kdc_cert_db(). + */ +extern void krb5_pkinit_release_cert_db( + krb5_pkinit_cert_db_t cert_db) +{ + if(cert_db == NULL) { + return; + } + CFRelease((CFTypeRef)cert_db); +} + + +/* + * Obtain a mallocd C-string representation of a certificate's SHA1 digest. + * Only error is a NULL return indicating memory failure. + * Caller must free the returned string. + */ +char *krb5_pkinit_cert_hash_str( + const krb5_data *cert) +{ + CC_SHA1_CTX ctx; + char *outstr; + char *cpOut; + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + unsigned dex; + + assert(cert != NULL); + CC_SHA1_Init(&ctx); + CC_SHA1_Update(&ctx, cert->data, cert->length); + CC_SHA1_Final(digest, &ctx); + + outstr = (char *)malloc((2 * CC_SHA1_DIGEST_LENGTH) + 1); + if(outstr == NULL) { + return NULL; + } + cpOut = outstr; + for(dex=0; dex<CC_SHA1_DIGEST_LENGTH; dex++) { + sprintf(cpOut, "%02X", (unsigned)(digest[dex])); + cpOut += 2; + } + *cpOut = '\0'; + return outstr; +} + +/* + * Obtain a client's optional list of trusted KDC CA certs (trustedCertifiers) + * and/or trusted KDC cert (kdcPkId) for a given client and server. + * All returned values are mallocd and must be freed by caller; the contents + * of the krb5_datas are DER-encoded certificates. + */ +krb5_error_code krb5_pkinit_get_server_certs( + const char *client_principal, + const char *server_principal, + krb5_data **trusted_CAs, /* RETURNED, though return value may be NULL */ + krb5_ui_4 *num_trusted_CAs, /* RETURNED */ + krb5_data *kdc_cert) /* RETURNED, though may be 0/NULL */ +{ + /* nothing for now */ + *trusted_CAs = NULL; + *num_trusted_CAs = 0; + kdc_cert->data = NULL; + kdc_cert->length = 0; + return 0; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/lib/krb5/krb/pkinit_apple_client.c b/src/lib/krb5/krb/pkinit_apple_client.c new file mode 100644 index 0000000..d98fc76 --- /dev/null +++ b/src/lib/krb5/krb/pkinit_apple_client.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_client.c - Client side routines for PKINIT, Mac OS X version + * + * Created 20 May 2004 by Doug Mitchell at Apple. + */ + +#if APPLE_PKINIT + +#include "pkinit_client.h" +#include "pkinit_asn1.h" +#include "pkinit_apple_utils.h" +#include "pkinit_cms.h" +#include <assert.h> +#include <sys/errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> + +/* + * Create a PA-PK-AS-REQ message. + */ +krb5_error_code krb5int_pkinit_as_req_create( + krb5_context context, + krb5_timestamp kctime, + krb5_int32 cusec, /* microseconds */ + krb5_ui_4 nonce, + const krb5_checksum *cksum, + krb5_pkinit_signing_cert_t client_cert, /* required */ + const krb5_data *trusted_CAs, /* optional list of CA certs */ + krb5_ui_4 num_trusted_CAs, + const krb5_data *kdc_cert, /* optional KDC cert */ + krb5_data *as_req) /* mallocd and RETURNED */ +{ + krb5_data auth_pack = {0}; + krb5_error_code krtn; + krb5_data content_info = {0}; + krb5int_algorithm_id *cms_types = NULL; + krb5_ui_4 num_cms_types = 0; + + /* issuer/serial numbers for trusted_CAs and kdc_cert, if we have them */ + krb5_data *ca_issuer_sn = NULL; /* issuer/serial_num for trusted_CAs */ + krb5_data kdc_issuer_sn = {0}; /* issuer/serial_num for kdc_cert */ + krb5_data *kdc_issuer_sn_p = NULL; + + /* optional platform-dependent CMS algorithm preference */ + krtn = krb5int_pkinit_get_cms_types(&cms_types, &num_cms_types); + if(krtn) { + return krtn; + } + + /* encode the core authPack */ + krtn = krb5int_pkinit_auth_pack_encode(kctime, cusec, nonce, cksum, + cms_types, num_cms_types, + &auth_pack); + if(krtn) { + goto errOut; + } + + /* package the AuthPack up in a SignedData inside a ContentInfo */ + krtn = krb5int_pkinit_create_cms_msg(&auth_pack, + client_cert, + NULL, /* recip_cert */ + ECT_PkAuthData, + 0, NULL, /* cms_types */ + &content_info); + if(krtn) { + goto errOut; + } + + /* if we have trusted_CAs, get issuer/serials */ + if(trusted_CAs) { + unsigned dex; + ca_issuer_sn = (krb5_data *)malloc(num_trusted_CAs * sizeof(krb5_data)); + if(ca_issuer_sn == NULL) { + krtn = ENOMEM; + goto errOut; + } + for(dex=0; dex<num_trusted_CAs; dex++) { + krtn = krb5int_pkinit_get_issuer_serial(&trusted_CAs[dex], + &ca_issuer_sn[dex]); + if(krtn) { + goto errOut; + } + } + } + + /* If we have a KDC cert, get its issuer/serial */ + if(kdc_cert) { + krtn = krb5int_pkinit_get_issuer_serial(kdc_cert, &kdc_issuer_sn); + if(krtn) { + goto errOut; + } + kdc_issuer_sn_p = &kdc_issuer_sn; + } + + /* cook up PA-PK-AS-REQ */ + krtn = krb5int_pkinit_pa_pk_as_req_encode(&content_info, + ca_issuer_sn, num_trusted_CAs, + kdc_issuer_sn_p, + as_req); + +errOut: + if(cms_types) { + krb5int_pkinit_free_cms_types(cms_types, num_cms_types); + } + if(auth_pack.data) { + free(auth_pack.data); + } + if(content_info.data) { + free(content_info.data); + } + if(trusted_CAs) { + unsigned dex; + for(dex=0; dex<num_trusted_CAs; dex++) { + free(ca_issuer_sn[dex].data); + } + free(ca_issuer_sn); + } + if(kdc_cert) { + free(kdc_issuer_sn.data); + } + return krtn; +} + +/* + * Parse PA-PK-AS-REP message. Optionally evaluates the message's certificate chain. + * Optionally returns various components. + */ +krb5_error_code krb5int_pkinit_as_rep_parse( + krb5_context context, + const krb5_data *as_rep, + krb5_pkinit_signing_cert_t client_cert, /* required */ + krb5_keyblock *key_block, /* RETURNED */ + krb5_checksum *checksum, /* checksum of corresponding AS-REQ */ + /* contents mallocd and RETURNED */ + krb5int_cert_sig_status *cert_status, /* RETURNED */ + + /* + * Cert fields, all optionally RETURNED. + * + * signer_cert is the full X.509 leaf cert from the incoming SignedData. + * all_certs is an array of all of the certs in the incoming SignedData, + * in full X.509 form. + */ + krb5_data *signer_cert, /* content mallocd */ + unsigned *num_all_certs, /* sizeof *all_certs */ + krb5_data **all_certs) /* krb5_data's and their content mallocd */ +{ + krb5_data reply_key_pack = {0, 0, NULL}; + krb5_error_code krtn; + krb5_data enc_key_pack = {0, 0, NULL}; + krb5_data dh_signed_data = {0, 0, NULL}; + krb5int_cms_content_type content_type; + krb5_pkinit_cert_db_t cert_db = NULL; + krb5_boolean is_signed; + krb5_boolean is_encrypted; + + assert((as_rep != NULL) && (checksum != NULL) && + (key_block != NULL) && (cert_status != NULL)); + + /* + * Decode the top-level PA-PK-AS-REP + */ + krtn = krb5int_pkinit_pa_pk_as_rep_decode(as_rep, &dh_signed_data, &enc_key_pack); + if(krtn) { + pkiCssmErr("krb5int_pkinit_pa_pk_as_rep_decode", krtn); + return krtn; + } + if(dh_signed_data.data) { + /* not for this implementation... */ + pkiDebug("krb5int_pkinit_as_rep_parse: unexpected dh_signed_data\n"); + krtn = ASN1_BAD_FORMAT; + goto err_out; + } + if(enc_key_pack.data == NULL) { + /* REQUIRED for this implementation... */ + pkiDebug("krb5int_pkinit_as_rep_parse: no enc_key_pack\n"); + krtn = ASN1_BAD_FORMAT; + goto err_out; + } + + krtn = krb5_pkinit_get_client_cert_db(NULL, client_cert, &cert_db); + if(krtn) { + pkiDebug("krb5int_pkinit_as_rep_parse: error in krb5_pkinit_get_client_cert_db\n"); + goto err_out; + } + + /* + * enc_key_pack is an EnvelopedData(SignedData(keyPack), encrypted + * with our cert (which krb5int_pkinit_parse_content_info() finds + * implicitly). + */ + krtn = krb5int_pkinit_parse_cms_msg(&enc_key_pack, cert_db, FALSE, + &is_signed, &is_encrypted, + &reply_key_pack, &content_type, + signer_cert, cert_status, num_all_certs, all_certs); + if(krtn) { + pkiDebug("krb5int_pkinit_as_rep_parse: error decoding EnvelopedData\n"); + goto err_out; + } + if(!is_encrypted || !is_signed) { + pkiDebug("krb5int_pkinit_as_rep_parse: not signed and encrypted!\n"); + krtn = KRB5_PARSE_MALFORMED; + goto err_out; + } + if(content_type != ECT_PkReplyKeyKata) { + pkiDebug("replyKeyPack eContentType %d!\n", (int)content_type); + krtn = KRB5_PARSE_MALFORMED; + goto err_out; + } + + /* + * Finally, decode that inner content as the ReplyKeyPack which contains + * the actual key and nonce + */ + krtn = krb5int_pkinit_reply_key_pack_decode(&reply_key_pack, key_block, checksum); + if(krtn) { + pkiDebug("krb5int_pkinit_as_rep_parse: error decoding ReplyKeyPack\n"); + } + +err_out: + /* free temp mallocd data that we didn't pass back to caller */ + if(reply_key_pack.data) { + free(reply_key_pack.data); + } + if(enc_key_pack.data) { + free(enc_key_pack.data); + } + if(dh_signed_data.data) { + free(dh_signed_data.data); + } + if(cert_db) { + krb5_pkinit_release_cert_db(cert_db); + } + return krtn; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/lib/krb5/krb/pkinit_apple_cms.c b/src/lib/krb5/krb/pkinit_apple_cms.c new file mode 100644 index 0000000..353bcab --- /dev/null +++ b/src/lib/krb5/krb/pkinit_apple_cms.c @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_cms.c - CMS encode/decode routines, Mac OS X version + * + * Created 19 May 2004 by Doug Mitchell at Apple. + */ + +#if APPLE_PKINIT + +#include "pkinit_cms.h" +#include "pkinit_asn1.h" +#include "pkinit_apple_utils.h" +#include <CoreFoundation/CoreFoundation.h> +#include <Security/CMSEncoder.h> +#include <Security/CMSDecoder.h> +#include <Security/Security.h> +#include <assert.h> +#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> +#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h> + +/* + * Custom OIDS to specify as eContentType + */ +#define OID_PKINIT 0x2B, 6, 1, 5, 2, 3 +#define OID_PKINIT_LEN 6 + +static const uint8 OID_PKINIT_AUTH_DATA[] = {OID_PKINIT, 1}; +static const uint8 OID_PKINIT_RKEY_DATA[] = {OID_PKINIT, 3}; + +/* these may go public so keep these symbols private */ +static const CSSM_OID _CSSMOID_PKINIT_AUTH_DATA = + {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_AUTH_DATA}; +static const CSSM_OID _CSSMOID_PKINIT_RKEY_DATA = + {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_RKEY_DATA}; + + +#pragma mark ----- CMS utilities ---- + +#define CFRELEASE(cf) if(cf) { CFRelease(cf); } + +/* + * Convert platform-specific cert/signature status to krb5int_cert_sig_status. + */ +static krb5int_cert_sig_status pkiCertSigStatus( + OSStatus certStatus) +{ + switch(certStatus) { + case CSSM_OK: + return pki_cs_good; + case CSSMERR_CSP_VERIFY_FAILED: + return pki_cs_sig_verify_fail; + case CSSMERR_TP_NOT_TRUSTED: + return pki_cs_no_root; + case CSSMERR_TP_INVALID_ANCHOR_CERT: + return pki_cs_unknown_root; + case CSSMERR_TP_CERT_EXPIRED: + return pki_cs_expired; + case CSSMERR_TP_CERT_NOT_VALID_YET: + return pki_cs_not_valid_yet; + case CSSMERR_TP_CERT_REVOKED: + return pki_cs_revoked; + case KRB5_KDB_UNAUTH: + return pki_cs_untrusted; + case CSSMERR_TP_INVALID_CERTIFICATE: + return pki_cs_bad_leaf; + default: + return pki_cs_other_err; + } +} + +/* + * Infer krb5int_cert_sig_status from CMSSignerStatus and a CSSM TO + * cert veriofy result code (obtained via the certVerifyResultCode argument + * in CMSDecoderCopySignerStatus()). + */ +static krb5int_cert_sig_status pkiInferSigStatus( + CMSSignerStatus cms_status, + OSStatus tp_status) +{ + switch(cms_status) { + case kCMSSignerUnsigned: + return pki_not_signed; + case kCMSSignerValid: + return pki_cs_good; + case kCMSSignerNeedsDetachedContent: + return pki_bad_cms; + case kCMSSignerInvalidSignature: + return pki_cs_sig_verify_fail; + case kCMSSignerInvalidCert: + /* proceed with TP status */ + break; + default: + return pki_cs_other_err; + } + + /* signature good, infer end status from TP verify */ + return pkiCertSigStatus(tp_status); +} + +/* + * Cook up a SecCertificateRef from a krb5_data. + */ +static OSStatus pkiKrb5DataToSecCert( + const krb5_data *rawCert, + SecCertificateRef *secCert) /* RETURNED */ +{ + CSSM_DATA certData; + OSStatus ortn; + + assert((rawCert != NULL) && (secCert != NULL)); + + certData.Data = (uint8 *)rawCert->data; + certData.Length = rawCert->length; + ortn = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3, + CSSM_CERT_ENCODING_DER, secCert); + if(ortn) { + pkiCssmErr("SecCertificateCreateFromData", ortn); + } + return ortn; +} + +/* + * Convert CFArray of SecCertificateRefs to a mallocd array of krb5_datas. + */ +static krb5_error_code pkiCertArrayToKrb5Data( + CFArrayRef cf_certs, + unsigned *num_all_certs, + krb5_data **all_certs) +{ + CFIndex num_certs; + krb5_data *allCerts = NULL; + krb5_error_code krtn = 0; + CFIndex dex; + + if(cf_certs == NULL) { + *all_certs = NULL; + return 0; + } + num_certs = CFArrayGetCount(cf_certs); + *num_all_certs = (unsigned)num_certs; + if(num_certs == 0) { + *all_certs = NULL; + return 0; + } + allCerts = (krb5_data *)malloc(sizeof(krb5_data) * num_certs); + if(allCerts == NULL) { + return ENOMEM; + } + for(dex=0; dex<num_certs; dex++) { + CSSM_DATA cert_data; + OSStatus ortn; + SecCertificateRef sec_cert; + + sec_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cf_certs, dex); + ortn = SecCertificateGetData(sec_cert, &cert_data); + if(ortn) { + pkiCssmErr("SecCertificateGetData", ortn); + krtn = KRB5_PARSE_MALFORMED; + break; + } + krtn = pkiCssmDataToKrb5Data(&cert_data, &allCerts[dex]); + if(krtn) { + break; + } + } + if(krtn) { + if(allCerts) { + free(allCerts); + } + } + else { + *all_certs = allCerts; + } + return krtn; +} + +#pragma mark ----- Create CMS message ----- + +/* + * Create a CMS message: either encrypted (EnvelopedData), signed + * (SignedData), or both (EnvelopedData(SignedData(content)). + * + * The message is signed iff signing_cert is non-NULL. + * The message is encrypted iff recip_cert is non-NULL. + * + * The content_type argument specifies to the eContentType + * for a SignedData's EncapsulatedContentInfo. + */ +krb5_error_code krb5int_pkinit_create_cms_msg( + const krb5_data *content, /* Content */ + krb5_pkinit_signing_cert_t signing_cert, /* optional: signed by this cert */ + const krb5_data *recip_cert, /* optional: encrypted with this cert */ + krb5int_cms_content_type content_type, /* OID for EncapsulatedData */ + krb5_ui_4 num_cms_types, /* optional, unused here */ + const krb5int_algorithm_id *cms_types, /* optional, unused here */ + krb5_data *content_info) /* contents mallocd and RETURNED */ +{ + krb5_error_code krtn; + OSStatus ortn; + SecCertificateRef sec_recip = NULL; + CFDataRef cf_content = NULL; + const CSSM_OID *eContentOid = NULL; + + if((signing_cert == NULL) && (recip_cert == NULL)) { + /* must have one or the other */ + pkiDebug("krb5int_pkinit_create_cms_msg: no signer or recipient\n"); + return KRB5_CRYPTO_INTERNAL; + } + + /* + * Optional signer cert. Note signing_cert, if present, is + * a SecIdentityRef. + */ + if(recip_cert) { + if(pkiKrb5DataToSecCert(recip_cert, &sec_recip)) { + krtn = ASN1_BAD_FORMAT; + goto errOut; + } + } + + /* optional eContentType */ + if(signing_cert) { + switch(content_type) { + case ECT_PkAuthData: + eContentOid = &_CSSMOID_PKINIT_AUTH_DATA; + break; + case ECT_PkReplyKeyKata: + eContentOid = &_CSSMOID_PKINIT_RKEY_DATA; + break; + case ECT_Data: + /* the only standard/default case we allow */ + break; + default: + /* others: no can do */ + pkiDebug("krb5int_pkinit_create_cms_msg: bad contentType\n"); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + } + + /* GO */ + ortn = CMSEncode((SecIdentityRef)signing_cert, sec_recip, + eContentOid, + FALSE, /* detachedContent */ + kCMSAttrNone, /* no signed attributes that I know of */ + content->data, content->length, + &cf_content); + if(ortn) { + pkiCssmErr("CMSEncode", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + krtn = pkiCfDataToKrb5Data(cf_content, content_info); +errOut: + CFRELEASE(sec_recip); + CFRELEASE(cf_content); + return krtn; +} + +#pragma mark ----- Generalized parse ContentInfo ---- + +/* + * Parse a ContentInfo as best we can. All return fields are optional. + * If signer_cert_status is NULL on entry, NO signature or cert evaluation + * will be performed. + */ +krb5_error_code krb5int_pkinit_parse_cms_msg( + const krb5_data *content_info, + krb5_pkinit_cert_db_t cert_db, /* may be required for SignedData */ + krb5_boolean is_client_msg, /* TRUE : msg is from client */ + krb5_boolean *is_signed, /* RETURNED */ + krb5_boolean *is_encrypted, /* RETURNED */ + krb5_data *raw_data, /* RETURNED */ + krb5int_cms_content_type *inner_content_type,/* Returned, ContentType of */ + /* EncapsulatedData */ + krb5_data *signer_cert, /* RETURNED */ + krb5int_cert_sig_status *signer_cert_status,/* RETURNED */ + unsigned *num_all_certs, /* size of *all_certs RETURNED */ + krb5_data **all_certs) /* entire cert chain RETURNED */ +{ + SecPolicySearchRef policy_search = NULL; + SecPolicyRef policy = NULL; + OSStatus ortn; + krb5_error_code krtn = 0; + CMSDecoderRef decoder = NULL; + size_t num_signers; + CMSSignerStatus signer_status; + OSStatus cert_verify_status; + CFArrayRef cf_all_certs = NULL; + int msg_is_signed = 0; + + if(content_info == NULL) { + pkiDebug("krb5int_pkinit_parse_cms_msg: no ContentInfo\n"); + return KRB5_CRYPTO_INTERNAL; + } + + ortn = CMSDecoderCreate(&decoder); + if(ortn) { + return ENOMEM; + } + ortn = CMSDecoderUpdateMessage(decoder, content_info->data, content_info->length); + if(ortn) { + /* no verify yet, must be bad message */ + krtn = KRB5_PARSE_MALFORMED; + goto errOut; + } + ortn = CMSDecoderFinalizeMessage(decoder); + if(ortn) { + pkiCssmErr("CMSDecoderFinalizeMessage", ortn); + krtn = KRB5_PARSE_MALFORMED; + goto errOut; + } + + /* expect zero or one signers */ + ortn = CMSDecoderGetNumSigners(decoder, &num_signers); + switch(num_signers) { + case 0: + msg_is_signed = 0; + break; + case 1: + msg_is_signed = 1; + break; + default: + krtn = KRB5_PARSE_MALFORMED; + goto errOut; + } + + /* + * We need a cert verify policy even if we're not actually evaluating + * the cert due to requirements in libsecurity_smime. + */ + ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3, + is_client_msg ? &CSSMOID_APPLE_TP_PKINIT_CLIENT : &CSSMOID_APPLE_TP_PKINIT_SERVER, + NULL, &policy_search); + if(ortn) { + pkiCssmErr("SecPolicySearchCreate", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + ortn = SecPolicySearchCopyNext(policy_search, &policy); + if(ortn) { + pkiCssmErr("SecPolicySearchCopyNext", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + + /* get some basic status that doesn't need heavyweight evaluation */ + if(msg_is_signed) { + if(is_signed) { + *is_signed = TRUE; + } + if(inner_content_type) { + CSSM_OID ec_oid = {0, NULL}; + CFDataRef ec_data = NULL; + + krb5int_cms_content_type ctype; + + ortn = CMSDecoderCopyEncapsulatedContentType(decoder, &ec_data); + if(ortn || (ec_data == NULL)) { + pkiCssmErr("CMSDecoderCopyEncapsulatedContentType", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + ec_oid.Data = (uint8 *)CFDataGetBytePtr(ec_data); + ec_oid.Length = CFDataGetLength(ec_data); + if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_Data)) { + ctype = ECT_Data; + } + else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_SignedData)) { + ctype = ECT_SignedData; + } + else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EnvelopedData)) { + ctype = ECT_EnvelopedData; + } + else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EncryptedData)) { + ctype = ECT_EncryptedData; + } + else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_AUTH_DATA)) { + ctype = ECT_PkAuthData; + } + else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_RKEY_DATA)) { + ctype = ECT_PkReplyKeyKata; + } + else { + ctype = ECT_Other; + } + *inner_content_type = ctype; + CFRelease(ec_data); + } + + /* + * Get SignedData's certs if the caller wants them + */ + if(all_certs) { + ortn = CMSDecoderCopyAllCerts(decoder, &cf_all_certs); + if(ortn) { + pkiCssmErr("CMSDecoderCopyAllCerts", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + krtn = pkiCertArrayToKrb5Data(cf_all_certs, num_all_certs, all_certs); + if(krtn) { + goto errOut; + } + } + + /* optional signer cert */ + if(signer_cert) { + SecCertificateRef sec_signer_cert = NULL; + CSSM_DATA cert_data; + + ortn = CMSDecoderCopySignerCert(decoder, 0, &sec_signer_cert); + if(ortn) { + /* should never happen if it's signed */ + pkiCssmErr("CMSDecoderCopySignerStatus", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + ortn = SecCertificateGetData(sec_signer_cert, &cert_data); + if(ortn) { + pkiCssmErr("SecCertificateGetData", ortn); + CFRelease(sec_signer_cert); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + krtn = pkiDataToKrb5Data(cert_data.Data, cert_data.Length, signer_cert); + CFRelease(sec_signer_cert); + if(krtn) { + goto errOut; + } + } + } + else { + /* not signed */ + if(is_signed) { + *is_signed = FALSE; + } + if(inner_content_type) { + *inner_content_type = ECT_Other; + } + if(signer_cert) { + signer_cert->data = NULL; + signer_cert->length = 0; + } + if(signer_cert_status) { + *signer_cert_status = pki_not_signed; + } + if(num_all_certs) { + *num_all_certs = 0; + } + if(all_certs) { + *all_certs = NULL; + } + } + if(is_encrypted) { + Boolean bencr; + ortn = CMSDecoderIsContentEncrypted(decoder, &bencr); + if(ortn) { + pkiCssmErr("CMSDecoderCopySignerStatus", ortn); + krtn = KRB5_CRYPTO_INTERNAL; + goto errOut; + } + *is_encrypted = bencr ? TRUE : FALSE; + } + + /* + * Verify signature and cert. The actual verify operation is optional, + * per our signer_cert_status argument, but we do this anyway if we need + * to get the signer cert. + */ + if((signer_cert_status != NULL) || (signer_cert != NULL)) { + + ortn = CMSDecoderCopySignerStatus(decoder, + 0, /* signerIndex */ + policy, + signer_cert_status ? TRUE : FALSE, /* evaluateSecTrust */ + &signer_status, + NULL, /* secTrust - not needed */ + &cert_verify_status); + if(ortn) { + /* gross error - subsequent processing impossible */ + pkiCssmErr("CMSDecoderCopySignerStatus", ortn); + krtn = KRB5_PARSE_MALFORMED; + goto errOut; + } + } + /* obtain & return status */ + if(signer_cert_status) { + *signer_cert_status = pkiInferSigStatus(signer_status, cert_verify_status); + } + + /* finally, the payload */ + if(raw_data) { + CFDataRef cf_content = NULL; + + ortn = CMSDecoderCopyContent(decoder, &cf_content); + if(ortn) { + pkiCssmErr("CMSDecoderCopyContent", ortn); + krtn = KRB5_PARSE_MALFORMED; + goto errOut; + } + krtn = pkiCfDataToKrb5Data(cf_content, raw_data); + CFRELEASE(cf_content); + } +errOut: + CFRELEASE(policy_search); + CFRELEASE(policy); + CFRELEASE(cf_all_certs); + CFRELEASE(decoder); + return krtn; +} + +krb5_error_code krb5int_pkinit_get_cms_types( + krb5int_algorithm_id **supported_cms_types, /* RETURNED */ + krb5_ui_4 *num_supported_cms_types) /* RETURNED */ +{ + /* no preference */ + *supported_cms_types = NULL; + *num_supported_cms_types = 0; + return 0; +} + +krb5_error_code krb5int_pkinit_free_cms_types( + krb5int_algorithm_id *supported_cms_types, + krb5_ui_4 num_supported_cms_types) +{ + /* + * We don't return anything from krb5int_pkinit_get_cms_types(), and + * if we did, it would be a pointer to a statically declared array, + * so this is a nop. + */ + return 0; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/lib/krb5/krb/pkinit_apple_utils.c b/src/lib/krb5/krb/pkinit_apple_utils.c new file mode 100644 index 0000000..a457833 --- /dev/null +++ b/src/lib/krb5/krb/pkinit_apple_utils.c @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved. + * + * Export of this software from the United States of America may require + * a specific license from the United States Government. It is the + * responsibility of any person or organization contemplating export to + * obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of Apple Inc. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Apple Inc. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* + * pkinit_apple_utils.c - PKINIT utilities, Mac OS X version + * + * Created 19 May 2004 by Doug Mitchell at Apple. + */ + +#if APPLE_PKINIT + +#include "pkinit_apple_utils.h" +#include "pkinit_asn1.h" +#include <sys/errno.h> +#include <assert.h> +#include <string.h> +#include <time.h> +#include <stdio.h> +#include <ctype.h> +#include <Security/Security.h> + +/* + * Cruft needed to attach to a module + */ +static CSSM_VERSION vers = {2, 0}; +static const CSSM_GUID testGuid = { 0xFADE, 0, 0, { 1,2,3,4,5,6,7,0 }}; + +/* + * Standard app-level memory functions required by CDSA. + */ +static void * cuAppMalloc (CSSM_SIZE size, void *allocRef) { + return( malloc(size) ); +} + +static void cuAppFree (void *mem_ptr, void *allocRef) { + free(mem_ptr); + return; +} + +static void * cuAppRealloc (void *ptr, CSSM_SIZE size, void *allocRef) { + return( realloc( ptr, size ) ); +} + +static void * cuAppCalloc (uint32 num, CSSM_SIZE size, void *allocRef) { + return( calloc( num, size ) ); +} + +static CSSM_API_MEMORY_FUNCS memFuncs = { + cuAppMalloc, + cuAppFree, + cuAppRealloc, + cuAppCalloc, + NULL +}; + +/* + * Init CSSM; returns CSSM_FALSE on error. Reusable. + */ +static CSSM_BOOL cssmInitd = CSSM_FALSE; + +static CSSM_BOOL cuCssmStartup() +{ + CSSM_RETURN crtn; + CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE; + + if(cssmInitd) { + return CSSM_TRUE; + } + crtn = CSSM_Init (&vers, + CSSM_PRIVILEGE_SCOPE_NONE, + &testGuid, + CSSM_KEY_HIERARCHY_NONE, + &pvcPolicy, + NULL /* reserved */); + if(crtn != CSSM_OK) + { + return CSSM_FALSE; + } + else { + cssmInitd = CSSM_TRUE; + return CSSM_TRUE; + } +} + +CSSM_CL_HANDLE pkiClStartup(void) +{ + CSSM_CL_HANDLE clHand; + CSSM_RETURN crtn; + + if(cuCssmStartup() == CSSM_FALSE) { + return 0; + } + crtn = CSSM_ModuleLoad(&gGuidAppleX509CL, + CSSM_KEY_HIERARCHY_NONE, + NULL, /* eventHandler */ + NULL); /* AppNotifyCallbackCtx */ + if(crtn) { + return 0; + } + crtn = CSSM_ModuleAttach (&gGuidAppleX509CL, + &vers, + &memFuncs, /* memFuncs */ + 0, /* SubserviceID */ + CSSM_SERVICE_CL, /* SubserviceFlags - Where is this used? */ + 0, /* AttachFlags */ + CSSM_KEY_HIERARCHY_NONE, + NULL, /* FunctionTable */ + 0, /* NumFuncTable */ + NULL, /* reserved */ + &clHand); + if(crtn) { + return 0; + } + else { + return clHand; + } +} + +CSSM_RETURN pkiClDetachUnload( + CSSM_CL_HANDLE clHand) +{ + CSSM_RETURN crtn = CSSM_ModuleDetach(clHand); + if(crtn) { + return crtn; + } + return CSSM_ModuleUnload(&gGuidAppleX509CL, NULL, NULL); +} + +/* + * CSSM_DATA <--> krb5_ui_4 + */ +krb5_error_code pkiDataToInt( + const CSSM_DATA *cdata, + krb5_int32 *i) /* RETURNED */ +{ + krb5_ui_4 len; + krb5_int32 rtn = 0; + krb5_ui_4 dex; + + if((cdata->Length == 0) || (cdata->Data == NULL)) { + *i = 0; + return 0; + } + len = cdata->Length; + if(len > sizeof(krb5_int32)) { + return ASN1_BAD_LENGTH; + } + + uint8 *cp = cdata->Data; + for(dex=0; dex<len; dex++) { + rtn = (rtn << 8) | *cp++; + } + *i = rtn; + return 0; +} + +krb5_error_code pkiIntToData( + krb5_int32 num, + CSSM_DATA *cdata, + SecAsn1CoderRef coder) +{ + krb5_ui_4 unum = (krb5_ui_4)num; + uint32 len = 0; + uint8 *cp = NULL; + unsigned i; + + if(unum < 0x100) { + len = 1; + } + else if(unum < 0x10000) { + len = 2; + } + else if(unum < 0x1000000) { + len = 3; + } + else { + len = 4; + } + if(SecAsn1AllocItem(coder, cdata, len)) { + return ENOMEM; + } + cp = &cdata->Data[len - 1]; + for(i=0; i<len; i++) { + *cp-- = unum & 0xff; + unum >>= 8; + } + return 0; +} + +/* + * raw data --> krb5_data + */ +krb5_error_code pkiDataToKrb5Data( + const void *data, + unsigned dataLen, + krb5_data *kd) +{ + assert(data != NULL); + assert(kd != NULL); + kd->data = (char *)malloc(dataLen); + if(kd->data == NULL) { + return ENOMEM; + } + kd->length = dataLen; + memmove(kd->data, data, dataLen); + return 0; +} + +/* + * CSSM_DATA <--> krb5_data + * + * CSSM_DATA data is managed by a SecAsn1CoderRef; krb5_data data is mallocd. + * + * Both return nonzero on error. + */ +krb5_error_code pkiCssmDataToKrb5Data( + const CSSM_DATA *cd, + krb5_data *kd) +{ + assert(cd != NULL); + return pkiDataToKrb5Data(cd->Data, cd->Length, kd); +} + +krb5_error_code pkiKrb5DataToCssm( + const krb5_data *kd, + CSSM_DATA *cd, + SecAsn1CoderRef coder) +{ + assert((cd != NULL) && (kd != NULL)); + if(SecAsn1AllocCopy(coder, kd->data, kd->length, cd)) { + return ENOMEM; + } + return 0; +} + +/* + * CFDataRef --> krb5_data, mallocing the destination contents. + */ +krb5_error_code pkiCfDataToKrb5Data( + CFDataRef cfData, + krb5_data *kd) /* content mallocd and RETURNED */ +{ + return pkiDataToKrb5Data(CFDataGetBytePtr(cfData), + CFDataGetLength(cfData), kd); +} + +krb5_boolean pkiCompareCssmData( + const CSSM_DATA *d1, + const CSSM_DATA *d2) +{ + if((d1 == NULL) || (d2 == NULL)) { + return FALSE; + } + if(d1->Length != d2->Length) { + return FALSE; + } + if(memcmp(d1->Data, d2->Data, d1->Length)) { + return FALSE; + } + else { + return TRUE; + } +} + +/* + * krb5_timestamp --> a mallocd string in generalized format + */ +krb5_error_code pkiKrbTimestampToStr( + krb5_timestamp kts, + char **str) /* mallocd and RETURNED */ +{ + time_t gmt_time = kts; + struct tm *utc = gmtime(&gmt_time); + if (utc == NULL || + utc->tm_year > 8099 || utc->tm_mon > 11 || + utc->tm_mday > 31 || utc->tm_hour > 23 || + utc->tm_min > 59 || utc->tm_sec > 59) { + return ASN1_BAD_GMTIME; + } + char *outStr = (char *)malloc(16); + if(outStr == NULL) { + return ENOMEM; + } + sprintf(outStr, "%04d%02d%02d%02d%02d%02dZ", + utc->tm_year + 1900, utc->tm_mon + 1, + utc->tm_mday, utc->tm_hour, utc->tm_min, utc->tm_sec); + *str = outStr; + return 0; +} + +krb5_error_code pkiTimeStrToKrbTimestamp( + const char *str, + unsigned len, + krb5_timestamp *kts) /* RETURNED */ +{ + char szTemp[5]; + unsigned x; + unsigned i; + char *cp; + struct tm tmp; + time_t t; + + if(len != 15) { + return ASN1_BAD_LENGTH; + } + + if((str == NULL) || (kts == NULL)) { + return KRB5_CRYPTO_INTERNAL; + } + + cp = (char *)str; + memset(&tmp, 0, sizeof(tmp)); + + /* check that all characters except last are digits */ + for(i=0; i<(len - 1); i++) { + if ( !(isdigit(cp[i])) ) { + return ASN1_BAD_TIMEFORMAT; + } + } + + /* check last character is a 'Z' */ + if(cp[len - 1] != 'Z' ) { + return ASN1_BAD_TIMEFORMAT; + } + + /* YEAR */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = *cp++; + szTemp[3] = *cp++; + szTemp[4] = '\0'; + x = atoi( szTemp ); + /* by definition - tm_year is year - 1900 */ + tmp.tm_year = x - 1900; + + /* MONTH */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = '\0'; + x = atoi( szTemp ); + /* in the string, months are from 1 to 12 */ + if((x > 12) || (x <= 0)) { + return ASN1_BAD_TIMEFORMAT; + } + /* in a tm, 0 to 11 */ + tmp.tm_mon = x - 1; + + /* DAY */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = '\0'; + x = atoi( szTemp ); + /* 1..31 */ + if((x > 31) || (x <= 0)) { + return ASN1_BAD_TIMEFORMAT; + } + tmp.tm_mday = x; + + /* HOUR */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = '\0'; + x = atoi( szTemp ); + if((x > 23) || (x < 0)) { + return ASN1_BAD_TIMEFORMAT; + } + tmp.tm_hour = x; + + /* MINUTE */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = '\0'; + x = atoi( szTemp ); + if((x > 59) || (x < 0)) { + return ASN1_BAD_TIMEFORMAT; + } + tmp.tm_min = x; + + /* SECOND */ + szTemp[0] = *cp++; + szTemp[1] = *cp++; + szTemp[2] = '\0'; + x = atoi( szTemp ); + if((x > 59) || (x < 0)) { + return ASN1_BAD_TIMEFORMAT; + } + tmp.tm_sec = x; + t = timegm(&tmp); + if(t == -1) { + return ASN1_BAD_TIMEFORMAT; + } + *kts = t; + return 0; +} + +/* + * How many items in a NULL-terminated array of pointers? + */ +unsigned pkiNssArraySize( + const void **array) +{ + unsigned count = 0; + if (array) { + while (*array++) { + count++; + } + } + return count; +} + +#endif /* APPLE_PKINIT */ diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index dbd00bf..96df2db 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -30,6 +30,10 @@ */ #include "k5-int.h" +#if APPLE_PKINIT +#include "pkinit_client.h" +#include "pkinit_cert_store.h" +#endif /* APPLE_PKINIT */ #include "osconf.h" #include <krb5/preauth_plugin.h> #include "int-proto.h" @@ -991,6 +995,279 @@ krb5_error_code pa_sam(krb5_context context, return(0); } +#if APPLE_PKINIT +/* + * PKINIT. One function to generate AS-REQ, one to parse AS-REP + */ +#define PKINIT_DEBUG 0 +#if PKINIT_DEBUG +#define kdcPkinitDebug(args...) printf(args) +#else +#define kdcPkinitDebug(args...) +#endif + +static krb5_error_code pa_pkinit_gen_req( + krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_data *s2kparams, + krb5_enctype *etype, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data) +{ + krb5_error_code krtn; + krb5_data out_data = {0, 0, NULL}; + krb5_timestamp kctime = 0; + krb5_int32 cusec = 0; + krb5_ui_4 nonce = 0; + krb5_checksum cksum; + krb5_pkinit_signing_cert_t client_cert; + krb5_data *der_req = NULL; + char *client_principal = NULL; + char *server_principal = NULL; + unsigned char nonce_bytes[4]; + krb5_data nonce_data = {0, 4, (char *)nonce_bytes}; + int dex; + + /* + * Trusted CA list and specific KC cert optionally obtained via + * krb5_pkinit_get_server_certs(). All are DER-encoded certs. + */ + krb5_data *trusted_CAs = NULL; + krb5_ui_4 num_trusted_CAs; + krb5_data kdc_cert = {0}; + + kdcPkinitDebug("pa_pkinit_gen_req\n"); + + /* If we don't have a client cert, we're done */ + if(request->client == NULL) { + kdcPkinitDebug("No request->client; aborting PKINIT\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + krtn = krb5_unparse_name(context, request->client, &client_principal); + if(krtn) { + return krtn; + } + krtn = krb5_pkinit_get_client_cert(client_principal, &client_cert); + free(client_principal); + if(krtn) { + kdcPkinitDebug("No client cert; aborting PKINIT\n"); + return krtn; + } + + /* optional platform-dependent CA list and KDC cert */ + krtn = krb5_unparse_name(context, request->server, &server_principal); + if(krtn) { + goto cleanup; + } + krtn = krb5_pkinit_get_server_certs(client_principal, server_principal, + &trusted_CAs, &num_trusted_CAs, &kdc_cert); + if(krtn) { + goto cleanup; + } + + /* checksum of the encoded KDC-REQ-BODY */ + krtn = encode_krb5_kdc_req_body(request, &der_req); + if(krtn) { + kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn); + goto cleanup; + } + krtn = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, der_req, &cksum); + if(krtn) { + goto cleanup; + } + + krtn = krb5_us_timeofday(context, &kctime, &cusec); + if(krtn) { + goto cleanup; + } + + /* cook up a random 4-byte nonce */ + krtn = krb5_c_random_make_octets(context, &nonce_data); + if(krtn) { + goto cleanup; + } + for(dex=0; dex<4; dex++) { + nonce <<= 8; + nonce |= nonce_bytes[dex]; + } + + krtn = krb5int_pkinit_as_req_create(context, + kctime, cusec, nonce, &cksum, + client_cert, + trusted_CAs, num_trusted_CAs, + (kdc_cert.data ? &kdc_cert : NULL), + &out_data); + if(krtn) { + kdcPkinitDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", (int)krtn); + goto cleanup; + } + *out_padata = (krb5_pa_data *)malloc(sizeof(krb5_pa_data)); + if(*out_padata == NULL) { + krtn = ENOMEM; + free(out_data.data); + goto cleanup; + } + (*out_padata)->magic = KV5M_PA_DATA; + (*out_padata)->pa_type = KRB5_PADATA_PK_AS_REQ; + (*out_padata)->length = out_data.length; + (*out_padata)->contents = (krb5_octet *)out_data.data; + krtn = 0; +cleanup: + if(client_cert) { + krb5_pkinit_release_cert(client_cert); + } + if(cksum.contents) { + free(cksum.contents); + } + if (der_req) { + krb5_free_data(context, der_req); + } + if(server_principal) { + free(server_principal); + } + /* free data mallocd by krb5_pkinit_get_server_certs() */ + if(trusted_CAs) { + unsigned udex; + for(udex=0; udex<num_trusted_CAs; udex++) { + free(trusted_CAs[udex].data); + } + free(trusted_CAs); + } + if(kdc_cert.data) { + free(kdc_cert.data); + } + return krtn; + +} + +static krb5_error_code pa_pkinit_parse_rep( + krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_data *s2kparams, + krb5_enctype *etype, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data) +{ + krb5int_cert_sig_status sig_status = (krb5int_cert_sig_status)-999; + krb5_error_code krtn; + krb5_data asRep; + krb5_keyblock local_key = {0}; + krb5_pkinit_signing_cert_t client_cert; + char *princ_name = NULL; + krb5_checksum as_req_checksum_rcd = {0}; /* received checksum */ + krb5_checksum as_req_checksum_gen = {0}; /* calculated checksum */ + krb5_data *encoded_as_req = NULL; + + *out_padata = NULL; + kdcPkinitDebug("pa_pkinit_parse_rep\n"); + if((in_padata == NULL) || (in_padata->length== 0)) { + kdcPkinitDebug("pa_pkinit_parse_rep: no in_padata\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + + /* If we don't have a client cert, we're done */ + if(request->client == NULL) { + kdcPkinitDebug("No request->client; aborting PKINIT\n"); + return KRB5KDC_ERR_PREAUTH_FAILED; + } + krtn = krb5_unparse_name(context, request->client, &princ_name); + if(krtn) { + return krtn; + } + krtn = krb5_pkinit_get_client_cert(princ_name, &client_cert); + free(princ_name); + if(krtn) { + kdcPkinitDebug("No client cert; aborting PKINIT\n"); + return krtn; + } + + memset(&local_key, 0, sizeof(local_key)); + asRep.data = (char *)in_padata->contents; + asRep.length = in_padata->length; + krtn = krb5int_pkinit_as_rep_parse(context, &asRep, client_cert, + &local_key, &as_req_checksum_rcd, &sig_status, + /* don't care about returned certs - do we? */ + NULL, NULL, NULL); + if(krtn) { + kdcPkinitDebug("pkinit_as_rep_parse returned %d\n", (int)krtn); + return krtn; + } + switch(sig_status) { + case pki_cs_good: + break; + default: + kdcPkinitDebug("pa_pkinit_parse_rep: bad cert/sig status %d\n", + (int)sig_status); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto error_out; + } + + /* calculate checksum of incoming AS-REQ using the decryption key + * we just got from the ReplyKeyPack */ + krtn = encode_krb5_as_req(request, &encoded_as_req); + if(krtn) { + goto error_out; + } + krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype, + &local_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + encoded_as_req, &as_req_checksum_gen); + if(krtn) { + goto error_out; + } + if((as_req_checksum_gen.length != as_req_checksum_rcd.length) || + memcmp(as_req_checksum_gen.contents, + as_req_checksum_rcd.contents, + as_req_checksum_gen.length)) { + kdcPkinitDebug("pa_pkinit_parse_rep: checksum miscompare\n"); + krtn = KRB5KDC_ERR_PREAUTH_FAILED; + goto error_out; + } + + /* We have the key; transfer to caller */ + if (as_key->length) { + krb5_free_keyblock_contents(context, as_key); + } + *as_key = local_key; + + #if PKINIT_DEBUG + fprintf(stderr, "pa_pkinit_parse_rep: SUCCESS\n"); + fprintf(stderr, "enctype %d keylen %d keydata %02x %02x %02x %02x...\n", + (int)as_key->enctype, (int)as_key->length, + as_key->contents[0], as_key->contents[1], + as_key->contents[2], as_key->contents[3]); + #endif + + krtn = 0; + +error_out: + if(as_req_checksum_rcd.contents) { + free(as_req_checksum_rcd.contents); + } + if(as_req_checksum_gen.contents) { + free(as_req_checksum_gen.contents); + } + if(encoded_as_req) { + krb5_free_data(context, encoded_as_req); + } + if(krtn && (local_key.contents != NULL)) { + krb5_free_keyblock_contents(context, &local_key); + } + return krtn; +} +#endif /* APPLE_PKINIT */ + static krb5_error_code pa_sam_2(krb5_context context, krb5_kdc_req *request, @@ -1320,6 +1597,7 @@ krb5_error_code pa_sam_2(krb5_context context, return(0); } +/* FIXME - order significant? */ static const pa_types_t pa_types[] = { { KRB5_PADATA_PW_SALT, @@ -1331,6 +1609,18 @@ static const pa_types_t pa_types[] = { pa_salt, PA_INFO, }, +#if APPLE_PKINIT + { + KRB5_PADATA_PK_AS_REQ, + pa_pkinit_gen_req, + PA_INFO, + }, + { + KRB5_PADATA_PK_AS_REP, + pa_pkinit_parse_rep, + PA_REAL, + }, +#endif /* APPLE_PKINIT */ { KRB5_PADATA_ENC_TIMESTAMP, pa_enc_timestamp, @@ -1596,6 +1886,17 @@ krb5_do_preauth(krb5_context context, salt, s2kparams, etype, as_key, prompter, prompter_data, gak_fct, gak_data)))) { + if (paorder[h] == PA_INFO) { +#ifdef DEBUG + fprintf (stderr, + "internal function for type %d, flag %d " + "failed with err %d\n", + in_padata[i]->pa_type, paorder[h], ret); +#endif + ret = 0; + continue; /* PA_INFO type failed, ignore */ + } + goto cleanup; } diff --git a/src/lib/krb5/krb/str_conv.c b/src/lib/krb5/krb/str_conv.c index a650496..fdc4d72 100644 --- a/src/lib/krb5/krb/str_conv.c +++ b/src/lib/krb5/krb/str_conv.c @@ -73,7 +73,10 @@ static const struct salttype_lookup_entry salttype_table[] = { { KRB5_KDB_SALTTYPE_NOREALM, "norealm", "Version 5 - No Realm" }, { KRB5_KDB_SALTTYPE_ONLYREALM, "onlyrealm", "Version 5 - Realm Only" }, { KRB5_KDB_SALTTYPE_SPECIAL, "special", "Special" }, -{ KRB5_KDB_SALTTYPE_AFS3, "afs3", "AFS version 3" } +{ KRB5_KDB_SALTTYPE_AFS3, "afs3", "AFS version 3" }, +#if PKINIT_APPLE +{ KRB5_KDB_SALTTYPE_CERTHASH, "certhash", "PKINIT Cert Hash" } +#endif /* PKINIT_APPLE */ }; static const int salttype_table_nents = sizeof(salttype_table)/ sizeof(salttype_table[0]); |