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
	 Nicolas Williams
					Nicolas Williams