Files
heimdal/kdc/process.c
Nicolas Williams 6a7e7eace6 Add kx509 client and revamp kx509 service
This commit adds support for kx509 in libkrb5, and revamps the KDC's
kx509 service (fixing bugs, adding features).

Of note is that kx509 is attempted optimistically by the client, with
the certificate and private key stored in the ccache, and optionally in
an external PEM or DER file.

NOTE: We do not optimistically use kx509 in krb5_cc_store_cred() if the
      ccache is a MEMORY ccache so we don't generate a key when
      accepting a GSS context with a delegated credential.

kx509 protocol issues to be fixed in an upcoming commit:

 - no proof of possession (this is mostly not too bad, but we'll want to
   fix it by using CSRs)
 - no algorithm agility (only plain RSA is supported)
 - very limited (no way to request any options in regards to the
   requested cert)
 - error codes are not very useful

Things we're adding in this commit:

 - libkrb5 kx509 client
 - automatic kx509 usage hooked in via krb5_cc_store_cred() of start TGT
 - per-realm templates on the KDC side
 - per-realm issuer certificates
 - send error messages on the KDC side
   (this is essential to avoid client-side timeouts on error)
 - authenticate as many error messages
 - add a protocol probe feature so we can avoid generating a
   keypair if the service is not enabled
   (once we add support for ECC algorithms we won't need this
    anymore; the issue is that RSA keygen is slow)
 - support for different types of client principals, not just username:

    - host-based service and domain-based service, each with its own
      template set per-{realm, service} or per-service

   (the idea is to support issuance of server certificates too, not
    just client/user certs)
 - more complete support for SAN types
 - tests (including that PKINIT->kx509->PKINIT works, which makes it
   possible to have "delegation" of PKIX credentials by just delegating
   Kerberos credentials)
 - document the protocol in lib/krb5/kx509.c

Future work:

 - add option for longer-ticket-lifetime service certs
 - add support for ECDSA, and some day for ed25519 and ed448
 - reuse private key when running kinit
   (this will require rethinking how we trigger optimistic kx509
    usage)
 - HDB lookup for:
    - optional revocation check (not strictly necessary)
    - adding to certificates those SANs listed in HDB
       - hostname aliases (dNSName SANs)
       - rfc822Name (email)
       - XMPP SANs
       - id-pkinit-san (a user could have aliases too)
 - support username wild-card A RRs, ala OSKT/krb5_admin
    i.e., if a host/f.q.d.n principal asks for a certificate for
    some service at some-label.f.q.d.n, then issue it
   (this is not needed at OSKT sites because OSKT already
    supports keying such service principals, which means kx509
    will issue certificates for them, however, it would be nice
    to be able to have this independent of OSKT)
   (a better way to do this would be to integrate more of OSKT
    into Heimdal proper)
 - a kx509 command, or heimtools kx509 subcommand for explicitly
   attempting use of the kx509 protocol (as opposed to implicit, as is
   done in kinit via krb5_cc_store_cred() magic right now)

Issues:

 - optimistically trying kx509 on start realm TGT store -> timeout issues!
    - newer KDCs will return errors because of this commit; older ones
      will not, which causes timouts
    - need a separate timeout setting for kx509 for optimistic case
    - need a [realm] config item and DNS SRV RR lookup for whether a
      realm is expected to support kx509 service
2019-10-08 21:26:50 -05:00

329 lines
7.4 KiB
C

