1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/preauth_sam2.c - SAM-2 clpreauth module */
3 /*
4 * Copyright 1995, 2003, 2008, 2012 by the Massachusetts Institute of Technology. All
5 * Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 *
26 */
27
28 #include <k5-int.h>
29 #include <krb5/clpreauth_plugin.h>
30 #include "int-proto.h"
31 #include "os-proto.h"
32 #include "init_creds_ctx.h"
33
34 /* this macro expands to the int,ptr necessary for "%.*s" in an sprintf */
35
36 #define SAMDATA(kdata, str, maxsize) \
37 (int)((kdata.length)? \
38 ((((kdata.length)<=(maxsize))?(kdata.length):strlen(str))): \
39 strlen(str)), \
40 (kdata.length)? \
41 ((((kdata.length)<=(maxsize))?(kdata.data):(str))):(str)
42 static char *
sam_challenge_banner(krb5_int32 sam_type)43 sam_challenge_banner(krb5_int32 sam_type)
44 {
45 char *label;
46
47 switch (sam_type) {
48 case PA_SAM_TYPE_ENIGMA: /* Enigma Logic */
49 label = _("Challenge for Enigma Logic mechanism");
50 break;
51 case PA_SAM_TYPE_DIGI_PATH: /* Digital Pathways */
52 case PA_SAM_TYPE_DIGI_PATH_HEX: /* Digital Pathways */
53 label = _("Challenge for Digital Pathways mechanism");
54 break;
55 case PA_SAM_TYPE_ACTIVCARD_DEC: /* Digital Pathways */
56 case PA_SAM_TYPE_ACTIVCARD_HEX: /* Digital Pathways */
57 label = _("Challenge for Activcard mechanism");
58 break;
59 case PA_SAM_TYPE_SKEY_K0: /* S/key where KDC has key 0 */
60 label = _("Challenge for Enhanced S/Key mechanism");
61 break;
62 case PA_SAM_TYPE_SKEY: /* Traditional S/Key */
63 label = _("Challenge for Traditional S/Key mechanism");
64 break;
65 case PA_SAM_TYPE_SECURID: /* Security Dynamics */
66 label = _("Challenge for Security Dynamics mechanism");
67 break;
68 case PA_SAM_TYPE_SECURID_PREDICT: /* predictive Security Dynamics */
69 label = _("Challenge for Security Dynamics mechanism");
70 break;
71 default:
72 label = _("Challenge from authentication server");
73 break;
74 }
75
76 return(label);
77 }
78
79 static krb5_error_code
sam2_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 * padata,krb5_prompter_fct prompter,void * prompter_data,krb5_pa_data *** out_padata)80 sam2_process(krb5_context context, krb5_clpreauth_moddata moddata,
81 krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
82 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
83 krb5_kdc_req *request, krb5_data *encoded_request_body,
84 krb5_data *encoded_previous_request, krb5_pa_data *padata,
85 krb5_prompter_fct prompter, void *prompter_data,
86 krb5_pa_data ***out_padata)
87 {
88 krb5_init_creds_context ctx = (krb5_init_creds_context)rock;
89 krb5_error_code retval;
90 krb5_sam_challenge_2 *sc2 = NULL;
91 krb5_sam_challenge_2_body *sc2b = NULL;
92 krb5_data tmp_data;
93 krb5_data response_data;
94 char name[100], banner[100], prompt[100], response[100];
95 krb5_prompt kprompt;
96 krb5_prompt_type prompt_type;
97 krb5_data defsalt, *salt;
98 krb5_checksum **cksum;
99 krb5_data *scratch = NULL;
100 krb5_boolean valid_cksum = 0;
101 krb5_enc_sam_response_enc_2 enc_sam_response_enc_2;
102 krb5_sam_response_2 sr2;
103 size_t ciph_len;
104 krb5_pa_data **sam_padata;
105
106 if (prompter == NULL)
107 return KRB5_LIBOS_CANTREADPWD;
108
109 tmp_data.length = padata->length;
110 tmp_data.data = (char *)padata->contents;
111
112 if ((retval = decode_krb5_sam_challenge_2(&tmp_data, &sc2)))
113 return(retval);
114
115 retval = decode_krb5_sam_challenge_2_body(&sc2->sam_challenge_2_body, &sc2b);
116
117 if (retval) {
118 krb5_free_sam_challenge_2(context, sc2);
119 return(retval);
120 }
121
122 if (!sc2->sam_cksum || ! *sc2->sam_cksum) {
123 krb5_free_sam_challenge_2(context, sc2);
124 krb5_free_sam_challenge_2_body(context, sc2b);
125 return(KRB5_SAM_NO_CHECKSUM);
126 }
127
128 if (sc2b->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) {
129 krb5_free_sam_challenge_2(context, sc2);
130 krb5_free_sam_challenge_2_body(context, sc2b);
131 return(KRB5_SAM_UNSUPPORTED);
132 }
133
134 if (!krb5_c_valid_enctype(sc2b->sam_etype)) {
135 krb5_free_sam_challenge_2(context, sc2);
136 krb5_free_sam_challenge_2_body(context, sc2b);
137 return(KRB5_SAM_INVALID_ETYPE);
138 }
139
140 /* All of the above error checks are KDC-specific, that is, they */
141 /* assume a failure in the KDC reply. By returning anything other */
142 /* than KRB5_KDC_UNREACH, KRB5_PREAUTH_FAILED, */
143 /* KRB5_LIBOS_PWDINTR, or KRB5_REALM_CANT_RESOLVE, the client will */
144 /* most likely go on to try the AS_REQ against master KDC */
145
146 if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
147 /* We will need the password to obtain the key used for */
148 /* the checksum, and encryption of the sam_response. */
149 /* Go ahead and get it now, preserving the ordering of */
150 /* prompts for the user. */
151
152 salt = ctx->default_salt ? NULL : &ctx->salt;
153 retval = ctx->gak_fct(context, request->client, sc2b->sam_etype,
154 prompter, prompter_data, salt, &ctx->s2kparams,
155 &ctx->as_key, ctx->gak_data, ctx->rctx.items);
156 if (retval) {
157 krb5_free_sam_challenge_2(context, sc2);
158 krb5_free_sam_challenge_2_body(context, sc2b);
159 return(retval);
160 }
161 }
162
163 snprintf(name, sizeof(name), "%.*s",
164 SAMDATA(sc2b->sam_type_name, _("SAM Authentication"),
165 sizeof(name) - 1));
166
167 snprintf(banner, sizeof(banner), "%.*s",
168 SAMDATA(sc2b->sam_challenge_label,
169 sam_challenge_banner(sc2b->sam_type),
170 sizeof(banner)-1));
171
172 snprintf(prompt, sizeof(prompt), "%s%.*s%s%.*s",
173 sc2b->sam_challenge.length?"Challenge is [":"",
174 SAMDATA(sc2b->sam_challenge, "", 20),
175 sc2b->sam_challenge.length?"], ":"",
176 SAMDATA(sc2b->sam_response_prompt, "passcode", 55));
177
178 response_data.data = response;
179 response_data.length = sizeof(response);
180 kprompt.prompt = prompt;
181 kprompt.hidden = 1;
182 kprompt.reply = &response_data;
183
184 prompt_type = KRB5_PROMPT_TYPE_PREAUTH;
185 k5_set_prompt_types(context, &prompt_type);
186
187 if ((retval = ((*prompter)(context, prompter_data, name,
188 banner, 1, &kprompt)))) {
189 krb5_free_sam_challenge_2(context, sc2);
190 krb5_free_sam_challenge_2_body(context, sc2b);
191 k5_set_prompt_types(context, NULL);
192 return(retval);
193 }
194
195 k5_set_prompt_types(context, NULL);
196
197 /* Generate salt used by string_to_key() */
198 if (ctx->default_salt) {
199 if ((retval =
200 krb5_principal2salt(context, request->client, &defsalt))) {
201 krb5_free_sam_challenge_2(context, sc2);
202 krb5_free_sam_challenge_2_body(context, sc2b);
203 return(retval);
204 }
205 salt = &defsalt;
206 } else {
207 salt = &ctx->salt;
208 defsalt.length = 0;
209 }
210
211 /* Get encryption key to be used for checksum and sam_response */
212 if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
213 /* Retain as_key from above gak_fct call. */
214 if (defsalt.length)
215 free(defsalt.data);
216
217 if (!(sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD)) {
218 /*
219 * If no flags are set, the protocol calls for us to combine the
220 * initial reply key with the SAD, using a method which is only
221 * specified for DES and 3DES enctypes. We no longer support this
222 * case.
223 */
224 krb5_free_sam_challenge_2(context, sc2);
225 krb5_free_sam_challenge_2_body(context, sc2b);
226 return(KRB5_SAM_UNSUPPORTED);
227 }
228 } else {
229 /* as_key = string_to_key(SAD) */
230
231 if (ctx->as_key.length) {
232 krb5_free_keyblock_contents(context, &ctx->as_key);
233 ctx->as_key.length = 0;
234 }
235
236 /* generate a key using the supplied password */
237 retval = krb5_c_string_to_key(context, sc2b->sam_etype,
238 &response_data, salt, &ctx->as_key);
239
240 if (defsalt.length)
241 free(defsalt.data);
242
243 if (retval) {
244 krb5_free_sam_challenge_2(context, sc2);
245 krb5_free_sam_challenge_2_body(context, sc2b);
246 return(retval);
247 }
248 }
249
250 /* Now we have a key, verify the checksum on the sam_challenge */
251
252 cksum = sc2->sam_cksum;
253
254 for (; *cksum; cksum++) {
255 if (!krb5_c_is_keyed_cksum((*cksum)->checksum_type))
256 continue;
257 /* Check this cksum */
258 retval = krb5_c_verify_checksum(context, &ctx->as_key,
259 KRB5_KEYUSAGE_PA_SAM_CHALLENGE_CKSUM,
260 &sc2->sam_challenge_2_body,
261 *cksum, &valid_cksum);
262 if (retval) {
263 krb5_free_data(context, scratch);
264 krb5_free_sam_challenge_2(context, sc2);
265 krb5_free_sam_challenge_2_body(context, sc2b);
266 return(retval);
267 }
268 if (valid_cksum)
269 break;
270 }
271
272 if (!valid_cksum) {
273 krb5_free_sam_challenge_2(context, sc2);
274 krb5_free_sam_challenge_2_body(context, sc2b);
275 /*
276 * Note: We return AP_ERR_BAD_INTEGRITY so upper-level applications
277 * can interpret that as "password incorrect", which is probably
278 * the best error we can return in this situation.
279 */
280 return(KRB5KRB_AP_ERR_BAD_INTEGRITY);
281 }
282
283 /* fill in enc_sam_response_enc_2 */
284 enc_sam_response_enc_2.magic = KV5M_ENC_SAM_RESPONSE_ENC_2;
285 enc_sam_response_enc_2.sam_nonce = sc2b->sam_nonce;
286 if (sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) {
287 enc_sam_response_enc_2.sam_sad = response_data;
288 } else {
289 enc_sam_response_enc_2.sam_sad.data = NULL;
290 enc_sam_response_enc_2.sam_sad.length = 0;
291 }
292
293 /* encode and encrypt enc_sam_response_enc_2 with as_key */
294 retval = encode_krb5_enc_sam_response_enc_2(&enc_sam_response_enc_2,
295 &scratch);
296 if (retval) {
297 krb5_free_sam_challenge_2(context, sc2);
298 krb5_free_sam_challenge_2_body(context, sc2b);
299 return(retval);
300 }
301
302 /* Fill in sam_response_2 */
303 memset(&sr2, 0, sizeof(sr2));
304 sr2.sam_type = sc2b->sam_type;
305 sr2.sam_flags = sc2b->sam_flags;
306 sr2.sam_track_id = sc2b->sam_track_id;
307 sr2.sam_nonce = sc2b->sam_nonce;
308
309 /* Now take care of sr2.sam_enc_nonce_or_sad by encrypting encoded */
310 /* enc_sam_response_enc_2 from above */
311
312 retval = krb5_c_encrypt_length(context, ctx->as_key.enctype,
313 scratch->length, &ciph_len);
314 if (retval) {
315 krb5_free_sam_challenge_2(context, sc2);
316 krb5_free_sam_challenge_2_body(context, sc2b);
317 krb5_free_data(context, scratch);
318 return(retval);
319 }
320 sr2.sam_enc_nonce_or_sad.ciphertext.length = ciph_len;
321
322 sr2.sam_enc_nonce_or_sad.ciphertext.data =
323 (char *)malloc(sr2.sam_enc_nonce_or_sad.ciphertext.length);
324
325 if (!sr2.sam_enc_nonce_or_sad.ciphertext.data) {
326 krb5_free_sam_challenge_2(context, sc2);
327 krb5_free_sam_challenge_2_body(context, sc2b);
328 krb5_free_data(context, scratch);
329 return(ENOMEM);
330 }
331
332 retval = krb5_c_encrypt(context, &ctx->as_key,
333 KRB5_KEYUSAGE_PA_SAM_RESPONSE, NULL, scratch,
334 &sr2.sam_enc_nonce_or_sad);
335 if (retval) {
336 krb5_free_sam_challenge_2(context, sc2);
337 krb5_free_sam_challenge_2_body(context, sc2b);
338 krb5_free_data(context, scratch);
339 krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
340 return(retval);
341 }
342 krb5_free_data(context, scratch);
343 scratch = NULL;
344
345 /* Encode the sam_response_2 */
346 retval = encode_krb5_sam_response_2(&sr2, &scratch);
347 krb5_free_sam_challenge_2(context, sc2);
348 krb5_free_sam_challenge_2_body(context, sc2b);
349 krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
350
351 if (retval) {
352 return (retval);
353 }
354
355 /* Almost there, just need to make padata ! */
356 sam_padata = malloc(2 * sizeof(*sam_padata));
357 if (sam_padata == NULL) {
358 krb5_free_data(context, scratch);
359 return(ENOMEM);
360 }
361 sam_padata[0] = malloc(sizeof(krb5_pa_data));
362 if (sam_padata[0] == NULL) {
363 krb5_free_data(context, scratch);
364 free(sam_padata);
365 return(ENOMEM);
366 }
367
368 sam_padata[0]->magic = KV5M_PA_DATA;
369 sam_padata[0]->pa_type = KRB5_PADATA_SAM_RESPONSE_2;
370 sam_padata[0]->length = scratch->length;
371 sam_padata[0]->contents = (krb5_octet *) scratch->data;
372 free(scratch);
373 sam_padata[1] = NULL;
374
375 *out_padata = sam_padata;
376 cb->disable_fallback(context, rock);
377
378 return(0);
379 }
380
381 static krb5_preauthtype sam2_pa_types[] = {
382 KRB5_PADATA_SAM_CHALLENGE_2, 0};
383
384 krb5_error_code
clpreauth_sam2_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)385 clpreauth_sam2_initvt(krb5_context context, int maj_ver, int min_ver,
386 krb5_plugin_vtable vtable)
387 {
388 krb5_clpreauth_vtable vt;
389
390 if (maj_ver != 1)
391 return KRB5_PLUGIN_VER_NOTSUPP;
392 vt = (krb5_clpreauth_vtable)vtable;
393 vt->name = "sam2";
394 vt->pa_type_list = sam2_pa_types;
395 vt->process = sam2_process;
396 return 0;
397 }
398