xref: /freebsd/crypto/krb5/src/plugins/preauth/otp/main.c (revision 4b15965daa99044daf184221b7c283bf7f2d7e66)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/otp/main.c - OTP kdcpreauth module definition */
3 /*
4  * Copyright 2011 NORDUnet A/S.  All rights reserved.
5  * Copyright 2013 Red Hat, Inc.  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 are met:
9  *
10  *    1. Redistributions of source code must retain the above copyright
11  *       notice, this list of conditions and the following disclaimer.
12  *
13  *    2. Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "k5-int.h"
32 #include "k5-json.h"
33 #include <krb5/preauth_plugin.h>
34 #include "otp_state.h"
35 
36 #include <errno.h>
37 #include <ctype.h>
38 
39 static krb5_preauthtype otp_pa_type_list[] =
40   { KRB5_PADATA_OTP_REQUEST, 0 };
41 
42 struct request_state {
43     krb5_context context;
44     krb5_kdcpreauth_verify_respond_fn respond;
45     void *arg;
46     krb5_enc_tkt_part *enc_tkt_reply;
47     krb5_kdcpreauth_callbacks preauth_cb;
48     krb5_kdcpreauth_rock rock;
49 };
50 
51 static krb5_error_code
52 decrypt_encdata(krb5_context context, krb5_keyblock *armor_key,
53                 krb5_pa_otp_req *req, krb5_data *out)
54 {
55     krb5_error_code retval;
56     krb5_data plaintext;
57 
58     if (req == NULL)
59         return EINVAL;
60 
61     retval = alloc_data(&plaintext, req->enc_data.ciphertext.length);
62     if (retval)
63         return retval;
64 
65     retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST,
66                             NULL, &req->enc_data, &plaintext);
67     if (retval != 0) {
68         com_err("otp", retval, "Unable to decrypt encData in PA-OTP-REQUEST");
69         free(plaintext.data);
70         return retval;
71     }
72 
73     *out = plaintext;
74     return 0;
75 }
76 
77 static krb5_error_code
78 nonce_verify(krb5_context ctx, krb5_keyblock *armor_key,
79              const krb5_data *nonce)
80 {
81     krb5_error_code retval;
82     krb5_timestamp ts;
83     krb5_data *er = NULL;
84 
85     if (armor_key == NULL || nonce->data == NULL) {
86         retval = EINVAL;
87         goto out;
88     }
89 
90     /* Decode the PA-OTP-ENC-REQUEST structure. */
91     retval = decode_krb5_pa_otp_enc_req(nonce, &er);
92     if (retval != 0)
93         goto out;
94 
95     /* Make sure the nonce is exactly the same size as the one generated. */
96     if (er->length != armor_key->length + sizeof(krb5_timestamp))
97         goto out;
98 
99     /* Check to make sure the timestamp at the beginning is still valid. */
100     ts = load_32_be(er->data);
101     retval = krb5_check_clockskew(ctx, ts);
102 
103 out:
104     krb5_free_data(ctx, er);
105     return retval;
106 }
107 
108 static krb5_error_code
109 timestamp_verify(krb5_context ctx, const krb5_data *nonce)
110 {
111     krb5_error_code retval = EINVAL;
112     krb5_pa_enc_ts *et = NULL;
113 
114     if (nonce->data == NULL)
115         goto out;
116 
117     /* Decode the PA-ENC-TS-ENC structure. */
118     retval = decode_krb5_pa_enc_ts(nonce, &et);
119     if (retval != 0)
120         goto out;
121 
122     /* Check the clockskew. */
123     retval = krb5_check_clockskew(ctx, et->patimestamp);
124 
125 out:
126     krb5_free_pa_enc_ts(ctx, et);
127     return retval;
128 }
129 
130 static krb5_error_code
131 nonce_generate(krb5_context ctx, unsigned int length, krb5_data *nonce_out)
132 {
133     krb5_data nonce;
134     krb5_error_code retval;
135     krb5_timestamp now;
136 
137     retval = krb5_timeofday(ctx, &now);
138     if (retval != 0)
139         return retval;
140 
141     retval = alloc_data(&nonce, sizeof(now) + length);
142     if (retval != 0)
143         return retval;
144 
145     retval = krb5_c_random_make_octets(ctx, &nonce);
146     if (retval != 0) {
147         free(nonce.data);
148         return retval;
149     }
150 
151     store_32_be(now, nonce.data);
152     *nonce_out = nonce;
153     return 0;
154 }
155 
156 static void
157 on_response(void *data, krb5_error_code retval, otp_response response,
158             char *const *indicators)
159 {
160     struct request_state rs = *(struct request_state *)data;
161     krb5_context context = rs.context;
162     krb5_keyblock *armor_key;
163     char *const *ind;
164 
165     free(data);
166 
167     if (retval == 0 && response != otp_response_success)
168         retval = KRB5_PREAUTH_FAILED;
169     if (retval)
170         goto done;
171 
172     rs.enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
173     armor_key = rs.preauth_cb->fast_armor(context, rs.rock);
174     if (armor_key == NULL) {
175         retval = ENOENT;
176         goto done;
177     }
178 
179     retval = rs.preauth_cb->replace_reply_key(context, rs.rock, armor_key,
180                                               FALSE);
181     if (retval)
182         goto done;
183 
184     for (ind = indicators; ind != NULL && *ind != NULL; ind++) {
185         retval = rs.preauth_cb->add_auth_indicator(context, rs.rock, *ind);
186         if (retval)
187             goto done;
188     }
189 
190 done:
191     rs.respond(rs.arg, retval, NULL, NULL, NULL);
192 }
193 
194 static krb5_error_code
195 otp_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,
196          const char **realmnames)
197 {
198     krb5_error_code retval;
199     otp_state *state;
200 
201     retval = otp_state_new(context, &state);
202     if (retval)
203         return retval;
204     *moddata_out = (krb5_kdcpreauth_moddata)state;
205     return 0;
206 }
207 
208 static void
209 otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
210 {
211     otp_state_free((otp_state *)moddata);
212 }
213 
214 static int
215 otp_flags(krb5_context context, krb5_preauthtype pa_type)
216 {
217     return PA_REPLACES_KEY;
218 }
219 
220 static void
221 otp_edata(krb5_context context, krb5_kdc_req *request,
222           krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
223           krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
224           krb5_kdcpreauth_edata_respond_fn respond, void *arg)
225 {
226     krb5_otp_tokeninfo ti, *tis[2] = { &ti, NULL };
227     krb5_keyblock *armor_key = NULL;
228     krb5_pa_otp_challenge chl;
229     krb5_pa_data *pa = NULL;
230     krb5_error_code retval;
231     krb5_data *encoding, nonce = empty_data();
232     char *config;
233 
234     /* Determine if otp is enabled for the user. */
235     retval = cb->get_string(context, rock, "otp", &config);
236     if (retval == 0 && config == NULL)
237         retval = ENOENT;
238     if (retval != 0)
239         goto out;
240     cb->free_string(context, rock, config);
241 
242     /* Get the armor key.  This indicates the length of random data to use in
243      * the nonce. */
244     armor_key = cb->fast_armor(context, rock);
245     if (armor_key == NULL) {
246         retval = ENOENT;
247         goto out;
248     }
249 
250     /* Build the (mostly empty) challenge. */
251     memset(&ti, 0, sizeof(ti));
252     memset(&chl, 0, sizeof(chl));
253     chl.tokeninfo = tis;
254     ti.format = -1;
255     ti.length = -1;
256     ti.iteration_count = -1;
257 
258     /* Generate the nonce. */
259     retval = nonce_generate(context, armor_key->length, &nonce);
260     if (retval != 0)
261         goto out;
262     chl.nonce = nonce;
263 
264     /* Build the output pa-data. */
265     retval = encode_krb5_pa_otp_challenge(&chl, &encoding);
266     if (retval != 0)
267         goto out;
268     pa = k5alloc(sizeof(krb5_pa_data), &retval);
269     if (pa == NULL) {
270         krb5_free_data(context, encoding);
271         goto out;
272     }
273     pa->pa_type = KRB5_PADATA_OTP_CHALLENGE;
274     pa->contents = (krb5_octet *)encoding->data;
275     pa->length = encoding->length;
276     free(encoding);
277 
278 out:
279     krb5_free_data_contents(context, &nonce);
280     (*respond)(arg, retval, pa);
281 }
282 
283 static void
284 otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
285            krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa,
286            krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
287            krb5_kdcpreauth_moddata moddata,
288            krb5_kdcpreauth_verify_respond_fn respond, void *arg)
289 {
290     krb5_keyblock *armor_key = NULL;
291     krb5_pa_otp_req *req = NULL;
292     struct request_state *rs;
293     krb5_error_code retval;
294     krb5_data d, plaintext;
295     char *config;
296 
297     /* Get the FAST armor key. */
298     armor_key = cb->fast_armor(context, rock);
299     if (armor_key == NULL) {
300         retval = KRB5KDC_ERR_PREAUTH_FAILED;
301         com_err("otp", retval, "No armor key found when verifying padata");
302         goto error;
303     }
304 
305     /* Decode the request. */
306     d = make_data(pa->contents, pa->length);
307     retval = decode_krb5_pa_otp_req(&d, &req);
308     if (retval != 0) {
309         com_err("otp", retval, "Unable to decode OTP request");
310         goto error;
311     }
312 
313     /* Decrypt the nonce from the request. */
314     retval = decrypt_encdata(context, armor_key, req, &plaintext);
315     if (retval != 0) {
316         com_err("otp", retval, "Unable to decrypt nonce");
317         goto error;
318     }
319 
320     /* Verify the nonce or timestamp. */
321     retval = nonce_verify(context, armor_key, &plaintext);
322     if (retval != 0)
323         retval = timestamp_verify(context, &plaintext);
324     krb5_free_data_contents(context, &plaintext);
325     if (retval != 0) {
326         com_err("otp", retval, "Unable to verify nonce or timestamp");
327         goto error;
328     }
329 
330     /* Create the request state.  Save the response callback, and the
331      * enc_tkt_reply pointer so we can set the TKT_FLG_PRE_AUTH flag later. */
332     rs = k5alloc(sizeof(struct request_state), &retval);
333     if (rs == NULL)
334         goto error;
335     rs->context = context;
336     rs->arg = arg;
337     rs->respond = respond;
338     rs->enc_tkt_reply = enc_tkt_reply;
339     rs->preauth_cb = cb;
340     rs->rock = rock;
341 
342     /* Get the principal's OTP configuration string. */
343     retval = cb->get_string(context, rock, "otp", &config);
344     if (retval == 0 && config == NULL)
345         retval = KRB5_PREAUTH_FAILED;
346     if (retval != 0) {
347         free(rs);
348         goto error;
349     }
350 
351     /* Send the request. */
352     otp_state_verify((otp_state *)moddata, cb->event_context(context, rock),
353                      cb->client_name(context, rock), config, req, on_response,
354                      rs);
355     cb->free_string(context, rock, config);
356 
357     k5_free_pa_otp_req(context, req);
358     return;
359 
360 error:
361     k5_free_pa_otp_req(context, req);
362     (*respond)(arg, retval, NULL, NULL, NULL);
363 }
364 
365 krb5_error_code
366 kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
367                       krb5_plugin_vtable vtable);
368 
369 krb5_error_code
370 kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
371                       krb5_plugin_vtable vtable)
372 {
373     krb5_kdcpreauth_vtable vt;
374 
375     if (maj_ver != 1)
376         return KRB5_PLUGIN_VER_NOTSUPP;
377 
378     vt = (krb5_kdcpreauth_vtable)vtable;
379     vt->name = "otp";
380     vt->pa_type_list = otp_pa_type_list;
381     vt->init = otp_init;
382     vt->fini = otp_fini;
383     vt->flags = otp_flags;
384     vt->edata = otp_edata;
385     vt->verify = otp_verify;
386 
387     com_err("otp", 0, "Loaded");
388 
389     return 0;
390 }
391