/*
* Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "kdc_locl.h"
/*
*
*/
void
krb5_kdc_update_time(struct timeval *tv)
{
if (tv == NULL)
gettimeofday(&_kdc_now, NULL);
else
_kdc_now = *tv;
}
static krb5_error_code
kdc_as_req(krb5_context context,
krb5_kdc_configuration *config,
krb5_data *req_buffer,
krb5_data *reply,
const char *from,
struct sockaddr *addr,
int datagram_reply,
int *claim)
{
struct kdc_request_desc r;
krb5_error_code ret;
size_t len;
memset(&r, 0, sizeof(r));
ret = decode_AS_REQ(req_buffer->data, req_buffer->length, &r.req, &len);
if (ret)
return ret;
r.context = context;
r.config = config;
r.request.data = req_buffer->data;
r.request.length = req_buffer->length;
*claim = 1;
ret = _kdc_as_rep(&r, reply, from, addr, datagram_reply);
free_AS_REQ(&r.req);
return ret;
}
static krb5_error_code
kdc_tgs_req(krb5_context context,
krb5_kdc_configuration *config,
krb5_data *req_buffer,
krb5_data *reply,
const char *from,
struct sockaddr *addr,
int datagram_reply,
int *claim)
{
krb5_error_code ret;
KDC_REQ req;
size_t len;
ret = decode_TGS_REQ(req_buffer->data, req_buffer->length, &req, &len);
if (ret)
return ret;
*claim = 1;
ret = _kdc_tgs_rep(context, config, &req, reply,
from, addr, datagram_reply);
free_TGS_REQ(&req);
return ret;
}
#ifdef DIGEST
static krb5_error_code
kdc_digest(krb5_context context,
krb5_kdc_configuration *config,
krb5_data *req_buffer,
krb5_data *reply,
const char *from,
struct sockaddr *addr,
int datagram_reply,
int *claim)
{
DigestREQ digestreq;
krb5_error_code ret;
size_t len;
ret = decode_DigestREQ(req_buffer->data, req_buffer->length,
&digestreq, &len);
if (ret)
return ret;
*claim = 1;
ret = _kdc_do_digest(context, config, &digestreq, reply, from, addr);
free_DigestREQ(&digestreq);
return ret;
}
#endif
#ifdef KX509
static krb5_error_code
kdc_kx509(krb5_context context,
krb5_kdc_configuration *config,
krb5_data *req_buffer,
krb5_data *reply,
const char *from,
struct sockaddr *addr,
int datagram_reply,
int *claim)
{
Kx509Request kx509req;
krb5_error_code ret;
ret = _kdc_try_kx509_request(req_buffer->data, req_buffer->length,
&kx509req);
if (ret)
return ret;
*claim = 1;
ret = _kdc_do_kx509(context, config, &kx509req, reply, from, addr);
free_Kx509Request(&kx509req);
return ret;
}
#endif
static struct krb5_kdc_service services[] = {
{ KS_KRB5, kdc_as_req },
{ KS_KRB5, kdc_tgs_req },
#ifdef DIGEST
{ 0, kdc_digest },
#endif
#ifdef KX509
{ 0, kdc_kx509 },
#endif
{ 0, NULL }
};
/*
* handle the request in `buf, len', from `addr' (or `from' as a string),
* sending a reply in `reply'.
*/
int
krb5_kdc_process_request(krb5_context context,
krb5_kdc_configuration *config,
unsigned char *buf,
size_t len,
krb5_data *reply,
krb5_boolean *prependlength,
const char *from,
struct sockaddr *addr,
int datagram_reply)
{
krb5_error_code ret;
unsigned int i;
krb5_data req_buffer;
int claim = 0;
heim_auto_release_t pool = heim_auto_release_create();
req_buffer.data = buf;
req_buffer.length = len;
for (i = 0; services[i].process != NULL; i++) {
ret = (*services[i].process)(context, config, &req_buffer,
reply, from, addr, datagram_reply,
&claim);
if (claim) {
if (services[i].flags & KS_NO_LENGTH)
*prependlength = 0;
heim_release(pool);
return ret;
}
}
heim_release(pool);
return -1;
}
/*
* handle the request in `buf, len', from `addr' (or `from' as a string),
* sending a reply in `reply'.
*
* This only processes krb5 requests
*/
int
krb5_kdc_process_krb5_request(krb5_context context,
krb5_kdc_configuration *config,
unsigned char *buf,
size_t len,
krb5_data *reply,
const char *from,
struct sockaddr *addr,
int datagram_reply)
{
krb5_error_code ret;
unsigned int i;
krb5_data req_buffer;
int claim = 0;
req_buffer.data = buf;
req_buffer.length = len;
for (i = 0; services[i].process != NULL; i++) {
if ((services[i].flags & KS_KRB5) == 0)
continue;
ret = (*services[i].process)(context, config, &req_buffer,
reply, from, addr, datagram_reply,
&claim);
if (claim)
return ret;
}
return -1;
}
/*
*
*/
int
krb5_kdc_save_request(krb5_context context,
const char *fn,
const unsigned char *buf,
size_t len,
const krb5_data *reply,
const struct sockaddr *sa)
{
krb5_storage *sp;
krb5_address a;
int fd, ret;
uint32_t t;
krb5_data d;
memset(&a, 0, sizeof(a));
d.data = rk_UNCONST(buf);
d.length = len;
t = _kdc_now.tv_sec;
fd = open(fn, O_WRONLY|O_CREAT|O_APPEND, 0600);
if (fd < 0) {
int saved_errno = errno;
krb5_set_error_message(context, saved_errno, "Failed to open: %s", fn);
return saved_errno;
}
sp = krb5_storage_from_fd(fd);
close(fd);
if (sp == NULL) {
krb5_set_error_message(context, ENOMEM, "Storage failed to open fd");
return ENOMEM;
}
ret = krb5_sockaddr2address(context, sa, &a);
if (ret)
goto out;
krb5_store_uint32(sp, 1);
krb5_store_uint32(sp, t);
krb5_store_address(sp, a);
krb5_store_data(sp, d);
{
Der_class cl;
Der_type ty;
unsigned int tag;
ret = der_get_tag (reply->data, reply->length,
&cl, &ty, &tag, NULL);
if (ret) {
krb5_store_uint32(sp, 0xffffffff);
krb5_store_uint32(sp, 0xffffffff);
} else {
krb5_store_uint32(sp, MAKE_TAG(cl, ty, 0));
krb5_store_uint32(sp, tag);
}
}
krb5_free_address(context, &a);
out:
krb5_storage_free(sp);
return 0;
}