Add bx509d

This commit is contained in:
Nicolas Williams
2019-10-09 20:18:01 -05:00
parent 4d4c7078cd
commit 575c67806b
41 changed files with 5794 additions and 684 deletions

View File

@@ -12,6 +12,7 @@ before_install:
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get update -qq; fi - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get update -qq; fi
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq bison comerr-dev flex libcap-ng-dev libdb-dev libedit-dev libjson-perl libldap2-dev libncurses5-dev libperl4-corelibs-perl libsqlite3-dev libkeyutils-dev pkg-config python ss-dev texinfo unzip netbase keyutils; fi - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq bison comerr-dev flex libcap-ng-dev libdb-dev libedit-dev libjson-perl libldap2-dev libncurses5-dev libperl4-corelibs-perl libsqlite3-dev libkeyutils-dev pkg-config python ss-dev texinfo unzip netbase keyutils; fi
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq ldap-utils gdb; fi - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq ldap-utils gdb; fi
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq libmicrohttpd-dev; fi
- if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi - if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi
- if [ $TRAVIS_OS_NAME = osx ]; then brew install cpanm bison flex berkeley-db lmdb openldap openssl; fi - if [ $TRAVIS_OS_NAME = osx ]; then brew install cpanm bison flex berkeley-db lmdb openldap openssl; fi
- if [ $TRAVIS_OS_NAME = osx ]; then sudo cpanm install JSON; fi - if [ $TRAVIS_OS_NAME = osx ]; then sudo cpanm install JSON; fi

2
README
View File

@@ -1,5 +1,5 @@
Heimdal is a Kerberos 5 implementation. Heimdal is an implementation of: ASN.1/DER, PKIX, and Kerberos.
For information how to install see <http://www.h5l.org/compile.html>. For information how to install see <http://www.h5l.org/compile.html>.

View File

