aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
committerAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
commit7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a (patch)
tree7cbcc11e0de0af794f9c2f16d03a6b1505e20a1d
parent8505824cad8ed0b6e8b96a5103cd43373c266996 (diff)
downloadkrb5-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.h1
-rw-r--r--src/include/krb5/krb5.hin9
-rw-r--r--src/include/pkinit_apple_utils.h151
-rw-r--r--src/include/pkinit_asn1.h172
-rw-r--r--src/include/pkinit_cert_store.h154
-rw-r--r--src/include/pkinit_client.h94
-rw-r--r--src/include/pkinit_cms.h179
-rw-r--r--src/kadmin/cli/kadmin.c95
-rw-r--r--src/kdc/do_as_req.c19
-rw-r--r--src/kdc/kdc_preauth.c502
-rw-r--r--src/kdc/pkinit_apple_server.c241
-rw-r--r--src/kdc/pkinit_server.h112
-rw-r--r--src/lib/krb5/krb/get_in_tkt.c76
-rw-r--r--src/lib/krb5/krb/pkinit_apple_asn1.c957
-rw-r--r--src/lib/krb5/krb/pkinit_apple_cert_store.c599
-rw-r--r--src/lib/krb5/krb/pkinit_apple_client.c263
-rw-r--r--src/lib/krb5/krb/pkinit_apple_cms.c559
-rw-r--r--src/lib/krb5/krb/pkinit_apple_utils.c433
-rw-r--r--src/lib/krb5/krb/preauth2.c301
-rw-r--r--src/lib/krb5/krb/str_conv.c5
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]);