summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
committerAlexandra Ellwood <lxs@mit.edu>2008-05-30 20:47:03 +0000
commit7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a (patch)
tree7cbcc11e0de0af794f9c2f16d03a6b1505e20a1d /src/lib
parent8505824cad8ed0b6e8b96a5103cd43373c266996 (diff)
downloadkrb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.gz
krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.tar.xz
krb5-7ee1ef1a8a7d8424faa3cf7df88b184b0f911b3a.zip
Apple PKINIT patch commit
Commit of Apple PKINIT patches under "APPLE_PKINIT" preprocessor symbol. Long term goal is to merge these patches with the pkinit preauth plugin which does not currently have support for Mac OS X crypto libraries or the exported functions used by Back To My Mac. ticket: new status: open git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20346 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/krb5/krb/get_in_tkt.c76
-rw-r--r--src/lib/krb5/krb/pkinit_apple_asn1.c957
-rw-r--r--src/lib/krb5/krb/pkinit_apple_cert_store.c599
-rw-r--r--src/lib/krb5/krb/pkinit_apple_client.c263
-rw-r--r--src/lib/krb5/krb/pkinit_apple_cms.c559
-rw-r--r--src/lib/krb5/krb/pkinit_apple_utils.c433
-rw-r--r--src/lib/krb5/krb/preauth2.c301
-rw-r--r--src/lib/krb5/krb/str_conv.c5
8 files changed, 3189 insertions, 4 deletions
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
index a4e2b0131..5edbc26fa 100644
--- a/src/lib/krb5/krb/get_in_tkt.c
+++ b/src/lib/krb5/krb/get_in_tkt.c
@@ -33,6 +33,15 @@
#include "int-proto.h"
#include "os-proto.h"
+#if APPLE_PKINIT
+#define IN_TKT_DEBUG 0
+#if IN_TKT_DEBUG
+#define inTktDebug(args...) printf(args)
+#else
+#define inTktDebug(args...)
+#endif
+#endif /* APPLE_PKINIT */
+
/*
All-purpose initial ticket routine, usually called via
krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
@@ -99,6 +108,26 @@ static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y)
return x + y;
}
+#if APPLE_PKINIT
+/*
+ * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this
+ * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this
+ * value is used interchangeably with an explicit now_time throughout this module...
+ */
+static krb5_error_code
+gen_nonce(krb5_context context,
+ krb5_int32 *nonce)
+{
+ krb5_int32 time_now;
+ krb5_error_code retval = krb5_timeofday(context, &time_now);
+ if(retval) {
+ return retval;
+ }
+ *nonce = time_now;
+ return 0;
+}
+#endif /* APPLE_PKINIT */
+
/*
* This function sends a request to the KDC, and gets back a response;
* the response is parsed into ret_err_reply or ret_as_reply if the
@@ -138,6 +167,10 @@ send_again:
retval = krb5_sendto_kdc(context, packet,
krb5_princ_realm(context, request->client),
&reply, use_master, tcp_only);
+#if APPLE_PKINIT
+ inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval);
+#endif /* APPLE_PKINIT */
+
if (retval)
goto cleanup;
@@ -285,8 +318,20 @@ verify_as_reply(krb5_context context,
(as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
(request->till != 0) &&
(as_reply->enc_part2->times.renew_till > request->till))
- )
+ ) {
+#if APPLE_PKINIT
+ inTktDebug("verify_as_reply: KDCREP_MODIFIED\n");
+ #if IN_TKT_DEBUG
+ if(request->client->realm.length && request->client->data->length)
+ inTktDebug("request: name %s realm %s\n",
+ request->client->realm.data, request->client->data->data);
+ if(as_reply->client->realm.length && as_reply->client->data->length)
+ inTktDebug("reply : name %s realm %s\n",
+ as_reply->client->realm.data, as_reply->client->data->data);
+ #endif
+#endif /* APPLE_PKINIT */
return KRB5_KDCREP_MODIFIED;
+ }
if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
retval = krb5_set_real_time(context,
@@ -464,6 +509,10 @@ krb5_get_in_tkt(krb5_context context,
krb5_int32 do_more = 0;
int use_master = 0;
+#if APPLE_PKINIT
+ inTktDebug("krb5_get_in_tkt top\n");
+#endif /* APPLE_PKINIT */
+
if (! krb5_realm_compare(context, creds->client, creds->server))
return KRB5_IN_TKT_REALM_MISMATCH;
@@ -490,6 +539,13 @@ krb5_get_in_tkt(krb5_context context,
request.from = creds->times.starttime;
request.till = creds->times.endtime;
request.rtime = creds->times.renew_till;
+#if APPLE_PKINIT
+ retval = gen_nonce(context, (krb5_int32 *)&time_now);
+ if(retval) {
+ goto cleanup;
+ }
+ request.nonce = time_now;
+#endif /* APPLE_PKINIT */
request.ktype = malloc (sizeof(get_in_tkt_enctypes));
if (request.ktype == NULL) {
@@ -545,6 +601,9 @@ krb5_get_in_tkt(krb5_context context,
goto cleanup;
}
+#if APPLE_PKINIT
+ inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n");
+#endif /* APPLE_PKINIT */
if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
keyseed, creds, &request)) != 0)
goto cleanup;
@@ -884,7 +943,10 @@ krb5_get_init_creds(krb5_context context,
salt.data = NULL;
local_as_reply = 0;
-
+#if APPLE_PKINIT
+ inTktDebug("krb5_get_init_creds top\n");
+#endif /* APPLE_PKINIT */
+
err_reply = NULL;
/*
@@ -1210,6 +1272,10 @@ krb5_get_init_creds(krb5_context context,
}
}
+#if APPLE_PKINIT
+ inTktDebug("krb5_get_init_creds done with send_as_request loop lc %d\n",
+ (int)loopcount);
+#endif /* APPLE_PKINIT */
if (loopcount == MAX_IN_TKT_LOOPS) {
ret = KRB5_GET_IN_TKT_LOOP;
goto cleanup;
@@ -1227,8 +1293,12 @@ krb5_get_init_creds(krb5_context context,
local_as_reply->padata, &kdc_padata,
&salt, &s2kparams, &etype, &as_key, prompter,
prompter_data, gak_fct, gak_data,
- &get_data_rock, options)))
+ &get_data_rock, options))) {
+#if APPLE_PKINIT
+ inTktDebug("krb5_get_init_creds krb5_do_preauth returned %d\n", (int)ret);
+#endif /* APPLE_PKINIT */
goto cleanup;
+ }
/* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
the AS_REP comes back encrypted in the user's longterm key
diff --git a/src/lib/krb5/krb/pkinit_apple_asn1.c b/src/lib/krb5/krb/pkinit_apple_asn1.c
new file mode 100644
index 000000000..52ae1b040
--- /dev/null
+++ b/src/lib/krb5/krb/pkinit_apple_asn1.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * pkinit_apple_asn1.c - ASN.1 encode/decode routines for PKINIT, Mac OS X version
+ *
+ * Created 19 May 2004 by Doug Mitchell.
+ */
+
+#if APPLE_PKINIT
+
+#include "k5-int.h"
+#include "pkinit_asn1.h"
+#include "pkinit_apple_utils.h"
+#include <stddef.h>
+#include <Security/SecAsn1Types.h>
+#include <Security/SecAsn1Templates.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/Security.h>
+#include <sys/errno.h>
+#include <assert.h>
+#include <strings.h>
+
+#pragma mark ----- utility routines -----
+
+/* malloc a NULL-ed array of pointers of size num+1 */
+static void **pkiNssNullArray(
+ uint32 num,
+ SecAsn1CoderRef coder)
+{
+ unsigned len = (num + 1) * sizeof(void *);
+ void **p = (void **)SecAsn1Malloc(coder, len);
+ memset(p, 0, len);
+ return p;
+}
+
+#pragma mark ====== begin PA-PK-AS-REQ components ======
+
+#pragma mark ----- pkAuthenticator -----
+
+/*
+ * There is a unique error code for "missing paChecksum", so we mark it here
+ * as optional so the decoder can process a pkAuthenticator without the
+ * checksum; caller must verify that paChecksum.Data != NULL.
+ */
+typedef struct {
+ CSSM_DATA cusec; /* INTEGER, microseconds */
+ CSSM_DATA kctime; /* UTC time (with trailing 'Z') */
+ CSSM_DATA nonce; /* INTEGER */
+ CSSM_DATA paChecksum; /* OCTET STRING */
+} KRB5_PKAuthenticator;
+
+static const SecAsn1Template KRB5_PKAuthenticatorTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PKAuthenticator) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_PKAuthenticator,cusec),
+ kSecAsn1IntegerTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_PKAuthenticator,kctime),
+ kSecAsn1GeneralizedTimeTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 2,
+ offsetof(KRB5_PKAuthenticator,nonce),
+ kSecAsn1IntegerTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT |
+ SEC_ASN1_OPTIONAL | 3,
+ offsetof(KRB5_PKAuthenticator,paChecksum),
+ &kSecAsn1OctetStringTemplate },
+ { 0 }
+};
+
+#pragma mark ----- AuthPack -----
+
+typedef struct {
+ KRB5_PKAuthenticator pkAuth;
+ CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *pubKeyInfo; /* OPTIONAL */
+ CSSM_X509_ALGORITHM_IDENTIFIER **supportedCMSTypes;/* OPTIONAL */
+ CSSM_DATA *clientDHNonce; /* OPTIONAL */
+} KRB5_AuthPack;
+
+/*
+ * These are copied from keyTemplates.c in the libsecurity_asn1 project;
+ * they aren't public API.
+ */
+
+/* AlgorithmIdentifier : CSSM_X509_ALGORITHM_IDENTIFIER */
+static const SecAsn1Template AlgorithmIDTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CSSM_X509_ALGORITHM_IDENTIFIER) },
+ { SEC_ASN1_OBJECT_ID,
+ offsetof(CSSM_X509_ALGORITHM_IDENTIFIER,algorithm), },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY,
+ offsetof(CSSM_X509_ALGORITHM_IDENTIFIER,parameters), },
+ { 0, }
+};
+
+
+/* SubjectPublicKeyInfo : CSSM_X509_SUBJECT_PUBLIC_KEY_INFO */
+static const SecAsn1Template SubjectPublicKeyInfoTemplate[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO) },
+ { SEC_ASN1_INLINE,
+ offsetof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO,algorithm),
+ AlgorithmIDTemplate },
+ { SEC_ASN1_BIT_STRING,
+ offsetof(CSSM_X509_SUBJECT_PUBLIC_KEY_INFO,subjectPublicKey), },
+ { 0, }
+};
+
+/* end of copied templates */
+
+static const SecAsn1Template kSecAsn1SequenceOfAlgIdTemplate[] = {
+ { SEC_ASN1_SEQUENCE_OF, 0, AlgorithmIDTemplate }
+};
+
+static const SecAsn1Template KRB5_AuthPackTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_AuthPack) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_AuthPack,pkAuth),
+ KRB5_PKAuthenticatorTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL |
+ SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 1,
+ offsetof(KRB5_AuthPack,pubKeyInfo),
+ SubjectPublicKeyInfoTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL |
+ SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 2,
+ offsetof(KRB5_AuthPack,supportedCMSTypes),
+ kSecAsn1SequenceOfAlgIdTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL |
+ SEC_ASN1_EXPLICIT | SEC_ASN1_POINTER | 3,
+ offsetof(KRB5_AuthPack,clientDHNonce),
+ kSecAsn1OctetStringTemplate },
+ { 0 }
+};
+
+/*
+ * Encode AuthPack, public key version (no Diffie-Hellman components).
+ */
+krb5_error_code krb5int_pkinit_auth_pack_encode(
+ krb5_timestamp kctime,
+ krb5_int32 cusec, /* microseconds */
+ krb5_ui_4 nonce,
+ const krb5_checksum *pa_checksum,
+ const krb5int_algorithm_id *cms_types, /* optional */
+ krb5_ui_4 num_cms_types,
+ krb5_data *auth_pack) /* mallocd and RETURNED */
+{
+ KRB5_AuthPack localAuthPack;
+ SecAsn1CoderRef coder;
+ CSSM_DATA *cksum = &localAuthPack.pkAuth.paChecksum;
+ krb5_error_code ourRtn = 0;
+ CSSM_DATA ber = {0, NULL};
+ OSStatus ortn;
+ char *timeStr = NULL;
+
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&localAuthPack, 0, sizeof(localAuthPack));
+ if(pkiKrbTimestampToStr(kctime, &timeStr)) {
+ ourRtn = -1;
+ goto errOut;
+ }
+ localAuthPack.pkAuth.kctime.Data = (uint8 *)timeStr;
+ localAuthPack.pkAuth.kctime.Length = strlen(timeStr);
+ if(pkiIntToData(cusec, &localAuthPack.pkAuth.cusec, coder)) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ if(pkiIntToData(nonce, &localAuthPack.pkAuth.nonce, coder)) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ cksum->Data = (uint8 *)pa_checksum->contents;
+ cksum->Length = pa_checksum->length;
+
+ if((cms_types != NULL) && (num_cms_types != 0)) {
+ unsigned dex;
+ CSSM_X509_ALGORITHM_IDENTIFIER **algIds;
+
+ /* build a NULL_terminated array of CSSM_X509_ALGORITHM_IDENTIFIERs */
+ localAuthPack.supportedCMSTypes = (CSSM_X509_ALGORITHM_IDENTIFIER **)
+ SecAsn1Malloc(coder,
+ (num_cms_types + 1) * sizeof(CSSM_X509_ALGORITHM_IDENTIFIER *));
+ algIds = localAuthPack.supportedCMSTypes;
+ for(dex=0; dex<num_cms_types; dex++) {
+ algIds[dex] = (CSSM_X509_ALGORITHM_IDENTIFIER *)
+ SecAsn1Malloc(coder, sizeof(CSSM_X509_ALGORITHM_IDENTIFIER));
+ pkiKrb5DataToCssm(&cms_types[dex].algorithm,
+ &algIds[dex]->algorithm, coder);
+ if(cms_types[dex].parameters.data != NULL) {
+ pkiKrb5DataToCssm(&cms_types[dex].parameters,
+ &algIds[dex]->parameters, coder);
+ }
+ else {
+ algIds[dex]->parameters.Data = NULL;
+ algIds[dex]->parameters.Length = 0;
+ }
+ }
+ algIds[num_cms_types] = NULL;
+ }
+ ortn = SecAsn1EncodeItem(coder, &localAuthPack, KRB5_AuthPackTemplate, &ber);
+ if(ortn) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+
+ if(pkiCssmDataToKrb5Data(&ber, auth_pack)) {
+ ourRtn = ENOMEM;
+ }
+ else {
+ auth_pack->magic = KV5M_AUTHENTICATOR;
+ ourRtn = 0;
+ }
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+/*
+ * Decode AuthPack, public key version (no Diffie-Hellman components).
+ */
+krb5_error_code krb5int_pkinit_auth_pack_decode(
+ const krb5_data *auth_pack, /* DER encoded */
+ krb5_timestamp *kctime, /* RETURNED */
+ krb5_ui_4 *cusec, /* microseconds, RETURNED */
+ krb5_ui_4 *nonce, /* RETURNED */
+ krb5_checksum *pa_checksum, /* contents mallocd and RETURNED */
+ krb5int_algorithm_id **cms_types, /* optionally mallocd and RETURNED */
+ krb5_ui_4 *num_cms_types) /* optionally RETURNED */
+{
+ KRB5_AuthPack localAuthPack;
+ SecAsn1CoderRef coder;
+ CSSM_DATA der = {0, NULL};
+ krb5_error_code ourRtn = 0;
+ CSSM_DATA *cksum = &localAuthPack.pkAuth.paChecksum;
+
+ /* Decode --> localAuthPack */
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ PKI_KRB_TO_CSSM_DATA(auth_pack, &der);
+ memset(&localAuthPack, 0, sizeof(localAuthPack));
+ if(SecAsn1DecodeData(coder, &der, KRB5_AuthPackTemplate, &localAuthPack)) {
+ ourRtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+
+ /* optionally Convert KRB5_AuthPack to caller's params */
+ if(kctime) {
+ if((ourRtn = pkiTimeStrToKrbTimestamp((char *)localAuthPack.pkAuth.kctime.Data,
+ localAuthPack.pkAuth.kctime.Length, kctime))) {
+ goto errOut;
+ }
+ }
+ if(cusec) {
+ if((ourRtn = pkiDataToInt(&localAuthPack.pkAuth.cusec, (krb5_int32 *)cusec))) {
+ goto errOut;
+ }
+ }
+ if(nonce) {
+ if((ourRtn = pkiDataToInt(&localAuthPack.pkAuth.nonce, (krb5_int32 *)nonce))) {
+ goto errOut;
+ }
+ }
+ if(pa_checksum) {
+ if(cksum->Length == 0) {
+ /* This is the unique error for "no paChecksum" */
+ ourRtn = KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED;
+ goto errOut;
+ }
+ else {
+ pa_checksum->contents = (krb5_octet *)malloc(cksum->Length);
+ if(pa_checksum->contents == NULL) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ pa_checksum->length = cksum->Length;
+ memmove(pa_checksum->contents, cksum->Data, pa_checksum->length);
+ pa_checksum->magic = KV5M_CHECKSUM;
+ /* This used to be encoded with the checksum but no more... */
+ pa_checksum->checksum_type = CKSUMTYPE_NIST_SHA;
+ }
+ }
+ if(cms_types) {
+ if(localAuthPack.supportedCMSTypes == NULL) {
+ *cms_types = NULL;
+ *num_cms_types = 0;
+ }
+ else {
+ /*
+ * Convert NULL-terminated array of CSSM-style algIds to
+ * krb5int_algorithm_ids.
+ */
+ unsigned dex;
+ unsigned num_types = 0;
+ CSSM_X509_ALGORITHM_IDENTIFIER **alg_ids;
+ krb5int_algorithm_id *kalg_ids;
+
+ for(alg_ids=localAuthPack.supportedCMSTypes;
+ *alg_ids;
+ alg_ids++) {
+ num_types++;
+ }
+ *cms_types = kalg_ids = (krb5int_algorithm_id *)malloc(
+ sizeof(krb5int_algorithm_id) * num_types);
+ *num_cms_types = num_types;
+ memset(kalg_ids, 0, sizeof(krb5int_algorithm_id) * num_types);
+ alg_ids = localAuthPack.supportedCMSTypes;
+ for(dex=0; dex<num_types; dex++) {
+ if(alg_ids[dex]->algorithm.Data) {
+ pkiCssmDataToKrb5Data(&alg_ids[dex]->algorithm,
+ &kalg_ids[dex].algorithm);
+ }
+ if(alg_ids[dex]->parameters.Data) {
+ pkiCssmDataToKrb5Data(&alg_ids[dex]->parameters,
+ &kalg_ids[dex].parameters);
+ }
+ }
+ }
+ }
+ ourRtn = 0;
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+#pragma mark ----- IssuerAndSerialNumber -----
+
+/*
+ * Issuer/serial number - specify issuer as ASN_ANY because we can get it from
+ * CL in DER-encoded state.
+ */
+typedef struct {
+ CSSM_DATA derIssuer;
+ CSSM_DATA serialNumber;
+} KRB5_IssuerAndSerial;
+
+static const SecAsn1Template KRB5_IssuerAndSerialTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_IssuerAndSerial) },
+ { SEC_ASN1_ANY, offsetof(KRB5_IssuerAndSerial, derIssuer) },
+ { SEC_ASN1_INTEGER, offsetof(KRB5_IssuerAndSerial, serialNumber) },
+ { 0 }
+};
+
+/*
+ * Given DER-encoded issuer and serial number, create an encoded
+ * IssuerAndSerialNumber.
+ */
+krb5_error_code krb5int_pkinit_issuer_serial_encode(
+ const krb5_data *issuer, /* DER encoded */
+ const krb5_data *serial_num,
+ krb5_data *issuer_and_serial) /* content mallocd and RETURNED */
+{
+ KRB5_IssuerAndSerial issuerSerial;
+ SecAsn1CoderRef coder;
+ CSSM_DATA ber = {0, NULL};
+ OSStatus ortn;
+
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ PKI_KRB_TO_CSSM_DATA(issuer, &issuerSerial.derIssuer);
+ PKI_KRB_TO_CSSM_DATA(serial_num, &issuerSerial.serialNumber);
+ ortn = SecAsn1EncodeItem(coder, &issuerSerial, KRB5_IssuerAndSerialTemplate, &ber);
+ if(ortn) {
+ ortn = ENOMEM;
+ goto errOut;
+ }
+ ortn = pkiCssmDataToKrb5Data(&ber, issuer_and_serial);
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ortn;
+}
+
+/*
+ * Decode IssuerAndSerialNumber.
+ */
+krb5_error_code krb5int_pkinit_issuer_serial_decode(
+ const krb5_data *issuer_and_serial, /* DER encoded */
+ krb5_data *issuer, /* DER encoded, RETURNED */
+ krb5_data *serial_num) /* RETURNED */
+{
+ KRB5_IssuerAndSerial issuerSerial;
+ SecAsn1CoderRef coder;
+ CSSM_DATA der = {issuer_and_serial->length, (uint8 *)issuer_and_serial->data};
+ krb5_error_code ourRtn = 0;
+
+ /* Decode --> issuerSerial */
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&issuerSerial, 0, sizeof(issuerSerial));
+ if(SecAsn1DecodeData(coder, &der, KRB5_IssuerAndSerialTemplate, &issuerSerial)) {
+ ourRtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+
+ /* Convert KRB5_IssuerAndSerial to caller's params */
+ if((ourRtn = pkiCssmDataToKrb5Data(&issuerSerial.derIssuer, issuer))) {
+ goto errOut;
+ }
+ if((ourRtn = pkiCssmDataToKrb5Data(&issuerSerial.serialNumber, serial_num))) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+#pragma mark ----- ExternalPrincipalIdentifier -----
+
+/*
+ * Shown here for completeness; this module only implements the
+ * issuerAndSerialNumber option.
+ */
+typedef struct {
+ CSSM_DATA subjectName; /* [0] IMPLICIT OCTET STRING OPTIONAL */
+ /* contents = encoded Name */
+ CSSM_DATA issuerAndSerialNumber; /* [1] IMPLICIT OCTET STRING OPTIONAL */
+ /* contents = encoded Issuer&Serial */
+ CSSM_DATA subjectKeyIdentifier; /* [2] IMPLICIT OCTET STRING OPTIONAL */
+ /* contents = encoded subjectKeyIdentifier extension */
+} KRB5_ExternalPrincipalIdentifier;
+
+static const SecAsn1Template KRB5_ExternalPrincipalIdentifierTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_ExternalPrincipalIdentifier) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 0,
+ offsetof(KRB5_ExternalPrincipalIdentifier, subjectName),
+ kSecAsn1OctetStringTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 1,
+ offsetof(KRB5_ExternalPrincipalIdentifier, issuerAndSerialNumber),
+ kSecAsn1OctetStringTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_OPTIONAL | 2,
+ offsetof(KRB5_ExternalPrincipalIdentifier, subjectKeyIdentifier),
+ kSecAsn1OctetStringTemplate },
+ { 0 }
+};
+
+static const SecAsn1Template KRB5_SequenceOfExternalPrincipalIdentifierTemplate[] = {
+ { SEC_ASN1_SEQUENCE_OF, 0, KRB5_ExternalPrincipalIdentifierTemplate }
+};
+
+#pragma mark ----- PA-PK-AS-REQ -----
+
+/*
+ * Top-level PA-PK-AS-REQ. All fields except for trusted_CAs are pre-encoded
+ * before we encode this and are still DER-encoded after we decode.
+ * The signedAuthPack and kdcPkId fields are wrapped in OCTET STRINGs
+ * during encode; we strip off the OCTET STRING wrappers during decode.
+ */
+typedef struct {
+ CSSM_DATA signedAuthPack; /* ContentInfo, SignedData */
+ /* Content is KRB5_AuthPack */
+ KRB5_ExternalPrincipalIdentifier
+ **trusted_CAs; /* optional */
+ CSSM_DATA kdcPkId; /* optional */
+} KRB5_PA_PK_AS_REQ;
+
+static const SecAsn1Template KRB5_PA_PK_AS_REQTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PA_PK_AS_REQ) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | 0,
+ offsetof(KRB5_PA_PK_AS_REQ, signedAuthPack),
+ kSecAsn1OctetStringTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC |
+ SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_PA_PK_AS_REQ, trusted_CAs),
+ KRB5_SequenceOfExternalPrincipalIdentifierTemplate },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 2,
+ offsetof(KRB5_PA_PK_AS_REQ, kdcPkId),
+ kSecAsn1AnyTemplate },
+ { 0 }
+};
+
+/*
+ * Top-level encode for PA-PK-AS-REQ.
+ */
+krb5_error_code krb5int_pkinit_pa_pk_as_req_encode(
+ const krb5_data *signed_auth_pack, /* DER encoded ContentInfo */
+ const krb5_data *trusted_CAs, /* optional: trustedCertifiers. Contents are
+ * DER-encoded issuer/serialNumbers. */
+ krb5_ui_4 num_trusted_CAs,
+ const krb5_data *kdc_cert, /* optional kdcPkId, DER encoded issuer/serial */
+ krb5_data *pa_pk_as_req) /* mallocd and RETURNED */
+{
+ KRB5_PA_PK_AS_REQ req;
+ SecAsn1CoderRef coder;
+ CSSM_DATA ber = {0, NULL};
+ OSStatus ortn;
+ unsigned dex;
+
+ assert(signed_auth_pack != NULL);
+ assert(pa_pk_as_req != NULL);
+
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+
+ /* krb5_data ==> CSSM format */
+
+ memset(&req, 0, sizeof(req));
+ PKI_KRB_TO_CSSM_DATA(signed_auth_pack, &req.signedAuthPack);
+ if(num_trusted_CAs) {
+ /*
+ * Set up a NULL-terminated array of KRB5_ExternalPrincipalIdentifier
+ * pointers. We malloc the actual KRB5_ExternalPrincipalIdentifiers as
+ * a contiguous array; it's in temp SecAsn1CoderRef memory. The referents
+ * are just dropped in from the caller's krb5_datas.
+ */
+ KRB5_ExternalPrincipalIdentifier *cas =
+ (KRB5_ExternalPrincipalIdentifier *)SecAsn1Malloc(coder,
+ num_trusted_CAs * sizeof(KRB5_ExternalPrincipalIdentifier));
+ req.trusted_CAs =
+ (KRB5_ExternalPrincipalIdentifier **)
+ pkiNssNullArray(num_trusted_CAs, coder);
+ for(dex=0; dex<num_trusted_CAs; dex++) {
+ req.trusted_CAs[dex] = &cas[dex];
+ memset(&cas[dex], 0, sizeof(KRB5_ExternalPrincipalIdentifier));
+ PKI_KRB_TO_CSSM_DATA(&trusted_CAs[dex],
+ &cas[dex].issuerAndSerialNumber);
+ }
+ }
+ if(kdc_cert) {
+ PKI_KRB_TO_CSSM_DATA(kdc_cert, &req.kdcPkId);
+ }
+
+ /* encode */
+ ortn = SecAsn1EncodeItem(coder, &req, KRB5_PA_PK_AS_REQTemplate, &ber);
+ if(ortn) {
+ ortn = ENOMEM;
+ goto errOut;
+ }
+ ortn = pkiCssmDataToKrb5Data(&ber, pa_pk_as_req);
+
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ortn;
+}
+
+/*
+ * Top-level decode for PA-PK-AS-REQ.
+ */
+krb5_error_code krb5int_pkinit_pa_pk_as_req_decode(
+ const krb5_data *pa_pk_as_req,
+ krb5_data *signed_auth_pack, /* DER encoded ContentInfo, RETURNED */
+ /*
+ * Remainder are optionally RETURNED (specify NULL for pointers to
+ * items you're not interested in).
+ */
+ krb5_ui_4 *num_trusted_CAs, /* sizeof trusted_CAs */
+ krb5_data **trusted_CAs, /* mallocd array of DER-encoded TrustedCAs issuer/serial */
+ krb5_data *kdc_cert) /* DER encoded issuer/serial */
+{
+ KRB5_PA_PK_AS_REQ asReq;
+ SecAsn1CoderRef coder;
+ CSSM_DATA der;
+ krb5_error_code ourRtn = 0;
+
+ assert(pa_pk_as_req != NULL);
+
+ /* Decode --> KRB5_PA_PK_AS_REQ */
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ PKI_KRB_TO_CSSM_DATA(pa_pk_as_req, &der);
+ memset(&asReq, 0, sizeof(asReq));
+ if(SecAsn1DecodeData(coder, &der, KRB5_PA_PK_AS_REQTemplate, &asReq)) {
+ ourRtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+
+ /* Convert decoded results to caller's args; each is optional */
+ if(signed_auth_pack != NULL) {
+ if((ourRtn = pkiCssmDataToKrb5Data(&asReq.signedAuthPack, signed_auth_pack))) {
+ goto errOut;
+ }
+ }
+ if(asReq.trusted_CAs && (trusted_CAs != NULL)) {
+ /* NULL-terminated array of CSSM_DATA ptrs */
+ unsigned numCas = pkiNssArraySize((const void **)asReq.trusted_CAs);
+ unsigned dex;
+ krb5_data *kdcCas;
+
+ kdcCas = (krb5_data *)malloc(sizeof(krb5_data) * numCas);
+ if(kdcCas == NULL) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ for(dex=0; dex<numCas; dex++) {
+ KRB5_ExternalPrincipalIdentifier *epi = asReq.trusted_CAs[dex];
+ if(epi->issuerAndSerialNumber.Data) {
+ /* the only variant we support */
+ pkiCssmDataToKrb5Data(&epi->issuerAndSerialNumber, &kdcCas[dex]);
+ }
+ }
+ *trusted_CAs = kdcCas;
+ *num_trusted_CAs = numCas;
+ }
+ if(asReq.kdcPkId.Data && kdc_cert) {
+ if((ourRtn = pkiCssmDataToKrb5Data(&asReq.kdcPkId, kdc_cert))) {
+ goto errOut;
+ }
+ }
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+#pragma mark ====== begin PA-PK-AS-REP components ======
+
+typedef struct {
+ CSSM_DATA subjectPublicKey; /* BIT STRING */
+ CSSM_DATA nonce; /* from KRB5_PKAuthenticator.nonce */
+ CSSM_DATA *expiration; /* optional UTC time */
+} KRB5_KDC_DHKeyInfo;
+
+typedef struct {
+ CSSM_DATA keyType;
+ CSSM_DATA keyValue;
+} KRB5_EncryptionKey;
+
+static const SecAsn1Template KRB5_EncryptionKeyTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_EncryptionKey) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_EncryptionKey, keyType),
+ kSecAsn1IntegerTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_EncryptionKey, keyValue),
+ kSecAsn1OctetStringTemplate },
+ { 0 }
+};
+
+#pragma mark ----- Checksum -----
+
+typedef struct {
+ CSSM_DATA checksumType;
+ CSSM_DATA checksum;
+} KRB5_Checksum;
+
+static const SecAsn1Template KRB5_ChecksumTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_Checksum) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_Checksum,checksumType),
+ kSecAsn1IntegerTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_Checksum,checksum),
+ kSecAsn1OctetStringTemplate },
+ { 0 }
+};
+
+typedef struct {
+ KRB5_EncryptionKey encryptionKey;
+ KRB5_Checksum asChecksum;
+} KRB5_ReplyKeyPack;
+
+static const SecAsn1Template KRB5_ReplyKeyPackTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_ReplyKeyPack) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_ReplyKeyPack, encryptionKey),
+ KRB5_EncryptionKeyTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_ReplyKeyPack,asChecksum),
+ KRB5_ChecksumTemplate },
+ { 0 }
+};
+
+/*
+ * Encode a ReplyKeyPack. The result is used as the Content of a SignedData.
+ */
+krb5_error_code krb5int_pkinit_reply_key_pack_encode(
+ const krb5_keyblock *key_block,
+ const krb5_checksum *checksum,
+ krb5_data *reply_key_pack) /* mallocd and RETURNED */
+{
+ KRB5_ReplyKeyPack repKeyPack;
+ SecAsn1CoderRef coder;
+ krb5_error_code ourRtn = 0;
+ CSSM_DATA der = {0, NULL};
+ OSStatus ortn;
+ KRB5_EncryptionKey *encryptKey = &repKeyPack.encryptionKey;
+ KRB5_Checksum *cksum = &repKeyPack.asChecksum;
+
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&repKeyPack, 0, sizeof(repKeyPack));
+
+ if((ourRtn = pkiIntToData(key_block->enctype, &encryptKey->keyType, coder))) {
+ goto errOut;
+ }
+ encryptKey->keyValue.Length = key_block->length,
+ encryptKey->keyValue.Data = (uint8 *)key_block->contents;
+
+ if((ourRtn = pkiIntToData(checksum->checksum_type, &cksum->checksumType, coder))) {
+ goto errOut;
+ }
+ cksum->checksum.Data = (uint8 *)checksum->contents;
+ cksum->checksum.Length = checksum->length;
+
+ ortn = SecAsn1EncodeItem(coder, &repKeyPack, KRB5_ReplyKeyPackTemplate, &der);
+ if(ortn) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ ourRtn = pkiCssmDataToKrb5Data(&der, reply_key_pack);
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+/*
+ * Decode a ReplyKeyPack.
+ */
+krb5_error_code krb5int_pkinit_reply_key_pack_decode(
+ const krb5_data *reply_key_pack,
+ krb5_keyblock *key_block, /* RETURNED */
+ krb5_checksum *checksum) /* contents mallocd and RETURNED */
+{
+ KRB5_ReplyKeyPack repKeyPack;
+ SecAsn1CoderRef coder;
+ krb5_error_code ourRtn = 0;
+ KRB5_EncryptionKey *encryptKey = &repKeyPack.encryptionKey;
+ CSSM_DATA der = {reply_key_pack->length, (uint8 *)reply_key_pack->data};
+ krb5_data tmpData;
+ KRB5_Checksum *cksum = &repKeyPack.asChecksum;
+
+ /* Decode --> KRB5_ReplyKeyPack */
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&repKeyPack, 0, sizeof(repKeyPack));
+ if(SecAsn1DecodeData(coder, &der, KRB5_ReplyKeyPackTemplate, &repKeyPack)) {
+ ourRtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+
+ if((ourRtn = pkiDataToInt(&encryptKey->keyType, (krb5_int32 *)&key_block->enctype))) {
+ goto errOut;
+ }
+ if((ourRtn = pkiCssmDataToKrb5Data(&encryptKey->keyValue, &tmpData))) {
+ goto errOut;
+ }
+ key_block->contents = (krb5_octet *)tmpData.data;
+ key_block->length = tmpData.length;
+
+ if((ourRtn = pkiDataToInt(&cksum->checksumType, &checksum->checksum_type))) {
+ goto errOut;
+ }
+ checksum->contents = (krb5_octet *)malloc(cksum->checksum.Length);
+ if(checksum->contents == NULL) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ checksum->length = cksum->checksum.Length;
+ memmove(checksum->contents, cksum->checksum.Data, checksum->length);
+ checksum->magic = KV5M_CHECKSUM;
+
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+
+#pragma mark ----- KRB5_PA_PK_AS_REP -----
+/*
+ * Top-level PA-PK-AS-REP. Exactly one of the optional fields must be present.
+ */
+typedef struct {
+ CSSM_DATA *dhSignedData; /* ContentInfo, SignedData */
+ /* Content is KRB5_KDC_DHKeyInfo */
+ CSSM_DATA *encKeyPack; /* ContentInfo, SignedData */
+ /* Content is ReplyKeyPack */
+} KRB5_PA_PK_AS_REP;
+
+static const SecAsn1Template KRB5_PA_PK_AS_REPTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(KRB5_PA_PK_AS_REP) },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL |
+ SEC_ASN1_EXPLICIT | 0,
+ offsetof(KRB5_PA_PK_AS_REP, dhSignedData),
+ kSecAsn1PointerToAnyTemplate },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_OPTIONAL |
+ SEC_ASN1_EXPLICIT | 1,
+ offsetof(KRB5_PA_PK_AS_REP, encKeyPack),
+ kSecAsn1PointerToAnyTemplate },
+ { 0 }
+};
+
+/*
+ * Encode a KRB5_PA_PK_AS_REP.
+ */
+krb5_error_code krb5int_pkinit_pa_pk_as_rep_encode(
+ const krb5_data *dh_signed_data,
+ const krb5_data *enc_key_pack,
+ krb5_data *pa_pk_as_rep) /* mallocd and RETURNED */
+{
+ KRB5_PA_PK_AS_REP asRep;
+ SecAsn1CoderRef coder;
+ krb5_error_code ourRtn = 0;
+ CSSM_DATA der = {0, NULL};
+ OSStatus ortn;
+ CSSM_DATA dhSignedData;
+ CSSM_DATA encKeyPack;
+
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&asRep, 0, sizeof(asRep));
+ if(dh_signed_data) {
+ PKI_KRB_TO_CSSM_DATA(dh_signed_data, &dhSignedData);
+ asRep.dhSignedData = &dhSignedData;
+ }
+ if(enc_key_pack) {
+ PKI_KRB_TO_CSSM_DATA(enc_key_pack, &encKeyPack);
+ asRep.encKeyPack = &encKeyPack;
+ }
+
+ ortn = SecAsn1EncodeItem(coder, &asRep, KRB5_PA_PK_AS_REPTemplate, &der);
+ if(ortn) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+ ourRtn = pkiCssmDataToKrb5Data(&der, pa_pk_as_rep);
+
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+/*
+ * Decode a KRB5_PA_PK_AS_REP.
+ */
+krb5_error_code krb5int_pkinit_pa_pk_as_rep_decode(
+ const krb5_data *pa_pk_as_rep,
+ krb5_data *dh_signed_data,
+ krb5_data *enc_key_pack)
+{
+ KRB5_PA_PK_AS_REP asRep;
+ SecAsn1CoderRef coder;
+ CSSM_DATA der = {pa_pk_as_rep->length, (uint8 *)pa_pk_as_rep->data};
+ krb5_error_code ourRtn = 0;
+
+ /* Decode --> KRB5_PA_PK_AS_REP */
+ if(SecAsn1CoderCreate(&coder)) {
+ return ENOMEM;
+ }
+ memset(&asRep, 0, sizeof(asRep));
+ if(SecAsn1DecodeData(coder, &der, KRB5_PA_PK_AS_REPTemplate, &asRep)) {
+ ourRtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+
+ if(asRep.dhSignedData) {
+ if((ourRtn = pkiCssmDataToKrb5Data(asRep.dhSignedData, dh_signed_data))) {
+ goto errOut;
+ }
+ }
+ if(asRep.encKeyPack) {
+ ourRtn = pkiCssmDataToKrb5Data(asRep.encKeyPack, enc_key_pack);
+ }
+
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+
+#pragma mark ====== General utilities ======
+
+/*
+ * Given a DER encoded certificate, obtain the associated IssuerAndSerialNumber.
+ */
+krb5_error_code krb5int_pkinit_get_issuer_serial(
+ const krb5_data *cert,
+ krb5_data *issuer_and_serial)
+{
+ CSSM_HANDLE cacheHand = 0;
+ CSSM_RETURN crtn = CSSM_OK;
+ CSSM_DATA certData = { cert->length, (uint8 *)cert->data };
+ CSSM_HANDLE resultHand = 0;
+ CSSM_DATA_PTR derIssuer = NULL;
+ CSSM_DATA_PTR serial;
+ krb5_data krb_serial;
+ krb5_data krb_issuer;
+ uint32 numFields;
+ krb5_error_code ourRtn = 0;
+
+ CSSM_CL_HANDLE clHand = pkiClStartup();
+ if(clHand == 0) {
+ return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
+ }
+ /* subsequent errors to errOut: */
+
+ crtn = CSSM_CL_CertCache(clHand, &certData, &cacheHand);
+ if(crtn) {
+ pkiCssmErr("CSSM_CL_CertCache", crtn);
+ ourRtn = ASN1_PARSE_ERROR;
+ goto errOut;
+ }
+
+ /* obtain the two fields; issuer is DER encoded */
+ crtn = CSSM_CL_CertGetFirstCachedFieldValue(clHand, cacheHand,
+ &CSSMOID_X509V1IssuerNameStd, &resultHand, &numFields, &derIssuer);
+ if(crtn) {
+ pkiCssmErr("CSSM_CL_CertGetFirstCachedFieldValue(issuer)", crtn);
+ ourRtn = ASN1_PARSE_ERROR;
+ goto errOut;
+ }
+ crtn = CSSM_CL_CertGetFirstCachedFieldValue(clHand, cacheHand,
+ &CSSMOID_X509V1SerialNumber, &resultHand, &numFields, &serial);
+ if(crtn) {
+ pkiCssmErr("CSSM_CL_CertGetFirstCachedFieldValue(serial)", crtn);
+ ourRtn = ASN1_PARSE_ERROR;
+ goto errOut;
+ }
+ PKI_CSSM_TO_KRB_DATA(derIssuer, &krb_issuer);
+ PKI_CSSM_TO_KRB_DATA(serial, &krb_serial);
+ ourRtn = krb5int_pkinit_issuer_serial_encode(&krb_issuer, &krb_serial, issuer_and_serial);
+
+errOut:
+ if(derIssuer) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerNameStd, derIssuer);
+ }
+ if(serial) {
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SerialNumber, serial);
+ }
+ if(cacheHand) {
+ CSSM_CL_CertAbortCache(clHand, cacheHand);
+ }
+ if(clHand) {
+ pkiClDetachUnload(clHand);
+ }
+ return ourRtn;
+}
+
+#endif /* APPLE_PKINIT */
diff --git a/src/lib/krb5/krb/pkinit_apple_cert_store.c b/src/lib/krb5/krb/pkinit_apple_cert_store.c
new file mode 100644
index 000000000..06ac37d06
--- /dev/null
+++ b/src/lib/krb5/krb/pkinit_apple_cert_store.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * pkinit_apple_cert_store.c - PKINIT certificate storage/retrieval utilities,
+ * MAC OS X version
+ *
+ * Created 26 May 2004 by Doug Mitchell at Apple.
+ */
+
+#if APPLE_PKINIT
+
+#include "pkinit_cert_store.h"
+#include "pkinit_asn1.h"
+#include "pkinit_apple_utils.h"
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+#include <assert.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <sys/errno.h>
+
+/*
+ * Client cert info is stored in preferences with this following parameters:
+ *
+ * key = kPkinitClientCertKey
+ * appID = kPkinitClientCertApp
+ * username = kCFPreferencesCurrentUser
+ * hostname = kCFPreferencesAnyHost
+ *
+ * The stored property list is a CFDictionary. Keys in the dictionary are
+ * principal names (e.g. foobar@REALM.LOCAL).
+ *
+ * Values in the dictionary are raw data containing the DER-encoded issuer and
+ * serial number of the certificate.
+ *
+ * When obtaining a PKINIT cert, if an entry in the CFDictionary for the specified
+ * principal is not found, the entry for the default will be used if it's there.
+ */
+
+/*
+ * NOTE: ANSI C code requires an Apple-Custom -fconstant-cfstrings CFLAGS to
+ * use CFSTR in a const declaration so we just declare the C strings here.
+ */
+#define kPkinitClientCertKey "KRBClientCert"
+#define kPkinitClientCertApp "edu.mit.Kerberos.pkinit"
+
+/*
+ * KDC cert stored in this keychain. It's linked to systemkeychain so that if
+ * a root process tries to unlock it, it auto-unlocks.
+ */
+#define KDC_KEYCHAIN "/var/db/krb5kdc/kdc.keychain"
+
+/*
+ * Given a certificate, obtain the DER-encoded issuer and serial number. Result
+ * is mallocd and must be freed by caller.
+ */
+static OSStatus pkinit_get_cert_issuer_sn(
+ SecCertificateRef certRef,
+ CSSM_DATA *issuerSerial) /* mallocd and RETURNED */
+{
+ OSStatus ortn;
+ CSSM_DATA certData;
+ krb5_data INIT_KDATA(issuerSerialKrb);
+ krb5_data certDataKrb;
+ krb5_error_code krtn;
+
+ assert(certRef != NULL);
+ assert(issuerSerial != NULL);
+
+ ortn = SecCertificateGetData(certRef, &certData);
+ if(ortn) {
+ pkiCssmErr("SecCertificateGetData", ortn);
+ return ortn;
+ }
+ PKI_CSSM_TO_KRB_DATA(&certData, &certDataKrb);
+ krtn = krb5int_pkinit_get_issuer_serial(&certDataKrb, &issuerSerialKrb);
+ if(krtn) {
+ return CSSMERR_CL_INVALID_DATA;
+ }
+ PKI_KRB_TO_CSSM_DATA(&issuerSerialKrb, issuerSerial);
+ return noErr;
+}
+
+/*
+ * Determine if specified identity's cert's issuer and serial number match the
+ * provided issuer and serial number. Returns nonzero on match, else returns zero.
+ */
+static int pkinit_issuer_sn_match(
+ SecIdentityRef idRef,
+ const CSSM_DATA *matchIssuerSerial)
+{
+ OSStatus ortn;
+ SecCertificateRef certRef = NULL;
+ CSSM_DATA INIT_CDATA(certIssuerSerial);
+ int ourRtn = 0;
+
+ assert(idRef != NULL);
+ assert(matchIssuerSerial != NULL);
+
+ /* Get this cert's issuer/serial number */
+ ortn = SecIdentityCopyCertificate(idRef, &certRef);
+ if(ortn) {
+ pkiCssmErr("SecIdentityCopyCertificate", ortn);
+ return 0;
+ }
+ /* subsequent errors to errOut: */
+ ortn = pkinit_get_cert_issuer_sn(certRef, &certIssuerSerial);
+ if(ortn) {
+ pkiCssmErr("SecIdentityCopyCertificate", ortn);
+ goto errOut;
+ }
+ ourRtn = pkiCompareCssmData(matchIssuerSerial, &certIssuerSerial) ? 1 : 0;
+errOut:
+ if(certRef != NULL) {
+ CFRelease(certRef);
+ }
+ if(certIssuerSerial.Data != NULL) {
+ free(certIssuerSerial.Data);
+ }
+ return ourRtn;
+}
+
+/*
+ * Search specified keychain/array/NULL (NULL meaning the default search list) for
+ * an Identity matching specified key usage and optional Issuer/Serial number.
+ * If issuer/serial is specified and no identities match, or if no identities found
+ * matching specified Key usage, errSecItemNotFound is returned.
+ *
+ * Caller must CFRelease a non-NULL returned idRef.
+ */
+static OSStatus pkinit_search_ident(
+ CFTypeRef keychainOrArray,
+ CSSM_KEYUSE keyUsage,
+ const CSSM_DATA *issuerSerial, /* optional */
+ SecIdentityRef *foundId) /* RETURNED */
+{
+ OSStatus ortn;
+ SecIdentityRef idRef = NULL;
+ SecIdentitySearchRef srchRef = NULL;
+
+ ortn = SecIdentitySearchCreate(keychainOrArray, keyUsage, &srchRef);
+ if(ortn) {
+ pkiCssmErr("SecIdentitySearchCreate", ortn);
+ return ortn;
+ }
+ do {
+ ortn = SecIdentitySearchCopyNext(srchRef, &idRef);
+ if(ortn != noErr) {
+ break;
+ }
+ if(issuerSerial == NULL) {
+ /* no match needed, we're done - this is the KDC cert case */
+ break;
+ }
+ else if(pkinit_issuer_sn_match(idRef, issuerSerial)) {
+ /* match, we're done */
+ break;
+ }
+ /* finished with this one */
+ CFRelease(idRef);
+ idRef = NULL;
+ } while(ortn == noErr);
+
+ CFRelease(srchRef);
+ if(idRef == NULL) {
+ return errSecItemNotFound;
+ }
+ else {
+ *foundId = idRef;
+ return noErr;
+ }
+}
+
+/*
+ * In Mac OS terms, get the keychain on which a given identity resides.
+ */
+static krb5_error_code pkinit_cert_to_db(
+ krb5_pkinit_signing_cert_t idRef,
+ krb5_pkinit_cert_db_t *dbRef)
+{
+ SecKeychainRef kcRef = NULL;
+ SecKeyRef keyRef = NULL;
+ OSStatus ortn;
+
+ /* that's an identity - get the associated key's keychain */
+ ortn = SecIdentityCopyPrivateKey((SecIdentityRef)idRef, &keyRef);
+ if(ortn) {
+ pkiCssmErr("SecIdentityCopyPrivateKey", ortn);
+ return ortn;
+ }
+ ortn = SecKeychainItemCopyKeychain((SecKeychainItemRef)keyRef, &kcRef);
+ if(ortn) {
+ pkiCssmErr("SecKeychainItemCopyKeychain", ortn);
+ }
+ else {
+ *dbRef = (krb5_pkinit_cert_db_t)kcRef;
+ }
+ CFRelease(keyRef);
+ return ortn;
+}
+
+/*
+ * Obtain the CFDictionary representing this user's PKINIT client cert prefs, if it
+ * exists. Returns noErr or errSecItemNotFound as appropriate.
+ */
+static OSStatus pkinit_get_pref_dict(
+ CFDictionaryRef *dict)
+{
+ CFDictionaryRef theDict;
+ theDict = (CFDictionaryRef)CFPreferencesCopyValue(CFSTR(kPkinitClientCertKey),
+ CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ if(theDict == NULL) {
+ pkiDebug("pkinit_get_pref_dict: no kPkinitClientCertKey\n");
+ return errSecItemNotFound;
+ }
+ if(CFGetTypeID(theDict) != CFDictionaryGetTypeID()) {
+ pkiDebug("pkinit_get_pref_dict: bad kPkinitClientCertKey pref\n");
+ CFRelease(theDict);
+ return errSecItemNotFound;
+ }
+ *dict = theDict;
+ return noErr;
+}
+
+#pragma mark --- Public client side functions ---
+
+/*
+ * Obtain signing cert for specified principal. On successful return,
+ * caller must eventually release the cert with krb5_pkinit_release_cert().
+ */
+krb5_error_code krb5_pkinit_get_client_cert(
+ const char *principal, /* full principal string */
+ krb5_pkinit_signing_cert_t *client_cert)
+{
+ CFDataRef issuerSerial = NULL;
+ CSSM_DATA issuerSerialData;
+ SecIdentityRef idRef = NULL;
+ OSStatus ortn;
+ CFDictionaryRef theDict = NULL;
+ krb5_error_code ourRtn = 0;
+
+ if(principal == NULL) {
+ return KRB5_PRINC_NOMATCH;
+ }
+
+ /* Is there a stored preference for PKINIT certs for this user? */
+ ortn = pkinit_get_pref_dict(&theDict);
+ if(ortn) {
+ return KRB5_PRINC_NOMATCH;
+ }
+
+ /* Entry in the dictionary for specified principal? */
+ CFStringRef cfPrinc = CFStringCreateWithCString(NULL, principal,
+ kCFStringEncodingASCII);
+ issuerSerial = (CFDataRef)CFDictionaryGetValue(theDict, cfPrinc);
+ CFRelease(cfPrinc);
+ if(issuerSerial == NULL) {
+ pkiDebug("krb5_pkinit_get_client_cert: no identity found\n");
+ ourRtn = KRB5_PRINC_NOMATCH;
+ goto errOut;
+ }
+ if(CFGetTypeID(issuerSerial) != CFDataGetTypeID()) {
+ pkiDebug("krb5_pkinit_get_client_cert: bad kPkinitClientCertKey value\n");
+ ourRtn = KRB5_PRINC_NOMATCH;
+ goto errOut;
+ }
+
+ issuerSerialData.Data = (uint8 *)CFDataGetBytePtr(issuerSerial);
+ issuerSerialData.Length = CFDataGetLength(issuerSerial);
+
+ /* find a cert with that issuer/serial number in default search list */
+ ortn = pkinit_search_ident(NULL, CSSM_KEYUSE_SIGN | CSSM_KEYUSE_ENCRYPT,
+ &issuerSerialData, &idRef);
+ if(ortn) {
+ pkiDebug("krb5_pkinit_get_client_cert: no identity found!\n");
+ pkiCssmErr("pkinit_search_ident", ortn);
+ ourRtn = KRB5_PRINC_NOMATCH;
+ }
+ else {
+ *client_cert = (krb5_pkinit_signing_cert_t)idRef;
+ }
+errOut:
+ if(theDict) {
+ CFRelease(theDict);
+ }
+ return ourRtn;
+}
+
+/*
+ * Determine if the specified client has a signing cert. Returns TRUE
+ * if so, else returns FALSE.
+ */
+krb5_boolean krb5_pkinit_have_client_cert(
+ const char *principal) /* full principal string */
+{
+ krb5_pkinit_signing_cert_t signing_cert = NULL;
+ krb5_error_code krtn;
+
+ krtn = krb5_pkinit_get_client_cert(principal, &signing_cert);
+ if(krtn) {
+ return FALSE;
+ }
+ if(signing_cert != NULL) {
+ krb5_pkinit_release_cert(signing_cert);
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/*
+ * Store the specified certificate (or, more likely, some platform-dependent
+ * reference to it) as the specified principal's signing cert. Passing
+ * in NULL for the client_cert has the effect of deleting the relevant entry
+ * in the cert storage.
+ */
+krb5_error_code krb5_pkinit_set_client_cert(
+ const char *principal, /* full principal string */
+ krb5_pkinit_signing_cert_t client_cert)
+{
+ SecIdentityRef idRef = (SecIdentityRef)client_cert;
+ OSStatus ortn;
+ CSSM_DATA issuerSerial = {0, NULL};
+ CFDataRef cfIssuerSerial = NULL;
+ CFDictionaryRef existDict = NULL;
+ CFMutableDictionaryRef newDict = NULL;
+ SecCertificateRef certRef = NULL;
+ CFStringRef keyStr = NULL;
+ krb5_error_code ourRtn = 0;
+
+ if(idRef != NULL) {
+ if(CFGetTypeID(idRef) != SecIdentityGetTypeID()) {
+ return KRB5KRB_ERR_GENERIC;
+ }
+
+ /* Get the cert */
+ ortn = SecIdentityCopyCertificate(idRef, &certRef);
+ if(ortn) {
+ pkiCssmErr("SecIdentityCopyCertificate", ortn);
+ return KRB5KRB_ERR_GENERIC;
+ }
+
+ /* Cook up DER-encoded issuer/serial number */
+ ortn = pkinit_get_cert_issuer_sn(certRef, &issuerSerial);
+ if(ortn) {
+ ourRtn = KRB5KRB_ERR_GENERIC;
+ goto errOut;
+ }
+ }
+
+ /*
+ * Obtain the existing pref for kPkinitClientCertKey as a CFDictionary, or
+ * cook up a new one.
+ */
+ ortn = pkinit_get_pref_dict(&existDict);
+ if(ortn == noErr) {
+ /* dup to a mutable dictionary */
+ newDict = CFDictionaryCreateMutableCopy(NULL, 0, existDict);
+ }
+ else {
+ if(idRef == NULL) {
+ /* no existing entry, nothing to delete, we're done */
+ return 0;
+ }
+ newDict = CFDictionaryCreateMutable(NULL, 0,
+ &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ }
+ if(newDict == NULL) {
+ ourRtn = ENOMEM;
+ goto errOut;
+ }
+
+ /* issuer / serial number ==> that dictionary */
+ keyStr = CFStringCreateWithCString(NULL, principal, kCFStringEncodingASCII);
+ if(idRef == NULL) {
+ CFDictionaryRemoveValue(newDict, keyStr);
+ }
+ else {
+ cfIssuerSerial = CFDataCreate(NULL, issuerSerial.Data, issuerSerial.Length);
+ CFDictionarySetValue(newDict, keyStr, cfIssuerSerial);
+ }
+
+ /* dictionary ==> prefs */
+ CFPreferencesSetValue(CFSTR(kPkinitClientCertKey), newDict,
+ CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
+ if(CFPreferencesSynchronize(CFSTR(kPkinitClientCertApp), kCFPreferencesCurrentUser,
+ kCFPreferencesAnyHost)) {
+ ourRtn = 0;
+ }
+ else {
+ ourRtn = EACCES; /* any better ideas? */
+ }
+errOut:
+ if(certRef) {
+ CFRelease(certRef);
+ }
+ if(cfIssuerSerial) {
+ CFRelease(cfIssuerSerial);
+ }
+ if(issuerSerial.Data) {
+ free(issuerSerial.Data);
+ }
+ if(existDict) {
+ CFRelease(existDict);
+ }
+ if(newDict) {
+ CFRelease(newDict);
+ }
+ if(keyStr) {
+ CFRelease(keyStr);
+ }
+ return ourRtn;
+}
+
+/*
+ * Obtain a reference to the client's cert database. Specify either principal
+ * name or client_cert as obtained from krb5_pkinit_get_client_cert().
+ */
+krb5_error_code krb5_pkinit_get_client_cert_db(
+ const char *principal, /* full principal string */
+ krb5_pkinit_signing_cert_t client_cert, /* optional, from krb5_pkinit_get_client_cert() */
+ krb5_pkinit_cert_db_t *client_cert_db)/* RETURNED */
+{
+ krb5_error_code krtn;
+ krb5_pkinit_signing_cert_t local_cert;
+
+ assert((client_cert != NULL) || (principal != NULL));
+ if(client_cert == NULL) {
+ /* caller didn't provide, look it up */
+ krtn = krb5_pkinit_get_client_cert(principal, &local_cert);
+ if(krtn) {
+ return krtn;
+ }
+ }
+ else {
+ /* easy case */
+ local_cert = client_cert;
+ }
+ krtn = pkinit_cert_to_db(local_cert, client_cert_db);
+ if(client_cert == NULL) {
+ krb5_pkinit_release_cert(local_cert);
+ }
+ return krtn;
+}
+
+#pragma mark --- Public server side functions ---
+
+/*
+ * Obtain the KDC signing cert, with optional CA and specific cert specifiers.
+ * CAs and cert specifiers are in the form of DER-encoded issuerAndSerialNumbers.
+ *
+ * The client_spec argument is typically provided by the client as kdcPkId.
+ */
+krb5_error_code krb5_pkinit_get_kdc_cert(
+ krb5_ui_4 num_trusted_CAs, /* sizeof *trusted_CAs */
+ krb5_data *trusted_CAs, /* optional */
+ krb5_data *client_spec, /* optional */
+ krb5_pkinit_signing_cert_t *kdc_cert)
+{
+ SecIdentityRef idRef = NULL;
+ OSStatus ortn;
+ krb5_error_code ourRtn = 0;
+
+ /* OS X: trusted_CAs and client_spec ignored */
+
+ ortn = SecIdentityCopySystemIdentity(kSecIdentityDomainKerberosKDC,
+ &idRef, NULL);
+ if(ortn) {
+ pkiCssmErr("SecIdentityCopySystemIdentity", ortn);
+ return KRB5_PRINC_NOMATCH;
+ }
+ *kdc_cert = (krb5_pkinit_signing_cert_t)idRef;
+ return ourRtn;
+}
+
+/*
+ * Obtain a reference to the KDC's cert database.
+ */
+krb5_error_code krb5_pkinit_get_kdc_cert_db(
+ krb5_pkinit_cert_db_t *kdc_cert_db)
+{
+ krb5_pkinit_signing_cert_t kdcCert = NULL;
+ krb5_error_code krtn;
+
+ krtn = krb5_pkinit_get_kdc_cert(0, NULL, NULL, &kdcCert);
+ if(krtn) {
+ return krtn;
+ }
+ krtn = pkinit_cert_to_db(kdcCert, kdc_cert_db);
+ krb5_pkinit_release_cert(kdcCert);
+ return krtn;
+}
+
+/*
+ * Release certificate references obtained via krb5_pkinit_get_client_cert() and
+ * krb5_pkinit_get_kdc_cert().
+ */
+void krb5_pkinit_release_cert(
+ krb5_pkinit_signing_cert_t cert)
+{
+ if(cert == NULL) {
+ return;
+ }
+ CFRelease((CFTypeRef)cert);
+}
+
+/*
+ * Release database references obtained via krb5_pkinit_get_client_cert_db() and
+ * krb5_pkinit_get_kdc_cert_db().
+ */
+extern void krb5_pkinit_release_cert_db(
+ krb5_pkinit_cert_db_t cert_db)
+{
+ if(cert_db == NULL) {
+ return;
+ }
+ CFRelease((CFTypeRef)cert_db);
+}
+
+
+/*
+ * Obtain a mallocd C-string representation of a certificate's SHA1 digest.
+ * Only error is a NULL return indicating memory failure.
+ * Caller must free the returned string.
+ */
+char *krb5_pkinit_cert_hash_str(
+ const krb5_data *cert)
+{
+ CC_SHA1_CTX ctx;
+ char *outstr;
+ char *cpOut;
+ unsigned char digest[CC_SHA1_DIGEST_LENGTH];
+ unsigned dex;
+
+ assert(cert != NULL);
+ CC_SHA1_Init(&ctx);
+ CC_SHA1_Update(&ctx, cert->data, cert->length);
+ CC_SHA1_Final(digest, &ctx);
+
+ outstr = (char *)malloc((2 * CC_SHA1_DIGEST_LENGTH) + 1);
+ if(outstr == NULL) {
+ return NULL;
+ }
+ cpOut = outstr;
+ for(dex=0; dex<CC_SHA1_DIGEST_LENGTH; dex++) {
+ sprintf(cpOut, "%02X", (unsigned)(digest[dex]));
+ cpOut += 2;
+ }
+ *cpOut = '\0';
+ return outstr;
+}
+
+/*
+ * Obtain a client's optional list of trusted KDC CA certs (trustedCertifiers)
+ * and/or trusted KDC cert (kdcPkId) for a given client and server.
+ * All returned values are mallocd and must be freed by caller; the contents
+ * of the krb5_datas are DER-encoded certificates.
+ */
+krb5_error_code krb5_pkinit_get_server_certs(
+ const char *client_principal,
+ const char *server_principal,
+ krb5_data **trusted_CAs, /* RETURNED, though return value may be NULL */
+ krb5_ui_4 *num_trusted_CAs, /* RETURNED */
+ krb5_data *kdc_cert) /* RETURNED, though may be 0/NULL */
+{
+ /* nothing for now */
+ *trusted_CAs = NULL;
+ *num_trusted_CAs = 0;
+ kdc_cert->data = NULL;
+ kdc_cert->length = 0;
+ return 0;
+}
+
+#endif /* APPLE_PKINIT */
diff --git a/src/lib/krb5/krb/pkinit_apple_client.c b/src/lib/krb5/krb/pkinit_apple_client.c
new file mode 100644
index 000000000..d98fc76c0
--- /dev/null
+++ b/src/lib/krb5/krb/pkinit_apple_client.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * pkinit_apple_client.c - Client side routines for PKINIT, Mac OS X version
+ *
+ * Created 20 May 2004 by Doug Mitchell at Apple.
+ */
+
+#if APPLE_PKINIT
+
+#include "pkinit_client.h"
+#include "pkinit_asn1.h"
+#include "pkinit_apple_utils.h"
+#include "pkinit_cms.h"
+#include <assert.h>
+#include <sys/errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/*
+ * Create a PA-PK-AS-REQ message.
+ */
+krb5_error_code krb5int_pkinit_as_req_create(
+ krb5_context context,
+ krb5_timestamp kctime,
+ krb5_int32 cusec, /* microseconds */
+ krb5_ui_4 nonce,
+ const krb5_checksum *cksum,
+ krb5_pkinit_signing_cert_t client_cert, /* required */
+ const krb5_data *trusted_CAs, /* optional list of CA certs */
+ krb5_ui_4 num_trusted_CAs,
+ const krb5_data *kdc_cert, /* optional KDC cert */
+ krb5_data *as_req) /* mallocd and RETURNED */
+{
+ krb5_data auth_pack = {0};
+ krb5_error_code krtn;
+ krb5_data content_info = {0};
+ krb5int_algorithm_id *cms_types = NULL;
+ krb5_ui_4 num_cms_types = 0;
+
+ /* issuer/serial numbers for trusted_CAs and kdc_cert, if we have them */
+ krb5_data *ca_issuer_sn = NULL; /* issuer/serial_num for trusted_CAs */
+ krb5_data kdc_issuer_sn = {0}; /* issuer/serial_num for kdc_cert */
+ krb5_data *kdc_issuer_sn_p = NULL;
+
+ /* optional platform-dependent CMS algorithm preference */
+ krtn = krb5int_pkinit_get_cms_types(&cms_types, &num_cms_types);
+ if(krtn) {
+ return krtn;
+ }
+
+ /* encode the core authPack */
+ krtn = krb5int_pkinit_auth_pack_encode(kctime, cusec, nonce, cksum,
+ cms_types, num_cms_types,
+ &auth_pack);
+ if(krtn) {
+ goto errOut;
+ }
+
+ /* package the AuthPack up in a SignedData inside a ContentInfo */
+ krtn = krb5int_pkinit_create_cms_msg(&auth_pack,
+ client_cert,
+ NULL, /* recip_cert */
+ ECT_PkAuthData,
+ 0, NULL, /* cms_types */
+ &content_info);
+ if(krtn) {
+ goto errOut;
+ }
+
+ /* if we have trusted_CAs, get issuer/serials */
+ if(trusted_CAs) {
+ unsigned dex;
+ ca_issuer_sn = (krb5_data *)malloc(num_trusted_CAs * sizeof(krb5_data));
+ if(ca_issuer_sn == NULL) {
+ krtn = ENOMEM;
+ goto errOut;
+ }
+ for(dex=0; dex<num_trusted_CAs; dex++) {
+ krtn = krb5int_pkinit_get_issuer_serial(&trusted_CAs[dex],
+ &ca_issuer_sn[dex]);
+ if(krtn) {
+ goto errOut;
+ }
+ }
+ }
+
+ /* If we have a KDC cert, get its issuer/serial */
+ if(kdc_cert) {
+ krtn = krb5int_pkinit_get_issuer_serial(kdc_cert, &kdc_issuer_sn);
+ if(krtn) {
+ goto errOut;
+ }
+ kdc_issuer_sn_p = &kdc_issuer_sn;
+ }
+
+ /* cook up PA-PK-AS-REQ */
+ krtn = krb5int_pkinit_pa_pk_as_req_encode(&content_info,
+ ca_issuer_sn, num_trusted_CAs,
+ kdc_issuer_sn_p,
+ as_req);
+
+errOut:
+ if(cms_types) {
+ krb5int_pkinit_free_cms_types(cms_types, num_cms_types);
+ }
+ if(auth_pack.data) {
+ free(auth_pack.data);
+ }
+ if(content_info.data) {
+ free(content_info.data);
+ }
+ if(trusted_CAs) {
+ unsigned dex;
+ for(dex=0; dex<num_trusted_CAs; dex++) {
+ free(ca_issuer_sn[dex].data);
+ }
+ free(ca_issuer_sn);
+ }
+ if(kdc_cert) {
+ free(kdc_issuer_sn.data);
+ }
+ return krtn;
+}
+
+/*
+ * Parse PA-PK-AS-REP message. Optionally evaluates the message's certificate chain.
+ * Optionally returns various components.
+ */
+krb5_error_code krb5int_pkinit_as_rep_parse(
+ krb5_context context,
+ const krb5_data *as_rep,
+ krb5_pkinit_signing_cert_t client_cert, /* required */
+ krb5_keyblock *key_block, /* RETURNED */
+ krb5_checksum *checksum, /* checksum of corresponding AS-REQ */
+ /* contents mallocd and RETURNED */
+ krb5int_cert_sig_status *cert_status, /* RETURNED */
+
+ /*
+ * Cert fields, all optionally RETURNED.
+ *
+ * signer_cert is the full X.509 leaf cert from the incoming SignedData.
+ * all_certs is an array of all of the certs in the incoming SignedData,
+ * in full X.509 form.
+ */
+ krb5_data *signer_cert, /* content mallocd */
+ unsigned *num_all_certs, /* sizeof *all_certs */
+ krb5_data **all_certs) /* krb5_data's and their content mallocd */
+{
+ krb5_data reply_key_pack = {0, 0, NULL};
+ krb5_error_code krtn;
+ krb5_data enc_key_pack = {0, 0, NULL};
+ krb5_data dh_signed_data = {0, 0, NULL};
+ krb5int_cms_content_type content_type;
+ krb5_pkinit_cert_db_t cert_db = NULL;
+ krb5_boolean is_signed;
+ krb5_boolean is_encrypted;
+
+ assert((as_rep != NULL) && (checksum != NULL) &&
+ (key_block != NULL) && (cert_status != NULL));
+
+ /*
+ * Decode the top-level PA-PK-AS-REP
+ */
+ krtn = krb5int_pkinit_pa_pk_as_rep_decode(as_rep, &dh_signed_data, &enc_key_pack);
+ if(krtn) {
+ pkiCssmErr("krb5int_pkinit_pa_pk_as_rep_decode", krtn);
+ return krtn;
+ }
+ if(dh_signed_data.data) {
+ /* not for this implementation... */
+ pkiDebug("krb5int_pkinit_as_rep_parse: unexpected dh_signed_data\n");
+ krtn = ASN1_BAD_FORMAT;
+ goto err_out;
+ }
+ if(enc_key_pack.data == NULL) {
+ /* REQUIRED for this implementation... */
+ pkiDebug("krb5int_pkinit_as_rep_parse: no enc_key_pack\n");
+ krtn = ASN1_BAD_FORMAT;
+ goto err_out;
+ }
+
+ krtn = krb5_pkinit_get_client_cert_db(NULL, client_cert, &cert_db);
+ if(krtn) {
+ pkiDebug("krb5int_pkinit_as_rep_parse: error in krb5_pkinit_get_client_cert_db\n");
+ goto err_out;
+ }
+
+ /*
+ * enc_key_pack is an EnvelopedData(SignedData(keyPack), encrypted
+ * with our cert (which krb5int_pkinit_parse_content_info() finds
+ * implicitly).
+ */
+ krtn = krb5int_pkinit_parse_cms_msg(&enc_key_pack, cert_db, FALSE,
+ &is_signed, &is_encrypted,
+ &reply_key_pack, &content_type,
+ signer_cert, cert_status, num_all_certs, all_certs);
+ if(krtn) {
+ pkiDebug("krb5int_pkinit_as_rep_parse: error decoding EnvelopedData\n");
+ goto err_out;
+ }
+ if(!is_encrypted || !is_signed) {
+ pkiDebug("krb5int_pkinit_as_rep_parse: not signed and encrypted!\n");
+ krtn = KRB5_PARSE_MALFORMED;
+ goto err_out;
+ }
+ if(content_type != ECT_PkReplyKeyKata) {
+ pkiDebug("replyKeyPack eContentType %d!\n", (int)content_type);
+ krtn = KRB5_PARSE_MALFORMED;
+ goto err_out;
+ }
+
+ /*
+ * Finally, decode that inner content as the ReplyKeyPack which contains
+ * the actual key and nonce
+ */
+ krtn = krb5int_pkinit_reply_key_pack_decode(&reply_key_pack, key_block, checksum);
+ if(krtn) {
+ pkiDebug("krb5int_pkinit_as_rep_parse: error decoding ReplyKeyPack\n");
+ }
+
+err_out:
+ /* free temp mallocd data that we didn't pass back to caller */
+ if(reply_key_pack.data) {
+ free(reply_key_pack.data);
+ }
+ if(enc_key_pack.data) {
+ free(enc_key_pack.data);
+ }
+ if(dh_signed_data.data) {
+ free(dh_signed_data.data);
+ }
+ if(cert_db) {
+ krb5_pkinit_release_cert_db(cert_db);
+ }
+ return krtn;
+}
+
+#endif /* APPLE_PKINIT */
diff --git a/src/lib/krb5/krb/pkinit_apple_cms.c b/src/lib/krb5/krb/pkinit_apple_cms.c
new file mode 100644
index 000000000..353bcab40
--- /dev/null
+++ b/src/lib/krb5/krb/pkinit_apple_cms.c
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * pkinit_apple_cms.c - CMS encode/decode routines, Mac OS X version
+ *
+ * Created 19 May 2004 by Doug Mitchell at Apple.
+ */
+
+#if APPLE_PKINIT
+
+#include "pkinit_cms.h"
+#include "pkinit_asn1.h"
+#include "pkinit_apple_utils.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/CMSEncoder.h>
+#include <Security/CMSDecoder.h>
+#include <Security/Security.h>
+#include <assert.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
+#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>
+
+/*
+ * Custom OIDS to specify as eContentType
+ */
+#define OID_PKINIT 0x2B, 6, 1, 5, 2, 3
+#define OID_PKINIT_LEN 6
+
+static const uint8 OID_PKINIT_AUTH_DATA[] = {OID_PKINIT, 1};
+static const uint8 OID_PKINIT_RKEY_DATA[] = {OID_PKINIT, 3};
+
+/* these may go public so keep these symbols private */
+static const CSSM_OID _CSSMOID_PKINIT_AUTH_DATA =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_AUTH_DATA};
+static const CSSM_OID _CSSMOID_PKINIT_RKEY_DATA =
+ {OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_RKEY_DATA};
+
+
+#pragma mark ----- CMS utilities ----
+
+#define CFRELEASE(cf) if(cf) { CFRelease(cf); }
+
+/*
+ * Convert platform-specific cert/signature status to krb5int_cert_sig_status.
+ */
+static krb5int_cert_sig_status pkiCertSigStatus(
+ OSStatus certStatus)
+{
+ switch(certStatus) {
+ case CSSM_OK:
+ return pki_cs_good;
+ case CSSMERR_CSP_VERIFY_FAILED:
+ return pki_cs_sig_verify_fail;
+ case CSSMERR_TP_NOT_TRUSTED:
+ return pki_cs_no_root;
+ case CSSMERR_TP_INVALID_ANCHOR_CERT:
+ return pki_cs_unknown_root;
+ case CSSMERR_TP_CERT_EXPIRED:
+ return pki_cs_expired;
+ case CSSMERR_TP_CERT_NOT_VALID_YET:
+ return pki_cs_not_valid_yet;
+ case CSSMERR_TP_CERT_REVOKED:
+ return pki_cs_revoked;
+ case KRB5_KDB_UNAUTH:
+ return pki_cs_untrusted;
+ case CSSMERR_TP_INVALID_CERTIFICATE:
+ return pki_cs_bad_leaf;
+ default:
+ return pki_cs_other_err;
+ }
+}
+
+/*
+ * Infer krb5int_cert_sig_status from CMSSignerStatus and a CSSM TO
+ * cert veriofy result code (obtained via the certVerifyResultCode argument
+ * in CMSDecoderCopySignerStatus()).
+ */
+static krb5int_cert_sig_status pkiInferSigStatus(
+ CMSSignerStatus cms_status,
+ OSStatus tp_status)
+{
+ switch(cms_status) {
+ case kCMSSignerUnsigned:
+ return pki_not_signed;
+ case kCMSSignerValid:
+ return pki_cs_good;
+ case kCMSSignerNeedsDetachedContent:
+ return pki_bad_cms;
+ case kCMSSignerInvalidSignature:
+ return pki_cs_sig_verify_fail;
+ case kCMSSignerInvalidCert:
+ /* proceed with TP status */
+ break;
+ default:
+ return pki_cs_other_err;
+ }
+
+ /* signature good, infer end status from TP verify */
+ return pkiCertSigStatus(tp_status);
+}
+
+/*
+ * Cook up a SecCertificateRef from a krb5_data.
+ */
+static OSStatus pkiKrb5DataToSecCert(
+ const krb5_data *rawCert,
+ SecCertificateRef *secCert) /* RETURNED */
+{
+ CSSM_DATA certData;
+ OSStatus ortn;
+
+ assert((rawCert != NULL) && (secCert != NULL));
+
+ certData.Data = (uint8 *)rawCert->data;
+ certData.Length = rawCert->length;
+ ortn = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3,
+ CSSM_CERT_ENCODING_DER, secCert);
+ if(ortn) {
+ pkiCssmErr("SecCertificateCreateFromData", ortn);
+ }
+ return ortn;
+}
+
+/*
+ * Convert CFArray of SecCertificateRefs to a mallocd array of krb5_datas.
+ */
+static krb5_error_code pkiCertArrayToKrb5Data(
+ CFArrayRef cf_certs,
+ unsigned *num_all_certs,
+ krb5_data **all_certs)
+{
+ CFIndex num_certs;
+ krb5_data *allCerts = NULL;
+ krb5_error_code krtn = 0;
+ CFIndex dex;
+
+ if(cf_certs == NULL) {
+ *all_certs = NULL;
+ return 0;
+ }
+ num_certs = CFArrayGetCount(cf_certs);
+ *num_all_certs = (unsigned)num_certs;
+ if(num_certs == 0) {
+ *all_certs = NULL;
+ return 0;
+ }
+ allCerts = (krb5_data *)malloc(sizeof(krb5_data) * num_certs);
+ if(allCerts == NULL) {
+ return ENOMEM;
+ }
+ for(dex=0; dex<num_certs; dex++) {
+ CSSM_DATA cert_data;
+ OSStatus ortn;
+ SecCertificateRef sec_cert;
+
+ sec_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cf_certs, dex);
+ ortn = SecCertificateGetData(sec_cert, &cert_data);
+ if(ortn) {
+ pkiCssmErr("SecCertificateGetData", ortn);
+ krtn = KRB5_PARSE_MALFORMED;
+ break;
+ }
+ krtn = pkiCssmDataToKrb5Data(&cert_data, &allCerts[dex]);
+ if(krtn) {
+ break;
+ }
+ }
+ if(krtn) {
+ if(allCerts) {
+ free(allCerts);
+ }
+ }
+ else {
+ *all_certs = allCerts;
+ }
+ return krtn;
+}
+
+#pragma mark ----- Create CMS message -----
+
+/*
+ * Create a CMS message: either encrypted (EnvelopedData), signed
+ * (SignedData), or both (EnvelopedData(SignedData(content)).
+ *
+ * The message is signed iff signing_cert is non-NULL.
+ * The message is encrypted iff recip_cert is non-NULL.
+ *
+ * The content_type argument specifies to the eContentType
+ * for a SignedData's EncapsulatedContentInfo.
+ */
+krb5_error_code krb5int_pkinit_create_cms_msg(
+ const krb5_data *content, /* Content */
+ krb5_pkinit_signing_cert_t signing_cert, /* optional: signed by this cert */
+ const krb5_data *recip_cert, /* optional: encrypted with this cert */
+ krb5int_cms_content_type content_type, /* OID for EncapsulatedData */
+ krb5_ui_4 num_cms_types, /* optional, unused here */
+ const krb5int_algorithm_id *cms_types, /* optional, unused here */
+ krb5_data *content_info) /* contents mallocd and RETURNED */
+{
+ krb5_error_code krtn;
+ OSStatus ortn;
+ SecCertificateRef sec_recip = NULL;
+ CFDataRef cf_content = NULL;
+ const CSSM_OID *eContentOid = NULL;
+
+ if((signing_cert == NULL) && (recip_cert == NULL)) {
+ /* must have one or the other */
+ pkiDebug("krb5int_pkinit_create_cms_msg: no signer or recipient\n");
+ return KRB5_CRYPTO_INTERNAL;
+ }
+
+ /*
+ * Optional signer cert. Note signing_cert, if present, is
+ * a SecIdentityRef.
+ */
+ if(recip_cert) {
+ if(pkiKrb5DataToSecCert(recip_cert, &sec_recip)) {
+ krtn = ASN1_BAD_FORMAT;
+ goto errOut;
+ }
+ }
+
+ /* optional eContentType */
+ if(signing_cert) {
+ switch(content_type) {
+ case ECT_PkAuthData:
+ eContentOid = &_CSSMOID_PKINIT_AUTH_DATA;
+ break;
+ case ECT_PkReplyKeyKata:
+ eContentOid = &_CSSMOID_PKINIT_RKEY_DATA;
+ break;
+ case ECT_Data:
+ /* the only standard/default case we allow */
+ break;
+ default:
+ /* others: no can do */
+ pkiDebug("krb5int_pkinit_create_cms_msg: bad contentType\n");
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ }
+
+ /* GO */
+ ortn = CMSEncode((SecIdentityRef)signing_cert, sec_recip,
+ eContentOid,
+ FALSE, /* detachedContent */
+ kCMSAttrNone, /* no signed attributes that I know of */
+ content->data, content->length,
+ &cf_content);
+ if(ortn) {
+ pkiCssmErr("CMSEncode", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ krtn = pkiCfDataToKrb5Data(cf_content, content_info);
+errOut:
+ CFRELEASE(sec_recip);
+ CFRELEASE(cf_content);
+ return krtn;
+}
+
+#pragma mark ----- Generalized parse ContentInfo ----
+
+/*
+ * Parse a ContentInfo as best we can. All return fields are optional.
+ * If signer_cert_status is NULL on entry, NO signature or cert evaluation
+ * will be performed.
+ */
+krb5_error_code krb5int_pkinit_parse_cms_msg(
+ const krb5_data *content_info,
+ krb5_pkinit_cert_db_t cert_db, /* may be required for SignedData */
+ krb5_boolean is_client_msg, /* TRUE : msg is from client */
+ krb5_boolean *is_signed, /* RETURNED */
+ krb5_boolean *is_encrypted, /* RETURNED */
+ krb5_data *raw_data, /* RETURNED */
+ krb5int_cms_content_type *inner_content_type,/* Returned, ContentType of */
+ /* EncapsulatedData */
+ krb5_data *signer_cert, /* RETURNED */
+ krb5int_cert_sig_status *signer_cert_status,/* RETURNED */
+ unsigned *num_all_certs, /* size of *all_certs RETURNED */
+ krb5_data **all_certs) /* entire cert chain RETURNED */
+{
+ SecPolicySearchRef policy_search = NULL;
+ SecPolicyRef policy = NULL;
+ OSStatus ortn;
+ krb5_error_code krtn = 0;
+ CMSDecoderRef decoder = NULL;
+ size_t num_signers;
+ CMSSignerStatus signer_status;
+ OSStatus cert_verify_status;
+ CFArrayRef cf_all_certs = NULL;
+ int msg_is_signed = 0;
+
+ if(content_info == NULL) {
+ pkiDebug("krb5int_pkinit_parse_cms_msg: no ContentInfo\n");
+ return KRB5_CRYPTO_INTERNAL;
+ }
+
+ ortn = CMSDecoderCreate(&decoder);
+ if(ortn) {
+ return ENOMEM;
+ }
+ ortn = CMSDecoderUpdateMessage(decoder, content_info->data, content_info->length);
+ if(ortn) {
+ /* no verify yet, must be bad message */
+ krtn = KRB5_PARSE_MALFORMED;
+ goto errOut;
+ }
+ ortn = CMSDecoderFinalizeMessage(decoder);
+ if(ortn) {
+ pkiCssmErr("CMSDecoderFinalizeMessage", ortn);
+ krtn = KRB5_PARSE_MALFORMED;
+ goto errOut;
+ }
+
+ /* expect zero or one signers */
+ ortn = CMSDecoderGetNumSigners(decoder, &num_signers);
+ switch(num_signers) {
+ case 0:
+ msg_is_signed = 0;
+ break;
+ case 1:
+ msg_is_signed = 1;
+ break;
+ default:
+ krtn = KRB5_PARSE_MALFORMED;
+ goto errOut;
+ }
+
+ /*
+ * We need a cert verify policy even if we're not actually evaluating
+ * the cert due to requirements in libsecurity_smime.
+ */
+ ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
+ is_client_msg ? &CSSMOID_APPLE_TP_PKINIT_CLIENT : &CSSMOID_APPLE_TP_PKINIT_SERVER,
+ NULL, &policy_search);
+ if(ortn) {
+ pkiCssmErr("SecPolicySearchCreate", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ ortn = SecPolicySearchCopyNext(policy_search, &policy);
+ if(ortn) {
+ pkiCssmErr("SecPolicySearchCopyNext", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+
+ /* get some basic status that doesn't need heavyweight evaluation */
+ if(msg_is_signed) {
+ if(is_signed) {
+ *is_signed = TRUE;
+ }
+ if(inner_content_type) {
+ CSSM_OID ec_oid = {0, NULL};
+ CFDataRef ec_data = NULL;
+
+ krb5int_cms_content_type ctype;
+
+ ortn = CMSDecoderCopyEncapsulatedContentType(decoder, &ec_data);
+ if(ortn || (ec_data == NULL)) {
+ pkiCssmErr("CMSDecoderCopyEncapsulatedContentType", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ ec_oid.Data = (uint8 *)CFDataGetBytePtr(ec_data);
+ ec_oid.Length = CFDataGetLength(ec_data);
+ if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_Data)) {
+ ctype = ECT_Data;
+ }
+ else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_SignedData)) {
+ ctype = ECT_SignedData;
+ }
+ else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EnvelopedData)) {
+ ctype = ECT_EnvelopedData;
+ }
+ else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EncryptedData)) {
+ ctype = ECT_EncryptedData;
+ }
+ else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_AUTH_DATA)) {
+ ctype = ECT_PkAuthData;
+ }
+ else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_RKEY_DATA)) {
+ ctype = ECT_PkReplyKeyKata;
+ }
+ else {
+ ctype = ECT_Other;
+ }
+ *inner_content_type = ctype;
+ CFRelease(ec_data);
+ }
+
+ /*
+ * Get SignedData's certs if the caller wants them
+ */
+ if(all_certs) {
+ ortn = CMSDecoderCopyAllCerts(decoder, &cf_all_certs);
+ if(ortn) {
+ pkiCssmErr("CMSDecoderCopyAllCerts", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ krtn = pkiCertArrayToKrb5Data(cf_all_certs, num_all_certs, all_certs);
+ if(krtn) {
+ goto errOut;
+ }
+ }
+
+ /* optional signer cert */
+ if(signer_cert) {
+ SecCertificateRef sec_signer_cert = NULL;
+ CSSM_DATA cert_data;
+
+ ortn = CMSDecoderCopySignerCert(decoder, 0, &sec_signer_cert);
+ if(ortn) {
+ /* should never happen if it's signed */
+ pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ ortn = SecCertificateGetData(sec_signer_cert, &cert_data);
+ if(ortn) {
+ pkiCssmErr("SecCertificateGetData", ortn);
+ CFRelease(sec_signer_cert);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ krtn = pkiDataToKrb5Data(cert_data.Data, cert_data.Length, signer_cert);
+ CFRelease(sec_signer_cert);
+ if(krtn) {
+ goto errOut;
+ }
+ }
+ }
+ else {
+ /* not signed */
+ if(is_signed) {
+ *is_signed = FALSE;
+ }
+ if(inner_content_type) {
+ *inner_content_type = ECT_Other;
+ }
+ if(signer_cert) {
+ signer_cert->data = NULL;
+ signer_cert->length = 0;
+ }
+ if(signer_cert_status) {
+ *signer_cert_status = pki_not_signed;
+ }
+ if(num_all_certs) {
+ *num_all_certs = 0;
+ }
+ if(all_certs) {
+ *all_certs = NULL;
+ }
+ }
+ if(is_encrypted) {
+ Boolean bencr;
+ ortn = CMSDecoderIsContentEncrypted(decoder, &bencr);
+ if(ortn) {
+ pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
+ krtn = KRB5_CRYPTO_INTERNAL;
+ goto errOut;
+ }
+ *is_encrypted = bencr ? TRUE : FALSE;
+ }
+
+ /*
+ * Verify signature and cert. The actual verify operation is optional,
+ * per our signer_cert_status argument, but we do this anyway if we need
+ * to get the signer cert.
+ */
+ if((signer_cert_status != NULL) || (signer_cert != NULL)) {
+
+ ortn = CMSDecoderCopySignerStatus(decoder,
+ 0, /* signerIndex */
+ policy,
+ signer_cert_status ? TRUE : FALSE, /* evaluateSecTrust */
+ &signer_status,
+ NULL, /* secTrust - not needed */
+ &cert_verify_status);
+ if(ortn) {
+ /* gross error - subsequent processing impossible */
+ pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
+ krtn = KRB5_PARSE_MALFORMED;
+ goto errOut;
+ }
+ }
+ /* obtain & return status */
+ if(signer_cert_status) {
+ *signer_cert_status = pkiInferSigStatus(signer_status, cert_verify_status);
+ }
+
+ /* finally, the payload */
+ if(raw_data) {
+ CFDataRef cf_content = NULL;
+
+ ortn = CMSDecoderCopyContent(decoder, &cf_content);
+ if(ortn) {
+ pkiCssmErr("CMSDecoderCopyContent", ortn);
+ krtn = KRB5_PARSE_MALFORMED;
+ goto errOut;
+ }
+ krtn = pkiCfDataToKrb5Data(cf_content, raw_data);
+ CFRELEASE(cf_content);
+ }
+errOut:
+ CFRELEASE(policy_search);
+ CFRELEASE(policy);
+ CFRELEASE(cf_all_certs);
+ CFRELEASE(decoder);
+ return krtn;
+}
+
+krb5_error_code krb5int_pkinit_get_cms_types(
+ krb5int_algorithm_id **supported_cms_types, /* RETURNED */
+ krb5_ui_4 *num_supported_cms_types) /* RETURNED */
+{
+ /* no preference */
+ *supported_cms_types = NULL;
+ *num_supported_cms_types = 0;
+ return 0;
+}
+
+krb5_error_code krb5int_pkinit_free_cms_types(
+ krb5int_algorithm_id *supported_cms_types,
+ krb5_ui_4 num_supported_cms_types)
+{
+ /*
+ * We don't return anything from krb5int_pkinit_get_cms_types(), and
+ * if we did, it would be a pointer to a statically declared array,
+ * so this is a nop.
+ */
+ return 0;
+}
+
+#endif /* APPLE_PKINIT */
diff --git a/src/lib/krb5/krb/pkinit_apple_utils.c b/src/lib/krb5/krb/pkinit_apple_utils.c
new file mode 100644
index 000000000..a4578336b
--- /dev/null
+++ b/src/lib/krb5/krb/pkinit_apple_utils.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2004-2008 Apple Inc. All Rights Reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Apple Inc. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Apple Inc. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * pkinit_apple_utils.c - PKINIT utilities, Mac OS X version
+ *
+ * Created 19 May 2004 by Doug Mitchell at Apple.
+ */
+
+#if APPLE_PKINIT
+
+#include "pkinit_apple_utils.h"
+#include "pkinit_asn1.h"
+#include <sys/errno.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <Security/Security.h>
+
+/*
+ * Cruft needed to attach to a module
+ */
+static CSSM_VERSION vers = {2, 0};
+static const CSSM_GUID testGuid = { 0xFADE, 0, 0, { 1,2,3,4,5,6,7,0 }};
+
+/*
+ * Standard app-level memory functions required by CDSA.
+ */
+static void * cuAppMalloc (CSSM_SIZE size, void *allocRef) {
+ return( malloc(size) );
+}
+
+static void cuAppFree (void *mem_ptr, void *allocRef) {
+ free(mem_ptr);
+ return;
+}
+
+static void * cuAppRealloc (void *ptr, CSSM_SIZE size, void *allocRef) {
+ return( realloc( ptr, size ) );
+}
+
+static void * cuAppCalloc (uint32 num, CSSM_SIZE size, void *allocRef) {
+ return( calloc( num, size ) );
+}
+
+static CSSM_API_MEMORY_FUNCS memFuncs = {
+ cuAppMalloc,
+ cuAppFree,
+ cuAppRealloc,
+ cuAppCalloc,
+ NULL
+};
+
+/*
+ * Init CSSM; returns CSSM_FALSE on error. Reusable.
+ */
+static CSSM_BOOL cssmInitd = CSSM_FALSE;
+
+static CSSM_BOOL cuCssmStartup()
+{
+ CSSM_RETURN crtn;
+ CSSM_PVC_MODE pvcPolicy = CSSM_PVC_NONE;
+
+ if(cssmInitd) {
+ return CSSM_TRUE;
+ }
+ crtn = CSSM_Init (&vers,
+ CSSM_PRIVILEGE_SCOPE_NONE,
+ &testGuid,
+ CSSM_KEY_HIERARCHY_NONE,
+ &pvcPolicy,
+ NULL /* reserved */);
+ if(crtn != CSSM_OK)
+ {
+ return CSSM_FALSE;
+ }
+ else {
+ cssmInitd = CSSM_TRUE;
+ return CSSM_TRUE;
+ }
+}
+
+CSSM_CL_HANDLE pkiClStartup(void)
+{
+ CSSM_CL_HANDLE clHand;
+ CSSM_RETURN crtn;
+
+ if(cuCssmStartup() == CSSM_FALSE) {
+ return 0;
+ }
+ crtn = CSSM_ModuleLoad(&gGuidAppleX509CL,
+ CSSM_KEY_HIERARCHY_NONE,
+ NULL, /* eventHandler */
+ NULL); /* AppNotifyCallbackCtx */
+ if(crtn) {
+ return 0;
+ }
+ crtn = CSSM_ModuleAttach (&gGuidAppleX509CL,
+ &vers,
+ &memFuncs, /* memFuncs */
+ 0, /* SubserviceID */
+ CSSM_SERVICE_CL, /* SubserviceFlags - Where is this used? */
+ 0, /* AttachFlags */
+ CSSM_KEY_HIERARCHY_NONE,
+ NULL, /* FunctionTable */
+ 0, /* NumFuncTable */
+ NULL, /* reserved */
+ &clHand);
+ if(crtn) {
+ return 0;
+ }
+ else {
+ return clHand;
+ }
+}
+
+CSSM_RETURN pkiClDetachUnload(
+ CSSM_CL_HANDLE clHand)
+{
+ CSSM_RETURN crtn = CSSM_ModuleDetach(clHand);
+ if(crtn) {
+ return crtn;
+ }
+ return CSSM_ModuleUnload(&gGuidAppleX509CL, NULL, NULL);
+}
+
+/*
+ * CSSM_DATA <--> krb5_ui_4
+ */
+krb5_error_code pkiDataToInt(
+ const CSSM_DATA *cdata,
+ krb5_int32 *i) /* RETURNED */
+{
+ krb5_ui_4 len;
+ krb5_int32 rtn = 0;
+ krb5_ui_4 dex;
+
+ if((cdata->Length == 0) || (cdata->Data == NULL)) {
+ *i = 0;
+ return 0;
+ }
+ len = cdata->Length;
+ if(len > sizeof(krb5_int32)) {
+ return ASN1_BAD_LENGTH;
+ }
+
+ uint8 *cp = cdata->Data;
+ for(dex=0; dex<len; dex++) {
+ rtn = (rtn << 8) | *cp++;
+ }
+ *i = rtn;
+ return 0;
+}
+
+krb5_error_code pkiIntToData(
+ krb5_int32 num,
+ CSSM_DATA *cdata,
+ SecAsn1CoderRef coder)
+{
+ krb5_ui_4 unum = (krb5_ui_4)num;
+ uint32 len = 0;
+ uint8 *cp = NULL;
+ unsigned i;
+
+ if(unum < 0x100) {
+ len = 1;
+ }
+ else if(unum < 0x10000) {
+ len = 2;
+ }
+ else if(unum < 0x1000000) {
+ len = 3;
+ }
+ else {
+ len = 4;
+ }
+ if(SecAsn1AllocItem(coder, cdata, len)) {
+ return ENOMEM;
+ }
+ cp = &cdata->Data[len - 1];
+ for(i=0; i<len; i++) {
+ *cp-- = unum & 0xff;
+ unum >>= 8;
+ }
+ return 0;
+}
+
+/*
+ * raw data --> krb5_data
+ */
+krb5_error_code pkiDataToKrb5Data(
+ const void *data,
+ unsigned dataLen,
+ krb5_data *kd)
+{
+ assert(data != NULL);
+ assert(kd != NULL);
+ kd->data = (char *)malloc(dataLen);
+ if(kd->data == NULL) {
+ return ENOMEM;
+ }
+ kd->length = dataLen;
+ memmove(kd->data, data, dataLen);
+ return 0;
+}
+
+/*
+ * CSSM_DATA <--> krb5_data
+ *
+ * CSSM_DATA data is managed by a SecAsn1CoderRef; krb5_data data is mallocd.
+ *
+ * Both return nonzero on error.
+ */
+krb5_error_code pkiCssmDataToKrb5Data(
+ const CSSM_DATA *cd,
+ krb5_data *kd)
+{
+ assert(cd != NULL);
+ return pkiDataToKrb5Data(cd->Data, cd->Length, kd);
+}
+
+krb5_error_code pkiKrb5DataToCssm(
+ const krb5_data *kd,
+ CSSM_DATA *cd,
+ SecAsn1CoderRef coder)
+{
+ assert((cd != NULL) && (kd != NULL));
+ if(SecAsn1AllocCopy(coder, kd->data, kd->length, cd)) {
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ * CFDataRef --> krb5_data, mallocing the destination contents.
+ */
+krb5_error_code pkiCfDataToKrb5Data(
+ CFDataRef cfData,
+ krb5_data *kd) /* content mallocd and RETURNED */
+{
+ return pkiDataToKrb5Data(CFDataGetBytePtr(cfData),
+ CFDataGetLength(cfData), kd);
+}
+
+krb5_boolean pkiCompareCssmData(
+ const CSSM_DATA *d1,
+ const CSSM_DATA *d2)
+{
+ if((d1 == NULL) || (d2 == NULL)) {
+ return FALSE;
+ }
+ if(d1->Length != d2->Length) {
+ return FALSE;
+ }
+ if(memcmp(d1->Data, d2->Data, d1->Length)) {
+ return FALSE;
+ }
+ else {
+ return TRUE;
+ }
+}
+
+/*
+ * krb5_timestamp --> a mallocd string in generalized format
+ */
+krb5_error_code pkiKrbTimestampToStr(
+ krb5_timestamp kts,
+ char **str) /* mallocd and RETURNED */
+{
+ time_t gmt_time = kts;
+ struct tm *utc = gmtime(&gmt_time);
+ if (utc == NULL ||
+ utc->tm_year > 8099 || utc->tm_mon > 11 ||
+ utc->tm_mday > 31 || utc->tm_hour > 23 ||
+ utc->tm_min > 59 || utc->tm_sec > 59) {
+ return ASN1_BAD_GMTIME;
+ }
+ char *outStr = (char *)malloc(16);
+ if(outStr == NULL) {
+ return ENOMEM;
+ }
+ sprintf(outStr, "%04d%02d%02d%02d%02d%02dZ",
+ utc->tm_year + 1900, utc->tm_mon + 1,
+ utc->tm_mday, utc->tm_hour, utc->tm_min, utc->tm_sec);
+ *str = outStr;
+ return 0;
+}
+
+krb5_error_code pkiTimeStrToKrbTimestamp(
+ const char *str,
+ unsigned len,
+ krb5_timestamp *kts) /* RETURNED */
+{
+ char szTemp[5];
+ unsigned x;
+ unsigned i;
+ char *cp;
+ struct tm tmp;
+ time_t t;
+
+ if(len != 15) {
+ return ASN1_BAD_LENGTH;
+ }
+
+ if((str == NULL) || (kts == NULL)) {
+ return KRB5_CRYPTO_INTERNAL;
+ }
+
+ cp = (char *)str;
+ memset(&tmp, 0, sizeof(tmp));
+
+ /* check that all characters except last are digits */
+ for(i=0; i<(len - 1); i++) {
+ if ( !(isdigit(cp[i])) ) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ }
+
+ /* check last character is a 'Z' */
+ if(cp[len - 1] != 'Z' ) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+
+ /* YEAR */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = *cp++;
+ szTemp[3] = *cp++;
+ szTemp[4] = '\0';
+ x = atoi( szTemp );
+ /* by definition - tm_year is year - 1900 */
+ tmp.tm_year = x - 1900;
+
+ /* MONTH */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = '\0';
+ x = atoi( szTemp );
+ /* in the string, months are from 1 to 12 */
+ if((x > 12) || (x <= 0)) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ /* in a tm, 0 to 11 */
+ tmp.tm_mon = x - 1;
+
+ /* DAY */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = '\0';
+ x = atoi( szTemp );
+ /* 1..31 */
+ if((x > 31) || (x <= 0)) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ tmp.tm_mday = x;
+
+ /* HOUR */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = '\0';
+ x = atoi( szTemp );
+ if((x > 23) || (x < 0)) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ tmp.tm_hour = x;
+
+ /* MINUTE */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = '\0';
+ x = atoi( szTemp );
+ if((x > 59) || (x < 0)) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ tmp.tm_min = x;
+
+ /* SECOND */
+ szTemp[0] = *cp++;
+ szTemp[1] = *cp++;
+ szTemp[2] = '\0';
+ x = atoi( szTemp );
+ if((x > 59) || (x < 0)) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ tmp.tm_sec = x;
+ t = timegm(&tmp);
+ if(t == -1) {
+ return ASN1_BAD_TIMEFORMAT;
+ }
+ *kts = t;
+ return 0;
+}
+
+/*
+ * How many items in a NULL-terminated array of pointers?
+ */
+unsigned pkiNssArraySize(
+ const void **array)
+{
+ unsigned count = 0;
+ if (array) {
+ while (*array++) {
+ count++;
+ }
+ }
+ return count;
+}
+
+#endif /* APPLE_PKINIT */
diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c
index dbd00bf25..96df2db04 100644
--- a/src/lib/krb5/krb/preauth2.c
+++ b/src/lib/krb5/krb/preauth2.c
@@ -30,6 +30,10 @@
*/
#include "k5-int.h"
+#if APPLE_PKINIT
+#include "pkinit_client.h"
+#include "pkinit_cert_store.h"
+#endif /* APPLE_PKINIT */
#include "osconf.h"
#include <krb5/preauth_plugin.h>
#include "int-proto.h"
@@ -991,6 +995,279 @@ krb5_error_code pa_sam(krb5_context context,
return(0);
}
+#if APPLE_PKINIT
+/*
+ * PKINIT. One function to generate AS-REQ, one to parse AS-REP
+ */
+#define PKINIT_DEBUG 0
+#if PKINIT_DEBUG
+#define kdcPkinitDebug(args...) printf(args)
+#else
+#define kdcPkinitDebug(args...)
+#endif
+
+static krb5_error_code pa_pkinit_gen_req(
+ krb5_context context,
+ krb5_kdc_req *request,
+ krb5_pa_data *in_padata,
+ krb5_pa_data **out_padata,
+ krb5_data *salt,
+ krb5_data *s2kparams,
+ krb5_enctype *etype,
+ krb5_keyblock *as_key,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ krb5_gic_get_as_key_fct gak_fct,
+ void *gak_data)
+{
+ krb5_error_code krtn;
+ krb5_data out_data = {0, 0, NULL};
+ krb5_timestamp kctime = 0;
+ krb5_int32 cusec = 0;
+ krb5_ui_4 nonce = 0;
+ krb5_checksum cksum;
+ krb5_pkinit_signing_cert_t client_cert;
+ krb5_data *der_req = NULL;
+ char *client_principal = NULL;
+ char *server_principal = NULL;
+ unsigned char nonce_bytes[4];
+ krb5_data nonce_data = {0, 4, (char *)nonce_bytes};
+ int dex;
+
+ /*
+ * Trusted CA list and specific KC cert optionally obtained via
+ * krb5_pkinit_get_server_certs(). All are DER-encoded certs.
+ */
+ krb5_data *trusted_CAs = NULL;
+ krb5_ui_4 num_trusted_CAs;
+ krb5_data kdc_cert = {0};
+
+ kdcPkinitDebug("pa_pkinit_gen_req\n");
+
+ /* If we don't have a client cert, we're done */
+ if(request->client == NULL) {
+ kdcPkinitDebug("No request->client; aborting PKINIT\n");
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+ krtn = krb5_unparse_name(context, request->client, &client_principal);
+ if(krtn) {
+ return krtn;
+ }
+ krtn = krb5_pkinit_get_client_cert(client_principal, &client_cert);
+ free(client_principal);
+ if(krtn) {
+ kdcPkinitDebug("No client cert; aborting PKINIT\n");
+ return krtn;
+ }
+
+ /* optional platform-dependent CA list and KDC cert */
+ krtn = krb5_unparse_name(context, request->server, &server_principal);
+ if(krtn) {
+ goto cleanup;
+ }
+ krtn = krb5_pkinit_get_server_certs(client_principal, server_principal,
+ &trusted_CAs, &num_trusted_CAs, &kdc_cert);
+ if(krtn) {
+ goto cleanup;
+ }
+
+ /* checksum of the encoded KDC-REQ-BODY */
+ krtn = encode_krb5_kdc_req_body(request, &der_req);
+ if(krtn) {
+ kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn);
+ goto cleanup;
+ }
+ krtn = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, der_req, &cksum);
+ if(krtn) {
+ goto cleanup;
+ }
+
+ krtn = krb5_us_timeofday(context, &kctime, &cusec);
+ if(krtn) {
+ goto cleanup;
+ }
+
+ /* cook up a random 4-byte nonce */
+ krtn = krb5_c_random_make_octets(context, &nonce_data);
+ if(krtn) {
+ goto cleanup;
+ }
+ for(dex=0; dex<4; dex++) {
+ nonce <<= 8;
+ nonce |= nonce_bytes[dex];
+ }
+
+ krtn = krb5int_pkinit_as_req_create(context,
+ kctime, cusec, nonce, &cksum,
+ client_cert,
+ trusted_CAs, num_trusted_CAs,
+ (kdc_cert.data ? &kdc_cert : NULL),
+ &out_data);
+ if(krtn) {
+ kdcPkinitDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", (int)krtn);
+ goto cleanup;
+ }
+ *out_padata = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
+ if(*out_padata == NULL) {
+ krtn = ENOMEM;
+ free(out_data.data);
+ goto cleanup;
+ }
+ (*out_padata)->magic = KV5M_PA_DATA;
+ (*out_padata)->pa_type = KRB5_PADATA_PK_AS_REQ;
+ (*out_padata)->length = out_data.length;
+ (*out_padata)->contents = (krb5_octet *)out_data.data;
+ krtn = 0;
+cleanup:
+ if(client_cert) {
+ krb5_pkinit_release_cert(client_cert);
+ }
+ if(cksum.contents) {
+ free(cksum.contents);
+ }
+ if (der_req) {
+ krb5_free_data(context, der_req);
+ }
+ if(server_principal) {
+ free(server_principal);
+ }
+ /* free data mallocd by krb5_pkinit_get_server_certs() */
+ if(trusted_CAs) {
+ unsigned udex;
+ for(udex=0; udex<num_trusted_CAs; udex++) {
+ free(trusted_CAs[udex].data);
+ }
+ free(trusted_CAs);
+ }
+ if(kdc_cert.data) {
+ free(kdc_cert.data);
+ }
+ return krtn;
+
+}
+
+static krb5_error_code pa_pkinit_parse_rep(
+ krb5_context context,
+ krb5_kdc_req *request,
+ krb5_pa_data *in_padata,
+ krb5_pa_data **out_padata,
+ krb5_data *salt,
+ krb5_data *s2kparams,
+ krb5_enctype *etype,
+ krb5_keyblock *as_key,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ krb5_gic_get_as_key_fct gak_fct,
+ void *gak_data)
+{
+ krb5int_cert_sig_status sig_status = (krb5int_cert_sig_status)-999;
+ krb5_error_code krtn;
+ krb5_data asRep;
+ krb5_keyblock local_key = {0};
+ krb5_pkinit_signing_cert_t client_cert;
+ char *princ_name = NULL;
+ krb5_checksum as_req_checksum_rcd = {0}; /* received checksum */
+ krb5_checksum as_req_checksum_gen = {0}; /* calculated checksum */
+ krb5_data *encoded_as_req = NULL;
+
+ *out_padata = NULL;
+ kdcPkinitDebug("pa_pkinit_parse_rep\n");
+ if((in_padata == NULL) || (in_padata->length== 0)) {
+ kdcPkinitDebug("pa_pkinit_parse_rep: no in_padata\n");
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ /* If we don't have a client cert, we're done */
+ if(request->client == NULL) {
+ kdcPkinitDebug("No request->client; aborting PKINIT\n");
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+ krtn = krb5_unparse_name(context, request->client, &princ_name);
+ if(krtn) {
+ return krtn;
+ }
+ krtn = krb5_pkinit_get_client_cert(princ_name, &client_cert);
+ free(princ_name);
+ if(krtn) {
+ kdcPkinitDebug("No client cert; aborting PKINIT\n");
+ return krtn;
+ }
+
+ memset(&local_key, 0, sizeof(local_key));
+ asRep.data = (char *)in_padata->contents;
+ asRep.length = in_padata->length;
+ krtn = krb5int_pkinit_as_rep_parse(context, &asRep, client_cert,
+ &local_key, &as_req_checksum_rcd, &sig_status,
+ /* don't care about returned certs - do we? */
+ NULL, NULL, NULL);
+ if(krtn) {
+ kdcPkinitDebug("pkinit_as_rep_parse returned %d\n", (int)krtn);
+ return krtn;
+ }
+ switch(sig_status) {
+ case pki_cs_good:
+ break;
+ default:
+ kdcPkinitDebug("pa_pkinit_parse_rep: bad cert/sig status %d\n",
+ (int)sig_status);
+ krtn = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto error_out;
+ }
+
+ /* calculate checksum of incoming AS-REQ using the decryption key
+ * we just got from the ReplyKeyPack */
+ krtn = encode_krb5_as_req(request, &encoded_as_req);
+ if(krtn) {
+ goto error_out;
+ }
+ krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype,
+ &local_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
+ encoded_as_req, &as_req_checksum_gen);
+ if(krtn) {
+ goto error_out;
+ }
+ if((as_req_checksum_gen.length != as_req_checksum_rcd.length) ||
+ memcmp(as_req_checksum_gen.contents,
+ as_req_checksum_rcd.contents,
+ as_req_checksum_gen.length)) {
+ kdcPkinitDebug("pa_pkinit_parse_rep: checksum miscompare\n");
+ krtn = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto error_out;
+ }
+
+ /* We have the key; transfer to caller */
+ if (as_key->length) {
+ krb5_free_keyblock_contents(context, as_key);
+ }
+ *as_key = local_key;
+
+ #if PKINIT_DEBUG
+ fprintf(stderr, "pa_pkinit_parse_rep: SUCCESS\n");
+ fprintf(stderr, "enctype %d keylen %d keydata %02x %02x %02x %02x...\n",
+ (int)as_key->enctype, (int)as_key->length,
+ as_key->contents[0], as_key->contents[1],
+ as_key->contents[2], as_key->contents[3]);
+ #endif
+
+ krtn = 0;
+
+error_out:
+ if(as_req_checksum_rcd.contents) {
+ free(as_req_checksum_rcd.contents);
+ }
+ if(as_req_checksum_gen.contents) {
+ free(as_req_checksum_gen.contents);
+ }
+ if(encoded_as_req) {
+ krb5_free_data(context, encoded_as_req);
+ }
+ if(krtn && (local_key.contents != NULL)) {
+ krb5_free_keyblock_contents(context, &local_key);
+ }
+ return krtn;
+}
+#endif /* APPLE_PKINIT */
+
static
krb5_error_code pa_sam_2(krb5_context context,
krb5_kdc_req *request,
@@ -1320,6 +1597,7 @@ krb5_error_code pa_sam_2(krb5_context context,
return(0);
}
+/* FIXME - order significant? */
static const pa_types_t pa_types[] = {
{
KRB5_PADATA_PW_SALT,
@@ -1331,6 +1609,18 @@ static const pa_types_t pa_types[] = {
pa_salt,
PA_INFO,
},
+#if APPLE_PKINIT
+ {
+ KRB5_PADATA_PK_AS_REQ,
+ pa_pkinit_gen_req,
+ PA_INFO,
+ },
+ {
+ KRB5_PADATA_PK_AS_REP,
+ pa_pkinit_parse_rep,
+ PA_REAL,
+ },
+#endif /* APPLE_PKINIT */
{
KRB5_PADATA_ENC_TIMESTAMP,
pa_enc_timestamp,
@@ -1596,6 +1886,17 @@ krb5_do_preauth(krb5_context context,
salt, s2kparams, etype, as_key,
prompter, prompter_data,
gak_fct, gak_data)))) {
+ if (paorder[h] == PA_INFO) {
+#ifdef DEBUG
+ fprintf (stderr,
+ "internal function for type %d, flag %d "
+ "failed with err %d\n",
+ in_padata[i]->pa_type, paorder[h], ret);
+#endif
+ ret = 0;
+ continue; /* PA_INFO type failed, ignore */
+ }
+
goto cleanup;
}
diff --git a/src/lib/krb5/krb/str_conv.c b/src/lib/krb5/krb/str_conv.c
index a650496fc..fdc4d727e 100644
--- a/src/lib/krb5/krb/str_conv.c
+++ b/src/lib/krb5/krb/str_conv.c
@@ -73,7 +73,10 @@ static const struct salttype_lookup_entry salttype_table[] = {
{ KRB5_KDB_SALTTYPE_NOREALM, "norealm", "Version 5 - No Realm" },
{ KRB5_KDB_SALTTYPE_ONLYREALM, "onlyrealm", "Version 5 - Realm Only" },
{ KRB5_KDB_SALTTYPE_SPECIAL, "special", "Special" },
-{ KRB5_KDB_SALTTYPE_AFS3, "afs3", "AFS version 3" }
+{ KRB5_KDB_SALTTYPE_AFS3, "afs3", "AFS version 3" },
+#if PKINIT_APPLE
+{ KRB5_KDB_SALTTYPE_CERTHASH, "certhash", "PKINIT Cert Hash" }
+#endif /* PKINIT_APPLE */
};
static const int salttype_table_nents = sizeof(salttype_table)/
sizeof(salttype_table[0]);