xref: /freebsd/crypto/krb5/src/lib/krb5/krb/preauth_sam2.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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