/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
*/
/*
* COPYRIGHT (C) 2006,2007
* THE REGENTS OF THE UNIVERSITY OF MICHIGAN
* ALL RIGHTS RESERVED
*
* Permission is granted to use, copy, create derivative works
* and redistribute this software and such derivative works
* for any purpose, so long as the name of The University of
* Michigan is not used in any advertising or publicity
* pertaining to the use of distribution of this software
* without specific, written prior authorization. If the
* above copyright notice or any other identification of the
* University of Michigan is included in any copy of any
* portion of this software, then the disclaimer below must
* also be included.
*
* THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
* FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
* PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
* MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
* WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
* REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
* FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
* CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
* OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
* IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGES.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <dlfcn.h>
#include "pkinit.h"
/* Remove when FAST PKINIT is settled. */
#include "../fast_factor.h"
/*
* It is anticipated that all the special checks currently
* required when talking to a Longhorn server will go away
* by the time it is officially released and all references
* to the longhorn global can be removed and any code
* #ifdef'd with LONGHORN_BETA_COMPAT can be removed.
*
* Current testing (20070620) is against a patched Beta 3
* version of Longhorn. Most, if not all, problems should
* be fixed in SP1 of Longhorn.
*/
static krb5_error_code
const krb5_checksum *cksum,
static krb5_error_code
static krb5_error_code
krb5_pa_data *** out_padata,
void *prompter_data,
{
pkiDebug("kdc_options = 0x%x till = %d\n",
/* If we don't have a client, we're done */
pkiDebug("No request->client; aborting PKINIT\n");
return KRB5KDC_ERR_PREAUTH_FAILED;
}
if (retval) {
goto cleanup;
}
/* checksum of the encoded KDC-REQ-BODY */
if (retval) {
goto cleanup;
}
if (retval)
goto cleanup;
#ifdef DEBUG_CKSUM
#endif
if (retval)
goto cleanup;
/* XXX PKINIT RFC says that nonce in PKAuthenticator doesn't have be the
* same as in the AS_REQ. However, if we pick a different nonce, then we
* need to remember that info when AS_REP is returned. I'm choosing to
* reuse the AS_REQ nonce.
*/
pkiDebug("error %d on pkinit_as_req_create; aborting PKINIT\n",
(int) retval);
goto cleanup;
}
/*
* The most we'll return is two pa_data, normally just one.
* We need to make room for the NULL terminator.
*/
if (return_pa_data == NULL)
goto cleanup;
if (return_pa_data[0] == NULL)
goto cleanup;
goto cleanup;
else
/*
* LH Beta 3 requires the extra pa-data, even for RFC requests,
* in order to get the Checksum rather than a Nonce in the reply.
* This can be removed when LH SP1 is released.
*/
} else {
}
retval = 0;
if (retval) {
if (return_pa_data) {
free(return_pa_data[0]);
}
if (out_data) {
}
}
return retval;
}
static krb5_error_code
const krb5_checksum * cksum,
{
/* Create the authpack */
if (auth_pack9 == NULL)
goto cleanup;
break;
case KRB5_PADATA_PK_AS_REQ:
goto cleanup;
goto cleanup;
/* add List of CMS algorithms */
if (retval)
goto cleanup;
break;
default:
pkiDebug("as_req: unrecognized pa_type = %d\n",
retval = -1;
goto cleanup;
}
switch(protocol) {
case DH_PROTOCOL:
pkiDebug("as_req: DH key transport algorithm\n");
if (retval) {
pkiDebug("failed to copy dh_oid\n");
goto cleanup;
}
/* create client-side DH keys */
pkiDebug("failed to create dh parameters\n");
goto cleanup;
}
break;
case RSA_PROTOCOL:
pkiDebug("as_req: RSA key transport algorithm\n");
break;
case KRB5_PADATA_PK_AS_REQ:
break;
}
break;
default:
pkiDebug("as_req: unknown key transport protocol %d\n",
protocol);
retval = -1;
goto cleanup;
}
/* Encode the authpack */
case KRB5_PADATA_PK_AS_REQ:
break;
break;
}
if (retval) {
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/client_auth_pack");
#endif
/* create PKCS7 object from authpack */
case KRB5_PADATA_PK_AS_REQ:
goto cleanup;
}
/* For the new protocol, we support anonymous. */
krb5_anonymous_principal())) {
CMS_SIGN_CLIENT, (unsigned char *)
} else {
CMS_SIGN_CLIENT, 1,
(unsigned char *)
}
#ifdef DEBUG_ASN1
"/tmp/client_signed_data");
#endif
break;
goto cleanup;
}
break;
#ifdef DEBUG_ASN1
#endif
}
if (retval) {
pkiDebug("failed to create pkcs7 signed data\n");
goto cleanup;
}
/* create a list of trusted CAs */
case KRB5_PADATA_PK_AS_REQ:
if (retval)
goto cleanup;
if (retval)
goto cleanup;
/* Encode the as-req */
break;
#if 0
/* W2K3 KDC doesn't like this */
if (retval)
goto cleanup;
#endif
if (retval)
goto cleanup;
/* Encode the as-req */
break;
}
#ifdef DEBUG_ASN1
if (!retval)
"/tmp/client_as_req");
#endif
case KRB5_PADATA_PK_AS_REQ:
break;
break;
}
return retval;
}
static krb5_error_code
{
/*
* One way or the other - success or failure - no other PA systems can
* work if the server sent us a PKINIT reply, since only we know how to
* decrypt the key.
*/
pkiDebug("pa_pkinit_parse_rep: no in_padata\n");
return KRB5KDC_ERR_PREAUTH_FAILED;
}
retval =
if (retval) {
pkiDebug("pkinit_as_rep_parse returned %d (%s)\n",
goto cleanup;
}
retval = 0;
return retval;
}
static krb5_error_code
int *valid_san,
int *need_eku_checking)
{
unsigned char ***get_dns;
int i, j;
*valid_san = 0;
*need_eku_checking = 1;
&cfghosts);
pkiDebug("%s: No pkinit_kdc_hostname values found in config file\n",
} else {
pkiDebug("%s: pkinit_kdc_hostname values found in config file\n",
}
if (retval) {
goto out;
}
#if 0
pkiDebug("%s: call_san_checking_plugins() returned retval %d\n",
if (retval) {
goto out;
}
pkiDebug("%s: call_san_checking_plugins() returned decision %d and "
"need_eku_checking %d\n",
if (plugin_decision != NO_DECISION) {
goto out;
}
#endif
*valid_san = 1;
*need_eku_checking = 0;
retval = 0;
goto out;
}
}
pkiDebug("%s: no certhosts (or we wouldn't accept them anyway)\n",
goto out;
}
pkiDebug("%s: comparing cert name '%s' with config name '%s'\n",
/* Solaris Kerberos */
*valid_san = 1;
retval = 0;
goto out;
}
}
}
/* We found no match */
retval = 0;
out:
}
}
pkiDebug("%s: returning retval %d, valid_san %d, need_eku_checking %d\n",
return retval;
}
static krb5_error_code
int *eku_accepted)
{
*eku_accepted = 0;
*eku_accepted = 1;
retval = 0;
goto out;
}
1, /* kdc cert */
if (retval) {
pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n",
goto out;
}
out:
pkiDebug("%s: returning retval %d, eku_accepted %d\n",
return retval;
}
/*
* Parse PA-PK-AS-REP message. Optionally evaluates the message's
* certificate chain.
* Optionally returns various components.
*/
static krb5_error_code
{
unsigned int client_key_len = 0;
int valid_san = 0;
int valid_eku = 0;
#ifdef DEBUG_ASN1
"/tmp/client_as_rep");
#endif
return retval;
}
pkiDebug("as_rep: DH key transport algorithm\n");
#ifdef DEBUG_ASN1
#endif
pkiDebug("failed to verify pkcs7 signed data\n");
goto cleanup;
}
break;
pkiDebug("as_rep: RSA key transport algorithm\n");
pkiDebug("failed to verify pkcs7 enveloped data\n");
goto cleanup;
}
break;
default:
retval = -1;
goto cleanup;
}
0);
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (!valid_san) {
pkiDebug("%s: did not find an acceptable SAN in KDC certificate\n",
goto cleanup;
}
if (need_eku_checking) {
&valid_eku);
if (retval)
goto cleanup;
if (!valid_eku) {
pkiDebug("%s: did not find an acceptable EKU in KDC certificate\n",
goto cleanup;
}
} else
#ifdef DEBUG_ASN1
"/tmp/client_dh_key");
#endif
&kdc_dh)) != 0) {
pkiDebug("failed to decode kdc_dh_key_info\n");
goto cleanup;
}
/* client after KDC reply */
&client_key, &client_key_len)) != 0) {
pkiDebug("failed to process dh params\n");
goto cleanup;
}
if (retval) {
pkiDebug("failed to create key pkinit_octetstring2key %s\n",
goto cleanup;
}
break;
#ifdef DEBUG_ASN1
"/tmp/client_key_pack");
#endif
&key_pack)) != 0) {
pkiDebug("failed to decode reply_key_pack\n");
#ifdef LONGHORN_BETA_COMPAT
/*
* LH Beta 3 requires the extra pa-data, even for RFC requests,
* in order to get the Checksum rather than a Nonce in the reply.
* This can be removed when LH SP1 is released.
*/
#else
if (pa_type == KRB5_PADATA_PK_AS_REP)
#endif
goto cleanup;
else {
if ((retval =
&key_pack9)) != 0) {
pkiDebug("failed to decode reply_key_pack_draft9\n");
goto cleanup;
}
pkiDebug("decode reply_key_pack_draft9\n");
retval = -1;
goto cleanup;
}
break;
}
}
/*
* This is hack but Windows sends back SHA1 checksum
* with checksum type of 14. There is currently no
* checksum type of 14 defined.
*/
encoded_request, &cksum);
if (retval) {
pkiDebug("failed to make a checksum\n");
goto cleanup;
}
pkiDebug("failed to match the checksums\n");
#ifdef DEBUG_CKSUM
pkiDebug("calculating checksum on buf size (%d)\n",
pkiDebug("received checksum type=%d size=%d ",
pkiDebug("expected checksum type=%d size=%d ",
#endif
goto cleanup;
} else
pkiDebug("checksums match\n");
break;
default:
goto cleanup;
}
retval = 0;
}
pkiDebug("pkinit_as_rep_parse returning %d (%s)\n",
return retval;
}
static void
{
pkiDebug("pkinit_client_profile %p %p %p %p\n",
pkiDebug("%s: invalid value (%d) for pkinit_dh_min_bits, "
"using default value (%d) instead\n", __FUNCTION__,
}
&eku_string);
if (eku_string != NULL) {
} else {
pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n",
}
}
#ifdef LONGHORN_BETA_COMPAT
/* Temporarily just set global flag from config file */
0,
&longhorn);
#endif
/* Only process anchors here if they were not specified on command line */
}
static krb5_error_code
void *plugin_context,
void *request_context,
struct _krb5_preauth_client_rock *rock,
void *prompter_data,
void *gak_data,
{
int processing_request = 0;
pkiDebug("pkinit_client_process %p %p %p %p\n",
/* Remove (along with armor_key) when FAST PKINIT is settled. */
/* Don't use PKINIT if also using FAST. */
return EINVAL;
}
return EINVAL;
case KRB5_PADATA_PK_AS_REQ:
pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");
processing_request = 1;
break;
case KRB5_PADATA_PK_AS_REP:
pkiDebug("processing KRB5_PADATA_PK_AS_REP\n");
break;
pkiDebug("processing KRB5_PADATA_PK_AS_REQ_OLD\n");
processing_request = 1;
} else {
pkiDebug("processing KRB5_PADATA_PK_AS_REP_OLD\n");
}
break;
default:
pkiDebug("unrecognized patype = %d for PKINIT\n",
return EINVAL;
}
if (processing_request) {
/* Solaris Kerberos - check return value */
if (retval) {
pkiDebug("pkinit_identity_set_prompter returned %d (%s)\n",
return retval;
}
if (retval) {
pkiDebug("pkinit_identity_initialize returned %d (%s)\n",
return retval;
}
} else {
/*
* Get the enctype of the reply.
*/
if (retval) {
pkiDebug("get_data_proc returned %d (%s)\n",
return retval;
}
}
pkiDebug("pkinit_client_process: returning %d (%s)\n",
return retval;
}
static krb5_error_code
void *plugin_context,
void *request_context,
struct _krb5_preauth_client_rock *rock,
void *prompter_data,
void *gak_data,
{
int do_again = 0;
pkiDebug("pkinit_client_tryagain %p %p %p %p\n",
return retval;
#ifdef DEBUG_ASN1
#endif
if (retval) {
pkiDebug("decode_krb5_typed_data failed\n");
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/client_typed_data");
#endif
switch(typed_data[0]->type) {
case TD_TRUSTED_CERTIFIERS:
case TD_INVALID_CERTIFICATES:
if (retval) {
pkiDebug("failed to decode sequence of trusted certifiers\n");
goto cleanup;
}
if (!retval)
do_again = 1;
break;
case TD_DH_PARAMETERS:
if (retval) {
pkiDebug("failed to decode td_dh_parameters\n");
goto cleanup;
}
if (!retval)
do_again = 1;
break;
default:
break;
}
if (do_again) {
if (retval)
goto cleanup;
}
retval = 0;
if (krb5_trusted_certifiers != NULL)
if (typed_data != NULL)
pkiDebug("pkinit_client_tryagain: returning %d (%s)\n",
return retval;
}
static int
{
return PA_REAL;
}
0
};
static void
void *plugin_context,
void **request_context)
{
*request_context = NULL;
return;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
if (retval)
goto cleanup;
*request_context = (void *) reqctx;
if (retval) {
}
return;
}
static void
void *plugin_context,
void *request_context)
{
return;
pkiDebug("%s: Bad magic value (%x) in req ctx\n",
return;
}
return;
}
static int
{
return ENOMEM;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
return retval;
}
static void
{
return;
}
}
static krb5_error_code
{
return ENOMEM;
return ENOMEM;
}
} else {
int i;
char **a = *array;
for (i = 0; a[i] != NULL; i++);
return ENOMEM;
for (i = 0; a[i] != NULL; i++) {
out[i] = a[i];
}
return ENOMEM;
}
}
return 0;
}
static krb5_error_code
const char *attr,
const char *value)
{
"X509_user_identity can not be given twice\n");
return KRB5_PREAUTH_FAILED;
}
"Could not duplicate X509_user_identity value\n");
return ENOMEM;
}
if (retval)
return retval;
pkiDebug("Setting flag to use RSA_PROTOCOL\n");
}
/* Solaris Kerberos: handle our PIN attr */
return ENOMEM;
}
return 0;
}
static krb5_error_code
void *plugin_context,
const char *attr,
const char *value)
{
if (retval)
return retval;
return 0;
}
/* Only necessary for static plugin linking support. */
#include "k5-plugin.h"
"pkinit", /* name */
supported_client_pa_types, /* pa_type_list */
NULL, /* enctype_list */
pkinit_client_plugin_init, /* (*init) */
pkinit_client_plugin_fini, /* (*fini) */
pkinit_client_get_flags, /* (*flags) */
pkinit_client_req_init, /* (*client_req_init) */
pkinit_client_req_fini, /* (*client_req_fini) */
pkinit_client_process, /* (*process) */
pkinit_client_tryagain, /* (*tryagain) */
pkinit_client_gic_opt /* (*gic_opt) */
};