@@ -4,7 +4,11 @@
Heimdal Heimdal
======= =======
Heimdal is a Kerberos 5 implementation. Heimdal is an implementation of:
- ASN.1/DER,
- PKIX, and
- Kerberos.
For information how to install see [here](http://www.h5l.org/compile.html). For information how to install see [here](http://www.h5l.org/compile.html).

View File

@@ -182,6 +182,54 @@ AM_CONDITIONAL([HAVE_CAPNG], [test "$with_capng" != "no"])
AC_SUBST([CAPNG_CFLAGS]) AC_SUBST([CAPNG_CFLAGS])
AC_SUBST([CAPNG_LIBS]) AC_SUBST([CAPNG_LIBS])
dnl libmicrohttpd
AC_ARG_WITH([microhttpd],
AC_HELP_STRING([--with-microhttpd], [use microhttpd to serve KDC REST API @<:@default=check@:>@]),
[],
[with_microhttpd=check])
if test "$with_microhttpd" != "no"; then
PKG_CHECK_MODULES([MICROHTTPD], [libmicrohttpd >= 0.9.59],
[with_microhttpd=yes],[with_microhttpd=no])
fi
if test "$with_microhttpd" = "yes"; then
AC_DEFINE_UNQUOTED([HAVE_MICROHTTPD], 1, [whether libmicrohttpd is available for KDC REST API])
fi
AM_CONDITIONAL([HAVE_MICROHTTPD], [test "$with_microhttpd" != "no"])
AC_SUBST([MICROHTTPD_CFLAGS])
AC_SUBST([MICROHTTPD_LIBS])
dnl libcjwt
AC_ARG_WITH([cjwt],
AC_HELP_STRING([--with-cjwt], [(Experimental) use cjwt to validate JWT tokens @<:@default=check@:>@]),
[],
[with_cjwt=check])
if test "$with_cjwt" != "no"; then
PKG_CHECK_MODULES([CJWT], [libcjwt >= 1.0.0],
[with_cjwt=yes],[with_cjwt=no])
fi
if test "$with_cjwt" = "yes"; then
AC_DEFINE_UNQUOTED([HAVE_CJWT], 1, [whether libcjwt is available for KDC REST API])
fi
AM_CONDITIONAL([HAVE_CJWT], [test "$with_cjwt" != "no"])
AC_SUBST([CJWT_CFLAGS])
AC_SUBST([CJWT_LIBS])
dnl libcjson
AC_ARG_WITH([cjson],
AC_HELP_STRING([--with-cjson], [(Experimental) use cJSON to extract private claims from JWT tokens @<:@default=check@:>@]),
[],
[with_cjson=check])
if test "$with_cjson" != "no"; then
PKG_CHECK_MODULES([CJSON], [libcjson >= 1.0.0],
[with_cjson=yes],[with_cjson=no])
fi
if test "$with_cjson" = "yes"; then
AC_DEFINE_UNQUOTED([HAVE_CJSON], 1, [whether libcjson is available for KDC REST API])
fi
AM_CONDITIONAL([HAVE_CJSON], [test "$with_cjson" != "no"])
AC_SUBST([CJSON_CFLAGS])
AC_SUBST([CJSON_LIBS])
dnl Check for sqlite dnl Check for sqlite
rk_TEST_PACKAGE(sqlite3, rk_TEST_PACKAGE(sqlite3,
[#include <sqlite3.h> [#include <sqlite3.h>

View File

@@ -22,19 +22,20 @@
@ifinfo @ifinfo
@dircategory Security @dircategory Security
@direntry @direntry
* Heimdal: (heimdal). The Kerberos 5 distribution from KTH * Heimdal: (heimdal). The Kerberos 5 and PKIX distribution from KTH
@end direntry @end direntry
@end ifinfo @end ifinfo
@c title page @c title page
@titlepage @titlepage
@title Heimdal @title Heimdal
@subtitle Kerberos 5 from KTH @subtitle Kerberos 5 and PKIX from KTH
@subtitle Edition @value{EDITION}, for version @value{VERSION} @subtitle Edition @value{EDITION}, for version @value{VERSION}
@subtitle 2008 @subtitle 2008
@author Johan Danielsson @author Johan Danielsson
@author Love Hörnquist Åstrand @author Love Hörnquist Åstrand
@author Assar Westerlund @author Assar Westerlund
@author et al
@end titlepage @end titlepage
@@ -64,6 +65,8 @@ This manual for version @value{VERSION} of Heimdal.
@menu @menu
* Introduction:: * Introduction::
* What is Kerberos?:: * What is Kerberos?::
* What is PKIX?::
* What is a Certification Authority (CA)?::
* Building and Installing:: * Building and Installing::
* Setting up a realm:: * Setting up a realm::
* Applications:: * Applications::

View File

@@ -48,7 +48,7 @@
@page @page
@copyrightstart @copyrightstart
Copyright (c) 1994-2008 Kungliga Tekniska Högskolan Copyright (c) 1994-2019 Kungliga Tekniska Högskolan
(Royal Institute of Technology, Stockholm, Sweden). (Royal Institute of Technology, Stockholm, Sweden).
All rights reserved. All rights reserved.
@@ -187,7 +187,7 @@ This manual is for version @value{VERSION} of hx509.
@menu @menu
* Introduction:: * Introduction::
* What is X.509 ?:: * What are X.509 and PKIX ?::
* Setting up a CA:: * Setting up a CA::
* CMS signing and encryption:: * CMS signing and encryption::
* Certificate matching:: * Certificate matching::
@@ -230,13 +230,20 @@ Software PKCS 11 module
@end detailmenu @end detailmenu
@end menu @end menu
@node Introduction, What is X.509 ?, Top, Top @node Introduction, What are X.509 and PKIX ?, Top, Top
@chapter Introduction @chapter Introduction
The goals of a PKI infrastructure (as defined in A Public Key Infrastructure (PKI) is an authentication mechanism based on
<a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280</a>) is to meet entities having certified cryptographic public keys and corresponding private
@emph{the needs of deterministic, automated identification, authentication, access control, and authorization}. (secret) keys.
The ITU-T PKI specifications are designated "x.509", while the IETF PKI
specifications (PKIX) are specified by a number of Internet RFCs and are based
on x.509.
The goals of a PKI (as stated in
<a href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280</a>) is to meet
@emph{the needs of deterministic, automated identification, authentication, access control, and authorization}.
The administrator should be aware of certain terminologies as explained by the aforementioned The administrator should be aware of certain terminologies as explained by the aforementioned
RFC before attemping to put in place a PKI infrastructure. Briefly, these are: RFC before attemping to put in place a PKI infrastructure. Briefly, these are:
@@ -246,6 +253,9 @@ RFC before attemping to put in place a PKI infrastructure. Briefly, these are:
Certificate Authority Certificate Authority
@item RA @item RA
Registration Authority, i.e., an optional system to which a CA delegates certain management functions. Registration Authority, i.e., an optional system to which a CA delegates certain management functions.
@item Certificate
A binary document that names an entity and its public key and which is signed
by an issuing CA.
@item CRL Issuer @item CRL Issuer
An optional system to which a CA delegates the publication of certificate revocation lists. An optional system to which a CA delegates the publication of certificate revocation lists.
@item Repository @item Repository
@@ -253,7 +263,7 @@ A system or collection of distributed systems that stores certificates and CRLs
and serves as a means of distributing these certificates and CRLs to end entities and serves as a means of distributing these certificates and CRLs to end entities
@end itemize @end itemize
hx509 (Heimdal x509 support) is a near complete X.509 stack that can hx509 (Heimdal x509 support) is a near complete X.509/PKIX stack that can
handle CMS messages (crypto system used in S/MIME and Kerberos PK-INIT) handle CMS messages (crypto system used in S/MIME and Kerberos PK-INIT)
and basic certificate processing tasks, path construction, path and basic certificate processing tasks, path construction, path
validation, OCSP and CRL validation, PKCS10 message construction, CMS validation, OCSP and CRL validation, PKCS10 message construction, CMS
@@ -263,10 +273,13 @@ signed), and CMS EnvelopedData (certificate encrypted).
hx509 can use PKCS11 tokens, PKCS12 files, PEM files, and/or DER encoded hx509 can use PKCS11 tokens, PKCS12 files, PEM files, and/or DER encoded
files. files.
@node What is X.509 ?, Setting up a CA, Introduction, Top hx509 consists of a library (libhx509) and a command-line utility (hxtool), as
@chapter What is X.509, PKIX, PKCS7 and CMS ? well as a RESTful, HTTPS-based service that implements an online CA.
X.509 was created by CCITT (later ITU) for the X.500 directory @node What are X.509 and PKIX ?, Setting up a CA, Introduction, Top
@chapter What are X.509 and PKIX, PKIX, PKCS7 and CMS ?
X.509 was created by CCITT (later ITU-T) for the X.500 directory
service. Today, X.509 discussions and implementations commonly reference service. Today, X.509 discussions and implementations commonly reference
the IETF's PKIX Certificate and CRL Profile of the X.509 v3 certificate the IETF's PKIX Certificate and CRL Profile of the X.509 v3 certificate
standard, as specified in RFC 3280. standard, as specified in RFC 3280.
@@ -348,7 +361,7 @@ The process starts by looking at the issuing CA of the certificate, by
Name or Key Identifier, and tries to find that certificate while at the Name or Key Identifier, and tries to find that certificate while at the
same time evaluting any policies in-place. same time evaluting any policies in-place.
@node Setting up a CA, Creating a CA certificate, What is X.509 ?, Top @node Setting up a CA, Creating a CA certificate, What are X.509 and PKIX ?, Top
@chapter Setting up a CA @chapter Setting up a CA
Do not let information overload scare you off! If you are simply testing Do not let information overload scare you off! If you are simply testing

View File

@@ -1,6 +1,6 @@
@c $Id$ @c $Id$
@node What is Kerberos?, Building and Installing, Introduction, Top @node What is Kerberos?, What is PKIX?, Introduction, Top
@chapter What is Kerberos? @chapter What is Kerberos?
@quotation @quotation
@@ -162,3 +162,32 @@ from 1988.
These documents can be found on our web-page at These documents can be found on our web-page at
@url{http://www.pdc.kth.se/kth-krb/}. @url{http://www.pdc.kth.se/kth-krb/}.
@node What is PKIX?, What is a Certification Authority (CA)?, Introduction, Top
@chapter What is PKIX?
PKIX is the set of Internet standards for Public Key Infrastructure (PKI),
based on the ITU-T's x.509 standads. PKI is an authentication mechanism based
on public keys (the 'PK' in 'PKI').
In PKIX we have public keys "certified" by certification authorities (CAs). A
"relying party" is software that validates an entity's certificate and, if
valid, trusts the certified public key to "speak for" the entity identified by
the certificate.
In a PKI every entity has one (or more) certified public/private key pairs.
@node What is a Certification Authority (CA)?, Building and Installing, Introduction, Top
A Certification Authority (CA) is an entity in a PKI that issues certificates
to other entities -- a CA certifies that a public key speaks for a particular,
named entity.
There are two types of CAs: off-line and online. Typically PKI hierarchies are
organized such that the most security-critical private keys are only used by
off-line CAs to certify the less security-critical public keys of online CAs.
Heimdal has support for off-line CAs using its Hx509 library and hxtool
command.
Heimdal also has an online CA with a RESTful, HTTPS-based protocol.

View File

@@ -4,13 +4,17 @@ include $(top_srcdir)/Makefile.am.common
AM_CPPFLAGS += $(INCLUDE_libintl) $(INCLUDE_openssl_crypto) -I$(srcdir)/../lib/krb5 AM_CPPFLAGS += $(INCLUDE_libintl) $(INCLUDE_openssl_crypto) -I$(srcdir)/../lib/krb5
lib_LTLIBRARIES = libkdc.la lib_LTLIBRARIES = simple_csr_authorizer.la \
ipc_csr_authorizer.la \
libkdc.la cjwt_token_validator.la \
negotiate_token_validator.la
bin_PROGRAMS = string2key bin_PROGRAMS = string2key
sbin_PROGRAMS = kstash sbin_PROGRAMS = kstash
libexec_PROGRAMS = hprop hpropd kdc digest-service libexec_PROGRAMS = hprop hpropd kdc digest-service \
test_token_validator test_csr_authorizer test_kdc_ca
noinst_PROGRAMS = kdc-replay kdc-tester noinst_PROGRAMS = kdc-replay kdc-tester
@@ -23,6 +27,21 @@ kstash_SOURCES = kstash.c headers.h
string2key_SOURCES = string2key.c headers.h string2key_SOURCES = string2key.c headers.h
if HAVE_MICROHTTPD
bx509d_SOURCES = bx509d.c
bx509d_AM_CPPFLAGS = $(AM_CPPFLAGS) $(MICROHTTPD_CFLAGS)
bx509d_LDADD = -ldl \
libkdc.la \
$(MICROHTTPD_LIBS) \
$(LIB_roken) \
$(top_builddir)/lib/sl/libsl.la \
$(top_builddir)/lib/asn1/libasn1.la \
$(top_builddir)/lib/krb5/libkrb5.la \
$(top_builddir)/lib/hx509/libhx509.la \
$(top_builddir)/lib/gssapi/libgssapi.la
libexec_PROGRAMS += bx509d
endif
digest_service_SOURCES = \ digest_service_SOURCES = \
digest-service.c digest-service.c
@@ -35,8 +54,29 @@ kdc_tester_SOURCES = \
config.c \ config.c \
kdc-tester.c kdc-tester.c
test_token_validator_SOURCES = test_token_validator.c
test_csr_authorizer_SOURCES = test_csr_authorizer.c
test_kdc_ca_SOURCES = test_kdc_ca.c
# Token plugins (for bx509d)
cjwt_token_validator_la_SOURCES = cjwt_token_validator.c
cjwt_token_validator_la_AM_CPPFLAGS = $(CJSON_FLAGS) $(CJWT_FLAGS)
cjwt_token_validator_la_LDFLAGS = -module $(CJSON_LIBS) $(CJWT_LIBS)
negotiate_token_validator_la_SOURCES = negotiate_token_validator.c
negotiate_token_validator_la_LDFLAGS = -module $(top_builddir)/lib/gssapi/libgssapi.la
# CSR Authorizer plugins (for kdc/kx509 and bx509d)
simple_csr_authorizer_la_SOURCES = simple_csr_authorizer.c
simple_csr_authorizer_la_LDFLAGS = -module
ipc_csr_authorizer_la_SOURCES = ipc_csr_authorizer.c
ipc_csr_authorizer_la_LDFLAGS = -module \
$(top_builddir)/lib/krb5/libkrb5.la \
$(top_builddir)/lib/hx509/libhx509.la \
$(top_builddir)/lib/ipc/libheim-ipcc.la \
$(top_builddir)/lib/roken/libroken.la
libkdc_la_SOURCES = \ libkdc_la_SOURCES = \
default_config.c \ default_config.c \
ca.c \
set_dbinfo.c \ set_dbinfo.c \
digest.c \ digest.c \
fast.c \ fast.c \
@@ -48,6 +88,8 @@ libkdc_la_SOURCES = \
log.c \ log.c \
misc.c \ misc.c \
kx509.c \ kx509.c \
token_validator.c \
csr_authorizer.c \
process.c \ process.c \
windc.c \ windc.c \
rx.h rx.h
@@ -57,6 +99,9 @@ KDC_PROTOS = $(srcdir)/kdc-protos.h $(srcdir)/kdc-private.h
ALL_OBJECTS = $(kdc_OBJECTS) ALL_OBJECTS = $(kdc_OBJECTS)
ALL_OBJECTS += $(kdc_replay_OBJECTS) ALL_OBJECTS += $(kdc_replay_OBJECTS)
ALL_OBJECTS += $(kdc_tester_OBJECTS) ALL_OBJECTS += $(kdc_tester_OBJECTS)
ALL_OBJECTS += $(test_token_validator_OBJECTS)
ALL_OBJECTS += $(test_csr_authorizer_OBJECTS)
ALL_OBJECTS += $(test_kdc_ca_OBJECTS)
ALL_OBJECTS += $(libkdc_la_OBJECTS) ALL_OBJECTS += $(libkdc_la_OBJECTS)
ALL_OBJECTS += $(string2key_OBJECTS) ALL_OBJECTS += $(string2key_OBJECTS)
ALL_OBJECTS += $(kstash_OBJECTS) ALL_OBJECTS += $(kstash_OBJECTS)
@@ -135,13 +180,16 @@ digest_service_LDADD = \
$(LDADD) $(LIB_pidfile) $(LDADD) $(LIB_pidfile)
kdc_replay_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) kdc_replay_LDADD = libkdc.la $(LDADD) $(LIB_pidfile)
kdc_tester_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase) kdc_tester_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
test_token_validator_LDADD = libkdc.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
test_csr_authorizer_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
test_kdc_ca_LDADD = libkdc.la $(top_builddir)/lib/hx509/libhx509.la $(LDADD) $(LIB_pidfile) $(LIB_heimbase)
include_HEADERS = kdc.h $(srcdir)/kdc-protos.h include_HEADERS = kdc.h $(srcdir)/kdc-protos.h
noinst_HEADERS = $(srcdir)/kdc-private.h noinst_HEADERS = $(srcdir)/kdc-private.h
krb5dir = $(includedir)/krb5 krb5dir = $(includedir)/krb5
krb5_HEADERS = windc_plugin.h krb5_HEADERS = windc_plugin.h token_validator_plugin.h csr_authorizer_plugin.h
build_HEADERZ = $(krb5_HEADERS) # XXX build_HEADERZ = $(krb5_HEADERS) # XXX

View File

@@ -92,17 +92,21 @@ $(LIBEXECDIR)\kdc.exe: \
LIBKDC_OBJS=\ LIBKDC_OBJS=\
$(OBJ)\default_config.obj \ $(OBJ)\default_config.obj \
$(OBJ)\set_dbinfo.obj \ $(OBJ)\ca.obj \
$(OBJ)\digest.obj \ $(OBJ)\kx509.obj \
$(OBJ)\fast.obj \ $(OBJ)\set_dbinfo.obj \
$(OBJ)\kerberos5.obj \ $(OBJ)\digest.obj \
$(OBJ)\krb5tgs.obj \ $(OBJ)\fast.obj \
$(OBJ)\pkinit.obj \ $(OBJ)\kerberos5.obj \
$(OBJ)\pkinit-ec.obj \ $(OBJ)\krb5tgs.obj \
$(OBJ)\log.obj \ $(OBJ)\pkinit.obj \
$(OBJ)\misc.obj \ $(OBJ)\pkinit-ec.obj \
$(OBJ)\kx509.obj \ $(OBJ)\log.obj \
$(OBJ)\process.obj \ $(OBJ)\misc.obj \
$(OBJ)\kx509.obj \
$(OBJ)\token_validator.obj \
$(OBJ)\csr_authorizer.obj \
$(OBJ)\process.obj \
$(OBJ)\windc.obj $(OBJ)\windc.obj
LIBKDC_LIBS=\ LIBKDC_LIBS=\
@@ -126,9 +130,10 @@ clean::
libkdc_la_SOURCES = \ libkdc_la_SOURCES = \
default_config.c \ default_config.c \
ca.c \
set_dbinfo.c \ set_dbinfo.c \
digest.c \ digest.c \
fast.c \ fast.c \
kdc_locl.h \ kdc_locl.h \
kerberos5.c \ kerberos5.c \
krb5tgs.c \ krb5tgs.c \
@@ -137,6 +142,8 @@ libkdc_la_SOURCES = \
log.c \ log.c \
misc.c \ misc.c \
kx509.c \ kx509.c \
token_validator.c \
csr_authorizer.c \
process.c \ process.c \
windc.c \ windc.c \
rx.h rx.h

1722
kdc/bx509d.c Normal file

File diff suppressed because it is too large Load Diff

727
kdc/ca.c Normal file
View File

@@ -0,0 +1,727 @@
/*
* Copyright (c) 2019 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"
#include <hex.h>
#include <rfc2459_asn1.h>
#include <hx509.h>
#include <hx509_err.h>
#include <stdarg.h>
/*
* This file implements a singular utility function `kdc_issue_certificate()'
* for certificate issuance for kx509 and bx509, which takes a principal name,
* an `hx509_request' resulting from parsing a CSR and possibly adding
* SAN/EKU/KU extensions, the start/end times of request's authentication
* method, and whether to include a full certificate chain in the result.
*/
typedef enum {
CERT_NOTSUP = 0,
CERT_CLIENT = 1,
CERT_SERVER = 2,
CERT_MIXED = 3
} cert_type;
static void
frees(char **s)
{
free(*s);
*s = NULL;
}
static krb5_error_code
count_sans(hx509_request req, size_t *n)
{
size_t i;
char *s = NULL;
int ret = 0;
*n = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
frees(&s);
ret = hx509_request_get_san(req, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_DNSNAME:
case HX509_SAN_TYPE_EMAIL:
case HX509_SAN_TYPE_XMPP:
case HX509_SAN_TYPE_PKINIT:
case HX509_SAN_TYPE_MS_UPN:
(*n)++;
break;
default:
ret = ENOTSUP;
}
frees(&s);
}
return ret == HX509_NO_ITEM ? 0 : ret;
}
static int
has_sans(hx509_request req)
{
hx509_san_type san_type;
char *s = NULL;
int ret = hx509_request_get_san(req, 0, &san_type, &s);
frees(&s);
return ret == HX509_NO_ITEM ? 0 : 1;
}
static cert_type
characterize_cprinc(krb5_context context,
krb5_principal cprinc)
{
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
switch (ncomp) {
case 1:
return CERT_CLIENT;
case 2:
if (strchr(comp1, '.') == NULL)
return CERT_CLIENT;
return CERT_SERVER;
case 3:
if (strchr(comp1, '.'))
return CERT_SERVER;
return CERT_NOTSUP;
default:
return CERT_NOTSUP;
}
}
/* Characterize request as client or server cert req */
static cert_type
characterize(krb5_context context,
krb5_principal cprinc,
hx509_request req)
{
krb5_error_code ret = 0;
cert_type res = CERT_NOTSUP;
size_t i;
char *s = NULL;
int want_ekus = 0;
if (!has_sans(req))
return characterize_cprinc(context, cprinc);
for (i = 0; ret == 0; i++) {
heim_oid oid;
frees(&s);
ret = hx509_request_get_eku(req, i, &s);
if (ret)
break;
want_ekus = 1;
ret = der_parse_heim_oid(s, ".", &oid);
if (ret)
break;
/*
* If the client wants only a server certificate, then we'll be
* willing to issue one that may be longer-lived than the client's
* ticket/token.
*
* There may be other server EKUs, but these are the ones we know
* of.
*/
if (der_heim_oid_cmp(&asn1_oid_id_pkix_kp_serverAuth, &oid) &&
der_heim_oid_cmp(&asn1_oid_id_pkix_kp_OCSPSigning, &oid) &&
der_heim_oid_cmp(&asn1_oid_id_pkix_kp_secureShellServer, &oid))
res |= CERT_CLIENT;
else
res |= CERT_SERVER;
der_free_oid(&oid);
}
frees(&s);
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
frees(&s);
ret = hx509_request_get_san(req, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_DNSNAME:
if (!want_ekus)
res |= CERT_SERVER;
break;
case HX509_SAN_TYPE_EMAIL:
case HX509_SAN_TYPE_XMPP:
case HX509_SAN_TYPE_PKINIT:
case HX509_SAN_TYPE_MS_UPN:
if (!want_ekus)
res |= CERT_CLIENT;
break;
default:
ret = ENOTSUP;
}
if (ret)
break;
}
frees(&s);
if (ret == HX509_NO_ITEM)
ret = 0;
return ret ? CERT_NOTSUP : res;
}
/*
* Get a configuration sub-tree for kx509 based on what's being requested and
* by whom.
*
* We have a number of cases:
*
* - default certificate (no CSR used, or no certificate extensions requested)
* - for client principals
* - for service principals
* - client certificate requested (CSR used and client-y SANs/EKUs requested)
* - server certificate requested (CSR used and server-y SANs/EKUs requested)
* - mixed client/server certificate requested (...)
*/
static const krb5_config_binding *
get_cf(krb5_context context,
const char *toplevel,
hx509_request req,
krb5_principal cprinc)
{
krb5_error_code ret;
const krb5_config_binding *cf = NULL;
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *realm = krb5_principal_get_realm(context, cprinc);
const char *comp0 = krb5_principal_get_comp_string(context, cprinc, 0);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
const char *label = NULL;
const char *svc = NULL;
const char *def = NULL;
cert_type certtype = CERT_NOTSUP;
size_t nsans = 0;
if (ncomp == 0) {
krb5_set_error_message(context, ENOTSUP,
"Client principal has no components!");
return NULL;
}
if ((ret = count_sans(req, &nsans)) ||
(certtype = characterize(context, cprinc, req)) == CERT_NOTSUP) {
krb5_set_error_message(context, ret,
"Could not characterize CSR");
return NULL;
}
if (nsans) {
def = "custom";
/* Client requested some certificate extension, a SAN or EKU */
switch (certtype) {
case CERT_MIXED: label = "mixed"; break;
case CERT_CLIENT: label = "client"; break;
case CERT_SERVER: label = "server"; break;
default: return NULL;
}
} else {
def = "default";
/* Default certificate desired */
if (ncomp == 1) {
label = "user";
} else if (ncomp == 2 && strcmp(comp1, "root") == 0) {
label = "root_user";
} else if (ncomp == 2 && strcmp(comp1, "admin") == 0) {
label = "admin_user";
} else if (strchr(comp1, '.')) {
label = "hostbased_service";
svc = comp0;
} else {
label = "other";
}
}
if (strcmp(toplevel, "kdc") == 0)
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm,
"kx509", label, svc, NULL);
else
cf = krb5_config_get_list(context, NULL, toplevel, "realms", realm,
label, svc, NULL);
if (cf == NULL)
krb5_set_error_message(context, ENOTSUP,
"No %s configuration for %s %s certificates [%s] realm "
"-> %s -> kx509 -> %s%s%s",
strcmp(toplevel, "bx509") == 0 ? "bx509" : "kx509",
def, label, toplevel, realm, label,
svc ? " -> " : "", svc ? svc : "");
return cf;
}
/*
* Find and set a certificate template using a configuration sub-tree
* appropriate to the requesting principal.
*
* This allows for the specification of the following in configuration:
*
* - certificates as templates, with ${var} tokens in subjectName attribute
* values that will be expanded later
* - a plain string with ${var} tokens to use as the subjectName
* - EKUs
* - whether to include a PKINIT SAN
*/
static krb5_error_code
set_template(krb5_context context,
const krb5_config_binding *cf,
hx509_ca_tbs tbs)
{
krb5_error_code ret = 0;
const char *cert_template = NULL;
const char *subj_name = NULL;
char **ekus = NULL;
if (cf == NULL)
return KRB5KDC_ERR_POLICY; /* Can't happen */
cert_template = krb5_config_get_string(context, cf, "template_cert", NULL);
subj_name = krb5_config_get_string(context, cf, "subject_name", NULL);
ekus = krb5_config_get_strings(context, cf, "ekus", NULL);
if (cert_template) {
hx509_certs certs;
hx509_cert template;
ret = hx509_certs_init(context->hx509ctx, cert_template, 0,
NULL, &certs);
if (ret == 0)
ret = hx509_get_one_cert(context->hx509ctx, certs, &template);
hx509_certs_free(&certs);
if (ret) {
krb5_set_error_message(context, KRB5KDC_ERR_POLICY,
"Failed to load certificate template from "
"%s", cert_template);
return ret;
}
/*
* Only take the subjectName, the keyUsage, and EKUs from the template
* certificate.
*/
ret = hx509_ca_tbs_set_template(context->hx509ctx, tbs,
HX509_CA_TEMPLATE_SUBJECT |
HX509_CA_TEMPLATE_KU |
HX509_CA_TEMPLATE_EKU,
template);
hx509_cert_free(template);
if (ret)
return ret;
}
if (subj_name) {
hx509_name dn = NULL;
ret = hx509_parse_name(context->hx509ctx, subj_name, &dn);
if (ret == 0)
ret = hx509_ca_tbs_set_subject(context->hx509ctx, tbs, dn);
hx509_name_free(&dn);
if (ret)
return ret;
}
if (cert_template == NULL && subj_name == NULL) {
hx509_name dn = NULL;
ret = hx509_empty_name(context->hx509ctx, &dn);
if (ret == 0)
ret = hx509_ca_tbs_set_subject(context->hx509ctx, tbs, dn);
hx509_name_free(&dn);
if (ret)
return ret;
}
if (ekus) {
size_t i;
for (i = 0; ret == 0 && ekus[i]; i++) {
heim_oid oid = { 0, 0 };
if ((ret = der_find_or_parse_heim_oid(ekus[i], ".", &oid)) == 0)
ret = hx509_ca_tbs_add_eku(context->hx509ctx, tbs, &oid);
der_free_oid(&oid);
}
krb5_config_free_strings(ekus);
}
/*
* XXX A KeyUsage template would be nice, but it needs some smarts to
* remove, e.g., encipherOnly, decipherOnly, keyEncipherment, if the SPKI
* algorithm does not support encryption. The same logic should be added
* to hx509_ca_tbs_set_template()'s HX509_CA_TEMPLATE_KU functionality.
*/
return ret;
}
/*
* Find and set a certificate template, set "variables" in `env', and add add
* default SANs/EKUs as appropriate.
*
* TODO:
* - lookup a template for the client principal in its HDB entry
* - lookup subjectName, SANs for a principal in its HDB entry
* - lookup a host-based client principal's HDB entry and add its canonical
* name / aliases as dNSName SANs
* (this would have to be if requested by the client, perhaps)
*/
static krb5_error_code
set_tbs(krb5_context context,
const krb5_config_binding *cf,
hx509_request req,
krb5_principal cprinc,
hx509_env *env,
hx509_ca_tbs tbs)
{
krb5_error_code ret;
unsigned int ncomp = krb5_principal_get_num_comp(context, cprinc);
const char *realm = krb5_principal_get_realm(context, cprinc);
const char *comp0 = krb5_principal_get_comp_string(context, cprinc, 0);
const char *comp1 = krb5_principal_get_comp_string(context, cprinc, 1);
const char *comp2 = krb5_principal_get_comp_string(context, cprinc, 2);
char *princ_no_realm = NULL;
char *princ = NULL;
ret = krb5_unparse_name_flags(context, cprinc, 0, &princ);
if (ret == 0)
ret = krb5_unparse_name_flags(context, cprinc,
KRB5_PRINCIPAL_UNPARSE_NO_REALM,
&princ_no_realm);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env,
"principal-name-without-realm", princ_no_realm);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-name", princ);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-name-realm",
realm);
/* Populate requested certificate extensions from CSR/CSRPlus if allowed */
ret = hx509_ca_tbs_set_from_csr(context->hx509ctx, tbs, req);
if (ret == 0)
ret = set_template(context, cf, tbs);
/*
* Optionally add PKINIT SAN.
*
* Adding an id-pkinit-san means the client can use the certificate to
* initiate PKINIT. That might seem odd, but it enables a sort of PKIX
* credential delegation by allowing forwarded Kerberos tickets to be
* used to acquire PKIX credentials. Thus this can work:
*
* PKIX (w/ HW token) -> Kerberos ->
* PKIX (w/ softtoken) -> Kerberos ->
* PKIX (w/ softtoken) -> Kerberos ->
* ...
*
* Note that we may not have added the PKINIT EKU -- that depends on the
* template, and host-based service templates might well not include it.
*/
if (ret == 0 && !has_sans(req) &&
krb5_config_get_bool_default(context, cf, FALSE, "include_pkinit_san",
NULL)) {
ret = hx509_ca_tbs_add_san_pkinit(context->hx509ctx, tbs, princ);
}
if (ret)
goto out;
if (ncomp == 1) {
const char *email_domain;
ret = hx509_env_add(context->hx509ctx, env, "principal-component0",
princ_no_realm);
/*
* If configured, include an rfc822Name that's just the client's
* principal name sans realm @ configured email domain.
*/
if (ret == 0 && !has_sans(req) &&
(email_domain = krb5_config_get_string(context, cf, "email_domain",
NULL))) {
char *email;
if (asprintf(&email, "%s@%s", princ_no_realm, email_domain) == -1 ||
email == NULL)
goto enomem;
ret = hx509_ca_tbs_add_san_rfc822name(context->hx509ctx, tbs, email);
free(email);
}
goto out;
} else if (ncomp == 2 || ncomp == 3) {
/*
* 2- and 3-component principal name.
*
* We do not have a reliable name-type indicator. If the second
* component has a '.' in it then we'll assume that the name is a
* host-based (2-component) or domain-based (3-component) service
* principal name. Else we'll assume it's a two-component admin-style
* username.
*/
ret = hx509_env_add(context->hx509ctx, env, "principal-component0",
comp0);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-component1",
comp1);
if (ret == 0 && ncomp == 3)
ret = hx509_env_add(context->hx509ctx, env, "principal-component2",
comp2);
if (ret)
goto out;
if (ret == 0 && strchr(comp1, '.')) {
/* Looks like host-based or domain-based service */
ret = hx509_env_add(context->hx509ctx, env,
"principal-service-name", comp0);
if (ret == 0)
ret = hx509_env_add(context->hx509ctx, env, "principal-host-name", comp1);
if (ret == 0 && ncomp == 3)
ret = hx509_env_add(context->hx509ctx, env, "principal-domain-name", comp2);
if (ret == 0 && !has_sans(req) &&
krb5_config_get_bool_default(context, cf, FALSE,
"include_dnsname_san", NULL)) {
ret = hx509_ca_tbs_add_san_hostname(context->hx509ctx, tbs, comp1);
}
}
} else {
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"kx509/bx509 client %s has too many "
"components!", princ);
}
out:
krb5_xfree(princ_no_realm);
krb5_xfree(princ);
return ret;
enomem:
ret = krb5_enomem(context);
goto out;
}
static krb5_error_code
tbs_set_times(krb5_context context,
const krb5_config_binding *cf,
krb5_times *auth_times,
time_t req_life,
hx509_ca_tbs tbs)
{
time_t now = time(NULL);
time_t endtime = auth_times->endtime;
time_t starttime = auth_times->starttime ?
auth_times->starttime : now - 5 * 60;
time_t fudge =
krb5_config_get_time_default(context, cf, 5 * 24 * 3600,
"force_cert_lifetime", NULL);
time_t clamp =
krb5_config_get_time_default(context, cf, 0, "max_cert_lifetime",
NULL);
if (fudge && now + fudge > endtime)
endtime = now + fudge;
if (req_life && req_life < endtime - now)
endtime = now + req_life;
if (clamp && clamp < endtime - now)
endtime = now + clamp;
hx509_ca_tbs_set_notAfter(context->hx509ctx, tbs, endtime);
hx509_ca_tbs_set_notBefore(context->hx509ctx, tbs, starttime);
return 0;
}
/*
* Build a certifate for `principal' and its CSR.
*/
krb5_error_code
kdc_issue_certificate(krb5_context context,
const krb5_kdc_configuration *config,
hx509_request req,
krb5_principal cprinc,
krb5_times *auth_times,
int send_chain,
hx509_certs *out)
{
const krb5_config_binding *cf;
krb5_error_code ret;
const char *kx509_ca;
hx509_ca_tbs tbs = NULL;
hx509_certs chain = NULL;
hx509_cert signer = NULL;
hx509_cert cert = NULL;
hx509_env env = NULL;
KeyUsage ku;
*out = NULL;
/* Force KU */
ku = int2KeyUsage(0);
ku.digitalSignature = 1;
hx509_request_authorize_ku(req, ku);
/* Get configuration */
if ((cf = get_cf(context, config->app, req, cprinc)) == NULL)
return KRB5KDC_ERR_POLICY;
if ((kx509_ca = krb5_config_get_string(context, cf, "ca", NULL)) == NULL) {
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"No kx509 CA issuer credential specified");
return ret;
}
ret = hx509_ca_tbs_init(context->hx509ctx, &tbs);
if (ret)
return ret;
/* Lookup a template and set things in `env' and `tbs' as appropriate */
if (ret == 0)
ret = set_tbs(context, cf, req, cprinc, &env, tbs);
/* Populate generic template "env" variables */
/*
* The `tbs' and `env' are now complete as to naming and EKUs.
*
* We check that the `tbs' is not name-less, after which all remaining
* failures here will not be policy failures. So we also log the intent to
* issue a certificate now.
*/
if (ret == 0 && hx509_name_is_null_p(hx509_ca_tbs_get_name(tbs)) &&
!has_sans(req))
krb5_set_error_message(context, ret = KRB5KDC_ERR_POLICY,
"Not issuing certificate because it "
"would have no names");
if (ret)
goto out;
/*
* Still to be done below:
*
* - set certificate spki
* - set certificate validity
* - expand variables in certificate subject name template
* - sign certificate
* - encode certificate and chain
*/
/* Load the issuer certificate and private key */
{
hx509_certs certs;
hx509_query *q;
ret = hx509_certs_init(context->hx509ctx, kx509_ca, 0, NULL, &certs);
if (ret) {
krb5_set_error_message(context, ret, "Failed to load CA %s",
kx509_ca);
goto out;
}
ret = hx509_query_alloc(context->hx509ctx, &q);
if (ret) {
hx509_certs_free(&certs);
goto out;
}
hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY);
hx509_query_match_option(q, HX509_QUERY_OPTION_KU_KEYCERTSIGN);
ret = hx509_certs_find(context->hx509ctx, certs, q, &signer);
hx509_query_free(context->hx509ctx, q);
hx509_certs_free(&certs);
if (ret) {
krb5_set_error_message(context, ret, "Failed to find a CA in %s",
kx509_ca);
goto out;
}
}
/* Populate the subject public key in the TBS context */
{
SubjectPublicKeyInfo spki;
ret = hx509_request_get_SubjectPublicKeyInfo(context->hx509ctx,
req, &spki);
if (ret == 0)
ret = hx509_ca_tbs_set_spki(context->hx509ctx, tbs, &spki);
free_SubjectPublicKeyInfo(&spki);
if (ret)
goto out;
}
/* Work out cert expiration */
if (ret == 0)
ret = tbs_set_times(context, cf, auth_times, 0 /* XXX req_life */, tbs);
/* Expand the subjectName template in the TBS using the env */
if (ret == 0)
ret = hx509_ca_tbs_subject_expand(context->hx509ctx, tbs, env);
hx509_env_free(&env);
/* All done with the TBS, sign/issue the certificate */
ret = hx509_ca_sign(context->hx509ctx, tbs, signer, &cert);
if (ret)
goto out;
/* Gather the certificate and chain into a MEMORY store */
ret = hx509_certs_init(context->hx509ctx, "MEMORY:certs", 0, NULL, out);
if (ret == 0)
ret = hx509_certs_add(context->hx509ctx, *out, cert);
if (ret == 0 && send_chain) {
ret = hx509_certs_init(context->hx509ctx, kx509_ca, 0, NULL, &chain);
if (ret == 0)
ret = hx509_certs_merge(context->hx509ctx, *out, chain);
}
out:
hx509_certs_free(&chain);
if (env)
hx509_env_free(&env);
if (tbs)
hx509_ca_tbs_free(&tbs);
if (cert)
hx509_cert_free(cert);
if (signer)
hx509_cert_free(signer);
if (ret)
hx509_certs_free(out);
return ret;
}

