Add bx509d
This commit is contained in:
@@ -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 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 libmicrohttpd-dev; 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 sudo cpanm install JSON; fi
|
||||
|
2
README
2
README
@@ -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>.
|
||||
|
||||
|
@@ -4,7 +4,11 @@
|
||||
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).
|
||||
|
||||
|
48
configure.ac
48
configure.ac
@@ -182,6 +182,54 @@ AM_CONDITIONAL([HAVE_CAPNG], [test "$with_capng" != "no"])
|
||||
AC_SUBST([CAPNG_CFLAGS])
|
||||
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
|
||||
rk_TEST_PACKAGE(sqlite3,
|
||||
[#include <sqlite3.h>
|
||||
|
@@ -22,19 +22,20 @@
|
||||
@ifinfo
|
||||
@dircategory Security
|
||||
@direntry
|
||||
* Heimdal: (heimdal). The Kerberos 5 distribution from KTH
|
||||
* Heimdal: (heimdal). The Kerberos 5 and PKIX distribution from KTH
|
||||
@end direntry
|
||||
@end ifinfo
|
||||
|
||||
@c title page
|
||||
@titlepage
|
||||
@title Heimdal
|
||||
@subtitle Kerberos 5 from KTH
|
||||
@subtitle Kerberos 5 and PKIX from KTH
|
||||
@subtitle Edition @value{EDITION}, for version @value{VERSION}
|
||||
@subtitle 2008
|
||||
@author Johan Danielsson
|
||||
@author Love Hörnquist Åstrand
|
||||
@author Assar Westerlund
|
||||
@author et al
|
||||
|
||||
@end titlepage
|
||||
|
||||
@@ -64,6 +65,8 @@ This manual for version @value{VERSION} of Heimdal.
|
||||
@menu
|
||||
* Introduction::
|
||||
* What is Kerberos?::
|
||||
* What is PKIX?::
|
||||
* What is a Certification Authority (CA)?::
|
||||
* Building and Installing::
|
||||
* Setting up a realm::
|
||||
* Applications::
|
||||
|
@@ -48,7 +48,7 @@
|
||||
|
||||
@page
|
||||
@copyrightstart
|
||||
Copyright (c) 1994-2008 Kungliga Tekniska Högskolan
|
||||
Copyright (c) 1994-2019 Kungliga Tekniska Högskolan
|
||||
(Royal Institute of Technology, Stockholm, Sweden).
|
||||
All rights reserved.
|
||||
|
||||
@@ -187,7 +187,7 @@ This manual is for version @value{VERSION} of hx509.
|
||||
|
||||
@menu
|
||||
* Introduction::
|
||||
* What is X.509 ?::
|
||||
* What are X.509 and PKIX ?::
|
||||
* Setting up a CA::
|
||||
* CMS signing and encryption::
|
||||
* Certificate matching::
|
||||
@@ -230,13 +230,20 @@ Software PKCS 11 module
|
||||
@end detailmenu
|
||||
@end menu
|
||||
|
||||
@node Introduction, What is X.509 ?, Top, Top
|
||||
@node Introduction, What are X.509 and PKIX ?, Top, Top
|
||||
@chapter Introduction
|
||||
|
||||
The goals of a PKI infrastructure (as defined in
|
||||
<a href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280</a>) is to meet
|
||||
@emph{the needs of deterministic, automated identification, authentication, access control, and authorization}.
|
||||
A Public Key Infrastructure (PKI) is an authentication mechanism based on
|
||||
entities having certified cryptographic public keys and corresponding private
|
||||
(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
|
||||
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
|
||||
@item RA
|
||||
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
|
||||
An optional system to which a CA delegates the publication of certificate revocation lists.
|
||||
@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
|
||||
@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)
|
||||
and basic certificate processing tasks, path construction, path
|
||||
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
|
||||
files.
|
||||
|
||||
@node What is X.509 ?, Setting up a CA, Introduction, Top
|
||||
@chapter What is X.509, PKIX, PKCS7 and CMS ?
|
||||
hx509 consists of a library (libhx509) and a command-line utility (hxtool), as
|
||||
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
|
||||
the IETF's PKIX Certificate and CRL Profile of the X.509 v3 certificate
|
||||
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
|
||||
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
|
||||
|
||||
Do not let information overload scare you off! If you are simply testing
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@c $Id$
|
||||
|
||||
@node What is Kerberos?, Building and Installing, Introduction, Top
|
||||
@node What is Kerberos?, What is PKIX?, Introduction, Top
|
||||
@chapter What is Kerberos?
|
||||
|
||||
@quotation
|
||||
@@ -162,3 +162,32 @@ from 1988.
|
||||
|
||||
These documents can be found on our web-page at
|
||||
@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.
|
||||
|
@@ -4,13 +4,17 @@ include $(top_srcdir)/Makefile.am.common
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -23,6 +27,21 @@ kstash_SOURCES = kstash.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.c
|
||||
|
||||
@@ -35,8 +54,29 @@ kdc_tester_SOURCES = \
|
||||
config.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 = \
|
||||
default_config.c \
|
||||
ca.c \
|
||||
set_dbinfo.c \
|
||||
digest.c \
|
||||
fast.c \
|
||||
@@ -48,6 +88,8 @@ libkdc_la_SOURCES = \
|
||||
log.c \
|
||||
misc.c \
|
||||
kx509.c \
|
||||
token_validator.c \
|
||||
csr_authorizer.c \
|
||||
process.c \
|
||||
windc.c \
|
||||
rx.h
|
||||
@@ -57,6 +99,9 @@ KDC_PROTOS = $(srcdir)/kdc-protos.h $(srcdir)/kdc-private.h
|
||||
ALL_OBJECTS = $(kdc_OBJECTS)
|
||||
ALL_OBJECTS += $(kdc_replay_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 += $(string2key_OBJECTS)
|
||||
ALL_OBJECTS += $(kstash_OBJECTS)
|
||||
@@ -135,13 +180,16 @@ digest_service_LDADD = \
|
||||
$(LDADD) $(LIB_pidfile)
|
||||
kdc_replay_LDADD = libkdc.la $(LDADD) $(LIB_pidfile)
|
||||
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
|
||||
|
||||
noinst_HEADERS = $(srcdir)/kdc-private.h
|
||||
|
||||
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
|
||||
|
||||
|
@@ -92,17 +92,21 @@ $(LIBEXECDIR)\kdc.exe: \
|
||||
|
||||
LIBKDC_OBJS=\
|
||||
$(OBJ)\default_config.obj \
|
||||
$(OBJ)\set_dbinfo.obj \
|
||||
$(OBJ)\digest.obj \
|
||||
$(OBJ)\fast.obj \
|
||||
$(OBJ)\kerberos5.obj \
|
||||
$(OBJ)\krb5tgs.obj \
|
||||
$(OBJ)\pkinit.obj \
|
||||
$(OBJ)\pkinit-ec.obj \
|
||||
$(OBJ)\log.obj \
|
||||
$(OBJ)\misc.obj \
|
||||
$(OBJ)\kx509.obj \
|
||||
$(OBJ)\process.obj \
|
||||
$(OBJ)\ca.obj \
|
||||
$(OBJ)\kx509.obj \
|
||||
$(OBJ)\set_dbinfo.obj \
|
||||
$(OBJ)\digest.obj \
|
||||
$(OBJ)\fast.obj \
|
||||
$(OBJ)\kerberos5.obj \
|
||||
$(OBJ)\krb5tgs.obj \
|
||||
$(OBJ)\pkinit.obj \
|
||||
$(OBJ)\pkinit-ec.obj \
|
||||
$(OBJ)\log.obj \
|
||||
$(OBJ)\misc.obj \
|
||||
$(OBJ)\kx509.obj \
|
||||
$(OBJ)\token_validator.obj \
|
||||
$(OBJ)\csr_authorizer.obj \
|
||||
$(OBJ)\process.obj \
|
||||
$(OBJ)\windc.obj
|
||||
|
||||
LIBKDC_LIBS=\
|
||||
@@ -126,9 +130,10 @@ clean::
|
||||
|
||||
libkdc_la_SOURCES = \
|
||||
default_config.c \
|
||||
ca.c \
|
||||
set_dbinfo.c \
|
||||
digest.c \
|
||||
fast.c \
|
||||
fast.c \
|
||||
kdc_locl.h \
|
||||
kerberos5.c \
|
||||
krb5tgs.c \
|
||||
@@ -137,6 +142,8 @@ libkdc_la_SOURCES = \
|
||||
log.c \
|
||||
misc.c \
|
||||
kx509.c \
|
||||
token_validator.c \
|
||||
csr_authorizer.c \
|
||||
process.c \
|
||||
windc.c \
|
||||
rx.h
|
||||
|
1722
kdc/bx509d.c
Normal file
1722
kdc/bx509d.c
Normal file
File diff suppressed because it is too large
Load Diff
727
kdc/ca.c
Normal file
727
kdc/ca.c
Normal 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
330
kdc/cjwt_token_validator.c
Normal 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, ¤t->data, ¤t->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
91
kdc/csr_authorizer.c
Normal 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;
|
||||
}
|
76
kdc/csr_authorizer_plugin.h
Normal file
76
kdc/csr_authorizer_plugin.h
Normal 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 */
|
@@ -37,17 +37,53 @@
|
||||
#include <getarg.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_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;
|
||||
|
||||
heim_base_once_f(&load_kdc_plugins, context, load_kdc_plugins_once);
|
||||
|
||||
c = calloc(1, sizeof(*c));
|
||||
if (c == NULL) {
|
||||
krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
c->app = "kdc";
|
||||
c->num_kdc_processes = -1;
|
||||
c->require_preauth = TRUE;
|
||||
c->kdc_warn_pwexpire = 0;
|
||||
@@ -111,17 +147,7 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
|
||||
c->enable_kx509 =
|
||||
krb5_config_get_bool_default(context, NULL,
|
||||
FALSE,
|
||||
"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);
|
||||
}
|
||||
"kdc", "enable_kx509", NULL);
|
||||
#endif
|
||||
|
||||
c->tgt_use_strongest_session_key =
|
||||
|
443
kdc/ipc_csr_authorizer.c
Normal file
443
kdc/ipc_csr_authorizer.c
Normal 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;
|
||||
}
|
@@ -93,13 +93,12 @@ typedef struct krb5_kdc_configuration {
|
||||
size_t max_datagram_reply_length;
|
||||
|
||||
int enable_kx509;
|
||||
const char *kx509_template;
|
||||
const char *kx509_ca;
|
||||
|
||||
krb5_boolean enable_derived_keys;
|
||||
int derived_keys_ndots;
|
||||
int derived_keys_maxdots;
|
||||
|
||||
const char *app;
|
||||
} krb5_kdc_configuration;
|
||||
|
||||
struct krb5_kdc_service {
|
||||
|
836
kdc/kx509.c
836
kdc/kx509.c
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,12 @@
|
||||
EXPORTS
|
||||
kdc_authorize_csr
|
||||
kdc_get_instance
|
||||
kdc_issue_certificate
|
||||
kdc_log
|
||||
kdc_log_msg
|
||||
kdc_log_msg_va
|
||||
kdc_openlog
|
||||
kdc_validate_token
|
||||
krb5_kdc_windc_init
|
||||
krb5_kdc_get_config
|
||||
krb5_kdc_pkinit_config
|
||||
|
322
kdc/negotiate_token_validator.c
Normal file
322
kdc/negotiate_token_validator.c
Normal 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
337
kdc/simple_csr_authorizer.c
Normal 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
79
kdc/test_csr_authorizer.c
Normal 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
146
kdc/test_kdc_ca.c
Normal 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;
|
||||
}
|
86
kdc/test_token_validator.c
Normal file
86
kdc/test_token_validator.c
Normal 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
122
kdc/token_validator.c
Normal 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;
|
||||
}
|
85
kdc/token_validator_plugin.h
Normal file
85
kdc/token_validator_plugin.h
Normal 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 */
|
@@ -2,12 +2,15 @@
|
||||
|
||||
HEIMDAL_KDC_1.0 {
|
||||
global:
|
||||
kdc_authorize_csr;
|
||||
kdc_get_instance;
|
||||
kdc_issue_certificate;
|
||||
kdc_log;
|
||||
kdc_log_msg;
|
||||
kdc_log_msg_va;
|
||||
kdc_openlog;
|
||||
kdc_check_flags;
|
||||
kdc_validate_token;
|
||||
krb5_kdc_windc_init;
|
||||
krb5_kdc_get_config;
|
||||
krb5_kdc_pkinit_config;
|
||||
|
@@ -287,7 +287,8 @@ kx509(struct kx509_options *opt, int argc, char **argv)
|
||||
ret = krb5_kx509_ctx_set_key(context, req,
|
||||
opt->private_key_string);
|
||||
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);
|
||||
if (ret)
|
||||
|
@@ -216,7 +216,12 @@ AUTHDATA-TYPE ::= INTEGER {
|
||||
-- N.B. these assignments have not been confirmed 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
|
||||
|
@@ -7,7 +7,8 @@
|
||||
|
||||
KX509 DEFINITIONS ::= BEGIN
|
||||
IMPORTS Extensions FROM rfc2459
|
||||
KerberosTime, AUTHDATA-TYPE FROM krb5;
|
||||
KerberosTime FROM krb5
|
||||
KRB5PrincipalName FROM pkinit;
|
||||
|
||||
KX509-ERROR-CODE ::= INTEGER {
|
||||
KX509-STATUS-GOOD(0),
|
||||
@@ -61,12 +62,6 @@ KX509-ERROR-CODE ::= INTEGER {
|
||||
Kx509CSRPlus ::= [APPLICATION 35] SEQUENCE {
|
||||
-- PKCS#10, DER-encoded CSR, with or without meaningful attributes
|
||||
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
|
||||
-- subjectAlternativeName (SAN)
|
||||
exts Extensions OPTIONAL,
|
||||
|
@@ -753,79 +753,106 @@ Specifies the digests the kdc will reply to. The default is
|
||||
.Li ntlm-v2 .
|
||||
.It Li enable-kx509 = Va boolean
|
||||
Enables kx509 service.
|
||||
.It Li kx509_ca = Va file
|
||||
Specifies the PEM credentials for the kx509 certification authority.
|
||||
.Pp
|
||||
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
|
||||
Specified whether to require that tickets for the
|
||||
.Li kca_service
|
||||
service principal be INITIAL.
|
||||
This may be set on a per-realm basis as well as globally.
|
||||
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
|
||||
included in an
|
||||
.Li id-pkinit-san
|
||||
subject alternative name certificate extension.
|
||||
This can be set on a per-realm basis as well as globally.
|
||||
Defaults to true for the global setting.
|
||||
.It Li kx509_include_email_san = Va boolean
|
||||
If true then the kx509 client user principal's name and realm will be
|
||||
included in an
|
||||
.It Li email_domain = Va domain
|
||||
If set then the kx509 client user principal's name at the given
|
||||
domain will be included in an
|
||||
.Li rfc822Name
|
||||
subject alternative name certificate extension, with the downcased
|
||||
realm as the domainname.
|
||||
subject alternative name certificate extension.
|
||||
This can be set on a per-realm basis as well as globally.
|
||||
Defaults to false for the global setting.
|
||||
.It Li kx509_include_dnsname_san = Va boolean
|
||||
If true then the kx509 host-based or domain-based client principal's
|
||||
hostname will be included in an
|
||||
.It Li include_dnsname_san = Va boolean
|
||||
If true then a kx509 host-based or domain-based client
|
||||
principal's hostname will be included in an
|
||||
.Li dNSName
|
||||
subject alternative name certificate extension, with the
|
||||
downcased realm as the domainname. This can be set on a
|
||||
per-realm basis as well as
|
||||
globally. Defaults to false for the global setting.
|
||||
.It Li kx509_template = Va file
|
||||
Specifies the PEM file with a template for the certificates to be
|
||||
issued to kx509 clients whose principal names have one component
|
||||
(i.e., are user principals). A template is a certificate with
|
||||
variables to be interpolated in the subjectName. The following
|
||||
variables can be interpolated in the subject name using
|
||||
${variable} syntax:
|
||||
.Bl -tag -width "xxx" -offset indent
|
||||
.It principal-name
|
||||
The full name of the kx509 client principal.
|
||||
.It principal-name-without-realm
|
||||
The full name of the kx509 client principal, excluding the realm name.
|
||||
.It principal-name-realm
|
||||
The name of the client principal's realm.
|
||||
.El
|
||||
.It Li kx509_templates = {
|
||||
.Bl -tag -width "xxx" -offset indent
|
||||
.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
|
||||
.It Li ekus = Va OID
|
||||
List of OIDs to include as EKUs.
|
||||
.It Li subject_name = Va DN
|
||||
Specifies a subject name that should either be empty or contain
|
||||
variable interpolation as described below for
|
||||
.Va template_cert .
|
||||
The subject may be the empty string, causing the issued
|
||||
certificates' subject names to be empty.
|
||||
.It Li template_cert = Va store
|
||||
Specifies the hx509 store (e.g.,
|
||||
.Va PEM-FILE:path )
|
||||
with a template
|
||||
for the certificates to be issued to kx509 clients whose
|
||||
principal names have one component (i.e., are user principals).
|
||||
A template is a certificate with variables to be interpolated in
|
||||
the subjectName. The following variables can be interpolated in
|
||||
the subject name using
|
||||
.Va ${variable}
|
||||
syntax:
|
||||
.Bl -tag -width "xxx" -offset indent
|
||||
@@ -846,6 +873,12 @@ The name of the service.
|
||||
.It principal-host-name
|
||||
The name of the host.
|
||||
.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
|
||||
Enable the use of derived key namespaces.
|
||||
When enabled, principals of the form
|
||||
@@ -870,13 +903,6 @@ The maximim number of dots in a name matched via
|
||||
derived key namespaces.
|
||||
.El
|
||||
.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]
|
||||
.Bl -tag -width "xxx" -offset indent
|
||||
.It Li password_lifetime = Va time
|
||||
|
@@ -358,37 +358,6 @@ krb5_kx509_ctx_add_san_registeredID(krb5_context context,
|
||||
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
|
||||
load_priv_key(krb5_context context,
|
||||
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
|
||||
* 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",
|
||||
NULL);
|
||||
if (hostname == NULL)
|
||||
|
@@ -439,7 +439,6 @@ EXPORTS
|
||||
krb5_kt_start_seq_get
|
||||
krb5_kuserok
|
||||
krb5_kx509
|
||||
krb5_kx509_ctx_add_auth_data
|
||||
krb5_kx509_ctx_add_eku
|
||||
krb5_kx509_ctx_add_san_dns_name
|
||||
krb5_kx509_ctx_add_san_ms_upn
|
||||
|
@@ -432,7 +432,6 @@ HEIMDAL_KRB5_2.0 {
|
||||
krb5_kt_start_seq_get;
|
||||
krb5_kuserok;
|
||||
krb5_kx509;
|
||||
krb5_kx509_ctx_add_auth_data;
|
||||
krb5_kx509_ctx_add_eku;
|
||||
krb5_kx509_ctx_add_san_dns_name;
|
||||
krb5_kx509_ctx_add_san_ms_upn;
|
||||
|
@@ -15,6 +15,7 @@ NO_AFS="@NO_AFS@"
|
||||
# most commands in heimdal as variables
|
||||
|
||||
# regular apps
|
||||
bx509d="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/bx509d"
|
||||
hxtool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/hx509/hxtool"
|
||||
iprop_log="${TESTS_ENVIRONMENT} ${top_builddir}/lib/kadm5/iprop-log"
|
||||
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"
|
||||
kdc="${TESTS_ENVIRONMENT} ${top_builddir}/kdc/kdc"
|
||||
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"
|
||||
kdigest="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/kdigest"
|
||||
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"
|
||||
ktutil="${TESTS_ENVIRONMENT} ${top_builddir}/admin/ktutil"
|
||||
gsstool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gsstool"
|
||||
gsstoken="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gss-token"
|
||||
|
||||
# regression test tools
|
||||
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_ntlm="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/test_ntlm"
|
||||
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"
|
||||
rkvis="${TESTS_ENVIRONMENT} ${top_builddir}/lib/roken/rkvis"
|
||||
test_set_kvno0="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_set_kvno0"
|
||||
test_alname="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_alname"
|
||||
test_kuserok="${TESTS_ENVIRONMENT} ${top_builddir}/lib/krb5/test_kuserok"
|
||||
|
@@ -11,6 +11,7 @@ noinst_DATA = \
|
||||
krb5-hdb-mitdb.conf \
|
||||
krb5-weak.conf \
|
||||
krb5-pkinit.conf \
|
||||
krb5-bx509.conf \
|
||||
krb5-pkinit-win.conf \
|
||||
krb5-slave2.conf \
|
||||
krb5-slave.conf
|
||||
@@ -32,6 +33,7 @@ SCRIPT_TESTS = \
|
||||
check-keys \
|
||||
check-kpasswdd \
|
||||
check-pkinit \
|
||||
check-bx509 \
|
||||
check-iprop \
|
||||
check-referral \
|
||||
check-tester \
|
||||
@@ -141,6 +143,11 @@ check-pkinit: check-pkinit.in Makefile krb5-pkinit.conf
|
||||
$(chmod) +x check-pkinit.tmp && \
|
||||
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
|
||||
$(do_subst) < $(srcdir)/check-iprop.in > 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 && \
|
||||
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
|
||||
$(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
|
||||
@@ -260,6 +271,7 @@ CLEANFILES= \
|
||||
krb5-hdb-mitdb.conf \
|
||||
krb5-pkinit-win.conf \
|
||||
krb5-pkinit.conf \
|
||||
krb5-bx509.conf \
|
||||
krb5-slave2.conf \
|
||||
krb5-slave.conf \
|
||||
krb5-weak.conf \
|
||||
@@ -303,6 +315,7 @@ EXTRA_DIST = \
|
||||
check-keys.in \
|
||||
check-kpasswdd.in \
|
||||
check-pkinit.in \
|
||||
check-bx509.in \
|
||||
check-referral.in \
|
||||
check-tester.in \
|
||||
check-uu.in \
|
||||
@@ -317,6 +330,7 @@ EXTRA_DIST = \
|
||||
kdc-tester3.json \
|
||||
kdc-tester4.json.in \
|
||||
krb5-pkinit.conf.in \
|
||||
krb5-bx509.conf.in \
|
||||
krb5.conf.in \
|
||||
krb5-authz.conf.in \
|
||||
krb5-authz2.conf.in \
|
||||
|
407
tests/kdc/check-bx509.in
Normal file
407
tests/kdc/check-bx509.in
Normal 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
|
@@ -98,6 +98,7 @@ ${kadmin} \
|
||||
${kadmin} add -p foo --use-defaults foo@${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 foo --use-defaults host/server.test.h5l.se@${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
|
||||
|
||||
@@ -306,7 +307,7 @@ fi
|
||||
|
||||
|
||||
echo "killing kdc (${kdcpid})"
|
||||
sh ${leaks_kill} kdc $kdcpid || exit 1
|
||||
sh ${leaks_kill} kdc $kdcpid || ec=1
|
||||
|
||||
trap "" EXIT
|
||||
|
||||
|
129
tests/kdc/krb5-bx509.conf.in
Normal file
129
tests/kdc/krb5-bx509.conf.in
Normal 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
|
@@ -20,13 +20,12 @@
|
||||
pkinit_anchors = FILE:@objdir@/ca.crt
|
||||
pkinit_mappings_file = @srcdir@/pki-mapping
|
||||
|
||||
enable-kx509 = true
|
||||
kx509_include_email_san = true
|
||||
kx509_include_pkinit_san = true
|
||||
kx509_include_dnsname_san = true
|
||||
plugin_dir = @objdir@/../../kdc/.libs
|
||||
|
||||
simple_csr_authorizer_directory = @objdir@/simple_csr_authz
|
||||
|
||||
enable_kx509 = true
|
||||
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 = {
|
||||
dbname = @objdir@/current-db
|
||||
@@ -35,6 +34,38 @@
|
||||
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]
|
||||
db-dir = @objdir@
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#include <string.h>
|
||||
#include <krb5.h>
|
||||
#include <hdb.h>
|
||||
#include <hx509.h>
|
||||
#include <kdc.h>
|
||||
#include <windc_plugin.h>
|
||||
|
||||
|
Reference in New Issue
Block a user