1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/test/kdctest.c - Test kdcpreauth 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.  Currently, the
35  * kdcpreauth module does the following:
36  *
37  * - When generating initial method-data, it retrieves the "teststring"
38  *   attribute from the client principal and sends it to the client, encrypted
39  *   in the reply key.  (The plain text "no key" is sent if there is no reply
40  *   key; the encrypted message "no attr" is sent if there is no string
41  *   attribute.)  It also sets a cookie containing "method-data".
42  *
43  * - If the "err" attribute is set on the client principal, the verify method
44  *   returns an KDC_ERR_ETYPE_NOSUPP error on the first try, with the contents
45  *   of the err attribute as pa-data.  If the client tries again with the
46  *   padata value "tryagain", the verify method preuthenticates successfully
47  *   with no additional processing.
48  *
49  * - If the "failopt" attribute is set on the client principal, the verify
50  *   method returns KDC_ERR_PREAUTH_FAILED on optimistic preauth attempts.
51  *
52  * - If the "2rt" attribute is set on client principal, the verify method sends
53  *   the client a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error with the contents of
54  *   the 2rt attribute as pa-data, and sets a cookie containing "more".  If the
55  *   "fail2rt" attribute is set on the client principal, the client's second
56  *   try results in a KDC_ERR_PREAUTH_FAILED error.
57  *
58  * - It receives a space-separated list from the clpreauth module and asserts
59  *   each string as an authentication indicator.  It always succeeds in
60  *   pre-authenticating the request.
61  */
62 
63 #include "k5-int.h"
64 #include <krb5/kdcpreauth_plugin.h>
65 #include "common.h"
66 
67 #define TEST_PA_TYPE -123
68 
69 static krb5_preauthtype pa_types[] = { TEST_PA_TYPE, 0 };
70 
71 static void
test_edata(krb5_context context,krb5_kdc_req * req,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_preauthtype pa_type,krb5_kdcpreauth_edata_respond_fn respond,void * arg)72 test_edata(krb5_context context, krb5_kdc_req *req,
73            krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
74            krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
75            krb5_kdcpreauth_edata_respond_fn respond, void *arg)
76 {
77     krb5_error_code ret;
78     const krb5_keyblock *k = cb->client_keyblock(context, rock);
79     krb5_pa_data *pa;
80     size_t enclen;
81     krb5_enc_data enc;
82     krb5_data d;
83     char *attr;
84 
85     ret = cb->get_string(context, rock, "teststring", &attr);
86     assert(!ret);
87     if (k != NULL) {
88         d = string2data((attr != NULL) ? attr : "no attr");
89         ret = krb5_c_encrypt_length(context, k->enctype, d.length, &enclen);
90         assert(!ret);
91         ret = alloc_data(&enc.ciphertext, enclen);
92         assert(!ret);
93         ret = krb5_c_encrypt(context, k, 1024, NULL, &d, &enc);
94         assert(!ret);
95         pa = make_pa(enc.ciphertext.data, enc.ciphertext.length);
96         free(enc.ciphertext.data);
97     } else {
98         pa = make_pa("no key", 6);
99     }
100 
101     /* Exercise setting a cookie information from the edata method. */
102     d = string2data("method-data");
103     ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
104     assert(!ret);
105 
106     cb->free_string(context, rock, attr);
107     (*respond)(arg, 0, pa);
108 }
109 
110 static void
test_verify(krb5_context context,krb5_data * req_pkt,krb5_kdc_req * request,krb5_enc_tkt_part * enc_tkt_reply,krb5_pa_data * data,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_verify_respond_fn respond,void * arg)111 test_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
112             krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *data,
113             krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
114             krb5_kdcpreauth_moddata moddata,
115             krb5_kdcpreauth_verify_respond_fn respond, void *arg)
116 {
117     krb5_error_code ret;
118     krb5_boolean second_round_trip = FALSE, optimistic = FALSE;
119     krb5_pa_data **list = NULL;
120     krb5_data cookie_data, d;
121     char *str, *ind, *toksave = NULL;
122     char *attr_err, *attr_2rt, *attr_fail2rt, *attr_failopt;
123 
124     ret = cb->get_string(context, rock, "err", &attr_err);
125     assert(!ret);
126     ret = cb->get_string(context, rock, "2rt", &attr_2rt);
127     assert(!ret);
128     ret = cb->get_string(context, rock, "fail2rt", &attr_fail2rt);
129     assert(!ret);
130     ret = cb->get_string(context, rock, "failopt", &attr_failopt);
131     assert(!ret);
132 
133     /* Check the incoming cookie value. */
134     if (!cb->get_cookie(context, rock, TEST_PA_TYPE, &cookie_data)) {
135         /* Make sure we are seeing optimistic preauth and not a lost cookie. */
136         d = make_data(data->contents, data->length);
137         assert(data_eq_string(d, "optimistic"));
138         optimistic = TRUE;
139     } else if (data_eq_string(cookie_data, "more")) {
140         second_round_trip = TRUE;
141     } else {
142         assert(data_eq_string(cookie_data, "method-data") ||
143                data_eq_string(cookie_data, "err"));
144     }
145 
146     if (attr_err != NULL) {
147         d = make_data(data->contents, data->length);
148         if (data_eq_string(d, "tryagain")) {
149             /* Authenticate successfully. */
150             enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
151         } else {
152             d = string2data("err");
153             ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
154             assert(!ret);
155             ret = KRB5KDC_ERR_ETYPE_NOSUPP;
156             list = make_pa_list(attr_err, strlen(attr_err));
157         }
158     } else if (attr_2rt != NULL && !second_round_trip) {
159         d = string2data("more");
160         ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
161         assert(!ret);
162         ret = KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
163         list = make_pa_list(attr_2rt, strlen(attr_2rt));
164     } else if ((attr_fail2rt != NULL && second_round_trip) ||
165                (attr_failopt != NULL && optimistic)) {
166         ret = KRB5KDC_ERR_PREAUTH_FAILED;
167     } else {
168         /* Parse and assert the indicators. */
169         str = k5memdup0(data->contents, data->length, &ret);
170         if (ret)
171             abort();
172         ind = strtok_r(str, " ", &toksave);
173         while (ind != NULL) {
174             cb->add_auth_indicator(context, rock, ind);
175             ind = strtok_r(NULL, " ", &toksave);
176         }
177         free(str);
178         enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
179     }
180 
181     cb->free_string(context, rock, attr_err);
182     cb->free_string(context, rock, attr_2rt);
183     cb->free_string(context, rock, attr_fail2rt);
184     cb->free_string(context, rock, attr_failopt);
185     (*respond)(arg, ret, NULL, list, NULL);
186 }
187 
188 static krb5_error_code
test_return(krb5_context context,krb5_pa_data * padata,krb5_data * req_pkt,krb5_kdc_req * request,krb5_kdc_rep * reply,krb5_keyblock * encrypting_key,krb5_pa_data ** send_pa_out,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_modreq modreq)189 test_return(krb5_context context, krb5_pa_data *padata, krb5_data *req_pkt,
190             krb5_kdc_req *request, krb5_kdc_rep *reply,
191             krb5_keyblock *encrypting_key, krb5_pa_data **send_pa_out,
192             krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
193             krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq)
194 {
195     const krb5_keyblock *k = cb->client_keyblock(context, rock);
196 
197     assert(k == encrypting_key || k == NULL);
198     return 0;
199 }
200 
201 krb5_error_code
202 kdcpreauth_test_initvt(krb5_context context, int maj_ver,
203                              int min_ver, krb5_plugin_vtable vtable);
204 
205 krb5_error_code
kdcpreauth_test_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)206 kdcpreauth_test_initvt(krb5_context context, int maj_ver,
207                              int min_ver, krb5_plugin_vtable vtable)
208 {
209     krb5_kdcpreauth_vtable vt;
210 
211     if (maj_ver != 1)
212         return KRB5_PLUGIN_VER_NOTSUPP;
213     vt = (krb5_kdcpreauth_vtable)vtable;
214     vt->name = "test";
215     vt->pa_type_list = pa_types;
216     vt->edata = test_edata;
217     vt->verify = test_verify;
218     vt->return_padata = test_return;
219     return 0;
220 }
221