330
kdc/cjwt_token_validator.c Normal file
View File

@@ -0,0 +1,330 @@
/*
* Copyright (c) 2019 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.
*/
/*
* This is a plugin by which bx509d can validate JWT Bearer tokens using the
* cjwt library.
*
* Configuration:
*
* [kdc]
* realm = {
* A.REALM.NAME = {
* cjwt_jqk = PATH-TO-JWK-PEM-FILE
* }
* }
*
* where AUDIENCE-FOR-KDC is the value of the "audience" (i.e., the target) of
* the token.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <krb5.h>
#include <common_plugin.h>
#include <hdb.h>
#include <roken.h>
#include <token_validator_plugin.h>
#include <cjwt/cjwt.h>
#ifdef HAVE_CJSON
#include <cjson/cJSON.h>
#endif
static const char *
get_kv(krb5_context context, const char *realm, const char *k, const char *k2)
{
return krb5_config_get_string(context, NULL, "bx509", "realms", realm,
k, k2, NULL);
}
static krb5_error_code
get_issuer_pubkeys(krb5_context context,
const char *realm,
krb5_data *previous,
krb5_data *current,
krb5_data *next)
{
krb5_error_code save_ret = 0;
krb5_error_code ret;
const char *v;
size_t nkeys = 0;
previous->data = current->data = next->data = 0;
previous->length = current->length = next->length = 0;
if ((v = get_kv(context, realm, "cjwt_jwk_next", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &next->data, &next->length)))
save_ret = ret;
if ((v = get_kv(context, realm, "cjwt_jwk_previous", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &previous->data, &previous->length)) &&
save_ret == 0)
save_ret = ret;
if ((v = get_kv(context, realm, "cjwt_jwk_current", NULL)) &&
(++nkeys) &&
(ret = rk_undumpdata(v, &current->data, &current->length)) &&
save_ret == 0)
save_ret = ret;
if (nkeys == 0)
krb5_set_error_message(context, EINVAL, "jwk issuer key not specified in "
"[bx509]->realm->%s->cjwt_jwk_{previous,current,next}",
realm);
if (!previous->length && !current->length && !next->length)
krb5_set_error_message(context, save_ret,
"Could not read jwk issuer public key files");
if (current->length == next->length &&
memcmp(current->data, next->data, next->length) == 0) {
free(next->data);
next->data = 0;
next->length = 0;
}
if (current->length == previous->length &&
memcmp(current->data, previous->data, previous->length) == 0) {
free(previous->data);
previous->data = 0;
previous->length = 0;
}
return 0;
}
static krb5_error_code
check_audience(krb5_context context,
const char *realm,
cjwt_t *jwt,
const char * const *audiences,
size_t naudiences)
{
size_t i, k;
if (!jwt->aud) {
krb5_set_error_message(context, EACCES, "JWT bearer token has no "
"audience");
return EACCES;
}
for (i = 0; i < jwt->aud->count; i++)
for (k = 0; k < naudiences; k++)
if (strcasecmp(audiences[k], jwt->aud->names[i]) == 0)
return 0;
krb5_set_error_message(context, EACCES, "JWT bearer token's audience "
"does not match any expected audience");
return EACCES;
}
static krb5_error_code
get_princ(krb5_context context,
const char *realm,
cjwt_t *jwt,
krb5_principal *actual_principal)
{
krb5_error_code ret;
const char *force_realm = NULL;
const char *domain;
#ifdef HAVE_CJSON
if (jwt->private_claims) {
cJSON *jval;
if ((jval = cJSON_GetObjectItem(jwt->private_claims, "authz_sub")))
return krb5_parse_name(context, jval->valuestring, actual_principal);
}
#endif
if (jwt->sub == NULL) {
krb5_set_error_message(context, EACCES, "JWT token lacks 'sub' "
"(subject name)!");
return EACCES;
}
if ((domain = strchr(jwt->sub, '@'))) {
force_realm = get_kv(context, realm, "cjwt_force_realm", ++domain);
ret = krb5_parse_name(context, jwt->sub, actual_principal);
} else {
ret = krb5_parse_name_flags(context, jwt->sub,
KRB5_PRINCIPAL_PARSE_NO_REALM,
actual_principal);
}
if (ret)
krb5_set_error_message(context, ret, "JWT token 'sub' not a valid "
"principal name: %s", jwt->sub);
else if (force_realm)
ret = krb5_principal_set_realm(context, *actual_principal, realm);
else if (domain == NULL)
ret = krb5_principal_set_realm(context, *actual_principal, realm);
/* else leave the domain as the realm */
return ret;
}
static KRB5_LIB_CALL krb5_error_code
validate(void *ctx,
krb5_context context,
const char *realm,
const char *token_type,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_boolean *result,
krb5_principal *actual_principal,
krb5_times *token_times)
{
heim_octet_string jwk_previous;
heim_octet_string jwk_current;
heim_octet_string jwk_next;
cjwt_t *jwt = NULL;
char *tokstr = NULL;
char *defrealm = NULL;
int ret;
if (strcmp(token_type, "Bearer") != 0)
return KRB5_PLUGIN_NO_HANDLE; /* Not us */
if ((tokstr = calloc(1, token->length + 1)) == NULL)
return ENOMEM;
memcpy(tokstr, token->data, token->length);
if (realm == NULL) {
ret = krb5_get_default_realm(context, &defrealm);
if (ret) {
krb5_set_error_message(context, ret, "could not determine default "
"realm");
free(tokstr);
return ret;
}
realm = defrealm;
}
ret = get_issuer_pubkeys(context, realm, &jwk_previous, &jwk_current,
&jwk_next);
if (ret) {
free(defrealm);
free(tokstr);
return ret;
}
if ((ret = cjwt_decode(tokstr, 0, &jwt, jwk_current.data,
jwk_current.length)) == -2 &&
(ret = cjwt_decode(tokstr, 0, &jwt, jwk_next.data,
jwk_next.length)) == -2)
ret = cjwt_decode(tokstr, 0, &jwt, jwk_previous.data,
jwk_previous.length);
free(jwk_previous.data);
free(jwk_current.data);
free(jwk_next.data);
jwk_previous.data = jwk_current.data = jwk_next.data = NULL;
free(tokstr);
tokstr = NULL;
switch (ret) {
case 0:
if (jwt->header.alg == alg_none) {
krb5_set_error_message(context, EINVAL, "JWT signature algorithm "
"not supported");
free(defrealm);
return EPERM;
}
break;
case -1:
krb5_set_error_message(context, EINVAL, "invalid JWT format");
free(defrealm);
return EINVAL;
case -2:
krb5_set_error_message(context, EINVAL, "JWT signature validation "
"failed (wrong issuer?)");
free(defrealm);
return EPERM;
default:
krb5_set_error_message(context, ret, "misc token validation error");
free(defrealm);
return ret;
}
/* Success; check audience */
if ((ret = check_audience(context, realm, jwt, audiences, naudiences))) {
cjwt_destroy(&jwt);
free(defrealm);
return EACCES;
}
/* Success; extract principal name */
if ((ret = get_princ(context, realm, jwt, actual_principal)) == 0) {
token_times->authtime = jwt->iat.tv_sec;
token_times->starttime = jwt->nbf.tv_sec;
token_times->endtime = jwt->exp.tv_sec;
token_times->renew_till = jwt->exp.tv_sec;
*result = TRUE;
}
cjwt_destroy(&jwt);
free(defrealm);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
hcjwt_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
hcjwt_fini(void *c)
{
}
static krb5plugin_token_validator_ftable plug_desc =
{ 1, hcjwt_init, hcjwt_fini, validate };
static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
static uintptr_t
hcjwt_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
return 0;
}
krb5_plugin_load_ft kdc_token_validator_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_token_validator_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = hcjwt_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

