1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/test/cltest.c - Test clpreauth module */
3 /*
4  * Copyright (C) 2015, 2017 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * This module is used to test preauth interface features.  At this time, the
35  * clpreauth module does the following:
36  *
37  * - It decrypts a message from the initial KDC pa-data using the reply key and
38  *   prints it to stdout.  (The unencrypted message "no key" can also be
39  *   displayed.)
40  *
41  * - If a second round trip is requested, it prints the pa-data contents
42  *   accompanying the second round trip request.
43  *
44  * - It pulls an "indicators" attribute from the gic preauth options and sends
45  *   it to the server, instructing the kdcpreauth module to assert one or more
46  *   space-separated authentication indicators.  (This string is sent on both
47  *   round trips if a second round trip is requested.)
48  *
49  * - If a KDC_ERR_ENCTYPE_NOSUPP error with e-data is received, it prints the
50  *   accompanying error padata and sends a follow-up request containing
51  *   "tryagain".
52  *
53  * - If the "fail_optimistic", "fail_2rt", or "fail_tryagain" gic options are
54  *   set, it fails with a recognizable error string at the requested point in
55  *   processing.
56  *
57  * - If the "disable_fallback" gic option is set, fallback is disabled when a
58  *   client message is generated.
59  */
60 
61 #include "k5-int.h"
62 #include <krb5/clpreauth_plugin.h>
63 #include "common.h"
64 
65 static krb5_preauthtype pa_types[] = { TEST_PA_TYPE, 0 };
66 
67 struct client_state {
68     char *indicators;
69     krb5_boolean fail_optimistic;
70     krb5_boolean fail_2rt;
71     krb5_boolean fail_tryagain;
72     krb5_boolean disable_fallback;
73 };
74 
75 struct client_request_state {
76     krb5_boolean second_round_trip;
77 };
78 
79 static krb5_error_code
test_init(krb5_context context,krb5_clpreauth_moddata * moddata_out)80 test_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)
81 {
82     struct client_state *st;
83 
84     st = malloc(sizeof(*st));
85     assert(st != NULL);
86     st->indicators = NULL;
87     st->fail_optimistic = st->fail_2rt = st->fail_tryagain = FALSE;
88     st->disable_fallback = FALSE;
89     *moddata_out = (krb5_clpreauth_moddata)st;
90     return 0;
91 }
92 
93 static void
test_fini(krb5_context context,krb5_clpreauth_moddata moddata)94 test_fini(krb5_context context, krb5_clpreauth_moddata moddata)
95 {
96     struct client_state *st = (struct client_state *)moddata;
97 
98     free(st->indicators);
99     free(st);
100 }
101 
102 static void
test_request_init(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq * modreq_out)103 test_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
104                   krb5_clpreauth_modreq *modreq_out)
105 {
106     struct client_request_state *reqst;
107 
108     reqst = malloc(sizeof(*reqst));
109     assert(reqst != NULL);
110     reqst->second_round_trip = FALSE;
111     *modreq_out = (krb5_clpreauth_modreq)reqst;
112 }
113 
114 static void
test_request_fini(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq)115 test_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
116                   krb5_clpreauth_modreq modreq)
117 {
118     free(modreq);
119 }
120 
121 static krb5_error_code
test_process(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_get_init_creds_opt * opt,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_data * encoded_request_body,krb5_data * encoded_previous_request,krb5_pa_data * pa_data,krb5_prompter_fct prompter,void * prompter_data,krb5_pa_data *** out_pa_data)122 test_process(krb5_context context, krb5_clpreauth_moddata moddata,
123              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
124              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
125              krb5_kdc_req *request, krb5_data *encoded_request_body,
126              krb5_data *encoded_previous_request, krb5_pa_data *pa_data,
127              krb5_prompter_fct prompter, void *prompter_data,
128              krb5_pa_data ***out_pa_data)
129 {
130     struct client_state *st = (struct client_state *)moddata;
131     struct client_request_state *reqst = (struct client_request_state *)modreq;
132     krb5_error_code ret;
133     krb5_keyblock *k;
134     krb5_enc_data enc;
135     krb5_data plain;
136     const char *indstr;
137 
138     if (pa_data->length == 0) {
139         /* This is an optimistic preauth test.  Send a recognizable padata
140          * value so the KDC knows not to expect a cookie. */
141         if (st->fail_optimistic) {
142             k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced optimistic fail");
143             return KRB5_PREAUTH_FAILED;
144         }
145         *out_pa_data = make_pa_list("optimistic", 10);
146         if (st->disable_fallback)
147             cb->disable_fallback(context, rock);
148         return 0;
149     } else if (reqst->second_round_trip) {
150         printf("2rt: %.*s\n", pa_data->length, pa_data->contents);
151         if (st->fail_2rt) {
152             k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced 2rt fail");
153             return KRB5_PREAUTH_FAILED;
154         }
155     } else if (pa_data->length == 6 &&
156                memcmp(pa_data->contents, "no key", 6) == 0) {
157         printf("no key\n");
158     } else {
159         /* This fails during s4u_identify_user(), so don't assert. */
160         ret = cb->get_as_key(context, rock, &k);
161         if (ret)
162             return ret;
163         ret = alloc_data(&plain, pa_data->length);
164         assert(!ret);
165         enc.enctype = k->enctype;
166         enc.ciphertext = make_data(pa_data->contents, pa_data->length);
167         ret = krb5_c_decrypt(context, k, 1024, NULL, &enc, &plain);
168         assert(!ret);
169         printf("%.*s\n", plain.length, plain.data);
170         free(plain.data);
171     }
172     reqst->second_round_trip = TRUE;
173 
174     indstr = (st->indicators != NULL) ? st->indicators : "";
175     *out_pa_data = make_pa_list(indstr, strlen(indstr));
176     if (st->disable_fallback)
177         cb->disable_fallback(context, rock);
178     return 0;
179 }
180 
181 static krb5_error_code
test_tryagain(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_get_init_creds_opt * opt,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_data * enc_req,krb5_data * enc_prev,krb5_preauthtype pa_type,krb5_error * error,krb5_pa_data ** padata,krb5_prompter_fct prompter,void * prompter_data,krb5_pa_data *** padata_out)182 test_tryagain(krb5_context context, krb5_clpreauth_moddata moddata,
183               krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
184               krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
185               krb5_kdc_req *request, krb5_data *enc_req, krb5_data *enc_prev,
186               krb5_preauthtype pa_type, krb5_error *error,
187               krb5_pa_data **padata, krb5_prompter_fct prompter,
188               void *prompter_data, krb5_pa_data ***padata_out)
189 {
190     struct client_state *st = (struct client_state *)moddata;
191     int i;
192 
193     *padata_out = NULL;
194     if (st->fail_tryagain) {
195         k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced tryagain fail");
196         return KRB5_PREAUTH_FAILED;
197     }
198     if (error->error != KDC_ERR_ENCTYPE_NOSUPP)
199         return KRB5_PREAUTH_FAILED;
200     for (i = 0; padata[i] != NULL; i++) {
201         if (padata[i]->pa_type == TEST_PA_TYPE)
202             printf("tryagain: %.*s\n", padata[i]->length, padata[i]->contents);
203     }
204     *padata_out = make_pa_list("tryagain", 8);
205     return 0;
206 }
207 
208 static krb5_error_code
test_gic_opt(krb5_context kcontext,krb5_clpreauth_moddata moddata,krb5_get_init_creds_opt * opt,const char * attr,const char * value)209 test_gic_opt(krb5_context kcontext, krb5_clpreauth_moddata moddata,
210              krb5_get_init_creds_opt *opt, const char *attr, const char *value)
211 {
212     struct client_state *st = (struct client_state *)moddata;
213 
214     if (strcmp(attr, "indicators") == 0) {
215         free(st->indicators);
216         st->indicators = strdup(value);
217         assert(st->indicators != NULL);
218     } else if (strcmp(attr, "fail_optimistic") == 0) {
219         st->fail_optimistic = TRUE;
220     } else if (strcmp(attr, "fail_2rt") == 0) {
221         st->fail_2rt = TRUE;
222     } else if (strcmp(attr, "fail_tryagain") == 0) {
223         st->fail_tryagain = TRUE;
224     } else if (strcmp(attr, "disable_fallback") == 0) {
225         st->disable_fallback = TRUE;
226     }
227     return 0;
228 }
229 
230 krb5_error_code
231 clpreauth_test_initvt(krb5_context context, int maj_ver,
232                             int min_ver, krb5_plugin_vtable vtable);
233 
234 krb5_error_code
clpreauth_test_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)235 clpreauth_test_initvt(krb5_context context, int maj_ver,
236                             int min_ver, krb5_plugin_vtable vtable)
237 {
238     krb5_clpreauth_vtable vt;
239 
240     if (maj_ver != 1)
241         return KRB5_PLUGIN_VER_NOTSUPP;
242     vt = (krb5_clpreauth_vtable)vtable;
243     vt->name = "test";
244     vt->pa_type_list = pa_types;
245     vt->init = test_init;
246     vt->fini = test_fini;
247     vt->request_init = test_request_init;
248     vt->request_fini = test_request_fini;
249     vt->process = test_process;
250     vt->tryagain = test_tryagain;
251     vt->gic_opts = test_gic_opt;
252     return 0;
253 }
254