gsstool: Add GSS-based kinit-like acquire_cred cmd
This has most of the features needed to act as a kinit that uses GSS APIs, specifically gss_acquire_cred_from() and gss_store_cred_into2(). It's missing some functionality, such as being able to drive prompts from AS responses (if we add minor status codes for representing KDC pre-auth proposals, then we do drive prompts, but we would have to encode a lot of mechanism-specific knowledge into gsstool). The point of this commit is to explore: - GSS functionality for kinit-like actions - credential store key/value pairs supported by the mechanisms - document the credential store key/value pairs (in gsstool.1) that might lead to further enhancements. But gsstool acquire-cred is quite functional at this point!
This commit is contained in:
@@ -51,11 +51,86 @@ command = {
|
||||
argument = "mechanism"
|
||||
}
|
||||
}
|
||||
command = {
|
||||
name = "acquire-cred"
|
||||
help = "Acquire a credential"
|
||||
option = {
|
||||
long = "initiator"
|
||||
type = "flag"
|
||||
}
|
||||
option = {
|
||||
long = "acceptor"
|
||||
type = "flag"
|
||||
}
|
||||
option = {
|
||||
long = "mech"
|
||||
type = "strings"
|
||||
argument = "mechanism"
|
||||
}
|
||||
option = {
|
||||
long = "name-type"
|
||||
type = "string"
|
||||
argument = "name-type for desired name"
|
||||
}
|
||||
option = {
|
||||
long = "name"
|
||||
type = "string"
|
||||
argument = "desired name"
|
||||
}
|
||||
option = {
|
||||
long = "time-req"
|
||||
type = "integer"
|
||||
argument = "desired credential lifetime"
|
||||
}
|
||||
option = {
|
||||
long = "from"
|
||||
type = "strings"
|
||||
argument = "key=value pair"
|
||||
}
|
||||
option = {
|
||||
long = "from-prompt"
|
||||
type = "strings"
|
||||
argument = "key=prompt pair"
|
||||
}
|
||||
option = {
|
||||
long = "from-file"
|
||||
type = "strings"
|
||||
argument = "key=filename pair"
|
||||
}
|
||||
option = {
|
||||
long = "into"
|
||||
type = "strings"
|
||||
argument = "key=value pair"
|
||||
}
|
||||
option = {
|
||||
long = "into-prompt"
|
||||
type = "strings"
|
||||
argument = "key=prompt pair"
|
||||
}
|
||||
option = {
|
||||
long = "into-file"
|
||||
type = "strings"
|
||||
argument = "key=filename pair"
|
||||
}
|
||||
option = {
|
||||
long = "verbose"
|
||||
short = "v"
|
||||
type = "flag"
|
||||
help = "Verbose"
|
||||
}
|
||||
option = {
|
||||
long = "shell"
|
||||
short = "s"
|
||||
type = "flag"
|
||||
help = "Verbose"
|
||||
}
|
||||
argument = "[cmd args]"
|
||||
}
|
||||
command = {
|
||||
name = "help"
|
||||
name = "?"
|
||||
argument = "[command]"
|
||||
min_args = "0"
|
||||
max_args = "1"
|
||||
help = "Help! I need somebody."
|
||||
help = "gsstool mechanisms | attributes | acquire-cred"
|
||||
}
|
||||
|
||||
362
lib/gssapi/gsstool.1
Normal file
362
lib/gssapi/gsstool.1
Normal file
@@ -0,0 +1,362 @@
|
||||
.\" Copyright (c) 2022 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.
|
||||
.\"
|
||||
.\" $Id$
|
||||
.\"
|
||||
.Dd October 9, 2022
|
||||
.Dt GSSTOOL 1
|
||||
.Os HEIMDAL
|
||||
.Sh NAME
|
||||
.Nm gsstool
|
||||
.Nd command-line interface to GSS-API
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar command
|
||||
.Op Ar args
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a program for using the GSS-API from a shell.
|
||||
.Pp
|
||||
.Ar command
|
||||
can be one of the following:
|
||||
.Bl -tag -width srvconvert
|
||||
.It Nm mechanisms
|
||||
Lists available GSS-API mechanisms.
|
||||
.It Nm attributes Oo Fl Fl all Oc
|
||||
Lists all the mechanism attributes known to
|
||||
.Nm .
|
||||
.It Nm attributes Oo Fl Fl mech=MECH Oc
|
||||
Lists the attributes of the given
|
||||
.Ar MECH .
|
||||
.It Nm acquire-cred \
|
||||
Oo Fl Fl verbose Oc \
|
||||
Oo Fl s \*(Ba Xo Fl Fl shell Xc Oc \
|
||||
Oo Fl Fl initiator Oc \
|
||||
Oo Fl Fl acceptor Oc \
|
||||
Oo Fl Fl mech= Ns Ar MECH Oc \
|
||||
Oo Fl Fl name-type= Ns Ar NAME-TYPE Oc \
|
||||
Oo Fl Fl name= Ns Ar NAME Oc \
|
||||
Oo Fl Fl time-req= Ns Ar SECONDS Oc \
|
||||
Oo Fl Fl from= Ns Ar KEY=VALUE Oc \
|
||||
Oo Fl Fl from-file=echo-on: Ns Ar KEY=FILE Oc \
|
||||
Oo Fl Fl from-prompt= Ns Ar KEY=PROMPT Oc \
|
||||
Oo Fl Fl from-prompt= Ns Ar KEY=PROMPT Oc \
|
||||
Oo Fl Fl into= Ns Ar KEY=VALUE Oc \
|
||||
Oo Fl Fl into-file=echo-on: Ns Ar KEY=FILE Oc \
|
||||
Oo Fl Fl into-prompt=echo-on: Ns Ar KEY=PROMPT Oc \
|
||||
Oo Fl Fl into-prompt=echo-off: Ns Ar KEY=PROMPT Oc \
|
||||
Oo Ar cmd Oo arguments... Oc Oc
|
||||
.Pp
|
||||
Acquires a credential for each of the
|
||||
.Fl Fl mech= Ns Ar MECH
|
||||
mechanisms given for the given principal
|
||||
.Ar NAME ,
|
||||
or the default principal for the invoking user, as specified by
|
||||
the
|
||||
.Fl Fl from= Ns Ar KEY=VALUE
|
||||
options and stores it into the given credential store specified
|
||||
by the
|
||||
.Fl Fl into= Ns Ar KEY=VALUE
|
||||
options, if any are given.
|
||||
If no
|
||||
.Fl Fl mech= Ns Ar MECH
|
||||
is given, then the Kerberos mechanism will be used.
|
||||
If no
|
||||
.Fl Fl into= Ns Ar KEY=VALUE
|
||||
options are given, the credential acquired will not be stored
|
||||
anywhere, but the exit status code of
|
||||
.Nm
|
||||
can be used to test if a credential could be acquired.
|
||||
.Pp
|
||||
In other words,
|
||||
.Nm
|
||||
.Nm acquire-cred
|
||||
provides the same kind of functionality as
|
||||
.Xr kinit 1
|
||||
but using pure GSS-API functions rather than
|
||||
.Dq krb5
|
||||
APIs, though with some limitations, chiefly that the GSS-API
|
||||
functions used do not support interaction with the user,
|
||||
therefore the user must know a priori all the
|
||||
.Ar KEY=VALUE
|
||||
pairs needed to successfully acquire a credential.
|
||||
Because
|
||||
.Nm
|
||||
uses pure GSS-API functions, it can work with any mechanism that
|
||||
provides functionality similar to the Kerberos mechanism's
|
||||
initial credential acquisition functionality.
|
||||
.Pp
|
||||
If a
|
||||
.Ar cmd Oo arguments.. Oc
|
||||
is given, then the command will be run, and for as long as it
|
||||
runs the credentials will be kept fresh by renewing or
|
||||
re-acquiring them.
|
||||
Alternatively, if a
|
||||
.Ar cmd Oo arguments.. Oc
|
||||
is not given, and the
|
||||
.Fl Fl shell \*(Ba Xo Fl s Xc
|
||||
option is given, then environment variable settings may be
|
||||
output.
|
||||
.Pp
|
||||
Mechanisms may, and the Kerberos GSS-API mechanism does try all
|
||||
the strategies that are possible for the given
|
||||
.Fl Fl from= Ns Ar KEY=VALUE
|
||||
options, and in a reasonable order (such as: from a credentials
|
||||
cache, using a keytab, using PKINIT, or using a password).
|
||||
.Pp
|
||||
As well,
|
||||
.Nm
|
||||
.Nm acquire-cred
|
||||
can prompt for
|
||||
.Ar VALUEs
|
||||
with echo on and echo off, or even read them from files, so that
|
||||
the user experience can be quite like that of
|
||||
.Xr kinit 1 .
|
||||
.Pp
|
||||
Note that the
|
||||
.Ar KEY=VALUE
|
||||
pairs are specific to the GSS-API mechanisms.
|
||||
See
|
||||
.Sx CREDENTIAL STORE SPECIFICATION
|
||||
for more details.
|
||||
.Pp
|
||||
By default
|
||||
.Nm
|
||||
acquires initiator credentials, unless
|
||||
.Fl Fl acceptor
|
||||
is given.
|
||||
If both,
|
||||
.Fl Fl initiator
|
||||
and
|
||||
.Fl Fl acceptor
|
||||
are given, then
|
||||
.Nm
|
||||
acquires credentials for both.
|
||||
Note though that currently there is no support for storing
|
||||
acquired acceptor credentials.
|
||||
.Pp
|
||||
.Ar MECH
|
||||
values currently accepted:
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Ar all
|
||||
(try acquiring credentials for all mechanisms)
|
||||
.It Ar krb5
|
||||
.It Ar ntlm
|
||||
.It Ar spnego
|
||||
.It Ar sanon_x25519
|
||||
.It any mechanism OID, such as 1.3.6.1.5.5.2.
|
||||
.El
|
||||
.Pp
|
||||
.Ar NAME-TYPE
|
||||
values currently accepted:
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Ar anonymous
|
||||
.It Ar hostbased-service
|
||||
.It Ar machine-uid
|
||||
.It Ar string-uid
|
||||
.It Ar user
|
||||
.It any name-type OID, such as 1.3.6.1.5.5.2.
|
||||
.El
|
||||
.El
|
||||
.Sh CREDENTIAL STORE SPECIFICATION
|
||||
.Pp
|
||||
Some of the
|
||||
.Ar KEYs
|
||||
for use in the
|
||||
.Fl Fl from= Ns Ar KEY=VALUE
|
||||
options for the Kerberos GSS-API mechanism are:
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Nm appname= Ns Ar APP
|
||||
Use the given
|
||||
.Ar APP
|
||||
name for any appdefaults.
|
||||
.It Nm only=Ns Ar SOURCE
|
||||
Only uses the given
|
||||
.Ar SOURCE
|
||||
for credentials acquisition.
|
||||
Valid
|
||||
.Ar SOURCEs
|
||||
are:
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Ar cache
|
||||
Only look for cached credentials.
|
||||
.It Ar renew
|
||||
Only look for cached credentials, and renew them.
|
||||
Requires the
|
||||
.Ar renew KEY
|
||||
to be set.
|
||||
.It Ar keytab
|
||||
Only acquire credentials with a keytab.
|
||||
.It Ar pkinit
|
||||
Only acquire credentials with PKINIT.
|
||||
.It Ar password
|
||||
Only acquire credentials with a password.
|
||||
.El
|
||||
.It Nm ccache= Ns Ar CREDENTIALS-CACHE
|
||||
.It Nm renew
|
||||
Attempts to renew the credential found in the
|
||||
.Ar ccache .
|
||||
But note that the renewed credential will not be written to the
|
||||
cache.
|
||||
Use the
|
||||
.Fl Fl into= Ns Ar KEY=VALUE
|
||||
command-line options to cause fresh/renewed credentials to be
|
||||
stored.
|
||||
.It Nm fresh
|
||||
Acquire a fresh credential / do not use a cached credential
|
||||
without renewing it.
|
||||
If
|
||||
.Ar fresh
|
||||
is given without
|
||||
.Ar renew
|
||||
then no cached credential will be used.
|
||||
.It Nm initial
|
||||
Like
|
||||
.Ar fresh ,
|
||||
no cached credential will be acquired, but also no credential
|
||||
will be renewed even if
|
||||
.Ar renew
|
||||
is set.
|
||||
.It Nm service=PRINCIPAL
|
||||
Instead of getting the usual TGT for the initiator, get a TGT for
|
||||
the given
|
||||
.Ar PRINCIPAL .
|
||||
.It Nm keytab= Ns Ar KEYTAB
|
||||
.It Nm client_keytab= Ns Ar KEYTAB
|
||||
.It Nm password= Ns Ar PASSWORD
|
||||
.It Nm kdc= Ns Ar HOSTNAME
|
||||
Use the given KDC
|
||||
.Ar HOSTNAME .
|
||||
.It Nm sitename= Ns Ar SITENAME
|
||||
Use the given site name
|
||||
.Ar SITENAME
|
||||
when searching for KDCs.
|
||||
.It Nm pkinit_cert= Ns Ar CERT-STORE
|
||||
Location of PKINIT client certificate and private key.
|
||||
See
|
||||
.Xr hxtool 1 .
|
||||
.It Nm pkinit_pool= Ns Ar CERT-STORE
|
||||
Optional store of certificates used to construct the client
|
||||
certificate's chain.
|
||||
.It Nm pkinit_anchors= Ns Ar CERT-STORE
|
||||
Trust anchors for validating the KDCs' PKINIT certificates.
|
||||
.It Nm pkinit_crl= Ns Ar CRL
|
||||
CRL for KDC certificate validation.
|
||||
.It Nm pkinit_password= Ns Ar PASSWORD
|
||||
Optional password for the
|
||||
.Nm pkinit_cert= Ns Ar CERT-STORE .
|
||||
.It Nm pkinit_use_enckey
|
||||
.It Nm pkinit_no_anchors
|
||||
.It Nm pkinit_btmm
|
||||
.It Nm fast_armor_cache= Ns Ar CREDENTIALS-CACHE
|
||||
Use FAST armor, with the credentials in the given
|
||||
.Ar CREDENTIALS-CACHE
|
||||
.It Nm fast_anon_pkinit
|
||||
Use FAST armored with an armor ticket obtained via anonymous
|
||||
PKINIT.
|
||||
.It Nm optimistic_fast_anon_pkinit
|
||||
Try FAST armored with an armor ticket obtained via anonymous
|
||||
PKINIT.
|
||||
.It Nm renewable
|
||||
.It Nm forwardable
|
||||
.It Nm validate
|
||||
.It Nm request_pac
|
||||
.It Nm addressless
|
||||
.El
|
||||
.Pp
|
||||
Some of the
|
||||
.Ar KEYs
|
||||
for use in the
|
||||
.Fl Fl into= Ns Ar KEY=VALUE
|
||||
options for the Kerberos mechanism are:
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Nm unique_ccache_type=TYPE
|
||||
Create a new, unique credentials cache of the given
|
||||
.Ar TYPE .
|
||||
E.g.,
|
||||
.Dq Ar FILE .
|
||||
.It Nm ccache= Ns Ar CREDENTIALS-CACHE
|
||||
The specific credentials cache to store into.
|
||||
E.g.,
|
||||
.Dq Ar FILE:/tmp/some-ccache .
|
||||
.It Nm username=USER-NAME
|
||||
This is used for selecting the best credentials cache to use.
|
||||
.It Nm appname=APP-NAME
|
||||
This is used for resolving appdefaults.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
Test if there is an unexpired Kerberos credential:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --from=only=cache
|
||||
.Ed
|
||||
.Pp
|
||||
Test if there is an unexpired Kerberos credential for a specific
|
||||
principal in a principal-specific cache in the default cache
|
||||
collection:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --name=some-principal-here \\
|
||||
--from=only=cache
|
||||
.Ed
|
||||
.Pp
|
||||
Test if there is an unexpired Kerberos credential in a specific
|
||||
cache:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --from=only=cache \\
|
||||
--from=ccache=FILE:/tmp/some-ccache
|
||||
.Ed
|
||||
.Pp
|
||||
Test what mechanisms the user has cached credentials for:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --mech=all --from=only=cache
|
||||
.Ed
|
||||
.Pp
|
||||
Renew a credential:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --mech=krb5 --from=renew
|
||||
.Ed
|
||||
.Pp
|
||||
Acquire a fresh Kerberos credential with a password, prompting
|
||||
for it, then store it in a particular cache:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --into=ccache=FILE:/tmp/some-ccache \\
|
||||
--from-prompt=password="Password: "
|
||||
.Ed
|
||||
.Pp
|
||||
Acquire a fresh Kerberos credential with a password read from a
|
||||
file:
|
||||
.Bd -literal -offset indent
|
||||
gsstool acquire-cred --into=ccache=FILE:/tmp/some-ccache \\
|
||||
--from-file=password=/tmp/password
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr gss-token 1 ,
|
||||
.Xr hxtool 1 ,
|
||||
.Xr kinit 1 .
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <roken.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <gssapi.h>
|
||||
#include <gssapi_krb5.h>
|
||||
#include <gssapi_spnego.h>
|
||||
@@ -58,10 +59,122 @@ static void
|
||||
usage (int ret)
|
||||
{
|
||||
arg_printusage (args, sizeof(args)/sizeof(*args),
|
||||
NULL, "service@host");
|
||||
NULL, "help | mechanisms | attributes | acquire-cred");
|
||||
exit (ret);
|
||||
}
|
||||
|
||||
/* XXX Move the gss_display_status() wrappers into common code */
|
||||
static char *
|
||||
gss_fmt_errors(OM_uint32 code, int code_type, gss_OID mech, char *acc)
|
||||
{
|
||||
OM_uint32 maj, min;
|
||||
OM_uint32 more = 0;
|
||||
gss_buffer_desc buf;
|
||||
|
||||
do {
|
||||
char *tmp = NULL;
|
||||
char *s = NULL;
|
||||
|
||||
maj = gss_display_status(&min, code, code_type, mech, &more, &buf);
|
||||
switch (maj) {
|
||||
case GSS_S_COMPLETE:
|
||||
s = strndup(buf.value, buf.length);
|
||||
break;
|
||||
case GSS_S_BAD_MECH:
|
||||
s = strdup("<bad mechanism>");
|
||||
more = 0;
|
||||
break;
|
||||
case GSS_S_BAD_STATUS:
|
||||
if (asprintf(&s, "<unrecognized status code %u (%d)>", code, code_type) == -1)
|
||||
s = NULL;
|
||||
more = 0;
|
||||
break;
|
||||
default:
|
||||
errx(1, "Could not display status code %u (%d)", code, code_type);
|
||||
}
|
||||
gss_release_buffer(&min, &buf);
|
||||
|
||||
if (s == NULL)
|
||||
err(1, "Out of memory");
|
||||
if (acc == NULL) {
|
||||
acc = s;
|
||||
s = NULL;
|
||||
} else if (asprintf(&tmp, "%s; %s", acc, s) == -1 || tmp == NULL) {
|
||||
err(1, "Out of memory formatting \"%s; %s\"", acc, s);
|
||||
} else {
|
||||
free(acc);
|
||||
acc = tmp;
|
||||
}
|
||||
} while (more != 0);
|
||||
return acc;
|
||||
}
|
||||
|
||||
static void
|
||||
gss_vwarn(OM_uint32 maj,
|
||||
OM_uint32 min,
|
||||
gss_OID mech,
|
||||
const char *fmt,
|
||||
va_list ap)
|
||||
{
|
||||
char *acc = NULL;
|
||||
char *msg = NULL;
|
||||
|
||||
acc = gss_fmt_errors(maj, GSS_C_GSS_CODE, GSS_C_NO_OID, acc);
|
||||
acc = gss_fmt_errors(min, GSS_C_MECH_CODE, mech, acc);
|
||||
|
||||
if (vasprintf(&msg, fmt, ap) == -1 || msg == NULL)
|
||||
errx(1, "Out of memory formatting error message \"%s\"", fmt);
|
||||
warnx("%s: %s", msg, acc);
|
||||
}
|
||||
|
||||
static void
|
||||
gss_warn(OM_uint32 maj,
|
||||
OM_uint32 min,
|
||||
gss_OID mech,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
gss_vwarn(maj, min, mech, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void
|
||||
gss_verr(int code,
|
||||
OM_uint32 maj,
|
||||
OM_uint32 min,
|
||||
gss_OID mech,
|
||||
const char *fmt,
|
||||
va_list ap)
|
||||
{
|
||||
char *acc = NULL;
|
||||
char *msg = NULL;
|
||||
|
||||
acc = gss_fmt_errors(maj, GSS_C_GSS_CODE, GSS_C_NO_OID, acc);
|
||||
acc = gss_fmt_errors(min, GSS_C_MECH_CODE, mech, acc);
|
||||
|
||||
if (vasprintf(&msg, fmt, ap) == -1 || msg == NULL)
|
||||
errx(1, "Out of memory formatting error message \"%s\"", fmt);
|
||||
errx(code, "%s: %s", msg, acc);
|
||||
}
|
||||
|
||||
static void
|
||||
gss_err(int code,
|
||||
OM_uint32 maj,
|
||||
OM_uint32 min,
|
||||
gss_OID mech,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
gss_verr(code, maj, min, mech, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define COL_OID "OID"
|
||||
#define COL_NAME "Name"
|
||||
#define COL_DESC "Description"
|
||||
@@ -213,6 +326,529 @@ attributes(struct attributes_options *opt, int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
do_file(const char *arg,
|
||||
gss_key_value_element_desc *store,
|
||||
char **freeme,
|
||||
size_t *k)
|
||||
{
|
||||
char *key, *fn;
|
||||
void *contents;
|
||||
size_t n;
|
||||
|
||||
if ((key = strdup(arg)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
freeme[(*k)++] = key;
|
||||
|
||||
n = strcspn(key, "=");
|
||||
key[n] = '\0';
|
||||
fn = key + n + 1;
|
||||
|
||||
if ((errno = rk_undumpdata(fn, &contents, &n)))
|
||||
err(1, "Could not read file %s", fn);
|
||||
|
||||
freeme[(*k)++] = contents;
|
||||
store->key = key;
|
||||
store->value = contents;
|
||||
}
|
||||
|
||||
static sig_atomic_t intr_flag;
|
||||
|
||||
static void
|
||||
intr(int sig)
|
||||
{
|
||||
intr_flag++;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CONIO_H
|
||||
|
||||
/*
|
||||
* Windows does console slightly different then then unix case.
|
||||
*/
|
||||
|
||||
static int
|
||||
read_string(const char *preprompt, const char *prompt,
|
||||
char *buf, size_t len, int echo)
|
||||
{
|
||||
int of = 0;
|
||||
int c;
|
||||
char *p;
|
||||
void (*oldsigintr)(int);
|
||||
|
||||
printf("%s%s", preprompt, prompt);
|
||||
fflush(stdout);
|
||||
|
||||
oldsigintr = signal(SIGINT, intr);
|
||||
|
||||
p = buf;
|
||||
while(intr_flag == 0){
|
||||
c = ((echo)? _getche(): _getch());
|
||||
if(c == '\n' || c == '\r')
|
||||
break;
|
||||
if(of == 0)
|
||||
*p++ = c;
|
||||
of = (p == buf + len);
|
||||
}
|
||||
if(of)
|
||||
p--;
|
||||
*p = 0;
|
||||
|
||||
if(echo == 0){
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
signal(SIGINT, oldsigintr);
|
||||
|
||||
if(intr_flag)
|
||||
return -2;
|
||||
if(of)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* !HAVE_CONIO_H */
|
||||
|
||||
#ifndef NSIG
|
||||
#define NSIG 47
|
||||
#endif
|
||||
|
||||
static int
|
||||
read_string(const char *preprompt, const char *prompt,
|
||||
char *buf, size_t len, int echo)
|
||||
{
|
||||
struct sigaction sigs[NSIG];
|
||||
int oksigs[NSIG];
|
||||
struct sigaction sa;
|
||||
FILE *tty;
|
||||
int ret = 0;
|
||||
int of = 0;
|
||||
int i;
|
||||
int c;
|
||||
char *p;
|
||||
|
||||
struct termios t_new, t_old;
|
||||
|
||||
memset(&oksigs, 0, sizeof(oksigs));
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = intr;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
for(i = 1; i < sizeof(sigs) / sizeof(sigs[0]); i++)
|
||||
if (i != SIGALRM)
|
||||
if (sigaction(i, &sa, &sigs[i]) == 0)
|
||||
oksigs[i] = 1;
|
||||
|
||||
if((tty = fopen("/dev/tty", "r")) != NULL)
|
||||
rk_cloexec_file(tty);
|
||||
else
|
||||
tty = stdin;
|
||||
|
||||
fprintf(stderr, "%s%s", preprompt, prompt);
|
||||
fflush(stderr);
|
||||
|
||||
if(echo == 0){
|
||||
tcgetattr(fileno(tty), &t_old);
|
||||
memcpy(&t_new, &t_old, sizeof(t_new));
|
||||
t_new.c_lflag &= ~ECHO;
|
||||
tcsetattr(fileno(tty), TCSANOW, &t_new);
|
||||
}
|
||||
intr_flag = 0;
|
||||
p = buf;
|
||||
while(intr_flag == 0){
|
||||
c = getc(tty);
|
||||
if(c == EOF){
|
||||
if(!ferror(tty))
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
if(c == '\n')
|
||||
break;
|
||||
if(of == 0)
|
||||
*p++ = c;
|
||||
of = (p == buf + len);
|
||||
}
|
||||
if(of)
|
||||
p--;
|
||||
*p = 0;
|
||||
|
||||
if(echo == 0){
|
||||
fprintf(stderr, "\n");
|
||||
tcsetattr(fileno(tty), TCSANOW, &t_old);
|
||||
}
|
||||
|
||||
if(tty != stdin)
|
||||
fclose(tty);
|
||||
|
||||
for(i = 1; i < sizeof(sigs) / sizeof(sigs[0]); i++)
|
||||
if (oksigs[i])
|
||||
sigaction(i, &sigs[i], NULL);
|
||||
|
||||
if(ret)
|
||||
return -3;
|
||||
if(intr_flag)
|
||||
return -2;
|
||||
if(of)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_CONIO_H */
|
||||
|
||||
|
||||
static int
|
||||
gsstool_UI_UTIL_read_pw_string(char *buf, int length, const char *prompt)
|
||||
{
|
||||
return read_string("", prompt, buf, length, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
prompt(const char *arg,
|
||||
gss_key_value_element_desc *store,
|
||||
char **freeme,
|
||||
size_t *k)
|
||||
{
|
||||
char *key, *val, *prompt;
|
||||
char buf[1024];
|
||||
size_t n;
|
||||
int echo_on = 0;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
if (strncmp(arg, "echo-on:", sizeof("echo-on:") - 1) == 0) {
|
||||
arg += sizeof("echo-on:") - 1;
|
||||
echo_on = 1;
|
||||
} else if (strncmp(arg, "echo-off:", sizeof("echo-off:") - 1) == 0) {
|
||||
arg += sizeof("echo-off:") - 1;
|
||||
} else {
|
||||
errx(1, "Invalid prompt specification");
|
||||
}
|
||||
|
||||
if ((key = strdup(arg)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
freeme[(*k)++] = key;
|
||||
|
||||
n = strcspn(key, "=");
|
||||
prompt = key + n + 1;
|
||||
key[n] = '\0';
|
||||
|
||||
if (echo_on) {
|
||||
printf("%s", prompt);
|
||||
if (fgets(buf, sizeof(buf) - 1, stdin) == NULL)
|
||||
errx(1, "Could not read input");
|
||||
} else if (gsstool_UI_UTIL_read_pw_string(buf, sizeof(buf) - 1, prompt)) {
|
||||
memset(buf, 0, sizeof(buf));
|
||||
errx(1, "Could not read input");
|
||||
}
|
||||
if ((val = strdup(buf)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
freeme[(*k)++] = val;
|
||||
store->key = key;
|
||||
store->value = val;
|
||||
}
|
||||
|
||||
static void
|
||||
fill_in(const char *arg,
|
||||
gss_key_value_element_desc *store,
|
||||
char **freeme,
|
||||
size_t *k)
|
||||
{
|
||||
size_t n;
|
||||
char *s;
|
||||
|
||||
if ((s = strdup(arg)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
freeme[(*k)++] = s;
|
||||
|
||||
n = strcspn(s, "=");
|
||||
s[n] = '\0';
|
||||
store->key = s;
|
||||
store->value = s + n + 1;
|
||||
}
|
||||
|
||||
static void
|
||||
do_acquire(struct acquire_cred_options *opt,
|
||||
gss_name_t name,
|
||||
OM_uint32 time_req,
|
||||
gss_OID_set mechs,
|
||||
gss_cred_usage_t cred_usage,
|
||||
gss_key_value_set_t from,
|
||||
gss_key_value_set_t into,
|
||||
int argc,
|
||||
int renew,
|
||||
OM_uint32 *time_rec)
|
||||
{
|
||||
gss_buffer_set_t env = GSS_C_NO_BUFFER_SET;
|
||||
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
|
||||
gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
|
||||
OM_uint32 min, maj;
|
||||
OM_uint32 flags = GSS_C_STORE_CRED_OVERWRITE;
|
||||
|
||||
maj = gss_acquire_cred_from(&min, name, time_req, mechs, cred_usage,
|
||||
from, &cred, &actual_mechs, time_rec);
|
||||
if (maj != GSS_S_COMPLETE) {
|
||||
if (renew) {
|
||||
gss_warn(maj, min, GSS_C_NO_OID, "Could not acquire credential");
|
||||
return;
|
||||
}
|
||||
gss_err(1, maj, min, GSS_C_NO_OID, "Could not acquire credential");
|
||||
}
|
||||
|
||||
if (opt->verbose_flag) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < actual_mechs->count; i++) {
|
||||
gss_buffer_desc str;
|
||||
|
||||
maj = gss_oid_to_str(&min, &actual_mechs->elements[i], &str);
|
||||
if (maj != GSS_S_COMPLETE) {
|
||||
gss_warn(maj, min, GSS_C_NO_OID,
|
||||
"Could display mechanism OID");
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "Acquired credentials for mechanism %.*s "
|
||||
"with %us lifetime\n", (int)str.length, (char *)str.value,
|
||||
*time_rec);
|
||||
gss_release_buffer(&min, &str);
|
||||
}
|
||||
}
|
||||
gss_release_oid_set(&min, &actual_mechs);
|
||||
|
||||
if (into->count == 0 && argc == 0) {
|
||||
gss_release_cred(&min, &cred);
|
||||
warnx("Not storing acquired credentials; use --into option");
|
||||
return;
|
||||
}
|
||||
|
||||
if (argc)
|
||||
flags |= GSS_C_STORE_CRED_SET_PROCESS;
|
||||
|
||||
maj = gss_store_cred_into2(&min, cred, cred_usage, GSS_C_NO_OID,
|
||||
flags, into, NULL, NULL, &env);
|
||||
gss_release_cred(&min, &cred);
|
||||
if (maj != GSS_S_COMPLETE) {
|
||||
if (renew) {
|
||||
gss_warn(maj, min, GSS_C_NO_OID, "Could not store credential");
|
||||
return;
|
||||
}
|
||||
gss_err(1, maj, min, GSS_C_NO_OID, "Could not store credential");
|
||||
}
|
||||
if (opt->verbose_flag)
|
||||
fprintf(stderr, "Stored credentials\n");
|
||||
|
||||
if (env != GSS_C_NO_BUFFER_SET) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < env->count; i++) {
|
||||
if (argc) {
|
||||
char *envvar;
|
||||
|
||||
if (renew)
|
||||
continue;
|
||||
if ((envvar = strndup((char *)env->elements[i].value, env->elements[i].length)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
putenv(envvar);
|
||||
continue;
|
||||
}
|
||||
if (opt->verbose_flag)
|
||||
fprintf(stderr, "Environment variable: %.*s\n", (int)env->elements[i].length,
|
||||
(char *)env->elements[i].value);
|
||||
if (opt->shell_flag)
|
||||
printf("%.*s\n", (int)env->elements[i].length,
|
||||
(char *)env->elements[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct renew_ctx {
|
||||
struct acquire_cred_options *opt;
|
||||
gss_name_t name;
|
||||
OM_uint32 time_req;
|
||||
gss_OID_set mechs;
|
||||
gss_cred_usage_t cred_usage;
|
||||
gss_key_value_set_t from;
|
||||
gss_key_value_set_t into;
|
||||
};
|
||||
|
||||
static time_t
|
||||
renew_func(void *ptr)
|
||||
{
|
||||
struct renew_ctx *c = ptr;
|
||||
OM_uint32 time_rec = 0;
|
||||
|
||||
do_acquire(c->opt, c->name, c->time_req, c->mechs, c->cred_usage, c->from,
|
||||
c->into, 1, 1, &time_rec);
|
||||
|
||||
if (time_rec == 0)
|
||||
time_rec = c->time_req;
|
||||
if (time_rec > INT32_MAX)
|
||||
return INT32_MAX;
|
||||
return time_rec;
|
||||
}
|
||||
|
||||
int
|
||||
acquire_cred(struct acquire_cred_options *opt, int argc, char **argv)
|
||||
{
|
||||
gss_name_t name = GSS_C_NO_NAME;
|
||||
gss_key_value_element_desc *from = NULL;
|
||||
gss_key_value_element_desc *into = NULL;
|
||||
gss_key_value_set_desc from_store, into_store;
|
||||
gss_cred_usage_t cred_usage = 0;
|
||||
gss_OID_set mechs = GSS_C_NO_OID_SET;
|
||||
gss_OID name_type = GSS_C_NO_OID;
|
||||
OM_uint32 min, maj, time_req, time_rec;
|
||||
size_t k = 0;
|
||||
size_t i, idx;
|
||||
size_t num_from, num_into;
|
||||
char **freeme = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (opt->initiator_flag && opt->acceptor_flag)
|
||||
cred_usage = GSS_C_BOTH;
|
||||
else if (opt->acceptor_flag)
|
||||
cred_usage = GSS_C_ACCEPT;
|
||||
else
|
||||
cred_usage = GSS_C_INITIATE;
|
||||
|
||||
if (opt->time_req_integer < 0)
|
||||
time_req = GSS_C_INDEFINITE;
|
||||
else
|
||||
time_req = opt->time_req_integer;
|
||||
|
||||
if (opt->name_type_string) {
|
||||
if (strcmp(opt->name_type_string, "user") == 0)
|
||||
name_type = GSS_C_NT_USER_NAME;
|
||||
else if (strcmp(opt->name_type_string, "machine-uid") == 0)
|
||||
name_type = GSS_C_NT_MACHINE_UID_NAME;
|
||||
else if (strcmp(opt->name_type_string, "string-uid") == 0)
|
||||
name_type = GSS_C_NT_STRING_UID_NAME;
|
||||
else if (strcmp(opt->name_type_string, "hostbased-service") == 0)
|
||||
name_type = GSS_C_NT_HOSTBASED_SERVICE;
|
||||
else if (strcmp(opt->name_type_string, "anonymous") == 0)
|
||||
name_type = GSS_C_NT_ANONYMOUS;
|
||||
else
|
||||
name_type = gss_name_to_oid(opt->name_type_string);
|
||||
if (name_type == GSS_C_NO_OID)
|
||||
errx(1, "Could not parse the given name-type");
|
||||
}
|
||||
|
||||
if (opt->name_string) {
|
||||
gss_buffer_desc b;
|
||||
|
||||
b.length = strlen(opt->name_string);
|
||||
b.value = opt->name_string;
|
||||
maj = gss_import_name(&min, &b, name_type, &name);
|
||||
if (maj != GSS_S_COMPLETE)
|
||||
gss_err(1, maj, min, GSS_C_NO_OID, "Failed to import name");
|
||||
}
|
||||
|
||||
num_from =
|
||||
opt->from_strings.num_strings +
|
||||
opt->from_prompt_strings.num_strings +
|
||||
opt->from_file_strings.num_strings;
|
||||
num_into =
|
||||
opt->into_strings.num_strings +
|
||||
opt->into_prompt_strings.num_strings +
|
||||
opt->into_file_strings.num_strings;
|
||||
|
||||
from = calloc(num_from + 1, sizeof(*from));
|
||||
into = calloc(num_into + 1, sizeof(*into));
|
||||
freeme = calloc(2 * (num_from + num_into) + 1, sizeof(*freeme));
|
||||
if (from == NULL || into == NULL || freeme == NULL)
|
||||
err(1, "Out of memory");
|
||||
|
||||
/* Set up the cred store we're acquiring from */
|
||||
from_store.count = num_from;
|
||||
from_store.elements = from;
|
||||
for (i = idx = 0; i < opt->from_strings.num_strings; i++, idx++)
|
||||
fill_in(opt->from_strings.strings[i], &from[idx], freeme, &k);
|
||||
for (i = 0; i < opt->from_prompt_strings.num_strings; i++, idx++)
|
||||
prompt(opt->from_prompt_strings.strings[i], &from[idx], freeme, &k);
|
||||
for (i = 0; i < opt->from_file_strings.num_strings; i++, idx++)
|
||||
do_file(opt->from_file_strings.strings[i], &from[idx], freeme, &k);
|
||||
|
||||
/* Set up the cred store we're storing into */
|
||||
into_store.count = num_into;
|
||||
into_store.elements = into;
|
||||
for (i = idx = 0; i < opt->into_strings.num_strings; i++, idx++)
|
||||
fill_in(opt->into_strings.strings[i], &into[idx], freeme, &k);
|
||||
for (i = k = 0; i < opt->into_prompt_strings.num_strings; i++, idx++)
|
||||
prompt(opt->into_prompt_strings.strings[i], &into[idx], freeme, &k);
|
||||
for (i = k = 0; i < opt->into_file_strings.num_strings; i++, idx++)
|
||||
do_file(opt->into_file_strings.strings[i], &into[idx], freeme, &k);
|
||||
|
||||
if (opt->mech_strings.num_strings) {
|
||||
maj = gss_create_empty_oid_set(&min, &mechs);
|
||||
for (i = 0;
|
||||
maj == GSS_S_COMPLETE && i < opt->mech_strings.num_strings;
|
||||
i++) {
|
||||
if (strcmp(opt->mech_strings.strings[i], "all") == 0) {
|
||||
maj = gss_release_oid_set(&min, &mechs);
|
||||
} else if (strcmp(opt->mech_strings.strings[i], "krb5") == 0) {
|
||||
maj = gss_add_oid_set_member(&min, GSS_KRB5_MECHANISM, &mechs);
|
||||
} else if (strcmp(opt->mech_strings.strings[i], "ntlm") == 0) {
|
||||
maj = gss_add_oid_set_member(&min, GSS_NTLM_MECHANISM, &mechs);
|
||||
} else if (strcmp(opt->mech_strings.strings[i], "spnego") == 0) {
|
||||
maj = gss_add_oid_set_member(&min, GSS_SPNEGO_MECHANISM, &mechs);
|
||||
} else if (strcmp(opt->mech_strings.strings[i], "sanon_x25519") == 0) {
|
||||
maj = gss_add_oid_set_member(&min, GSS_SANON_X25519_MECHANISM, &mechs);
|
||||
} else {
|
||||
gss_OID mech = gss_name_to_oid(opt->mech_strings.strings[i]);
|
||||
|
||||
if (mech == GSS_C_NO_OID)
|
||||
errx(1, "Could not parse the given name-type");
|
||||
maj = gss_add_oid_set_member(&min, mech, &mechs);
|
||||
}
|
||||
}
|
||||
if (maj != GSS_S_COMPLETE)
|
||||
gss_err(1, min, maj, GSS_C_NO_OID,
|
||||
"Could not make a set of mechanism OIDs");
|
||||
} else {
|
||||
maj = gss_create_empty_oid_set(&min, &mechs);
|
||||
if (maj == GSS_S_COMPLETE)
|
||||
maj = gss_add_oid_set_member(&min, GSS_KRB5_MECHANISM, &mechs);
|
||||
if (maj != GSS_S_COMPLETE)
|
||||
gss_err(1, min, maj, GSS_C_NO_OID,
|
||||
"Could not make a set of the Kerberos mechanism OID");
|
||||
}
|
||||
|
||||
do_acquire(opt, name, time_req, mechs, cred_usage, &from_store,
|
||||
&into_store, argc, 0, &time_rec);
|
||||
|
||||
if (argc) {
|
||||
struct renew_ctx ctx;
|
||||
|
||||
/*
|
||||
* We have room for one more cred store item in `from'. We'll say we
|
||||
* want to renew if possible. If renewing doesn't work, we hope that
|
||||
* gss_acquire_cred_from() will then try to get fresh credentials (ours
|
||||
* will), though that can fail (e.g., if passwords get changed).
|
||||
*/
|
||||
from[from_store.count].key = "renew";
|
||||
from[from_store.count++].value = "";
|
||||
|
||||
ctx.opt = opt;
|
||||
ctx.name = name;
|
||||
ctx.time_req = time_req;
|
||||
ctx.mechs = mechs;
|
||||
ctx.cred_usage = cred_usage;
|
||||
ctx.from = &from_store;
|
||||
ctx.into = &into_store;
|
||||
|
||||
ret = simple_execvp_timed(argv[0], argv, renew_func, &ctx,
|
||||
/* Timeout at 75% of credential lifetime */
|
||||
(time_rec - (time_rec >> 2)));
|
||||
}
|
||||
|
||||
gss_release_name(&min, &name);
|
||||
for (i = 0; freeme[i] != NULL; i++)
|
||||
free(freeme[i]);
|
||||
free(freeme);
|
||||
free(from);
|
||||
free(into);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
|
||||
@@ -60,6 +60,7 @@ kadmin="${kadmin} -l -A -r $R"
|
||||
kdc="${kdc} --addresses=localhost -P $port"
|
||||
|
||||
acquire_cred="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_acquire_cred"
|
||||
acquire_cred2="${TESTS_ENVIRONMENT} $gsstool acquire-cred"
|
||||
test_kcred="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_kcred"
|
||||
test_add_store_cred="${TESTS_ENVIRONMENT} ../../lib/gssapi/test_add_store_cred"
|
||||
|
||||
@@ -215,6 +216,13 @@ test_run ${acquire_cred} \
|
||||
--ccache=${cache} \
|
||||
--acquire-name=host@host.test.h5l.se
|
||||
|
||||
echo "test gsstool acquire-cred"
|
||||
${acquire_cred2} \
|
||||
--mech=krb5 \
|
||||
--name=user \
|
||||
--from=password=upw \
|
||||
--into=unique_ccache_type=MEMORY || exit 1
|
||||
|
||||
trap "" EXIT
|
||||
|
||||
echo "killing kdc (${kdcpid})"
|
||||
|
||||
@@ -583,6 +583,192 @@ test_run ${context} \
|
||||
--max-loops=5 \
|
||||
--name-type=hostbased-service host@lucid.test.h5l.se
|
||||
|
||||
# PKINIT credential store tests using gsstool
|
||||
# These test the new pkinit_* credential store keys
|
||||
|
||||
gsstool="${TESTS_ENVIRONMENT} ../../lib/gssapi/gsstool"
|
||||
hxtool="${TESTS_ENVIRONMENT} ../../lib/hx509/hxtool"
|
||||
hx509_data="${top_srcdir}/lib/hx509/data"
|
||||
keyfile="${hx509_data}/key.der"
|
||||
keyfile2="${hx509_data}/key2.der"
|
||||
|
||||
# Check if PKINIT is available
|
||||
pkinit_available=no
|
||||
if ${kinit} --help 2>&1 | grep "CA certificates" > /dev/null; then
|
||||
pkinit_available=yes
|
||||
fi
|
||||
# Check if we have RSA support
|
||||
if ${hxtool} info 2>/dev/null | grep 'rsa: hx509 null RSA' > /dev/null; then
|
||||
pkinit_available=no
|
||||
fi
|
||||
if ${hxtool} info 2>/dev/null | grep 'rand: not available' > /dev/null; then
|
||||
pkinit_available=no
|
||||
fi
|
||||
|
||||
if test "$pkinit_available" = yes; then
|
||||
test_section "PKINIT credential store tests"
|
||||
|
||||
# Create certificates for PKINIT testing
|
||||
echo "Setting up PKINIT certificates"
|
||||
|
||||
${hxtool} request-create \
|
||||
--subject="CN=user1,DC=test,DC=h5l,DC=se" \
|
||||
--key=FILE:${keyfile2} \
|
||||
${objdir}/req-pkinit-user1.der || exit 1
|
||||
|
||||
# Issue self-signed CA cert
|
||||
${hxtool} issue-certificate \
|
||||
--self-signed \
|
||||
--issue-ca \
|
||||
--ca-private-key=FILE:${keyfile} \
|
||||
--subject="CN=CA,DC=test,DC=h5l,DC=se" \
|
||||
--certificate="FILE:${objdir}/pkinit-ca.crt" || exit 1
|
||||
|
||||
# Issue KDC certificate
|
||||
${hxtool} request-create \
|
||||
--subject="CN=kdc,DC=test,DC=h5l,DC=se" \
|
||||
--key=FILE:${keyfile2} \
|
||||
${objdir}/req-kdc.der || exit 1
|
||||
|
||||
${hxtool} issue-certificate \
|
||||
--ca-certificate=FILE:${objdir}/pkinit-ca.crt,${keyfile} \
|
||||
--type="pkinit-kdc" \
|
||||
--pk-init-principal="krbtgt/${R}@${R}" \
|
||||
--req="PKCS10:${objdir}/req-kdc.der" \
|
||||
--certificate="FILE:${objdir}/pkinit-kdc.crt" || exit 1
|
||||
|
||||
# Issue user certificate with PKINIT SAN
|
||||
${hxtool} issue-certificate \
|
||||
--ca-certificate=FILE:${objdir}/pkinit-ca.crt,${keyfile} \
|
||||
--type="pkinit-client" \
|
||||
--pk-init-principal="user1@${R}" \
|
||||
--req="PKCS10:${objdir}/req-pkinit-user1.der" \
|
||||
--lifetime=7d \
|
||||
--certificate="FILE:${objdir}/pkinit-user1.crt" || exit 1
|
||||
|
||||
# Restart KDC with PKINIT configuration
|
||||
echo "Restarting KDC with PKINIT support"
|
||||
kill ${kdcpid} 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
# Create PKINIT-enabled krb5.conf
|
||||
cat > ${objdir}/krb5-pkinit-gss.conf <<EOF
|
||||
[libdefaults]
|
||||
default_realm = ${R}
|
||||
no-addresses = TRUE
|
||||
allow_weak_crypto = TRUE
|
||||
|
||||
[realms]
|
||||
${R} = {
|
||||
kdc = localhost:${port}
|
||||
}
|
||||
|
||||
[kdc]
|
||||
database = {
|
||||
dbname = ${objdir}/current-db
|
||||
realm = ${R}
|
||||
mkey_file = ${objdir}/mkey.file
|
||||
}
|
||||
pkinit_identity = FILE:${objdir}/pkinit-kdc.crt,${keyfile2}
|
||||
pkinit_anchors = FILE:${objdir}/pkinit-ca.crt
|
||||
pkinit_mappings_file = ${objdir}/pki-mapping
|
||||
enable-pkinit = true
|
||||
pkinit_allow_proxy_certificate = true
|
||||
|
||||
[logging]
|
||||
kdc = 0-/FILE:${objdir}/messages.log
|
||||
default = 0-/FILE:${objdir}/messages.log
|
||||
EOF
|
||||
|
||||
# Create PKI mapping file
|
||||
cat > ${objdir}/pki-mapping <<EOF
|
||||
user1@${R} CN=user1,DC=test,DC=h5l,DC=se
|
||||
EOF
|
||||
|
||||
KRB5_CONFIG="${objdir}/krb5-pkinit-gss.conf"
|
||||
export KRB5_CONFIG
|
||||
|
||||
${kdc} --detach --testing || { echo "PKINIT KDC failed to start"; cat messages.log; exit 1; }
|
||||
kdcpid=`getpid kdc`
|
||||
|
||||
trap "kill ${kdcpid} 2>/dev/null; exit 1" EXIT INT TERM
|
||||
|
||||
test_section "gsstool acquire-cred with PKINIT credential store"
|
||||
|
||||
# Test basic PKINIT with pkinit_client_certs and pkinit_trust_anchors
|
||||
test_run ${gsstool} acquire-cred \
|
||||
--name=user1@${R} \
|
||||
--mech=krb5 \
|
||||
"--from=pkinit_client_certs=FILE:${objdir}/pkinit-user1.crt,${keyfile2}" \
|
||||
"--from=pkinit_trust_anchors=FILE:${objdir}/pkinit-ca.crt" \
|
||||
"--into=ccache=FILE:${objdir}/pkinit-gss-cache" \
|
||||
--verbose
|
||||
|
||||
# Verify the credential was acquired
|
||||
test_run ${klist} -c FILE:${objdir}/pkinit-gss-cache
|
||||
|
||||
# Test with multiple pkinit_intermediates (even if empty, tests the parsing)
|
||||
test_run ${gsstool} acquire-cred \
|
||||
--name=user1@${R} \
|
||||
--mech=krb5 \
|
||||
"--from=pkinit_client_certs=FILE:${objdir}/pkinit-user1.crt,${keyfile2}" \
|
||||
"--from=pkinit_trust_anchors=FILE:${objdir}/pkinit-ca.crt" \
|
||||
"--from=pkinit_intermediates=FILE:${objdir}/pkinit-ca.crt" \
|
||||
"--into=ccache=FILE:${objdir}/pkinit-gss-cache2" \
|
||||
--verbose
|
||||
|
||||
test_run ${klist} -c FILE:${objdir}/pkinit-gss-cache2
|
||||
|
||||
# Note: Anonymous PKINIT via GSS_C_NT_ANONYMOUS name type is not yet fully
|
||||
# supported. Anonymous PKINIT requires either:
|
||||
# 1. A credential store key to explicitly request anonymous mode
|
||||
# 2. Or proper handling of GSS_C_NT_ANONYMOUS name import
|
||||
# Skipping anonymous PKINIT test for now.
|
||||
|
||||
# Test fast_anon_pkinit credential store key (informational - may fail if
|
||||
# KDC doesn't support FAST or anonymous PKINIT)
|
||||
test_section "gsstool acquire-cred with FAST anonymous PKINIT (informational)"
|
||||
|
||||
if ${gsstool} acquire-cred \
|
||||
--name=user1@${R} \
|
||||
--mech=krb5 \
|
||||
"--from=pkinit_client_certs=FILE:${objdir}/pkinit-user1.crt,${keyfile2}" \
|
||||
"--from=pkinit_trust_anchors=FILE:${objdir}/pkinit-ca.crt" \
|
||||
"--from=fast_anon_pkinit=" \
|
||||
"--into=ccache=FILE:${objdir}/pkinit-fast-cache" \
|
||||
--verbose 2>&1; then
|
||||
echo "FAST anonymous PKINIT succeeded"
|
||||
test_run ${klist} -c FILE:${objdir}/pkinit-fast-cache
|
||||
else
|
||||
echo "FAST anonymous PKINIT not available (expected in some configurations)"
|
||||
fi
|
||||
|
||||
# Test fast_anon_pkinit_optimistic credential store key (informational)
|
||||
if ${gsstool} acquire-cred \
|
||||
--name=user1@${R} \
|
||||
--mech=krb5 \
|
||||
"--from=pkinit_client_certs=FILE:${objdir}/pkinit-user1.crt,${keyfile2}" \
|
||||
"--from=pkinit_trust_anchors=FILE:${objdir}/pkinit-ca.crt" \
|
||||
"--from=fast_anon_pkinit_optimistic=" \
|
||||
"--into=ccache=FILE:${objdir}/pkinit-fast-opt-cache" \
|
||||
--verbose 2>&1; then
|
||||
echo "FAST optimistic PKINIT succeeded"
|
||||
test_run ${klist} -c FILE:${objdir}/pkinit-fast-opt-cache
|
||||
else
|
||||
echo "FAST optimistic PKINIT not available (expected in some configurations)"
|
||||
fi
|
||||
|
||||
# Clean up PKINIT test files
|
||||
rm -f ${objdir}/req-pkinit-user1.der ${objdir}/req-kdc.der
|
||||
rm -f ${objdir}/pkinit-ca.crt ${objdir}/pkinit-kdc.crt ${objdir}/pkinit-user1.crt
|
||||
rm -f ${objdir}/pkinit-gss-cache ${objdir}/pkinit-gss-cache2
|
||||
rm -f ${objdir}/pkinit-anon-cache ${objdir}/pkinit-fast-cache ${objdir}/pkinit-fast-opt-cache
|
||||
rm -f ${objdir}/krb5-pkinit-gss.conf ${objdir}/pki-mapping
|
||||
|
||||
else
|
||||
echo "Skipping PKINIT credential store tests (PKINIT not available)"
|
||||
fi
|
||||
|
||||
trap "" EXIT
|
||||
|
||||
echo "killing kdc (${kdcpid})"
|
||||
|
||||
Reference in New Issue
Block a user