91
kdc/csr_authorizer.c Normal file
View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2019 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"
#include "csr_authorizer_plugin.h"
struct plctx {
krb5_kdc_configuration *config;
hx509_request csr;
krb5_const_principal client;
krb5_boolean result;
};
static krb5_error_code KRB5_LIB_CALL
plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
{
const krb5plugin_csr_authorizer_ftable *authorizer = plug;
struct plctx *plctx = userctx;
return authorizer->authorize(plugctx, context, plctx->config, plctx->csr,
plctx->client, &plctx->result);
}
static const char *plugin_deps[] = { "krb5", NULL };
static struct krb5_plugin_data csr_authorizer_data = {
"kdc",
KDC_CSR_AUTHORIZER,
1,
plugin_deps,
krb5_get_instance
};
/*
* Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
* access control.
*/
krb5_error_code
kdc_authorize_csr(krb5_context context,
krb5_kdc_configuration *config,
hx509_request csr,
krb5_const_principal client)
{
krb5_error_code ret;
struct plctx ctx;
ctx.config = config;
ctx.csr = csr;
ctx.client = client;
ctx.result = FALSE;
ret = _krb5_plugin_run_f(context, &csr_authorizer_data, 0, &ctx,
plcallback);
if (ret)
krb5_prepend_error_message(context, ret, "Authorization of requested "
"certificate extensions failed");
else if (!ctx.result)
krb5_set_error_message(context, ret, "Authorization of requested "
"certificate extensions failed");
return ret;
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H
#define HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H 1
#define KDC_CSR_AUTHORIZER "kdc_csr_authorizer"
#define KDC_CSR_AUTHORIZER_VERSION_0 0
/*
* @param init Plugin initialization function (see krb5-plugin(7))
* @param minor_version The plugin minor version number (0)
* @param fini Plugin finalization function
* @param authorize Plugin CSR authorization function
*
* The authorize field is the plugin entry point that performs authorization of
* CSRs for kx509 however the plugin desires. It is invoked in no particular
* order relative to other CSR authorization plugins. The plugin authorize
* function must return KRB5_PLUGIN_NO_HANDLE if the rule is not applicable to
* it.
*
* The plugin authorize function has the following arguments, in this
* order:
*
* -# plug_ctx, the context value output by the plugin's init function
* -# context, a krb5_context
* -# config, a krb5_kdc_configuration *
* -# csr, a hx509_request
* -# client, a krb5_const_principal
* -# authorization_result, a pointer to a krb5_boolean
*
* @ingroup krb5_support
*/
typedef struct krb5plugin_csr_authorizer_ftable_desc {
int minor_version;
krb5_error_code (KRB5_LIB_CALL *init)(krb5_context, void **);
void (KRB5_LIB_CALL *fini)(void *);
krb5_error_code (KRB5_LIB_CALL *authorize)(void *, /*plug_ctx*/
krb5_context,
krb5_kdc_configuration *,
hx509_request, /*CSR*/
krb5_const_principal,/*client*/
krb5_boolean *); /*authorized*/
} krb5plugin_csr_authorizer_ftable;
#endif /* HEIMDAL_KDC_CSR_AUTHORIZER_PLUGIN_H */

View File

@@ -37,17 +37,53 @@
#include <getarg.h> #include <getarg.h>
#include <parse_bytes.h> #include <parse_bytes.h>
static const char *sysplugin_dirs[] = {
#ifdef _WIN32
"$ORIGIN",
#else
"$ORIGIN/../lib/plugin/kdc",
#endif
#ifdef __APPLE__
LIBDIR "/plugin/kdc",
#endif
NULL
};
static void
load_kdc_plugins_once(void *ctx)
{
krb5_context context = ctx;
const char * const *dirs = sysplugin_dirs;
#ifndef _WIN32
char **cfdirs;
cfdirs = krb5_config_get_strings(context, NULL, "kdc", "plugin_dir", NULL);
if (cfdirs)
dirs = (const char * const *)cfdirs;
#endif
_krb5_load_plugins(context, "kdc", (const char **)dirs);
#ifndef _WIN32
krb5_config_free_strings(cfdirs);
#endif
}
krb5_error_code krb5_error_code
krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config) krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
{ {
static heim_base_once_t load_kdc_plugins = HEIM_BASE_ONCE_INIT;
krb5_kdc_configuration *c; krb5_kdc_configuration *c;
heim_base_once_f(&load_kdc_plugins, context, load_kdc_plugins_once);
c = calloc(1, sizeof(*c)); c = calloc(1, sizeof(*c));
if (c == NULL) { if (c == NULL) {
krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
return ENOMEM; return ENOMEM;
} }
c->app = "kdc";
c->num_kdc_processes = -1; c->num_kdc_processes = -1;
c->require_preauth = TRUE; c->require_preauth = TRUE;
c->kdc_warn_pwexpire = 0; c->kdc_warn_pwexpire = 0;
@@ -111,17 +147,7 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
c->enable_kx509 = c->enable_kx509 =
krb5_config_get_bool_default(context, NULL, krb5_config_get_bool_default(context, NULL,
FALSE, FALSE,
"kdc", "enable-kx509", NULL); "kdc", "enable_kx509", NULL);
if (c->enable_kx509) {
/* These are global defaults. There are also per-realm defaults. */
c->kx509_template =
krb5_config_get_string(context, NULL,
"kdc", "kx509_template", NULL);
c->kx509_ca =
krb5_config_get_string(context, NULL,
"kdc", "kx509_ca", NULL);
}
#endif #endif
c->tgt_use_strongest_session_key = c->tgt_use_strongest_session_key =

443
kdc/ipc_csr_authorizer.c Normal file
View File

@@ -0,0 +1,443 @@
/*
* Copyright (c) 2019 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.
*/
/*
* This plugin authorizes requested certificate SANs and EKUs by calling a
* service over IPC (Unix domain sockets on Linux/BSD/Illumos).
*
* The IPC protocol is request/response, with requests and responses sent as
*
* <length><string>
*
* where the <length> is 4 bytes, unsigned binary in network byte order, and
* <string> is an array of <length> bytes and does NOT include a NUL
* terminator.
*
* Requests are of the form:
*
* check <princ> <exttype>=<extvalue> ...
*
* where <princ> is a URL-escaped principal name, <exttype> is one of:
*
* - san_pkinit
* - san_xmpp
* - san_email
* - san_ms_upn
* - san_dnsname
* - eku
*
* and <extvalue> is a URL-escaped string representation of the SAN or OID.
*
* OIDs are in the form 1.2.3.4.5.6.
*
* Only characters other than alphanumeric, '@', '.', '-', '_', and '/' are
* URL-encoded.
*
* Responses are any of:
*
* - granted
* - denied
* - error message
*
* Example:
*
* C->S: check jane@TEST.H5L.SE san_dnsname=jane.foo.test.h5l.se eku=1.3.6.1.5.5.7.3.1
* S->C: granted
*
* Only digitalSignature and nonRepudiation key usages are allowed. Requested
* key usages are not sent to the CSR authorizer IPC server.
*/
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <roken.h>
#include <heim-ipc.h>
#include <krb5.h>
#include <hx509.h>
#include <kdc.h>
#include <common_plugin.h>
#include <csr_authorizer_plugin.h>
/*
* string_encode_sz() and string_encode() encode principal names and such to be
* safe for use in our IPC text messages. They function very much like URL
* encoders, but '~' also gets encoded, and '.' and '@' do not.
*
* An unescaper is not needed here.
*/
static size_t
string_encode_sz(const char *in)
{
size_t sz = strlen(in);
while (*in) {
char c = *(in++);
switch (c) {
case '@':
case '.':
case '-':
case '_':
case '/':
continue;
default:
if (isalnum(c))
continue;
sz += 2;
}
}
return sz;
}
static char *
string_encode(const char *in)
{
size_t len = strlen(in);
size_t sz = string_encode_sz(in);
size_t i, k;
char *s;
if ((s = malloc(sz + 1)) == NULL)
return NULL;
s[sz] = '\0';
for (i = k = 0; i < len; i++) {
unsigned char c = ((const unsigned char *)in)[i];
switch (c) {
case '@':
case '.':
case '-':
case '_':
case '/':
s[k++] = c;
break;
default:
if (isalnum(c)) {
s[k++] = c;
} else {
s[k++] = '%';
s[k++] = "0123456789abcdef"[(c&0xff)>>4];
s[k++] = "0123456789abcdef"[(c&0x0f)];
}
}
}
return s;
}
static int
cmd_append(struct rk_strpool **cmd, const char *s0, ...)
{
va_list ap;
const char *arg;
if ((*cmd = rk_strpoolprintf(*cmd, "%s", s0)) == NULL)
return ENOMEM;
va_start(ap, s0);
while ((arg = va_arg(ap, const char *))) {
char *s;
if ((s = string_encode(arg)) == NULL)
return rk_strpoolfree(*cmd), *cmd = NULL, ENOMEM;
*cmd = rk_strpoolprintf(*cmd, "%s", s);
free(s);
if (*cmd == NULL)
return ENOMEM;
}
return 0;
}
static int
call_svc(krb5_context context, heim_ipc ipc, const char *cmd)
{
heim_octet_string req, resp;
int ret;
req.data = (void *)(uintptr_t)cmd;
req.length = strlen(cmd);
resp.length = 0;
resp.data = NULL;
if ((ret = heim_ipc_call(ipc, &req, &resp, NULL))) {
if (resp.length && resp.length < INT_MAX) {
krb5_set_error_message(context, ret, "CSR denied: %.*s",
(int)resp.length, (const char *)resp.data);
ret = EACCES;
} else {
krb5_set_error_message(context, EACCES, "CSR denied because could "
"not reach CSR authorizer IPC service");
ret = EACCES;
}
return ret;
}
if (resp.data == NULL || resp.length == 0) {
free(resp.data);
krb5_set_error_message(context, ret, "CSR authorizer IPC service "
"failed silently");
return EACCES;
}
if (resp.length == sizeof("denied") - 1 &&
strncasecmp(resp.data, "denied", sizeof("denied") - 1) == 0) {
free(resp.data);
krb5_set_error_message(context, ret, "CSR authorizer rejected %s",
cmd);
return EACCES;
}
if (resp.length == sizeof("granted") - 1 &&
strncasecmp(resp.data, "granted", sizeof("granted") - 1) == 0) {
free(resp.data);
return 0;
}
krb5_set_error_message(context, ret, "CSR authorizer failed %s: %.*s",
cmd, resp.length < INT_MAX ? (int)resp.length : 0,
resp.data);
return EACCES;
}
static void
frees(char **s)
{
free(*s);
*s = NULL;
}
static krb5_error_code
mark_authorized(hx509_request csr)
{
size_t i;
char *s;
int ret = 0;
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(csr, i, &s);
if (ret == 0)
hx509_request_authorize_eku(csr, i);
frees(&s);
}
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret == 0)
hx509_request_authorize_eku(csr, i);
frees(&s);
}
return ret == HX509_NO_ITEM ? 0 : ret;
}
static KRB5_LIB_CALL krb5_error_code
authorize(void *ctx,
krb5_context context,
krb5_kdc_configuration *config,
hx509_request csr,
krb5_const_principal client,
krb5_boolean *result)
{
struct rk_strpool *cmd = NULL;
krb5_error_code ret;
hx509_context hx509ctx = NULL;
heim_ipc ipc = NULL;
const char *svc;
KeyUsage ku;
size_t i;
char *princ = NULL;
char *s = NULL;
int do_check = 0;
if ((svc = krb5_config_get_string(context, NULL,
config->app ? config->app : "kdc",
"ipc_csr_authorizer", "service",
NULL)) == NULL)
return KRB5_PLUGIN_NO_HANDLE;
if ((ret = heim_ipc_init_context(svc, &ipc))) {
krb5_set_error_message(context, ret, "Could not set up IPC client "
"end-point for service %s", svc);
return ret;
}
if ((ret = hx509_context_init(&hx509ctx)))
goto out;
if ((ret = krb5_unparse_name(context, client, &princ)))
goto out;
if ((ret = cmd_append(&cmd, "check ", princ, NULL)))
goto enomem;
frees(&princ);
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_EMAIL:
if ((ret = cmd_append(&cmd, " san_email=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_DNSNAME:
if ((ret = cmd_append(&cmd, " san_dnsname=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_XMPP:
if ((ret = cmd_append(&cmd, " san_xmpp=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_PKINIT:
if ((ret = cmd_append(&cmd, " san_pkinit=", s, NULL)))
goto enomem;
do_check = 1;
break;
case HX509_SAN_TYPE_MS_UPN:
if ((ret = cmd_append(&cmd, " san_ms_upn=", s, NULL)))
goto enomem;
do_check = 1;
break;
default:
if ((ret = hx509_request_reject_san(csr, i)))
goto out;
break;
}
frees(&s);
}
if (ret == HX509_NO_ITEM)
ret = 0;
if (ret)
goto out;
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(csr, i, &s);
if (ret)
break;
if ((ret = cmd_append(&cmd, " eku=", s, NULL)))
goto enomem;
do_check = 1;
frees(&s);
}
if (ret == HX509_NO_ITEM)
ret = 0;
if (ret)
goto out;
ku = int2KeyUsage(0);
ku.digitalSignature = 1;
ku.nonRepudiation = 1;
hx509_request_authorize_ku(csr, ku);
if (do_check) {
if ((s = rk_strpoolcollect(cmd)) == NULL)
goto enomem;
cmd = NULL;
if ((ret = call_svc(context, ipc, s)))
goto out;
} /* else -> permit */
if ((ret = mark_authorized(csr)))
goto out;
*result = TRUE;
ret = 0;
goto out;
enomem:
ret = krb5_enomem(context);
goto out;
out:
heim_ipc_free_context(ipc);
hx509_context_free(&hx509ctx);
if (cmd)
rk_strpoolfree(cmd);
free(princ);
free(s);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
ipc_csr_authorizer_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
ipc_csr_authorizer_fini(void *c)
{
}
static krb5plugin_csr_authorizer_ftable plug_desc =
{ 1, ipc_csr_authorizer_init, ipc_csr_authorizer_fini, authorize };
static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
static uintptr_t
ipc_csr_authorizer_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
if (strcmp(libname, "kdc") == 0)
return kdc_get_instance(libname);
if (strcmp(libname, "hx509") == 0)
return hx509_get_instance(libname);
return 0;
}
krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_csr_authorizer_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = ipc_csr_authorizer_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

View File

@@ -93,13 +93,12 @@ typedef struct krb5_kdc_configuration {
size_t max_datagram_reply_length; size_t max_datagram_reply_length;
int enable_kx509; int enable_kx509;
const char *kx509_template;
const char *kx509_ca;
krb5_boolean enable_derived_keys; krb5_boolean enable_derived_keys;
int derived_keys_ndots; int derived_keys_ndots;
int derived_keys_maxdots; int derived_keys_maxdots;
const char *app;
} krb5_kdc_configuration; } krb5_kdc_configuration;
struct krb5_kdc_service { struct krb5_kdc_service {

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,12 @@
EXPORTS EXPORTS
kdc_authorize_csr
kdc_get_instance kdc_get_instance
kdc_issue_certificate
kdc_log kdc_log
kdc_log_msg kdc_log_msg
kdc_log_msg_va kdc_log_msg_va
kdc_openlog kdc_openlog
kdc_validate_token
krb5_kdc_windc_init krb5_kdc_windc_init
krb5_kdc_get_config krb5_kdc_get_config
krb5_kdc_pkinit_config krb5_kdc_pkinit_config

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) 2019 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.
*/
/*
* This is a plugin by which bx509d can validate Negotiate tokens.
*
* [kdc]
* negotiate_token_validator = {
* keytab = ...
* }
*/
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <base64.h>
#include <roken.h>
#include <krb5.h>
#include <common_plugin.h>
#include <gssapi/gssapi.h>
#include <token_validator_plugin.h>
static int
display_status(krb5_context context,
OM_uint32 major,
OM_uint32 minor,
gss_cred_id_t acred,
gss_ctx_id_t gctx,
gss_OID mech_type)
{
gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
OM_uint32 dmaj, dmin;
OM_uint32 more = 0;
char *gmmsg = NULL;
char *gmsg = NULL;
char *s = NULL;
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
&more, &buf);
if (GSS_ERROR(dmaj) ||
buf.length >= INT_MAX ||
asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmsg);
gmsg = NULL;
break;
}
gmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
if (mech_type != GSS_C_NO_OID) {
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE, mech_type,
&more, &buf);
if (GSS_ERROR(dmaj) ||
asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmmsg);
gmmsg = NULL;
break;
}
gmmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
}
if (gmsg == NULL)
krb5_set_error_message(context, ENOMEM, "Error displaying GSS-API "
"status");
else
krb5_set_error_message(context, EACCES, "%s%s%s%s", gmmsg,
gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
if (acred && gctx)
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error examining "
"GSS-API security context");
else if (acred)
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error accepting "
"GSS-API security context token");
else
krb5_prepend_error_message(context, EACCES, "Failed to validate "
"Negotiate token due to error acquiring "
"GSS-API default acceptor credential");
return EACCES;
}
static KRB5_LIB_CALL krb5_error_code
validate(void *ctx,
krb5_context context,
const char *realm,
const char *token_type,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_boolean *result,
krb5_principal *actual_principal,
krb5_times *token_times)
{
gss_buffer_desc adisplay_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc idisplay_name = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc input_token;
gss_cred_id_t acred = GSS_C_NO_CREDENTIAL;
gss_ctx_id_t gctx = GSS_C_NO_CONTEXT;
gss_name_t aname = GSS_C_NO_NAME;
gss_name_t iname = GSS_C_NO_NAME;
gss_OID mech_type = GSS_C_NO_OID;
const char *kt = krb5_config_get_string(context, NULL, "kdc",
"negotiate_token_validator",
"keytab", NULL);
OM_uint32 major, minor, ret_flags, time_rec;
size_t i;
char *token_decoded = NULL;
void *token_copy = NULL;
char *princ_str = NULL;
int ret = 0;
if (strcmp(token_type, "Negotiate") != 0)
return KRB5_PLUGIN_NO_HANDLE;
if (kt) {
gss_key_value_element_desc store_keytab_kv;
gss_key_value_set_desc store;
gss_OID_desc mech_set[2] = { *GSS_KRB5_MECHANISM, *GSS_SPNEGO_MECHANISM };
gss_OID_set_desc mechs = { 2, mech_set };
store_keytab_kv.key = "keytab";
store_keytab_kv.value = kt;
store.elements = &store_keytab_kv;
store.count = 1;
major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
&mechs, GSS_C_ACCEPT, &store, &acred, NULL,
NULL);
if (major != GSS_S_COMPLETE)
return display_status(context, major, minor, acred, gctx, mech_type);
mechs.count = 1;
major = gss_set_neg_mechs(&minor, acred, &mechs);
if (major != GSS_S_COMPLETE)
return display_status(context, major, minor, acred, gctx, mech_type);
} /* else we'll use the default credential */
if ((token_decoded = malloc(token->length)) == NULL ||
(token_copy = calloc(1, token->length + 1)) == NULL)
goto enomem;
memcpy(token_copy, token->data, token->length);
if ((ret = rk_base64_decode(token_copy, token_decoded)) <= 0) {
krb5_set_error_message(context, EACCES, "Negotiate token malformed");
ret = EACCES;
goto out;
}
input_token.value = token_decoded;
input_token.length = ret;
major = gss_accept_sec_context(&minor, &gctx, acred, &input_token, NULL,
&iname, &mech_type, &output_token,
&ret_flags, &time_rec, NULL);
if (mech_type == GSS_C_NO_OID ||
!gss_oid_equal(mech_type, GSS_KRB5_MECHANISM)) {
krb5_set_error_message(context, ret = EACCES, "Negotiate token used "
"non-Kerberos mechanism");
goto out;
}
if (major != GSS_S_COMPLETE) {
ret = display_status(context, major, minor, acred, gctx, mech_type);
if (ret == 0)
ret = EINVAL;
goto out;
}
major = gss_inquire_context(&minor, gctx, NULL, &aname, NULL, NULL,
NULL, NULL, NULL);
if (major == GSS_S_COMPLETE)
major = gss_display_name(&minor, aname, &adisplay_name, NULL);
if (major == GSS_S_COMPLETE)
major = gss_display_name(&minor, iname, &idisplay_name, NULL);
if (major != GSS_S_COMPLETE) {
ret = display_status(context, major, minor, acred, gctx, mech_type);
if (ret == 0)
ret = EINVAL;
goto out;
}
for (i = 0; i < naudiences; i++) {
const char *s = adisplay_name.value;
size_t slen = adisplay_name.length;
size_t len = strlen(audiences[i]);
if (slen >= sizeof("HTTP/") - 1 &&
slen >= sizeof("HTTP/") - 1 + len &&
memcmp(s, "HTTP/", sizeof("HTTP/") - 1) == 0 &&
memcmp(s + sizeof("HTTP/") - 1, audiences[i], len) == 0 &&
s[sizeof("HTTP/") - 1 + len] == '@')
break;
}
if (i == naudiences) {
/* This handles the case where naudiences == 0 as an error */
krb5_set_error_message(context, EACCES, "Negotiate token used "
"wrong HTTP service host acceptor name");
goto out;
}
if ((princ_str = calloc(1, idisplay_name.length + 1)) == NULL)
goto enomem;
memcpy(princ_str, idisplay_name.value, idisplay_name.length);
if ((ret = krb5_parse_name(context, princ_str, actual_principal)))
goto out;
/* XXX Need name attributes to get authtime/starttime/renew_till */
token_times->authtime = 0;
token_times->starttime = time(NULL) - 300;
token_times->endtime = token_times->starttime + 300 + time_rec;
token_times->renew_till = 0;
*result = TRUE;
goto out;
enomem:
ret = krb5_enomem(context);
out:
gss_delete_sec_context(&minor, &gctx, NULL);
gss_release_buffer(&minor, &adisplay_name);
gss_release_buffer(&minor, &idisplay_name);
gss_release_buffer(&minor, &output_token);
gss_release_cred(&minor, &acred);
gss_release_name(&minor, &aname);
gss_release_name(&minor, &iname);
free(token_decoded);
free(token_copy);
free(princ_str);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
negotiate_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
negotiate_fini(void *c)
{
}
static krb5plugin_token_validator_ftable plug_desc =
{ 1, negotiate_init, negotiate_fini, validate };
static krb5plugin_token_validator_ftable *plugs[] = { &plug_desc };
static uintptr_t
negotiate_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
/* XXX gss_get_instance() doesn't exist :( */
return 0;
}
krb5_plugin_load_ft kdc_token_validator_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_token_validator_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = negotiate_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

