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
get_grail_key(krb5_context context,krb5_db_entry * client,krb5_keyblock * key_out)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
decrypt_track_data(krb5_context context,krb5_db_entry * client,krb5_data * enc_track_data,krb5_data * output)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
encrypt_track_data(krb5_context context,krb5_db_entry * client,krb5_data * track_data,krb5_data * output)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
get_grail_edata(krb5_context context,krb5_db_entry * client,krb5_keyblock * client_key,krb5_sam_challenge_2 * sc2_out)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
verify_grail_data(krb5_context context,krb5_db_entry * client,krb5_sam_response_2 * sr2,krb5_enc_tkt_part * enc_tkt_reply,krb5_pa_data * pa,krb5_sam_challenge_2 ** sc2_out)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