/*
* 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 <string.h>
#include "pkinit.h"
static krb5_error_code
struct _krb5_db_entry_new * client,
struct _krb5_db_entry_new * server,
void *pa_plugin_context,
krb5_pa_data * data);
static krb5_error_code
struct _krb5_db_entry_new * client,
krb5_pa_data * data,
void *pa_plugin_context,
void **pa_request_context,
krb5_authdata ***authz_data);
static krb5_error_code
struct _krb5_db_entry_new * client,
struct _krb5_key_data * client_key,
krb5_pa_data ** send_pa,
void *pa_plugin_context,
void **pa_request_context);
static int pkinit_server_get_flags
(krb5_context, void **blob);
static void pkinit_fini_kdc_req_context
static int pkinit_server_plugin_init_realm
static void pkinit_server_plugin_fini_realm
static int pkinit_server_plugin_init
static void pkinit_server_plugin_fini
static krb5_error_code
{
pkiDebug("pkinit_create_edata: creating edata for error %d (%s)\n",
switch(err_code) {
break;
break;
break;
default:
pkiDebug("no edata needed for error %d (%s)\n",
retval = 0;
goto cleanup;
}
return retval;
}
/* ARGSUSED */
static krb5_error_code
struct _krb5_db_entry_new * client,
struct _krb5_db_entry_new * server,
void *pa_plugin_context,
krb5_pa_data * data)
{
pkiDebug("pkinit_server_get_edata: entered!\n");
/*
* If we don't have a realm context for the given realm,
* don't tell the client that we support pkinit!
*/
return retval;
}
static krb5_error_code
int *valid_san)
{
int i;
#ifdef DEBUG_SAN_INFO
#endif
&princs,
NULL);
if (retval) {
goto out;
}
/* XXX Verify this is consistent with client side XXX */
#if 0
pkiDebug("%s: call_san_checking_plugins() returned retval %d\n",
if (retval) {
goto cleanup;
}
pkiDebug("%s: call_san_checking_plugins() returned decision %d\n",
if (plugin_decision != NO_DECISION) {
goto out;
}
#endif
#ifdef DEBUG_SAN_INFO
#endif
#ifdef DEBUG_SAN_INFO
pkiDebug("%s: Comparing client '%s' to pkinit san value '%s'\n",
#endif
*valid_san = 1;
retval = 0;
goto out;
}
}
/*
* XXX if cert has names but none match, should we
* be returning KRB5KDC_ERR_CLIENT_NAME_MISMATCH here?
*/
pkiDebug("%s: no upn sans (or we wouldn't accept them anyway)\n",
goto out;
}
#ifdef DEBUG_SAN_INFO
pkiDebug("%s: Comparing client '%s' to upn san value '%s'\n",
#endif
*valid_san = 1;
retval = 0;
goto out;
}
}
/* We found no match */
*valid_san = 0;
/* XXX ??? If there was one or more name in the cert, but
* none matched the client name, then return mismatch? */
}
retval = 0;
out:
}
}
#ifdef DEBUG_SAN_INFO
if (client_string != NULL)
#endif
pkiDebug("%s: returning retval %d, valid_san %d\n",
return retval;
}
static krb5_error_code
int *eku_accepted)
{
*eku_accepted = 0;
*eku_accepted = 1;
retval = 0;
goto out;
}
0, /* 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;
}
/* ARGSUSED */
static krb5_error_code
struct _krb5_db_entry_new * client,
krb5_pa_data * data,
void *pa_plugin_context,
void **pa_request_context,
{
/* Solaris Kerberos: set but not used */
#if 0
#endif
pkiDebug("pkinit_verify_padata: entered!\n");
/* Solaris Kerberos */
return 0;
return EINVAL;
return 0;
#ifdef DEBUG_ASN1
#endif
/* create a per-request context */
if (retval)
goto cleanup;
case KRB5_PADATA_PK_AS_REQ:
pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");
if (retval) {
pkiDebug("decode_krb5_pa_pk_as_req failed\n");
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/kdc_signed_data");
#endif
&krb5_authz.length);
break;
pkiDebug("processing KRB5_PADATA_PK_AS_REQ_OLD\n");
if (retval) {
pkiDebug("decode_krb5_pa_pk_as_req_draft9 failed\n");
goto cleanup;
}
#ifdef DEBUG_ASN1
#endif
&krb5_authz.length);
break;
default:
goto cleanup;
}
if (retval) {
pkiDebug("pkcs7_signeddata_verify failed\n");
goto cleanup;
}
&valid_san);
if (retval)
goto cleanup;
if (!valid_san) {
pkiDebug("%s: did not find an acceptable SAN in user certificate\n",
goto cleanup;
}
if (retval)
goto cleanup;
if (!valid_eku) {
pkiDebug("%s: did not find an acceptable EKU in user certificate\n",
goto cleanup;
}
#ifdef DEBUG_ASN1
#endif
case KRB5_PADATA_PK_AS_REQ:
if (retval) {
pkiDebug("failed to decode krb5_auth_pack\n");
goto cleanup;
}
/* check dh parameters */
if (retval) {
pkiDebug("bad dh parameters\n");
goto cleanup;
}
}
/*
* The KDC may have modified the request after decoding it.
* We need to compute the checksum on the data that
* came from the client. Therefore, we use the original
* packet contents.
*/
if (retval) {
goto cleanup;
}
if (retval) {
goto cleanup;
}
if (retval) {
pkiDebug("unable to calculate AS REQ checksum\n");
goto cleanup;
}
pkiDebug("failed to match the checksum\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;
}
/* check if kdcPkId present and match KDC's subjectIdentifier */
int valid_kdcPkId = 0;
if (retval)
goto cleanup;
if (!valid_kdcPkId)
pkiDebug("kdcPkId in AS_REQ does not match KDC's cert"
"RFC says to ignore and proceed\n");
}
/* remember the decoded auth_pack for verify_padata routine */
break;
if (retval) {
pkiDebug("failed to decode krb5_auth_pack_draft9\n");
goto cleanup;
}
if (retval) {
pkiDebug("bad dh parameters\n");
goto cleanup;
}
}
/* remember the decoded auth_pack for verify_padata routine */
auth_pack9 = NULL;
break;
}
/* return authorization data to be included in the ticket */
case KRB5_PADATA_PK_AS_REQ:
if (my_authz_data == NULL) {
pkiDebug("Couldn't allocate krb5_authdata ptr array\n");
goto cleanup;
}
if (my_authz_data[0] == NULL) {
pkiDebug("Couldn't allocate krb5_authdata\n");
goto cleanup;
}
/* AD-INITIAL-VERIFIED-CAS must be wrapped in AD-IF-RELEVANT */
/* create an internal AD-INITIAL-VERIFIED-CAS data */
if (pkinit_authz_data == NULL) {
pkiDebug("Couldn't allocate krb5_authdata\n");
free(my_authz_data[0]);
goto cleanup;
}
/* content of this ad-type contains the certification
path with which the client certificate was validated
*/
#ifdef DEBUG_ASN1
"/tmp/kdc_pkinit_authz_data");
#endif
if (retval) {
pkiDebug("k5int_encode_krb5_authdata_elt failed\n");
free(my_authz_data[0]);
goto cleanup;
}
my_authz_data[0]->contents =
pkiDebug("Returning %d bytes of authorization data\n",
break;
default:
*authz_data = NULL;
}
/* remember to set the PREAUTH flag in the reply */
pkiDebug("pkinit_verify_padata failed: creating e-data\n");
pkiDebug("pkinit_create_edata failed\n");
}
case KRB5_PADATA_PK_AS_REQ:
break;
}
if (tmp_as_req != NULL)
if (auth_pack9 != NULL)
return retval;
}
/* ARGSUSED */
static krb5_error_code
struct _krb5_db_entry_new * client,
struct _krb5_key_data * client_key,
krb5_pa_data ** send_pa,
void *pa_plugin_context,
void **pa_request_context)
{
int i = 0;
unsigned int subjectPublicKey_len = 0;
unsigned int num_types;
int fixed_keypack = 0;
/* Solaris Kerberos */
return 0;
pkiDebug("missing request context \n");
return EINVAL;
}
pkiDebug("Unable to locate correct realm context\n");
return ENOENT;
}
pkiDebug("pkinit_return_padata: entered!\n");
if (encrypting_key->contents) {
encrypting_key->length = 0;
}
if (!krb5_c_valid_enctype(enctype))
continue;
else {
break;
}
}
goto cleanup;
}
case KRB5_PADATA_PK_AS_REQ:
goto cleanup;
}
/* let's assume it's RSA. we'll reset it to DH if needed */
break;
goto cleanup;
}
break;
default:
goto cleanup;
}
}
/* if this DH, then process finish computing DH key */
pkiDebug("received DH key delivery AS REQ\n");
&server_key, &server_key_len);
if (retval) {
goto cleanup;
}
}
if (retval) {
pkiDebug("pkinit_octetstring2key failed: %s\n",
goto cleanup;
}
if (retval) {
pkiDebug("encode_krb5_kdc_dh_key_info failed\n");
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/kdc_dh_key_info");
#endif
case KRB5_PADATA_PK_AS_REQ:
(unsigned char *)encoded_dhkey_info->data,
if (retval) {
pkiDebug("failed to create pkcs7 signed data\n");
goto cleanup;
}
break;
(unsigned char *)encoded_dhkey_info->data,
if (retval) {
pkiDebug("failed to create pkcs7 signed data\n");
goto cleanup;
}
break;
}
} else {
pkiDebug("received RSA key delivery AS REQ\n");
if (retval) {
pkiDebug("unable to make a session key\n");
goto cleanup;
}
/* check if PA_TYPE of 132 is present which means the client is
* requesting that a checksum is send back instead of the nonce
*/
pkiDebug("%s: Checking pa_type 0x%08x\n",
fixed_keypack = 1;
}
pkiDebug("%s: return checksum instead of nonce = %d\n",
/* if this is an RFC reply or draft9 client requested a checksum
* in the reply instead of the nonce, create an RFC-style keypack
*/
goto cleanup;
}
/* retrieve checksums for a given enctype of the reply key */
if (retval)
goto cleanup;
/* pick the first of acceptable enctypes for the checksum */
if (retval) {
pkiDebug("unable to calculate AS REQ checksum\n");
goto cleanup;
}
#ifdef DEBUG_CKSUM
#endif
if (retval) {
pkiDebug("failed to encode reply_key_pack\n");
goto cleanup;
}
}
case KRB5_PADATA_PK_AS_REQ:
(unsigned char *)encoded_key_pack->data,
break;
/* if the request is from the broken draft9 client that
* expects back a nonce, create it now
*/
if (!fixed_keypack) {
goto cleanup;
}
if (retval) {
pkiDebug("failed to encode reply_key_pack\n");
goto cleanup;
}
}
(unsigned char *)encoded_key_pack->data,
break;
}
if (retval) {
pkiDebug("failed to create pkcs7 enveloped data: %s\n",
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/kdc_key_pack");
case KRB5_PADATA_PK_AS_REQ:
"/tmp/kdc_enc_key_pack");
break;
"/tmp/kdc_enc_key_pack");
break;
}
#endif
}
case KRB5_PADATA_PK_AS_REQ:
break;
break;
}
if (retval) {
pkiDebug("failed to encode AS_REP\n");
goto cleanup;
}
#ifdef DEBUG_ASN1
"/tmp/kdc_as_rep");
#endif
goto cleanup;
}
case KRB5_PADATA_PK_AS_REQ:
break;
break;
}
if (encoded_dhkey_info != NULL)
if (encoded_key_pack != NULL)
if (server_key != NULL)
if (cksum_types != NULL)
case KRB5_PADATA_PK_AS_REQ:
break;
if (!fixed_keypack)
else
break;
}
if (retval)
pkiDebug("pkinit_verify_padata failure");
return retval;
}
/* ARGSUSED */
static int
{
return PA_SUFFICIENT | PA_REPLACES_KEY;
}
0
};
/* ARGSUSED */
static void
{
/*
* There is nothing currently allocated by pkinit_init_kdc_profile()
* which needs to be freed here.
*/
}
static krb5_error_code
{
"pkinit_identity",
"No pkinit_identity supplied for realm %s",
goto errout;
}
"pkinit_anchors",
"No pkinit_anchors supplied for realm %s",
goto errout;
}
/* Solaris Kerberos */
"pkinit_pool",
"pkinit_revoke",
"pkinit_kdc_ocsp",
"pkinit_mappings_file",
"pkinit_dh_min_bits",
pkiDebug("%s: invalid value (%d) for pkinit_dh_min_bits, "
"using default value (%d) instead\n", __FUNCTION__,
}
"pkinit_allow_upn",
"pkinit_require_crl_checking",
"pkinit_eku_checking",
&eku_string);
if (eku_string != NULL) {
} else {
pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n",
}
}
return 0;
return retval;
}
/* ARGSUSED */
static pkinit_kdc_context
{
int i;
if (pa_plugin_context == NULL)
return NULL;
for (i = 0; realm_contexts[i] != NULL; i++) {
pkinit_kdc_context p = realm_contexts[i];
pkiDebug("%s: returning context at %p for realm '%s'\n",
__FUNCTION__, p, p->realmname);
return p;
}
}
pkiDebug("%s: unable to find realm context for realm '%.*s'\n",
return NULL;
}
static int
{
goto errout;
pkiDebug("%s: initializing context at %p for realm '%s'\n",
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
if (retval)
goto errout;
/*
* Solaris Kerberos:
* Some methods of storing key information (PKCS11, PKCS12,...) may
* require interactive prompting.
*/
NULL);
if (retval)
goto errout;
if (retval)
goto errout;
pkiDebug("%s: returning context at %p for realm '%s'\n",
retval = 0;
if (retval)
return retval;
}
static int
const char **realmnames)
{
int i, j;
if (retval)
return retval;
/* Determine how many realms we may need to support */
for (i = 0; realmnames[i] != NULL; i++) {};
numrealms = i;
if (realm_contexts == NULL)
return ENOMEM;
for (i = 0, j = 0; i < numrealms; i++) {
realm_contexts[j++] = plgctx;
}
if (j == 0) {
/*
* Solaris Kerberos
* Improve error messages for the common case of a single realm
*/
if (numrealms != 1) {
"correctly for pkinit support");
}
goto errout;
}
*blob = realm_contexts;
retval = 0;
if (retval)
return retval;
}
static void
{
return;
}
static void
{
int i;
if (realm_contexts == NULL)
return;
for (i = 0; realm_contexts[i] != NULL; i++) {
}
}
static krb5_error_code
{
return retval;
if (retval)
goto cleanup;
retval = 0;
if (retval)
return retval;
}
static void
{
return;
}
}
"pkinit", /* name */
supported_server_pa_types, /* pa_type_list */
pkinit_server_plugin_init, /* (*init_proc) */
pkinit_server_plugin_fini, /* (*fini_proc) */
pkinit_server_get_flags, /* (*flags_proc) */
pkinit_server_get_edata, /* (*edata_proc) */
pkinit_server_verify_padata,/* (*verify_proc) */
pkinit_server_return_padata,/* (*return_proc) */
NULL, /* (*freepa_reqcontext_proc) */
};