337
kdc/simple_csr_authorizer.c Normal file
View File

@@ -0,0 +1,337 @@
/*
* Copyright (c) 2019 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.
*/
/*
* This plugin authorizes requested certificate SANs and EKUs by checking for
* existence of files of the form:
*
*
* /<path>/<princ>/<ext>-<value>
*
* where <path> is the value of:
*
* [kdc] simple_csr_authorizer_directory = PATH
*
* <princ> is a requesting client principal name with all characters other than
* alphanumeric, '-', '_', and non-leading '.' URL-encoded.
*
* <ext> is one of:
*
* - pkinit (SAN)
* - xmpt (SAN)
* - emailt (SAN)
* - ms-upt (SAN)
* - dnsnamt (SAN)
* - eku (EKU OID)
*
* and <value> is a display form of the SAN or EKU OID, with SANs URL-encoded
* just like principal names (see above).
*
* OIDs are of the form "1.2.3.4.5".
*
* Only digitalSignature and nonRepudiation key usage values are permitted.
*/
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <roken.h>
#include <krb5.h>
#include <hx509.h>
#include <kdc.h>
#include <common_plugin.h>
#include <csr_authorizer_plugin.h>
/*
* string_encode_sz() and string_encode() encode a string to be safe for use as
* a file name. They function very much like URL encoders, but '~' also gets
* encoded, and '@', '-', '_', and non-leading '.' do not.
*
* A corresponding decoder is not needed.
*/
static size_t
string_encode_sz(const char *in)
{
size_t sz = strlen(in);
int first = 1;
while (*in) {
char c = *(in++);
switch (c) {
case '@':
case '-':
case '_':
break;
case '.':
if (first)
sz += 2;
break;
default:
if (!isalnum(c))
sz += 2;
}
first = 0;
}
return sz;
}
static char *
string_encode(const char *in)
{
size_t len = strlen(in);
size_t sz = string_encode_sz(in);
size_t i, k;
char *s;
int first = 1;
if ((s = malloc(sz + 1)) == NULL)
return NULL;
s[sz] = '\0';
for (i = k = 0; i < len; i++, first = 0) {
unsigned char c = ((const unsigned char *)in)[i];
switch (c) {
case '@':
case '-':
case '_':
s[k++] = c;
break;
case '.':
if (first) {
s[k++] = '%';
s[k++] = "0123456789abcdef"[(c&0xff)>>4];
s[k++] = "0123456789abcdef"[(c&0x0f)];
} else {
s[k++] = c;
}
break;
default:
if (isalnum(c)) {
s[k++] = c;
} else {
s[k++] = '%';
s[k++] = "0123456789abcdef"[(c&0xff)>>4];
s[k++] = "0123456789abcdef"[(c&0x0f)];
}
}
}
return s;
}
static KRB5_LIB_CALL krb5_error_code
authorize(void *ctx,
krb5_context context,
krb5_kdc_configuration *config,
hx509_request csr,
krb5_const_principal client,
krb5_boolean *result)
{
krb5_error_code ret;
hx509_context hx509ctx = NULL;
KeyUsage ku;
const char *d;
size_t i;
char *princ = NULL;
char *s = NULL;
if ((d = krb5_config_get_string(context, NULL, "kdc",
"simple_csr_authorizer_directory",
NULL)) == NULL)
return KRB5_PLUGIN_NO_HANDLE;
if ((ret = hx509_context_init(&hx509ctx)))
return ret;
if ((ret = krb5_unparse_name(context, client, &princ)))
goto out;
s = string_encode(princ);
free(princ);
princ = NULL;
if (s == NULL)
goto enomem;
princ = s;
s = NULL;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
struct stat st;
const char *prefix;
char *san;
char *p;
ret = hx509_request_get_san(csr, i, &san_type, &s);
if (ret)
break;
switch (san_type) {
case HX509_SAN_TYPE_EMAIL:
prefix = "email";
break;
case HX509_SAN_TYPE_DNSNAME:
prefix = "dnsname";
break;
case HX509_SAN_TYPE_XMPP:
prefix = "xmpp";
break;
case HX509_SAN_TYPE_PKINIT:
prefix = "pkinit";
break;
case HX509_SAN_TYPE_MS_UPN:
prefix = "ms-upn";
break;
default:
ret = ENOTSUP;
break;
}
if (ret)
break;
if ((san = string_encode(s)) == NULL ||
asprintf(&p, "%s/%s/%s-%s", d, princ, prefix, san) == -1 ||
p == NULL)
goto enomem;
ret = stat(p, &st) == -1 ? errno : 0;
free(san);
free(p);
free(s);
s = NULL;
if (ret)
goto skip;
ret = hx509_request_authorize_san(csr, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
if (ret)
goto out;
for (i = 0; ret == 0; i++) {
struct stat st;
char *p;
ret = hx509_request_get_eku(csr, i, &s);
if (ret)
break;
if (asprintf(&p, "%s/%s/eku-%s", d, princ, s) == -1 || p == NULL) {
free(princ);
free(s);
}
ret = stat(p, &st) == -1 ? errno : 0;
free(p);
free(s);
s = NULL;
if (ret)
goto skip;
ret = hx509_request_authorize_eku(csr, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
if (ret)
goto out;
ku = int2KeyUsage(0);
ku.digitalSignature = 1;
ku.nonRepudiation = 1;
hx509_request_authorize_ku(csr, ku);
*result = TRUE;
ret = 0;
goto out;
skip:
/* Allow another plugin to get a crack at this */
ret = KRB5_PLUGIN_NO_HANDLE;
goto out;
enomem:
ret = krb5_enomem(context);
goto out;
out:
hx509_context_free(&hx509ctx);
free(princ);
free(s);
return ret;
}
static KRB5_LIB_CALL krb5_error_code
simple_csr_authorizer_init(krb5_context context, void **c)
{
*c = NULL;
return 0;
}
static KRB5_LIB_CALL void
simple_csr_authorizer_fini(void *c)
{
}
static krb5plugin_csr_authorizer_ftable plug_desc =
{ 1, simple_csr_authorizer_init, simple_csr_authorizer_fini, authorize };
static krb5plugin_csr_authorizer_ftable *plugs[] = { &plug_desc };
static uintptr_t
simple_csr_authorizer_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
if (strcmp(libname, "kdc") == 0)
return kdc_get_instance(libname);
if (strcmp(libname, "hx509") == 0)
return hx509_get_instance(libname);
return 0;
}
krb5_plugin_load_ft kdc_csr_authorizer_plugin_load;
krb5_error_code KRB5_CALLCONV
kdc_csr_authorizer_plugin_load(krb5_context context,
krb5_get_instance_func_t *get_instance,
size_t *num_plugins,
krb5_plugin_common_ftable_cp **plugins)
{
*get_instance = simple_csr_authorizer_get_instance;
*num_plugins = sizeof(plugs) / sizeof(plugs[0]);
*plugins = (krb5_plugin_common_ftable_cp *)plugs;
return 0;
}

79
kdc/test_csr_authorizer.c Normal file
View File

@@ -0,0 +1,79 @@
#include "kdc_locl.h"
static int help_flag;
static int version_flag;
static const char *app_string = "kdc";
struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ "version", 'v', arg_flag, &version_flag,
"Print version", NULL },
{ "app", 'a', arg_string, &app_string,
"App to test (kdc or bx509); default: kdc", "APPNAME" },
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
usage(int e)
{
arg_printusage(args, num_args, NULL, "PATH-TO-DER-CSR PRINCIPAL");
fprintf(stderr,
"\n\tExercise CSR authorization plugins for a given CSR for a\n"
"\tgiven principal.\n"
"\n\tExample: %s PKCS10:/tmp/csr.der foo@TEST.H5L.SE\n",
getprogname());
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_context context;
hx509_request csr;
krb5_principal princ = NULL;
const char *argv0 = argv[0];
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
return 0;
}
argc -= optidx;
argv += optidx;
if (argc != 2)
usage(1);
if ((errno = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
config->app = app_string;
if ((ret = krb5_initlog(context, argv0, &config->logf)) ||
(ret = krb5_addlog_dest(context, config->logf, "0-5/STDERR")))
krb5_err(context, 1, ret, "Could not set up logging to stderr");
if ((ret = krb5_kdc_set_dbinfo(context, config)))
krb5_err(context, 1, ret, "Could not get KDC configuration (HDB)");
if ((ret = hx509_request_parse(context->hx509ctx, argv[0], &csr)))
krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[0]);
if ((ret = krb5_parse_name(context, argv[1], &princ)))
krb5_err(context, 1, ret, "Could not parse principal %s", argv[1]);
if ((ret = kdc_authorize_csr(context, config, csr, princ)))
krb5_err(context, 1, ret, "Authorization failed");
printf("Authorized!\n");
krb5_free_principal(context, princ);
krb5_free_context(context);
hx509_request_free(&csr);
/* FIXME There's no free function for config yet */
return 0;
}

146
kdc/test_kdc_ca.c Normal file
View File

@@ -0,0 +1,146 @@
#include "kdc_locl.h"
static int authorized_flag;
static int help_flag;
static const char *app_string = "kdc";
static int version_flag;
struct getargs args[] = {
{ "authorized", 'A', arg_flag, &authorized_flag,
"Assume CSR is authorized", NULL },
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ "app", 'a', arg_string, &app_string,
"Application name (kdc or bx509); default: kdc", "APPNAME" },
{ "version", 'v', arg_flag, &version_flag,
"Print version", NULL }
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
usage(int e)
{
arg_printusage(args, num_args, NULL,
"PRINC PKCS10:/path/to/der/CSR [HX509-STORE]");
fprintf(stderr,
"\n\tTest kx509/bx509 online CA issuer functionality.\n"
"\n\tIf --authorized / -A not given, then authorizer plugins\n"
"\twill be invoked.\n"
"\n\tUse --app kdc to test the kx509 configuration.\n"
"\tUse --app bx509 to test the bx509 configuration.\n\n\t"
"Example: %s foo@TEST.H5L.SE PKCS10:/tmp/csr PEM-FILE:/tmp/cert\n",
getprogname());
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_principal p = NULL;
krb5_context context;
krb5_times t;
hx509_request req = NULL;
hx509_certs store = NULL;
hx509_certs certs = NULL;
const char *argv0 = argv[0];
const char *out = "MEMORY:junk-it";
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
return 0;
}
argc -= optidx;
argv += optidx;
if (argc < 3 || argc > 4)
usage(1);
if ((errno = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
if ((ret = krb5_initlog(context, argv0, &config->logf)) ||
(ret = krb5_addlog_dest(context, config->logf, "0-5/STDERR")))
krb5_err(context, 1, ret, "Could not set up logging to stderr");
#if 0
if ((ret = krb5_kdc_set_dbinfo(context, config)))
krb5_err(context, 1, ret, "Could not get KDC configuration (HDB)");
#endif
if ((ret = krb5_parse_name(context, argv[0], &p)))
krb5_err(context, 1, ret, "Could not parse principal %s", argv[0]);
if ((ret = hx509_request_parse(context->hx509ctx, argv[1], &req)))
krb5_err(context, 1, ret, "Could not parse PKCS#10 CSR from %s", argv[1]);
if (authorized_flag) {
KeyUsage ku = int2KeyUsage(0);
size_t i;
char *s;
/* Mark all the things authorized */
ku.digitalSignature = 1;
hx509_request_authorize_ku(req, ku);
for (i = 0; ret == 0; i++) {
ret = hx509_request_get_eku(req, i, &s);
free(s); s = NULL;
if (ret == 0)
hx509_request_authorize_eku(req, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
for (i = 0; ret == 0; i++) {
hx509_san_type san_type;
ret = hx509_request_get_san(req, i, &san_type, &s);
free(s); s = NULL;
if (ret == 0)
hx509_request_authorize_san(req, i);
}
if (ret == HX509_NO_ITEM)
ret = 0;
} else if ((ret = kdc_authorize_csr(context, config, req, p))) {
krb5_err(context, 1, ret,
"Requested certificate extensions rejected by policy");
}
memset(&t, 0, sizeof(t));
t.starttime = time(NULL);
t.endtime = t.starttime + 3600;
if ((ret = kdc_issue_certificate(context, config, req, p, &t, 1,
&certs)))
krb5_err(context, 1, ret, "Certificate issuance failed");
if (argv[2])
out = argv[2];
if ((ret = hx509_certs_init(context->hx509ctx, out, HX509_CERTS_CREATE,
NULL, &store)) ||
(ret = hx509_certs_merge(context->hx509ctx, store, certs)) ||
(ret = hx509_certs_store(context->hx509ctx, store, 0, NULL)))
/*
* If the store is a MEMORY store, say, we're really not being asked to
* store -- we're just testing the online CA functionality without
* wanting to inspect the result.
*/
if (ret != HX509_UNSUPPORTED_OPERATION)
krb5_err(context, 1, ret,
"Could not store certificate and chain in %s", out);
krb5_free_principal(context, p);
krb5_free_context(context);
hx509_request_free(&req);
hx509_certs_free(&store);
hx509_certs_free(&certs);
/* FIXME There's no free function for config yet */
return 0;
}

View File

@@ -0,0 +1,86 @@
#include "kdc_locl.h"
static int help_flag;
static int version_flag;
static char *realm;
static struct getarg_strings audiences;
struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag,
"Print usage message", NULL },
{ NULL, 'r', arg_string, &realm,
"Realm name for plugin configuration", "REALM" },
{ NULL, 'a', arg_strings, &audiences,
"expected token acceptor audience (hostname)", "ACCEPTOR-HOSTNAME" },
{ "version", 'v', arg_flag, &version_flag, "Print version", NULL }
};
size_t num_args = sizeof(args) / sizeof(args[0]);
static int
usage(int e)
{
arg_printusage(args, num_args, NULL, "TOKEN-TYPE TOKEN");
exit(e);
return e;
}
int
main(int argc, char **argv)
{
krb5_kdc_configuration *config;
krb5_error_code ret;
krb5_context context;
krb5_data token;
const char *token_type;
krb5_principal actual_princ = NULL;
krb5_times token_times;
size_t bufsz = 0;
char *buf = NULL;
char *s = NULL;
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, num_args, argc, argv, &optidx))
return usage(1);
if (help_flag)
return usage(0);
if (version_flag) {
print_version(argv[0]);
return 0;
}
argc -= optidx;
argv += optidx;
if (argc != 2)
usage(1);
if ((ret = krb5_init_context(&context)))
err(1, "Could not initialize krb5_context");
if ((ret = krb5_kdc_get_config(context, &config)))
krb5_err(context, 1, ret, "Could not get KDC configuration");
token_type = argv[0];
token.data = argv[1];
if (strcmp(token.data, "-") == 0) {
if (getline(&buf, &bufsz, stdin) < 0)
err(1, "Could not read token from stdin");
token.length = bufsz;
token.data = buf;
} else {
token.length = strlen(token.data);
}
if ((ret = kdc_validate_token(context, realm, token_type, &token,
(const char * const *)audiences.strings,
audiences.num_strings, &actual_princ,
&token_times)))
krb5_err(context, 1, ret, "Could not validate %s token", token_type);
if (actual_princ && (ret = krb5_unparse_name(context, actual_princ, &s)))
krb5_err(context, 1, ret, "Could not display principal name");
if (s)
printf("Token is valid. Actual principal: %s\n", s);
else
printf("Token is valid.");
krb5_free_principal(context, actual_princ);
return 0;
}

