Apple PKINIT patch commit
authorAlexandra Ellwood <lxs@mit.edu>
Fri, 30 May 2008 20:47:03 +0000 (20:47 +0000)
committerAlexandra Ellwood <lxs@mit.edu>
Fri, 30 May 2008 20:47:03 +0000 (20:47 +0000)
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

20 files changed:
src/include/kdb.h
src/include/krb5/krb5.hin
src/include/pkinit_apple_utils.h [new file with mode: 0644]
src/include/pkinit_asn1.h [new file with mode: 0644]
src/include/pkinit_cert_store.h [new file with mode: 0644]
src/include/pkinit_client.h [new file with mode: 0644]
src/include/pkinit_cms.h [new file with mode: 0644]
src/kadmin/cli/kadmin.c
src/kdc/do_as_req.c
src/kdc/kdc_preauth.c
src/kdc/pkinit_apple_server.c [new file with mode: 0644]
src/kdc/pkinit_server.h [new file with mode: 0644]
src/lib/krb5/krb/get_in_tkt.c
src/lib/krb5/krb/pkinit_apple_asn1.c [new file with mode: 0644]
src/lib/krb5/krb/pkinit_apple_cert_store.c [new file with mode: 0644]
src/lib/krb5/krb/pkinit_apple_client.c [new file with mode: 0644]
src/lib/krb5/krb/pkinit_apple_cms.c [new file with mode: 0644]
src/lib/krb5/krb/pkinit_apple_utils.c [new file with mode: 0644]
src/lib/krb5/krb/preauth2.c
src/lib/krb5/krb/str_conv.c

index e8a58787c405034c58b5def170028cc51aa12d45..66e8d0698bf73deb7ad8b32cc1ffa8d57d78856e 100644 (file)
@@ -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
index 279304edfbac9347be6867d6c243ac5379243cf6..4192362c2db5c3a46c9e5d703370904619793c40 100644 (file)
@@ -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 (file)
index 0000000..313955f
--- /dev/null
@@ -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 (file)
index 0000000..b90ae59
--- /dev/null
@@ -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 (file)
index 0000000..1e848d7
--- /dev/null
@@ -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 (file)
index 0000000..31951ca
--- /dev/null
@@ -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 (file)
index 0000000..6e5fb96
--- /dev/null
@@ -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_ */
index 76f7c3e1acc7a331262f1bd3c6d07ec547a3b66e..98ff995008161218a30faf5cdcabff1c0be8460d 100644 (file)
@@ -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,
index 4c2a09b9522eb82629dd4c44baffb92cb0aa8fa8..5dbf7acc2e72fa299d677968ee50862436f5df59 100644 (file)
 #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;
index 67764b22e42a5617907153f5693009797d5f051c..a250cf4ecd7a68b7cbdb1d8b07722a85d366bc71 100644 (file)
 #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 (file)
index 0000000..b86c634
--- /dev/null
@@ -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 (file)
index 0000000..773b497
--- /dev/null
@@ -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_ */
index a4e2b013103cad1663bfa254249aae7700923ea9..5edbc26fa3abcf6d326f60ee2dcee32bf25accaf 100644 (file)
 #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 (file)
index 0000000..52ae1b0
--- /dev/null
@@ -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 (file)
index 0000000..06ac37d
--- /dev/null
@@ -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 (file)
index 0000000..d98fc76
--- /dev/null
@@ -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 (file)
index 0000000..353bcab
--- /dev/null
@@ -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 (file)
index 0000000..a457833
--- /dev/null
@@ -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 */
index dbd00bf2597b0c69d11f31338b87447ec191a743..96df2db0421a9f593091d6b4f0263c3ddcf6cd6e 100644 (file)
  */
 
 #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;
                    }
 
index a650496fc3acc3a8165d8d8bf1f70b9eed1a78fd..fdc4d727eb9ea494f530aa8208355a814e5c5e2e 100644 (file)
@@ -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]);