xref: /freebsd/crypto/krb5/src/plugins/preauth/securid_sam2/grail.c (revision ee3960cba1068e12fb032a68c46d74841d9edab3)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/preauth/securid_sam2/grail.c - Test method for SAM-2 preauth */
3 /*
4  * Copyright (C) 2012 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 test method exists to exercise the client SAM-2 code and some of the
35  * KDC SAM-2 code.  We make up a weakly random number and presents it to the
36  * client in the prompt (in plain text), as well as encrypted in the track ID.
37  * To verify, we compare the decrypted track ID to the entered value.
38  *
39  * Do not use this method in production; it is not secure.
40  */
41 
42 #ifdef GRAIL_PREAUTH
43 
44 #include "k5-int.h"
45 #include <kdb.h>
46 #include <adm_proto.h>
47 #include <ctype.h>
48 #include "extern.h"
49 
50 static krb5_error_code
51 get_grail_key(krb5_context context, krb5_db_entry *client,
52               krb5_keyblock *key_out)
53 {
54     krb5_db_entry *grail_entry = NULL;
55     krb5_key_data *kd;
56     int sam_type = PA_SAM_TYPE_GRAIL;
57     krb5_error_code ret = 0;
58 
59     ret = sam_get_db_entry(context, client->princ, &sam_type, &grail_entry);
60     if (ret)
61         return KRB5_PREAUTH_NO_KEY;
62     ret = krb5_dbe_find_enctype(context, grail_entry, -1, -1, -1, &kd);
63     if (ret)
64         goto cleanup;
65     ret = krb5_dbe_decrypt_key_data(context, NULL, kd, key_out, NULL);
66     if (ret)
67         goto cleanup;
68 
69 cleanup:
70     if (grail_entry)
71         krb5_db_free_principal(context, grail_entry);
72     return ret;
73 }
74 
75 static krb5_error_code
76 decrypt_track_data(krb5_context context, krb5_db_entry *client,
77                    krb5_data *enc_track_data, krb5_data *output)
78 {
79     krb5_error_code ret;
80     krb5_keyblock sam_key;
81     krb5_enc_data enc;
82     krb5_data result = empty_data();
83 
84     sam_key.contents = NULL;
85     *output = empty_data();
86 
87     ret = get_grail_key(context, client, &sam_key);
88     if (ret != 0)
89         return ret;
90     enc.ciphertext = *enc_track_data;
91     enc.enctype = ENCTYPE_UNKNOWN;
92     enc.kvno = 0;
93     ret = alloc_data(&result, enc_track_data->length);
94     if (ret)
95         goto cleanup;
96     ret = krb5_c_decrypt(context, &sam_key,
97                          KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID, 0, &enc,
98                          &result);
99     if (ret)
100         goto cleanup;
101 
102     *output = result;
103     result = empty_data();
104 
105 cleanup:
106     krb5_free_keyblock_contents(context, &sam_key);
107     krb5_free_data_contents(context, &result);
108     return ret;
109 }
110 
111 static krb5_error_code
112 encrypt_track_data(krb5_context context, krb5_db_entry *client,
113                    krb5_data *track_data, krb5_data *output)
114 {
115     krb5_error_code ret;
116     size_t olen;
117     krb5_keyblock sam_key;
118     krb5_enc_data enc;
119 
120     *output = empty_data();
121     enc.ciphertext = empty_data();
122     sam_key.contents = NULL;
123 
124     ret = get_grail_key(context, client, &sam_key);
125     if (ret != 0)
126         return ret;
127 
128     ret = krb5_c_encrypt_length(context, sam_key.enctype,
129                                    track_data->length, &olen);
130     if (ret != 0)
131         goto cleanup;
132     assert(olen <= 65536);
133     ret = alloc_data(&enc.ciphertext, olen);
134     if (ret)
135         goto cleanup;
136     enc.enctype = sam_key.enctype;
137     enc.kvno = 0;
138 
139     ret = krb5_c_encrypt(context, &sam_key,
140                          KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID, 0,
141                          track_data, &enc);
142     if (ret)
143         goto cleanup;
144 
145     *output = enc.ciphertext;
146     enc.ciphertext = empty_data();
147 
148 cleanup:
149     krb5_free_keyblock_contents(context, &sam_key);
150     krb5_free_data_contents(context, &enc.ciphertext);
151     return ret;
152 }
153 
154 krb5_error_code
155 get_grail_edata(krb5_context context, krb5_db_entry *client,
156                 krb5_keyblock *client_key, krb5_sam_challenge_2 *sc2_out)
157 {
158     krb5_error_code ret;
159     krb5_data tmp_data, track_id = empty_data();
160     int tval = time(NULL) % 77777;
161     krb5_sam_challenge_2_body sc2b;
162     char tval_string[256], prompt[256];
163 
164     snprintf(tval_string, sizeof(tval_string), "%d", tval);
165     snprintf(prompt, sizeof(prompt), "Enter %d", tval);
166 
167     memset(&sc2b, 0, sizeof(sc2b));
168     sc2b.magic = KV5M_SAM_CHALLENGE_2;
169     sc2b.sam_track_id = empty_data();
170     sc2b.sam_flags = KRB5_SAM_SEND_ENCRYPTED_SAD;
171     sc2b.sam_type_name = empty_data();
172     sc2b.sam_challenge_label = empty_data();
173     sc2b.sam_challenge = empty_data();
174     sc2b.sam_response_prompt = string2data(prompt);
175     sc2b.sam_pk_for_sad = empty_data();
176     sc2b.sam_type = PA_SAM_TYPE_GRAIL;
177     sc2b.sam_etype = client_key->enctype;
178 
179     tmp_data = string2data(tval_string);
180     ret = encrypt_track_data(context, client, &tmp_data, &track_id);
181     if (ret)
182         goto cleanup;
183     sc2b.sam_track_id = track_id;
184 
185     tmp_data = make_data(&sc2b.sam_nonce, sizeof(sc2b.sam_nonce));
186     ret = krb5_c_random_make_octets(context, &tmp_data);
187     if (ret)
188         goto cleanup;
189 
190     ret = sam_make_challenge(context, &sc2b, client_key, sc2_out);
191 
192 cleanup:
193     krb5_free_data_contents(context, &track_id);
194     return ret;
195 }
196 
197 krb5_error_code
198 verify_grail_data(krb5_context context, krb5_db_entry *client,
199                   krb5_sam_response_2 *sr2, krb5_enc_tkt_part *enc_tkt_reply,
200                   krb5_pa_data *pa, krb5_sam_challenge_2 **sc2_out)
201 {
202     krb5_error_code ret;
203     krb5_key_data *client_key_data = NULL;
204     krb5_keyblock client_key;
205     krb5_data scratch = empty_data(), track_id_data = empty_data();
206     krb5_enc_sam_response_enc_2 *esre2 = NULL;
207 
208     *sc2_out = NULL;
209     memset(&client_key, 0, sizeof(client_key));
210 
211     if ((sr2->sam_enc_nonce_or_sad.ciphertext.data == NULL) ||
212         (sr2->sam_enc_nonce_or_sad.ciphertext.length <= 0))
213         return KRB5KDC_ERR_PREAUTH_FAILED;
214 
215     ret = krb5_dbe_find_enctype(context, client,
216                                 sr2->sam_enc_nonce_or_sad.enctype, -1,
217                                 sr2->sam_enc_nonce_or_sad.kvno,
218                                 &client_key_data);
219     if (ret)
220         goto cleanup;
221 
222     ret = krb5_dbe_decrypt_key_data(context, NULL, client_key_data,
223                                     &client_key, NULL);
224     if (ret)
225         goto cleanup;
226     ret = alloc_data(&scratch, sr2->sam_enc_nonce_or_sad.ciphertext.length);
227     if (ret)
228         goto cleanup;
229     ret = krb5_c_decrypt(context, &client_key, KRB5_KEYUSAGE_PA_SAM_RESPONSE,
230                          NULL, &sr2->sam_enc_nonce_or_sad, &scratch);
231     if (ret)
232         goto cleanup;
233 
234     ret = decode_krb5_enc_sam_response_enc_2(&scratch, &esre2);
235     if (ret)
236         goto cleanup;
237 
238     if (sr2->sam_nonce != esre2->sam_nonce) {
239         ret = KRB5KDC_ERR_PREAUTH_FAILED;
240         goto cleanup;
241     }
242 
243     if (esre2->sam_sad.length == 0 || esre2->sam_sad.data == NULL) {
244         ret = KRB5KDC_ERR_PREAUTH_FAILED;
245         goto cleanup;
246     }
247 
248     ret = decrypt_track_data(context, client, &sr2->sam_track_id,
249                              &track_id_data);
250     if (ret)
251         goto cleanup;
252 
253     /* Some enctypes aren't length-preserving; try to work anyway. */
254     while (track_id_data.length > 0 &&
255            !isdigit(track_id_data.data[track_id_data.length - 1]))
256         track_id_data.length--;
257 
258     if (!data_eq(track_id_data, esre2->sam_sad)) {
259         ret = KRB5KDC_ERR_PREAUTH_FAILED;
260         goto cleanup;
261     }
262 
263     enc_tkt_reply->flags |= (TKT_FLG_HW_AUTH | TKT_FLG_PRE_AUTH);
264 
265 cleanup:
266     krb5_free_keyblock_contents(context, &client_key);
267     krb5_free_data_contents(context, &scratch);
268     krb5_free_enc_sam_response_enc_2(context, esre2);
269     return ret;
270 }
271 
272 #endif /* GRAIL_PREAUTH */
273