From ce02b69e27bcfa21bcab2ed195dfdbaa8040d773 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Mon, 15 Jul 2013 13:37:00 -0400 Subject: Add non-JSON APIs for PKINIT responder items Add wrappers for the JSON-oriented APIs for PKINIT responder items, modeled after the API we provide for OTP items: * krb5_responder_pkinit_get_challenge() returns the list of identities for which we need PINs * krb5_responder_pkinit_challenge_free() frees the structure that was returned by krb5_responder_pkinit_get_challenge() * krb5_responder_pkinit_set_answer() sets the answer to the PIN for one of the identities [ghudson@mit.edu: style cleanup; added comment pointing to main body of PKINIT module] ticket: 7680 --- doc/appdev/init_creds.rst | 16 +++ doc/appdev/refs/api/index.rst | 3 + src/include/krb5/krb5.hin | 59 +++++++++++ src/lib/krb5/krb/Makefile.in | 3 + src/lib/krb5/krb/deps | 12 +++ src/lib/krb5/krb/preauth_pkinit.c | 204 ++++++++++++++++++++++++++++++++++++++ src/lib/krb5/libkrb5.exports | 3 + src/lib/krb5_32.def | 3 + 8 files changed, 303 insertions(+) create mode 100644 src/lib/krb5/krb/preauth_pkinit.c diff --git a/doc/appdev/init_creds.rst b/doc/appdev/init_creds.rst index 63c9d617ed..07baa4a387 100644 --- a/doc/appdev/init_creds.rst +++ b/doc/appdev/init_creds.rst @@ -210,6 +210,22 @@ challenge into a krb5_responder_otp_challenge structure. The token information elements from the challenge and supplies the value and pin for that token. +PKINIT password or PIN question +############################### + +The :c:macro:`KRB5_RESPONDER_QUESTION_PKINIT` (or ``"pkinit"``) question +type requests PINs for hardware devices and/or passwords for encrypted +credentials which are stored on disk, potentially also supplying +information about the state of the hardware devices. The challenge and +answer are JSON-encoded strings, but an application can use convenience +functions to avoid doing any JSON processing itself. + +The :c:func:`krb5_responder_pkinit_get_challenge` function decodes the +challenges into a krb5_responder_pkinit_challenge structure. The +:c:func:`krb5_responder_pkinit_set_answer` function can be used to +supply the PIN or password for a particular client credential, and can +be called multiple times. + Example ####### diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst index 7009b30dca..b1a580a6aa 100644 --- a/doc/appdev/refs/api/index.rst +++ b/doc/appdev/refs/api/index.rst @@ -87,6 +87,9 @@ Frequently used public interfaces krb5_responder_otp_get_challenge.rst krb5_responder_otp_set_answer.rst krb5_responder_otp_challenge_free.rst + krb5_responder_pkinit_get_challenge.rst + krb5_responder_pkinit_set_answer.rst + krb5_responder_pkinit_challenge_free.rst krb5_set_default_realm.rst krb5_set_password.rst krb5_set_password_using_ccache.rst diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index faeabc7152..f2ba06ff98 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -6689,6 +6689,65 @@ krb5_responder_otp_challenge_free(krb5_context ctx, krb5_responder_context rctx, krb5_responder_otp_challenge *chl); +typedef struct _krb5_responder_pkinit_identity { + char *identity; + krb5_int32 token_flags; /* 0 when not specified or not applicable. */ +} krb5_responder_pkinit_identity; + +typedef struct _krb5_responder_pkinit_challenge { + krb5_responder_pkinit_identity **identities; +} krb5_responder_pkinit_challenge; + +/** + * Decode the KRB5_RESPONDER_QUESTION_PKINIT to a C struct. + * + * A convenience function which parses the KRB5_RESPONDER_QUESTION_PKINIT + * question challenge data, making it available in native C. The main feature + * of this function is the ability to read the challenge without parsing + * the JSON. + * + * The returned value must be passed to krb5_responder_pkinit_challenge_free() + * to be freed. + * + * @param [in] ctx Library context + * @param [in] rctx Responder context + * @param [out] chl_out Challenge structure + * + * @version New in 1.12 + */ +krb5_error_code KRB5_CALLCONV +krb5_responder_pkinit_get_challenge(krb5_context ctx, + krb5_responder_context rctx, + krb5_responder_pkinit_challenge **chl_out); + +/** + * Answer the KRB5_RESPONDER_QUESTION_PKINIT question for one identity. + * + * @param [in] ctx Library context + * @param [in] rctx Responder context + * @param [in] identity The identity for which a PIN is being supplied + * @param [in] pin The provided PIN, or NULL for none + * + * @version New in 1.12 + */ +krb5_error_code KRB5_CALLCONV +krb5_responder_pkinit_set_answer(krb5_context ctx, krb5_responder_context rctx, + const char *identity, const char *pin); + +/** + * Free the value returned by krb5_responder_pkinit_get_challenge(). + * + * @param [in] ctx Library context + * @param [in] rctx Responder context + * @param [in] chl The challenge to free + * + * @version New in 1.12 + */ +void KRB5_CALLCONV +krb5_responder_pkinit_challenge_free(krb5_context ctx, + krb5_responder_context rctx, + krb5_responder_pkinit_challenge *chl); + /** Store options for @c _krb5_get_init_creds */ typedef struct _krb5_get_init_creds_opt { krb5_flags flags; diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index d5db2749ba..3cf857e0d2 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -77,6 +77,7 @@ STLIBOBJS= \ preauth_ec.o \ preauth_encts.o \ preauth_otp.o \ + preauth_pkinit.o \ preauth_sam2.o \ gic_opt_set_pa.o \ princ_comp.o \ @@ -185,6 +186,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)preauth_ec.$(OBJEXT) \ $(OUTPRE)preauth_encts.$(OBJEXT) \ $(OUTPRE)preauth_otp.$(OBJEXT) \ + $(OUTPRE)preauth_pkinit.$(OBJEXT) \ $(OUTPRE)preauth_sam2.$(OBJEXT) \ $(OUTPRE)gic_opt_set_pa.$(OBJEXT) \ $(OUTPRE)princ_comp.$(OBJEXT) \ @@ -293,6 +295,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/preauth_ec.c \ $(srcdir)/preauth_encts.c \ $(srcdir)/preauth_otp.c \ + $(srcdir)/preauth_pkinit.c \ $(srcdir)/preauth_sam2.c \ $(srcdir)/gic_opt_set_pa.c \ $(srcdir)/princ_comp.c \ diff --git a/src/lib/krb5/krb/deps b/src/lib/krb5/krb/deps index f4bbc69351..798417308d 100644 --- a/src/lib/krb5/krb/deps +++ b/src/lib/krb5/krb/deps @@ -758,6 +758,18 @@ preauth_otp.so preauth_otp.po $(OUTPRE)preauth_otp.$(OBJEXT): \ $(top_srcdir)/include/krb5/clpreauth_plugin.h $(top_srcdir)/include/krb5/plugin.h \ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ int-proto.h preauth_otp.c +preauth_pkinit.so preauth_pkinit.po $(OUTPRE)preauth_pkinit.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-json.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h init_creds_ctx.h \ + int-proto.h preauth_pkinit.c preauth_sam2.so preauth_sam2.po $(OUTPRE)preauth_sam2.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ diff --git a/src/lib/krb5/krb/preauth_pkinit.c b/src/lib/krb5/krb/preauth_pkinit.c new file mode 100644 index 0000000000..02810f2bef --- /dev/null +++ b/src/lib/krb5/krb/preauth_pkinit.c @@ -0,0 +1,204 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/krb/preauth_pkinit.c - PKINIT clpreauth helpers */ +/* + * Copyright 2013 Red Hat, Inc. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER + * 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 file defines libkrb5 APIs for manipulating PKINIT responder questions + * and answers. The main body of the PKINIT clpreauth module is in the + * plugins/preauth/pkinit directory. + */ + +#include "k5-int.h" +#include "k5-json.h" +#include "int-proto.h" +#include "init_creds_ctx.h" + +struct get_one_challenge_data { + krb5_responder_pkinit_identity **identities; + krb5_error_code err; +}; + +static void +get_one_challenge(void *arg, const char *key, k5_json_value val) +{ + struct get_one_challenge_data *data; + unsigned long token_flags; + int i; + + data = arg; + if (data->err != 0) + return; + if (k5_json_get_tid(val) != K5_JSON_TID_NUMBER) { + data->err = EINVAL; + return; + } + + token_flags = k5_json_number_value(val); + /* Find the slot for this entry. */ + for (i = 0; data->identities[i] != NULL; i++) + continue; + /* Set the identity (a copy of the key) and the token flags. */ + data->identities[i] = k5alloc(sizeof(*data->identities[i]), &data->err); + if (data->identities[i] == NULL) + return; + data->identities[i]->identity = strdup(key); + if (data->identities[i]->identity == NULL) { + data->err = ENOMEM; + return; + } + data->identities[i]->token_flags = token_flags; +} + +krb5_error_code KRB5_CALLCONV +krb5_responder_pkinit_get_challenge(krb5_context ctx, + krb5_responder_context rctx, + krb5_responder_pkinit_challenge **chl_out) +{ + const char *challenge; + k5_json_value j; + struct get_one_challenge_data get_one_challenge_data; + krb5_responder_pkinit_challenge *chl = NULL; + unsigned int n_ids; + krb5_error_code ret; + + *chl_out = NULL; + challenge = krb5_responder_get_challenge(ctx, rctx, + KRB5_RESPONDER_QUESTION_PKINIT); + if (challenge == NULL) + return 0; + + ret = k5_json_decode(challenge, &j); + if (ret != 0) + return ret; + + /* Create the returned object. */ + chl = k5alloc(sizeof(*chl), &ret); + if (chl == NULL) + goto failed; + + /* Create the list of identities. */ + n_ids = k5_json_object_count(j); + chl->identities = k5calloc(n_ids + 1, sizeof(chl->identities[0]), &ret); + if (chl->identities == NULL) + goto failed; + + /* Populate the object with identities. */ + memset(&get_one_challenge_data, 0, sizeof(get_one_challenge_data)); + get_one_challenge_data.identities = chl->identities; + k5_json_object_iterate(j, get_one_challenge, &get_one_challenge_data); + if (get_one_challenge_data.err != 0) { + ret = get_one_challenge_data.err; + goto failed; + } + + /* All done. */ + k5_json_release(j); + *chl_out = chl; + return 0; + +failed: + k5_json_release(j); + krb5_responder_pkinit_challenge_free(ctx, rctx, chl); + return ret; +} + +krb5_error_code KRB5_CALLCONV +krb5_responder_pkinit_set_answer(krb5_context ctx, krb5_responder_context rctx, + const char *identity, const char *pin) +{ + char *answer = NULL; + const char *old_answer; + k5_json_value answers = NULL; + k5_json_string jpin = NULL; + krb5_error_code ret = ENOMEM; + + /* If there's an answer already set, we're adding/removing a value. */ + old_answer = k5_response_items_get_answer(rctx->items, + KRB5_RESPONDER_QUESTION_PKINIT); + + /* If we're removing a value, and we have no values, we're done. */ + if (old_answer == NULL && pin == NULL) + return 0; + + /* Decode the old answers. */ + if (old_answer == NULL) + old_answer = "{}"; + ret = k5_json_decode(old_answer, &answers); + if (ret != 0) + goto cleanup; + + if (k5_json_get_tid(answers) != K5_JSON_TID_OBJECT) { + ret = EINVAL; + goto cleanup; + } + + /* Create and add the new pin string, if we're adding a value. */ + if (pin != NULL) { + ret = k5_json_string_create(pin, &jpin); + if (ret != 0) + goto cleanup; + ret = k5_json_object_set(answers, identity, jpin); + if (ret != 0) + goto cleanup; + } else { + ret = k5_json_object_set(answers, identity, NULL); + if (ret != 0) + goto cleanup; + } + + /* Encode and we're done. */ + ret = k5_json_encode(answers, &answer); + if (ret != 0) + goto cleanup; + + ret = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_PKINIT, + answer); + +cleanup: + k5_json_release(jpin); + k5_json_release(answers); + free(answer); + return ret; +} + +void KRB5_CALLCONV +krb5_responder_pkinit_challenge_free(krb5_context ctx, + krb5_responder_context rctx, + krb5_responder_pkinit_challenge *chl) +{ + unsigned int i; + + if (chl == NULL) + return; + for (i = 0; chl->identities != NULL && chl->identities[i] != NULL; i++) { + free(chl->identities[i]->identity); + free(chl->identities[i]); + } + free(chl->identities); + free(chl); +} diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index f91e468d77..471671bf1b 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -525,6 +525,9 @@ krb5_responder_set_answer krb5_responder_otp_get_challenge krb5_responder_otp_set_answer krb5_responder_otp_challenge_free +krb5_responder_pkinit_get_challenge +krb5_responder_pkinit_set_answer +krb5_responder_pkinit_challenge_free krb5_salttype_to_string krb5_sendauth krb5_sendto_kdc diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def index 9bff8a7ebc..cd4c14849c 100644 --- a/src/lib/krb5_32.def +++ b/src/lib/krb5_32.def @@ -447,3 +447,6 @@ EXPORTS ; new in 1.12 krb5_free_enctypes @419 krb5_kt_dup @420 + krb5_responder_pkinit_get_challenge @421 + krb5_responder_pkinit_set_answer @422 + krb5_responder_pkinit_challenge_free @423 -- cgit