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
decrypt_encdata(krb5_context context,krb5_keyblock * armor_key,krb5_pa_otp_req * req,krb5_data * out)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
nonce_verify(krb5_context ctx,krb5_keyblock * armor_key,const krb5_data * nonce)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
timestamp_verify(krb5_context ctx,const krb5_data * nonce)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
nonce_generate(krb5_context ctx,unsigned int length,krb5_data * nonce_out)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
on_response(void * data,krb5_error_code retval,otp_response response,char * const * indicators)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
otp_init(krb5_context context,krb5_kdcpreauth_moddata * moddata_out,const char ** realmnames)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
otp_fini(krb5_context context,krb5_kdcpreauth_moddata moddata)209 otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)
210 {
211 otp_state_free((otp_state *)moddata);
212 }
213
214 static int
otp_flags(krb5_context context,krb5_preauthtype pa_type)215 otp_flags(krb5_context context, krb5_preauthtype pa_type)
216 {
217 return PA_REPLACES_KEY;
218 }
219
220 static void
otp_edata(krb5_context context,krb5_kdc_req * request,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_preauthtype pa_type,krb5_kdcpreauth_edata_respond_fn respond,void * arg)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
otp_verify(krb5_context context,krb5_data * req_pkt,krb5_kdc_req * request,krb5_enc_tkt_part * enc_tkt_reply,krb5_pa_data * pa,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_verify_respond_fn respond,void * arg)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
kdcpreauth_otp_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)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