122
kdc/token_validator.c Normal file
View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2019 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"
#include "token_validator_plugin.h"
struct plctx {
const char *realm;
const char *token_kind;
krb5_data *token;
const char * const *audiences;
size_t naudiences;
krb5_boolean result;
krb5_principal actual_principal;
krb5_times token_times;
};
static krb5_error_code KRB5_LIB_CALL
plcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
{
const krb5plugin_token_validator_ftable *validator = plug;
krb5_error_code ret;
struct plctx *plctx = userctx;
ret = validator->validate(plugctx, context, plctx->realm,
plctx->token_kind, plctx->token,
plctx->audiences, plctx->naudiences,
&plctx->result, &plctx->actual_principal,
&plctx->token_times);
if (ret) {
krb5_free_principal(context, plctx->actual_principal);
plctx->actual_principal = NULL;
}
return ret;
}
static const char *plugin_deps[] = { "krb5", NULL };
static struct krb5_plugin_data token_validator_data = {
"kdc",
KDC_PLUGIN_BEARER,
1,
plugin_deps,
krb5_get_instance
};
/*
* Invoke a plugin to validate a JWT/SAML/OIDC token and partially-evaluate
* access control.
*/
krb5_error_code
kdc_validate_token(krb5_context context,
const char *realm,
const char *token_kind,
krb5_data *token,
const char * const *audiences,
size_t naudiences,
krb5_principal *actual_principal,
krb5_times *token_times)
{
krb5_error_code ret;
struct plctx ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.realm = realm;
ctx.token_kind = token_kind;
ctx.token = token;
ctx.audiences = audiences;
ctx.naudiences = naudiences;
ctx.result = FALSE;
ctx.actual_principal = NULL;
krb5_clear_error_message(context);
ret = _krb5_plugin_run_f(context, &token_validator_data, 0, &ctx,
plcallback);
if (ret == 0 && ctx.result && actual_principal) {
*actual_principal = ctx.actual_principal;
ctx.actual_principal = NULL;
}
if (token_times)
*token_times = ctx.token_times;
krb5_free_principal(context, ctx.actual_principal);
if (ret)
krb5_prepend_error_message(context, ret, "bearer token validation "
"failed: ");
else if (!ctx.result)
krb5_set_error_message(context, ret = EACCES,
"bearer token validation failed");
return ret;
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H
#define HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H 1
#define KDC_PLUGIN_BEARER "kdc_token_validator"
#define KDC_PLUGIN_BEARER_VERSION_0 0
/*
* @param init Plugin initialization function (see krb5-plugin(7))
* @param minor_version The plugin minor version number (0)
* @param fini Plugin finalization function
* @param validate Plugin token validation function
*
* The validate field is the plugin entry point that performs the bearer token
* validation operation however the plugin desires. It is invoked in no
* particular order relative to other bearer token validator plugins. The
* plugin validate function must return KRB5_PLUGIN_NO_HANDLE if the rule is
* not applicable to it.
*
* The plugin validate function has the following arguments, in this
* order:
*
* -# plug_ctx, the context value output by the plugin's init function
* -# context, a krb5_context
* -# realm, a const char *
* -# token_type, a const char *
* -# token, a krb5_data *
* -# audiences, a const pointer to an array of const char * containing
* expected audiences of the token (aka, acceptor names)
* -# naudiences, a size_t count of audiences
* -# requested_principal, a krb5_const_principal
* -# validation result, a pointer to a krb5_boolean
* -# actual principal, a krb5_principal * output parameter (optional)
*
* @ingroup krb5_support
*/
typedef struct krb5plugin_token_validator_ftable_desc {
int minor_version;
krb5_error_code (KRB5_LIB_CALL *init)(krb5_context, void **);
void (KRB5_LIB_CALL *fini)(void *);
krb5_error_code (KRB5_LIB_CALL *validate)(void *, /*plug_ctx*/
krb5_context,
const char *, /*realm*/
const char *, /*token_type*/
krb5_data *, /*token*/
const char * const *, /*audiences*/
size_t, /*naudiences*/
krb5_boolean *, /*valid*/
krb5_principal *, /*actual_principal*/
krb5_times *); /*token_times*/
} krb5plugin_token_validator_ftable;
#endif /* HEIMDAL_KDC_BEARER_TOKEN_PLUGIN_H */

View File

@@ -2,12 +2,15 @@
HEIMDAL_KDC_1.0 { HEIMDAL_KDC_1.0 {
global: global:
kdc_authorize_csr;
kdc_get_instance; kdc_get_instance;
kdc_issue_certificate;
kdc_log; kdc_log;
kdc_log_msg; kdc_log_msg;
kdc_log_msg_va; kdc_log_msg_va;
kdc_openlog; kdc_openlog;
kdc_check_flags; kdc_check_flags;
kdc_validate_token;
krb5_kdc_windc_init; krb5_kdc_windc_init;
krb5_kdc_get_config; krb5_kdc_get_config;
krb5_kdc_pkinit_config; krb5_kdc_pkinit_config;

View File

@@ -287,7 +287,8 @@ kx509(struct kx509_options *opt, int argc, char **argv)
ret = krb5_kx509_ctx_set_key(context, req, ret = krb5_kx509_ctx_set_key(context, req,
opt->private_key_string); opt->private_key_string);
if (ret) if (ret)
krb5_err(context, 1, ret, "could not setup kx509 request options"); krb5_err(context, 1, ret,
"could not set up kx509 request options");
ret = krb5_kx509_ext(context, req, cc, opt->out_string, ccout); ret = krb5_kx509_ext(context, req, cc, opt->out_string, ccout);
if (ret) if (ret)

View File

@@ -216,7 +216,12 @@ AUTHDATA-TYPE ::= INTEGER {
-- N.B. these assignments have not been confirmed yet. -- N.B. these assignments have not been confirmed yet.
-- --
-- DO NOT USE in production yet! -- DO NOT USE in production yet!
KRB5-AUTHDATA-ON-BEHALF-OF(580) -- UTF8String princ name KRB5-AUTHDATA-ON-BEHALF-OF(580), -- UTF8String princ name
KRB5-AUTHDATA-BEARER-TOKEN-JWT(581), -- JWT token
KRB5-AUTHDATA-BEARER-TOKEN-SAML(582), -- SAML token
KRB5-AUTHDATA-BEARER-TOKEN-OIDC(583), -- OIDC token
KRB5-AUTHDATA-CSR-AUTHORIZED(584) -- Proxy has authorized client
-- to requested exts in CSR
} }
-- checksumtypes -- checksumtypes

View File

@@ -7,7 +7,8 @@
KX509 DEFINITIONS ::= BEGIN KX509 DEFINITIONS ::= BEGIN
IMPORTS Extensions FROM rfc2459 IMPORTS Extensions FROM rfc2459
KerberosTime, AUTHDATA-TYPE FROM krb5; KerberosTime FROM krb5
KRB5PrincipalName FROM pkinit;
KX509-ERROR-CODE ::= INTEGER { KX509-ERROR-CODE ::= INTEGER {
KX509-STATUS-GOOD(0), KX509-STATUS-GOOD(0),
@@ -61,12 +62,6 @@ KX509-ERROR-CODE ::= INTEGER {
Kx509CSRPlus ::= [APPLICATION 35] SEQUENCE { Kx509CSRPlus ::= [APPLICATION 35] SEQUENCE {
-- PKCS#10, DER-encoded CSR, with or without meaningful attributes -- PKCS#10, DER-encoded CSR, with or without meaningful attributes
csr OCTET STRING, csr OCTET STRING,
-- The AP-REQ's Authenticator may contain authz-data of interest here
-- for carrying confidential payloads. E.g., a bearer token for a user
-- to impersonate. This sequence tells the server what authz-data
-- elements there might be, effectively making them critical even if
-- they are in AD-IF-RELEVANT containers.
authz-datas SEQUENCE OF AUTHDATA-TYPE,
-- Desired certificate Extensions such as KeyUsage, ExtKeyUsage, or -- Desired certificate Extensions such as KeyUsage, ExtKeyUsage, or
-- subjectAlternativeName (SAN) -- subjectAlternativeName (SAN)
exts Extensions OPTIONAL, exts Extensions OPTIONAL,

View File

@@ -753,79 +753,106 @@ Specifies the digests the kdc will reply to. The default is
.Li ntlm-v2 . .Li ntlm-v2 .
.It Li enable-kx509 = Va boolean .It Li enable-kx509 = Va boolean
Enables kx509 service. Enables kx509 service.
.It Li kx509_ca = Va file .Pp
Specifies the PEM credentials for the kx509 certification authority. The kx509 service is configurable for a number of cases:
.Bl -tag -width "" -offset indent
.It Li default certificates for user or service principals,
.It Li non-default certificate requests including subject alternative names (SAN) and extended key usage (EKU) certificate extensions, for either client, server, or mixed usage.
.El
.Pp
Distinct configurations are supported for all of these cases as
shown below:
.Bd -literal -offset indent
[kdc]
enable-kx509 = yes | no
require_csr = yes | no
require_initial_kca_tickets = yes | no
realm = {
<REALM> = {
kx509 = {
<label> = {
<param> = <value>
}
hostbased_service = {
<service> = {
<param> = <value>
}
}
}
}
}
.Ed
where
.Va label
is one of:
.Bl -tag -width "xxx" -offset indent
.It Li user
for default certificates for user principals,
.It Li root_user
for default certificates for root user principals,
.It Li admin_user
for default certificates for admin user principals,
.It Li hostbased_service
for default certificates for host-based service principals, in which case the
service name is used as shown above,
.It Li client
for non-default client certificates,
.It Li server
for non-default server certificates,
.It Li mixed
for non-default client and server certificates.
.El
and where the parameters are as follows:
.Bl -tag -width "xxx" -offset indent
.It Li ca = Va file
Specifies the PEM credentials for the kx509 certification
authority. If not specified for any specific use-case, then that
use-case will be disabled.
.It Li require_initial_kca_tickets = Va boolean .It Li require_initial_kca_tickets = Va boolean
Specified whether to require that tickets for the Specified whether to require that tickets for the
.Li kca_service .Li kca_service
service principal be INITIAL. service principal be INITIAL.
This may be set on a per-realm basis as well as globally. This may be set on a per-realm basis as well as globally.
Defaults to true for the global setting. Defaults to true for the global setting.
.It Li kx509_include_pkinit_san = Va boolean .It Li include_pkinit_san = Va boolean
If true then the kx509 client principal's name and realm will be If true then the kx509 client principal's name and realm will be
included in an included in an
.Li id-pkinit-san .Li id-pkinit-san
subject alternative name certificate extension. subject alternative name certificate extension.
This can be set on a per-realm basis as well as globally. This can be set on a per-realm basis as well as globally.
Defaults to true for the global setting. Defaults to true for the global setting.
.It Li kx509_include_email_san = Va boolean .It Li email_domain = Va domain
If true then the kx509 client user principal's name and realm will be If set then the kx509 client user principal's name at the given
included in an domain will be included in an
.Li rfc822Name .Li rfc822Name
subject alternative name certificate extension, with the downcased subject alternative name certificate extension.
realm as the domainname.
This can be set on a per-realm basis as well as globally. This can be set on a per-realm basis as well as globally.
Defaults to false for the global setting. Defaults to false for the global setting.
.It Li kx509_include_dnsname_san = Va boolean .It Li include_dnsname_san = Va boolean
If true then the kx509 host-based or domain-based client principal's If true then a kx509 host-based or domain-based client
hostname will be included in an principal's hostname will be included in an
.Li dNSName .Li dNSName
subject alternative name certificate extension, with the subject alternative name certificate extension, with the
downcased realm as the domainname. This can be set on a downcased realm as the domainname. This can be set on a
per-realm basis as well as per-realm basis as well as
globally. Defaults to false for the global setting. globally. Defaults to false for the global setting.
.It Li kx509_template = Va file .It Li ekus = Va OID
Specifies the PEM file with a template for the certificates to be List of OIDs to include as EKUs.
issued to kx509 clients whose principal names have one component .It Li subject_name = Va DN
(i.e., are user principals). A template is a certificate with Specifies a subject name that should either be empty or contain
variables to be interpolated in the subjectName. The following variable interpolation as described below for
variables can be interpolated in the subject name using .Va template_cert .
${variable} syntax: The subject may be the empty string, causing the issued
.Bl -tag -width "xxx" -offset indent certificates' subject names to be empty.
.It principal-name .It Li template_cert = Va store
The full name of the kx509 client principal. Specifies the hx509 store (e.g.,
.It principal-name-without-realm .Va PEM-FILE:path )
The full name of the kx509 client principal, excluding the realm name. with a template
.It principal-name-realm for the certificates to be issued to kx509 clients whose
The name of the client principal's realm. principal names have one component (i.e., are user principals).
.El A template is a certificate with variables to be interpolated in
.It Li kx509_templates = { the subjectName. The following variables can be interpolated in
.Bl -tag -width "xxx" -offset indent the subject name using
.It Li two_component_user = {
.Bl -tag -width "xxx" -offset indent
.It Va first-component-of-principal-name = Va file
.It ...
.It Li }
.El
.It Li hostbased = {
.Bl -tag -width "xxx" -offset indent
.It Va service = Va file
.It ...
.It Li }
.El
.It Li domainbased = {
.Bl -tag -width "xxx" -offset indent
.It Va service = Va file
.It ...
.It Li }
.El
.It Li }
.El
Specifies the PEM files with templates for the certificates to be
issued to clients with principal names with two or three name
components. This is useful for issuing server certificates to
host-based principals. The following variables can be
interpolated in the subject name using
.Va ${variable} .Va ${variable}
syntax: syntax:
.Bl -tag -width "xxx" -offset indent .Bl -tag -width "xxx" -offset indent
@@ -846,6 +873,12 @@ The name of the service.
.It principal-host-name .It principal-host-name
The name of the host. The name of the host.
.El .El
.Pp
If a template and subject name are not specified and no default
SANs are configured, then no certificate will be issued.
Otherwise if a template and subject name are not specified, then
subject of the certificate will be empty.
.El
.It Li enable_derived_keys = Va boolean .It Li enable_derived_keys = Va boolean
Enable the use of derived key namespaces. Enable the use of derived key namespaces.
When enabled, principals of the form When enabled, principals of the form
@@ -870,13 +903,6 @@ The maximim number of dots in a name matched via
derived key namespaces. derived key namespaces.
.El .El
.Pp .Pp
The
.Li kx509 ,
.Li kx509_template ,
.Li kx509_include_pkinit_san ,
and
.Li require_initial_kca_tickets
parameters may be set on a per-realm basis as well.
.It Li [kadmin] .It Li [kadmin]
.Bl -tag -width "xxx" -offset indent .Bl -tag -width "xxx" -offset indent
.It Li password_lifetime = Va time .It Li password_lifetime = Va time

View File

@@ -358,37 +358,6 @@ krb5_kx509_ctx_add_san_registeredID(krb5_context context,
return ret; return ret;
} }
/**
* Adds authorization data to a kx509 request context.
*
* @param context The Kerberos library context
* @param ctx The kx509 request context
* @param ad_type The authorization data type
* @param ad_data The authorization data
*
* @return A krb5 error code.
*/
krb5_error_code
krb5_kx509_ctx_add_auth_data(krb5_context context,
krb5_kx509_req_ctx kx509_ctx,
krb5int32 ad_type,
krb5_data *ad_data)
{
AUTHDATA_TYPE *tmp;
Kx509CSRPlus *p = &kx509_ctx->csr_plus;
tmp = realloc(p->authz_datas.val,
sizeof(p->authz_datas.val[0]) * (p->authz_datas.len + 1));
if (tmp == NULL)
return krb5_enomem(context);
p->authz_datas.val = tmp;
p->authz_datas.val[p->authz_datas.len++] = ad_type;
return krb5_auth_con_add_AuthorizationDataIfRelevant(context,
kx509_ctx->ac,
ad_type, ad_data);
}
static krb5_error_code static krb5_error_code
load_priv_key(krb5_context context, load_priv_key(krb5_context context,
krb5_kx509_req_ctx kx509_ctx, krb5_kx509_req_ctx kx509_ctx,
@@ -833,7 +802,7 @@ mk_kx509_req(krb5_context context,
* that already unless there's no start_realm cc config, in which case * that already unless there's no start_realm cc config, in which case
* we'll use the ccache's default client principal's realm. * we'll use the ccache's default client principal's realm.
*/ */
hostname = krb5_config_get_string(context, NULL, "realm", hostname = krb5_config_get_string(context, NULL, "realms",
kx509_ctx->realm, "kx509_hostname", kx509_ctx->realm, "kx509_hostname",
NULL); NULL);
if (hostname == NULL) if (hostname == NULL)

View File

@@ -439,7 +439,6 @@ EXPORTS
krb5_kt_start_seq_get krb5_kt_start_seq_get
krb5_kuserok krb5_kuserok
krb5_kx509 krb5_kx509
krb5_kx509_ctx_add_auth_data
krb5_kx509_ctx_add_eku krb5_kx509_ctx_add_eku
krb5_kx509_ctx_add_san_dns_name krb5_kx509_ctx_add_san_dns_name
krb5_kx509_ctx_add_san_ms_upn krb5_kx509_ctx_add_san_ms_upn

View File

@@ -432,7 +432,6 @@ HEIMDAL_KRB5_2.0 {
krb5_kt_start_seq_get; krb5_kt_start_seq_get;
krb5_kuserok; krb5_kuserok;
krb5_kx509; krb5_kx509;
krb5_kx509_ctx_add_auth_data;
krb5_kx509_ctx_add_eku; krb5_kx509_ctx_add_eku;
krb5_kx509_ctx_add_san_dns_name; krb5_kx509_ctx_add_san_dns_name;
krb5_kx509_ctx_add_san_ms_upn; krb5_kx509_ctx_add_san_ms_upn;

View File

@@ -15,6 +15,7 @@ NO_AFS="@NO_AFS@"
# most commands in heimdal as variables # most commands in heimdal as variables
# regular apps # regular apps
bx509d="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/bx509d"
hxtool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/hx509/hxtool" hxtool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/hx509/hxtool"
iprop_log="${TESTS_ENVIRONMENT} ${top_builddir}/lib/kadm5/iprop-log" iprop_log="${TESTS_ENVIRONMENT} ${top_builddir}/lib/kadm5/iprop-log"
ipropd_master="${TESTS_ENVIRONMENT} ${top_builddir}/lib/kadm5/ipropd-master" ipropd_master="${TESTS_ENVIRONMENT} ${top_builddir}/lib/kadm5/ipropd-master"
@@ -23,6 +24,9 @@ kadmin="${TESTS_ENVIRONMENT} ${top_builddir}/kadmin/kadmin"
kadmind="${TESTS_ENVIRONMENT} ${top_builddir}/kadmin/kadmind" kadmind="${TESTS_ENVIRONMENT} ${top_builddir}/kadmin/kadmind"
kdc="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/kdc" kdc="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/kdc"
kdc_tester="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/kdc-tester" kdc_tester="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/kdc-tester"
test_csr_authorizer="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/test_csr_authorizer"
test_kdc_ca="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/test_kdc_ca"
test_token_validator="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/test_token_validator"
kdestroy="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kdestroy" kdestroy="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kdestroy"
kdigest="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kdigest" kdigest="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kdigest"
kgetcred="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kgetcred" kgetcred="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kgetcred"
@@ -35,6 +39,7 @@ kswitch="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools kswitch"
kx509="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools kx509" kx509="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools kx509"
ktutil="${TESTS_ENVIRONMENT} ${top_builddir}/admin/ktutil" ktutil="${TESTS_ENVIRONMENT} ${top_builddir}/admin/ktutil"
gsstool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gsstool" gsstool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gsstool"
gsstoken="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gss-token"
# regression test tools # regression test tools
test_ap_req="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_ap-req" test_ap_req="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_ap-req"
@@ -43,7 +48,9 @@ test_gic="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_gic"
test_renew="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_renew" test_renew="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_renew"
test_ntlm="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/test_ntlm" test_ntlm="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/test_ntlm"
test_context="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/test_context" test_context="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/test_context"
rkbase64="${TESTS_ENVIRONMENT} ${top_builddir}/lib/roken/rkbase64"
rkpty="${TESTS_ENVIRONMENT} ${top_builddir}/lib/roken/rkpty" rkpty="${TESTS_ENVIRONMENT} ${top_builddir}/lib/roken/rkpty"
rkvis="${TESTS_ENVIRONMENT} ${top_builddir}/lib/roken/rkvis"
test_set_kvno0="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_set_kvno0" test_set_kvno0="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_set_kvno0"
test_alname="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_alname" test_alname="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_alname"
test_kuserok="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_kuserok" test_kuserok="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_kuserok"

View File

@@ -11,6 +11,7 @@ noinst_DATA = \
krb5-hdb-mitdb.conf \ krb5-hdb-mitdb.conf \
krb5-weak.conf \ krb5-weak.conf \
krb5-pkinit.conf \ krb5-pkinit.conf \
krb5-bx509.conf \
krb5-pkinit-win.conf \ krb5-pkinit-win.conf \
krb5-slave2.conf \ krb5-slave2.conf \
krb5-slave.conf krb5-slave.conf
@@ -32,6 +33,7 @@ SCRIPT_TESTS = \
check-keys \ check-keys \
check-kpasswdd \ check-kpasswdd \
check-pkinit \ check-pkinit \
check-bx509 \
check-iprop \ check-iprop \
check-referral \ check-referral \
check-tester \ check-tester \
@@ -141,6 +143,11 @@ check-pkinit: check-pkinit.in Makefile krb5-pkinit.conf
$(chmod) +x check-pkinit.tmp && \ $(chmod) +x check-pkinit.tmp && \
mv check-pkinit.tmp check-pkinit mv check-pkinit.tmp check-pkinit
check-bx509: check-bx509.in Makefile krb5-bx509.conf
$(do_subst) < $(srcdir)/check-bx509.in > check-bx509.tmp && \
$(chmod) +x check-bx509.tmp && \
mv check-bx509.tmp check-bx509
check-iprop: check-iprop.in Makefile krb5.conf krb5-slave.conf krb5-slave2.conf check-iprop: check-iprop.in Makefile krb5.conf krb5-slave.conf krb5-slave2.conf
$(do_subst) < $(srcdir)/check-iprop.in > check-iprop.tmp && \ $(do_subst) < $(srcdir)/check-iprop.in > check-iprop.tmp && \
$(chmod) +x check-iprop.tmp && \ $(chmod) +x check-iprop.tmp && \
@@ -226,6 +233,10 @@ krb5-pkinit.conf: krb5-pkinit.conf.in Makefile
$(do_subst) -e 's,[@]w2k[@],no,g' < $(srcdir)/krb5-pkinit.conf.in > krb5-pkinit.conf.tmp && \ $(do_subst) -e 's,[@]w2k[@],no,g' < $(srcdir)/krb5-pkinit.conf.in > krb5-pkinit.conf.tmp && \
mv krb5-pkinit.conf.tmp krb5-pkinit.conf mv krb5-pkinit.conf.tmp krb5-pkinit.conf
krb5-bx509.conf: krb5-bx509.conf.in Makefile
$(do_subst) -e 's,[@]w2k[@],no,g' < $(srcdir)/krb5-bx509.conf.in > krb5-bx509.conf.tmp && \
mv krb5-bx509.conf.tmp krb5-bx509.conf
krb5-pkinit-win.conf: krb5-pkinit.conf.in Makefile krb5-pkinit-win.conf: krb5-pkinit.conf.in Makefile
$(do_subst) -e 's,[@]w2k[@],yes,g' < $(srcdir)/krb5-pkinit.conf.in > krb5-pkinit-win.conf.tmp && \ $(do_subst) -e 's,[@]w2k[@],yes,g' < $(srcdir)/krb5-pkinit.conf.in > krb5-pkinit-win.conf.tmp && \
mv krb5-pkinit-win.conf.tmp krb5-pkinit-win.conf mv krb5-pkinit-win.conf.tmp krb5-pkinit-win.conf
@@ -260,6 +271,7 @@ CLEANFILES= \
krb5-hdb-mitdb.conf \ krb5-hdb-mitdb.conf \
krb5-pkinit-win.conf \ krb5-pkinit-win.conf \
krb5-pkinit.conf \ krb5-pkinit.conf \
krb5-bx509.conf \
krb5-slave2.conf \ krb5-slave2.conf \
krb5-slave.conf \ krb5-slave.conf \
krb5-weak.conf \ krb5-weak.conf \
@@ -303,6 +315,7 @@ EXTRA_DIST = \
check-keys.in \ check-keys.in \
check-kpasswdd.in \ check-kpasswdd.in \
check-pkinit.in \ check-pkinit.in \
check-bx509.in \
check-referral.in \ check-referral.in \
check-tester.in \ check-tester.in \
check-uu.in \ check-uu.in \
@@ -317,6 +330,7 @@ EXTRA_DIST = \
kdc-tester3.json \ kdc-tester3.json \
kdc-tester4.json.in \ kdc-tester4.json.in \
krb5-pkinit.conf.in \ krb5-pkinit.conf.in \
krb5-bx509.conf.in \
krb5.conf.in \ krb5.conf.in \
krb5-authz.conf.in \ krb5-authz.conf.in \
krb5-authz2.conf.in \ krb5-authz2.conf.in \

407
tests/kdc/check-bx509.in Normal file
View File

@@ -0,0 +1,407 @@
#!/bin/sh
#
# Copyright (c) 2019 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.
top_builddir="@top_builddir@"
env_setup="@env_setup@"
objdir="@objdir@"
testfailed="echo test failed; cat messages.log; exit 1"
. ${env_setup}
# If there is no useful db support compiled in, disable test
${have_db} || exit 77
R=TEST.H5L.SE
DCs="DC=test,DC=h5l,DC=se"
H=datan.test.h5l.se
port=@port@
#kadmin="${kadmin} -l -r $R"
bx509="${bx509} --reverse-proxied -p $port"
server=datan.test.h5l.se
cache="FILE:${objdir}/cache.krb5"
keyfile="${hx509_data}/key.der"
keyfile2="${hx509_data}/key2.der"
keytab=FILE:${objdir}/kt
kt=${objdir}/kt
kinit="${kinit} -c $cache ${afs_no_afslog}"
klist="${klist} --hidden -v -c $cache"
kgetcred="${kgetcred} -c $cache"
kdestroy="${kdestroy} -c $cache ${afs_no_unlog}"
kx509="${kx509} -c $cache"
KRB5_CONFIG="${objdir}/krb5-bx509.conf"
export KRB5_CONFIG
rsa=yes
pkinit=no
if ${hxtool} info | grep 'rsa: hx509 null RSA' > /dev/null ; then
rsa=no
fi
if ${hxtool} info | grep 'rand: not available' > /dev/null ; then
rsa=no
fi
if ${kinit} --help 2>&1 | grep "CA certificates" > /dev/null; then
pkinit=yes
fi
# If we doesn't support pkinit and have RSA, give up
if test "$pkinit" != yes -o "$rsa" != yes ; then
exit 77
fi
rm -f current-db*
rm -f out-*
rm -f mkey.file*
rm -f *.pem *.crt *.der
rm -rf simple_csr_authz
mkdir -p simple_csr_authz
> messages.log
# We'll avoid using a KDC. We only need one for Negotiate tokens, and we'll
# use ktutil and kimpersonate to make it possible to create and accept those
# without a KDC.
# csr_grant ext-type value princ
csr_grant() {
mkdir -p "${objdir}/simple_csr_authz/${3}"
touch "${objdir}/simple_csr_authz/${3}/${1}-${2}"
}
csr_revoke() {
rm -rf "${objdir}/simple_csr_authz"
mkdir -p "${objdir}/simple_csr_authz"
}
# get_cert "" curl-opts
# get_cert "&qparams" curl-opts
get_cert() {
url="http://${server}:${port}/bx509?csr=$csr${1}"
shift
curl -g --connect-to ${server}:${port}:localhost:${port} \
-H "Authorization: Negotiate $token" \
"$@" "$url"
}
rm -f $kt
$ktutil -k $keytab add -r -V 1 -e aes128-cts-hmac-sha1-96 \
-p HTTP/datan.test.h5l.se@TEST.H5L.SE ||
{ echo "failed to setup kimpersonate credentials"; exit 2; }
$ktutil -k $keytab list ||
{ echo "failed to setup kimpersonate credentials"; exit 2; }
$kimpersonate --ccache=$cache -k $keytab -R -t aes128-cts-hmac-sha1-96 \
-c foo@TEST.H5L.SE -s HTTP/datan.test.h5l.se@TEST.H5L.SE ||
{ echo "failed to setup kimpersonate credentials"; exit 2; }
$klist ||
{ echo "failed to setup kimpersonate credentials"; exit 2; }
echo "Setting up certificates"
# We need:
#
# - a CA certificate for issuing client certificates
# - a CA certificate for issuing server certificates
# - a CA certificate for issuing mixed certificates
# - a certificate for bx509 itself (well, not in reverse proxy mode, but we'll
# make one anyways)
# Make the realm's user cert issuer CA certificate.
#
# NOTE WELL: We need all three KeyUsage values listed below!
# We also need this to be of type "pkinit-kdc",
# which means we'll get an appropriate EKU OID as
# well.
$hxtool ca --issue-ca --self-signed --type=pkinit-kdc \
--ku=digitalSignature --ku=keyCertSign --ku=cRLSign \
--pk-init-principal=krbtgt/${R}@${R} \
--generate-key=rsa --key-bits=1024 \
--subject="OU=Users,CN=KDC,${DCs}" \
--certificate=PEM-FILE:"${objdir}/user-issuer.pem" ||
{ echo "failed to setup CA certificate"; exit 2; }
# We'll use the user cert issuer as the PKINIT anchor, allowing bx509-issued
# certificates to be used for PKINIT. Though we won't be testing PKINIT here
# -- we test kx509->PKINIT in check-pkinit.
cp ${objdir}/user-issuer.pem ${objdir}/pkinit-anchor.pem
# Put the cert alone in the trust anchors file
#ex "${objdir}/pkinit-anchor.pem" <<"EOF"
#/-----BEGIN CERTIFICATE-----
#1,.-1 d
#wq
#EOF
$hxtool ca --issue-ca --self-signed \
--ku=digitalSignature --ku=keyCertSign --ku=cRLSign \
--generate-key=rsa --key-bits=1024 \
--subject="OU=Servers,CN=KDC,${DCs}" \
--certificate=PEM-FILE:"${objdir}/server-issuer.pem" ||
{ echo "failed to setup CA certificate"; exit 2; }
$hxtool ca --issue-ca --self-signed \
--ku=digitalSignature --ku=keyCertSign --ku=cRLSign \
--generate-key=rsa --key-bits=1024 \
--subject="OU=Users,CN=KDC,${DCs}" \
--certificate=PEM-FILE:"${objdir}/mixed-issuer.pem" ||
{ echo "failed to setup CA certificate"; exit 2; }
$hxtool ca --issue-ca --type=https-negotiate-server \
--ca-certificate=PEM-FILE:"${objdir}/server-issuer.pem" \
--ku=digitalSignature --pk-init-principal=HTTP/${H}@${R}\
--generate-key=rsa --key-bits=1024 --subject="" \
--certificate=PEM-FILE:"${objdir}/bx509.pem" ||
{ echo "failed to setup CA certificate"; exit 2; }
# XXX Before starting bx509d let us use kdc test programs to check that:
#
# - the negotiate token validator plugin works
# - the simple CSR authorizer plugin works
# - the KDC CA tester program works
echo "Check gss-token and Negotiate token validator plugin"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$H | tr A B)
$test_token_validator -a datan.test.h5l.se Negotiate "$token" &&
{ echo "Negotiate token validator accepted invalid token"; exit 2; }
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$H)
$test_token_validator -a datan.test.h5l.se Negotiate "$token" ||
{ echo "Negotiate token validator failed to validate valid token"; exit 2; }
echo "Making a plain CSR"
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--key=FILE:"${objdir}/k.der" "${objdir}/req" ||
{ echo "Failed to make a CSR"; exit 2; }
rm -f trivial.pem server.pem email.pem
echo "Testing plain user cert issuance KDC CA"
$test_kdc_ca -a bx509 -A foo@TEST.H5L.SE PKCS10:${objdir}/req \
PEM-FILE:${objdir}/trivial.pem ||
{ echo "Trivial offline CA test failed"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/trivial.pem ||
{ echo "Trivial offline CA test failed"; exit 2; }
echo "Testing other cert issuance KDC CA"
csr_revoke
# https server cert
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--key=FILE:"${objdir}/k.der" \
--eku=id_pkix_kp_serverAuth \
--dnsname=foo.test.h5l.se "${objdir}/req" ||
{ echo "Failed to make a CSR with a dNSName SAN request"; exit 2; }
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
PEM-FILE:${objdir}/server.pem &&
{ echo "Trivial offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant dnsname foo.test.h5l.se foo@TEST.H5L.SE
csr_grant eku 1.3.6.1.5.5.7.3.1 foo@TEST.H5L.SE
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
PEM-FILE:${objdir}/server.pem ||
{ echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/server.pem ||
{ echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
# email cert
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--key=FILE:"${objdir}/k.der" \
--eku=id_pkix_kp_clientAuth \
--email=foo@test.h5l.se "${objdir}/req" ||
{ echo "Failed to make a CSR with an rfc822Name SAN request"; exit 2; }
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
PEM-FILE:${objdir}/email.pem &&
{ echo "Trivial offline CA test failed: unauthorized issuance (dNSName)"; exit 2; }
csr_grant email foo@test.h5l.se foo@TEST.H5L.SE
csr_grant eku 1.3.6.1.5.5.7.3.2 foo@TEST.H5L.SE
$test_kdc_ca -a bx509 foo@TEST.H5L.SE PKCS10:${objdir}/req \
PEM-FILE:${objdir}/email.pem ||
{ echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
$hxtool print --content PEM-FILE:${objdir}/email.pem ||
{ echo "Offline CA test failed for explicitly authorized dNSName"; exit 2; }
if ! which curl; then
echo "curl is not available -- not testing bx509d"
exit 0
fi
echo "Starting bx509d"
${bx509d} --reverse-proxied -H $H --cert=${objdir}/bx509.pem -t -p $port --daemon ||
{ echo "bx509 failed to start"; exit 2; }
bx509pid=`getpid bx509d`
trap "kill -9 ${bx509pid}; echo signal killing bx509d; cat ca.crt kdc.crt pkinit.crt ;exit 1;" EXIT
ec=0
rm -f trivial.pem server.pem email.pem
echo "Making a plain CSR"
csr_revoke
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
--key=FILE:"${objdir}/k.der" "${objdir}/req" ||
{ echo "Failed to make a CSR"; exit 2; }
csr=$($rkbase64 -- ${objdir}/req | $rkvis -h --stdin)
# XXX Add autoconf check for curl?
# Create a barebones bx509 HTTP/1.1 client test program?
echo "Fetching a trivial user certificate"
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$H)
if (set -vx; get_cert '' -sf -o "${objdir}/trivial.pem"); then
$hxtool print --content "FILE:${objdir}/trivial.pem"
if $hxtool acert --end-entity \
--expr="%{certificate.subject} == \"CN=foo,$DCs\"" \
-P "foo@TEST.H5L.SE" "FILE:${objdir}/trivial.pem"; then
echo 'Successfully obtained a trivial client certificate!'
else
echo 'FAIL: Obtained a trivial client certificate w/o expected PKINIT SAN)'
exit 1
fi
else
echo 'Failed to get a certificate!'
exit 1
fi
echo "Checking that authorization is enforced"
csr_revoke
get_cert '&rfc822Name=foo@bar.example' -vvv -o "${objdir}/bad1.pem"
if (set -vx; get_cert '&rfc822Name=foo@bar.example' -sf -o "${objdir}/trivial.pem"); then
$hxtool print --content "FILE:${objdir}/bad1.pem"
echo 'Obtained a client certificate for a non-granted name!'
exit 1
else
echo 'Correctly failed to get a client certificate for a non-granted name'
fi
if (set -vx; get_cert "&dNSName=$server" -sf -o "${objdir}/bad2.pem"); then
$hxtool print --content "FILE:${objdir}/bad2.pem"
echo 'Obtained a server certificate for a non-granted name!'
exit 1
else
echo 'Correctly failed to get a server certificate for a non-granted name'
fi
echo "Fetching a server certificate with one dNSName SAN"
csr_grant dnsname $server foo@TEST.H5L.SE
if (set -vx; get_cert "&dNSName=$server" -sf -o "${objdir}/server.pem"); then
$hxtool print --content "FILE:${objdir}/server.pem"
if (set -vx; $hxtool acert --expr="%{certificate.subject} == \"\"" \
--end-entity -P foo@TEST.H5L.SE \
"FILE:${objdir}/server.pem"); then
echo 'Got a broken server certificate (has PKINIT SAN)'
exit 1
elif $hxtool acert --end-entity -D $server "FILE:${objdir}/server.pem"; then
echo 'Successfully obtained a server certificate!'
else
echo 'Got a broken server certificate'
exit 1
fi
else
echo 'Failed to get a server certificate!'
exit 1
fi
echo "Fetching a server certificate with two dNSName SANs"
csr_grant dnsname "second-$server" foo@TEST.H5L.SE
if (set -vx;
get_cert "&dNSName=${server}&dNSName=second-$server" -sf \
-o "${objdir}/server2.pem"); then
$hxtool print --content "FILE:${objdir}/server2.pem"
if $hxtool acert --expr="%{certificate.subject} == \"\"" \
--end-entity -P foo@TEST.H5L.SE \
"FILE:${objdir}/server2.pem"; then
echo 'Got a broken server certificate (has PKINIT SAN)'
exit 1
elif $hxtool acert --end-entity -D "$server" \
-D "second-$server" \
"FILE:${objdir}/server2.pem"; then
echo 'Successfully obtained a server certificate with two dNSName SANs!'
else
echo 'Got a broken server certificate (wanted two dNSName SANs)'
exit 1
fi
else
echo 'Failed to get a server certificate with two dNSName SANs!'
exit 1
fi
echo "Fetching an email certificate"
csr_grant email foo@bar.example foo@TEST.H5L.SE
if (set -vx; get_cert "&rfc822Name=foo@bar.example" -sf -o "${objdir}/email.pem"); then
$hxtool print --content "FILE:${objdir}/email.pem"
if $hxtool acert --end-entity -P "foo@TEST.H5L.SE" "FILE:${objdir}/email.pem"; then
echo 'Got a broken email certificate (has PKINIT SAN)'
exit 1
elif $hxtool acert --expr="%{certificate.subject} == \"\"" \
--end-entity -M foo@bar.example \
"FILE:${objdir}/email.pem"; then
echo 'Successfully obtained a email certificate!'
else
echo 'Got a broken email certificate'
exit 1
fi
else
echo 'Failed to get an email certificate!'
exit 1
fi
if false not yet; then
# XXX Need to start a KDC to test this.
echo "Fetching a Negotiate token"
if (set -vx;
curl -o negotiate-token -Lgsf --connect-to ${server}:${port}:localhost:${port} \
-H "Authorization: Negotiate $token" \
"http://${server}:${port}/bnegotiate?target=HTTP%40${server}"); then
# bx509 sends us a token w/o a newline for now; we add one because
# gss-token expects it.
[[ -s negotiate-token ]] && echo >> negotiate-token
if [[ -s negotiate-token ]] && KRB5_KTNAME="${etc}/keytab.user" $gsstoken -Nr < negotiate-token; then
echo 'Successfully obtained a Negotiate token!'
else
echo 'Failed to get a Negotiate token!'
exit 1
fi
else
echo 'Failed to get a Negotiate token!'
exit 1
fi
fi
echo "killing bx509d (${bx509pid})"
sh ${leaks_kill} bx509 $bx509pid || ec=1
trap "" EXIT
exit $ec

View File

@@ -98,6 +98,7 @@ ${kadmin} \
${kadmin} add -p foo --use-defaults foo@${R} || exit 1 ${kadmin} add -p foo --use-defaults foo@${R} || exit 1
${kadmin} add -p bar --use-defaults bar@${R} || exit 1 ${kadmin} add -p bar --use-defaults bar@${R} || exit 1
${kadmin} add -p baz --use-defaults baz@${R} || exit 1 ${kadmin} add -p baz --use-defaults baz@${R} || exit 1
${kadmin} add -p foo --use-defaults host/server.test.h5l.se@${R} || exit 1
${kadmin} modify --alias=baz2@test.h5l.se baz@${R} || exit 1 ${kadmin} modify --alias=baz2@test.h5l.se baz@${R} || exit 1
${kadmin} modify --pkinit-acl="CN=baz,DC=test,DC=h5l,DC=se" baz@${R} || exit 1 ${kadmin} modify --pkinit-acl="CN=baz,DC=test,DC=h5l,DC=se" baz@${R} || exit 1
@@ -306,7 +307,7 @@ fi
echo "killing kdc (${kdcpid})" echo "killing kdc (${kdcpid})"
sh ${leaks_kill} kdc $kdcpid || exit 1 sh ${leaks_kill} kdc $kdcpid || ec=1
trap "" EXIT trap "" EXIT

View File

@@ -0,0 +1,129 @@
[libdefaults]
default_realm = TEST.H5L.SE
no-addresses = TRUE
allow_weak_crypto = TRUE
rdns = false
fcache_strict_checking = false
name_canon_rules = as-is:realm=TEST.H5L.SE
[appdefaults]
pkinit_anchors = FILE:@objdir@/pkinit-anchor.pem
pkinit_pool = FILE:@objdir@/pkinit-anchor.pem
[realms]
TEST.H5L.SE = {
kdc = localhost:@port@
pkinit_win2k = @w2k@
}
[kdc]
num-kdc-processes = 1
strict-nametypes = true
enable-pkinit = true
pkinit_identity = PEM-FILE:@objdir@/user-issuer.pem
pkinit_anchors = PEM-FILE:@objdir@/pkinit-anchor.pem
pkinit_mappings_file = @srcdir@/pki-mapping
# Locate kdc plugins for testing
plugin_dir = @objdir@/../../kdc/.libs
# Configure kdc plugins for testing
simple_csr_authorizer_directory = @objdir@/simple_csr_authz
enable-pkinit = true
pkinit_identity = PEM-FILE:@objdir@/user-issuer.pem
pkinit_anchors = PEM-FILE:@objdir@/pkinit-anchor.pem
pkinit_mappings_file = @srcdir@/pki-mapping
database = {
dbname = @objdir@/current-db
realm = TEST.H5L.SE
mkey_file = @objdir@/mkey.file
log_file = @objdir@/log.current-db.log
}
negotiate_token_validator = {
keytab = FILE:@objdir@/kt
}
realms = {
TEST.H5L.SE = {
kx509 = {
user = {
include_pkinit_san = true
subject_name = CN=${principal-name-without-realm},DC=test,DC=h5l,DC=se
ekus = 1.3.6.1.5.5.7.3.2
ca = PEM-FILE:@objdir@/user-issuer.pem
}
hostbased_service = {
HTTP = {
include_dnsname_san = true
ekus = 1.3.6.1.5.5.7.3.1
ca = PEM-FILE:@objdir@/server-issuer.pem
}
}
client = {
ekus = 1.3.6.1.5.5.7.3.2
ca = PEM-FILE:@objdir@/user-issuer.pem
}
server = {
ekus = 1.3.6.1.5.5.7.3.1
ca = PEM-FILE:@objdir@/server-issuer.pem
}
mixed = {
ekus = 1.3.6.1.5.5.7.3.1
ekus = 1.3.6.1.5.5.7.3.2
ca = PEM-FILE:@objdir@/mixed-issuer.pem
}
}
}
}
[hdb]
db-dir = @objdir@
[bx509]
realms = {
TEST.H5L.SE = {
# Default (no cert exts requested)
user = {
# Use an issuer for user certs:
ca = PEM-FILE:@objdir@/user-issuer.pem
subject_name = CN=${principal-name-without-realm},DC=test,DC=h5l,DC=se
ekus = 1.3.6.1.5.5.7.3.2
include_pkinit_san = true
}
hostbased_service = {
# Only for HTTP services
HTTP = {
# Use an issuer for server certs:
ca = PEM-FILE:@objdir@/server-issuer.pem
include_dnsname_san = true
# Don't bother with a template
}
}
# Non-default certs (extensions requested)
#
# Use no templates -- get empty subject names,
# use SANs.
#
# Use appropriate issuers.
client = {
ca = PEM-FILE:@objdir@/user-issuer.pem
}
server = {
ca = PEM-FILE:@objdir@/server-issuer.pem
}
mixed = {
ca = PEM-FILE:@objdir@/mixed-issuer.pem
}
}
}
[logging]
kdc = 0-/FILE:@objdir@/messages.log
bx509d = 0-/FILE:@objdir@/messages.log
default = 0-/FILE:@objdir@/messages.log
[domain_realm]
. = TEST.H5L.SE

View File

@@ -20,13 +20,12 @@
pkinit_anchors = FILE:@objdir@/ca.crt pkinit_anchors = FILE:@objdir@/ca.crt
pkinit_mappings_file = @srcdir@/pki-mapping pkinit_mappings_file = @srcdir@/pki-mapping
enable-kx509 = true plugin_dir = @objdir@/../../kdc/.libs
kx509_include_email_san = true
kx509_include_pkinit_san = true simple_csr_authorizer_directory = @objdir@/simple_csr_authz
kx509_include_dnsname_san = true
enable_kx509 = true
require_initial_kca_tickets = false require_initial_kca_tickets = false
kx509_template = FILE:@objdir@/kx509-template.crt
kx509_ca = FILE:@objdir@/ca.crt,@srcdir@/../../lib/hx509/data/key.der
database = { database = {
dbname = @objdir@/current-db dbname = @objdir@/current-db
@@ -35,6 +34,38 @@
log_file = @objdir@/log.current-db.log log_file = @objdir@/log.current-db.log
} }
realms = {
TEST.H5L.SE = {
negotiate_token_validator = {
keytab = HDBGET:@objdir@/current-db
}
kx509 = {
user = {
include_pkinit_san = true
subject_name = CN=${principal-name-without-realm},DC=TEST,DC=H5L,DC=SE
ekus = 1.3.6.1.5.5.7.3.2
ca = FILE:@objdir@/ca.crt,@srcdir@/../../lib/hx509/data/key.der
template_cert = FILE:@objdir@/kx509-template.crt
}
hostbased_service = {
HTTP = {
include_dnsname_san = true
ekus = 1.3.6.1.5.5.7.3.1
ca = FILE:@objdir@/ca.crt,@srcdir@/../../lib/hx509/data/key.der
}
}
client = {
ca = FILE:@objdir@/ca.crt,@srcdir@/../../lib/hx509/data/key.der
}
server = {
ekus = 1.3.6.1.5.5.7.3.1
ca = FILE:@objdir@/ca.crt,@srcdir@/../../lib/hx509/data/key.der
}
}
}
}
[hdb] [hdb]
db-dir = @objdir@ db-dir = @objdir@

View File

@@ -1,6 +1,7 @@
#include <string.h> #include <string.h>
#include <krb5.h> #include <krb5.h>
#include <hdb.h> #include <hdb.h>
#include <hx509.h>
#include <kdc.h> #include <kdc.h>
#include <windc_plugin.h> #include <windc_plugin.h>