1 /*
2 * Test suite for libwebauth Kerberos credential import.
3 *
4 * Tests importing pre-generated credentials that are part of the package test
5 * data, which means that it can run without a Kerberos configuration.
6 *
7 * Written by Russ Allbery <eagle@eyrie.org>
8 * Copyright 2012, 2013
9 * The Board of Trustees of the Leland Stanford Junior University
10 *
11 * See LICENSE for licensing terms.
12 */
13
14 #include <config.h>
15 #include <portable/krb5.h>
16 #include <portable/system.h>
17
18 #include <tests/tap/basic.h>
19 #include <tests/tap/string.h>
20 #include <webauth/basic.h>
21 #include <webauth/krb5.h>
22
23 /* MIT doesn't provide this typedef. */
24 #ifdef HAVE_KRB5_MIT
25 typedef krb5_address **krb5_addresses;
26 #endif
27
28 /*
29 * How to reference addresses, whether addresses are present, and how to free
30 * them. This has to be done differently in MIT and Heimdal since the data
31 * structures they use for addresses are much different.
32 */
33 #ifdef HAVE_KRB5_MIT
34 # define ADDRESSES(data) (data->addresses)
35 # define HAS_ADDRESSES(data) (data->addresses != NULL)
36 # define FREE_ADDRESSES(data) \
37 do { \
38 if (data->addresses != NULL) \
39 krb5_free_addresses(data->ctx, data->addresses); \
40 } while (0)
41 #else
42 # define ADDRESSES(data) (&data->addresses)
43 # define HAS_ADDRESSES(data) (data->addresses.len > 0)
44 # define FREE_ADDRESSES(data) \
45 do { \
46 krb5_free_addresses(data->ctx, &data->addresses); \
47 } while (0)
48 #endif
49
50 /*
51 * Three addresses that we use for testing. The first IPv4 address is the one
52 * in tests/data/creds/service, the second is in tests/data/creds/addresses,
53 * and the third is the IPv6 address in tests/data/creds/addresses.
54 */
55 static const unsigned char test_addr1_data[4] = { 171, 67, 24, 175 };
56 static const unsigned char test_addr2_data[4] = { 171, 67, 225, 134 };
57 static const unsigned char test_addr3_data[16] = {
58 0x26, 0x07, 0xf6, 0xd0, 0x00, 0x00, 0xa2, 0x00,
59 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65
60 };
61
62 /* Build krb5_address structs, which are different across implementations. */
63 #ifdef HAVE_KRB5_MIT
64 static const krb5_address test_addr1 = {
65 KV5M_ADDRESS, ADDRTYPE_INET, 4, (unsigned char *) test_addr1_data
66 };
67 static const krb5_address test_addr2 = {
68 KV5M_ADDRESS, ADDRTYPE_INET, 4, (unsigned char *) test_addr2_data
69 };
70 static const krb5_address test_addr3 = {
71 KV5M_ADDRESS, ADDRTYPE_INET6, 16, (unsigned char *) test_addr3_data
72 };
73 #else
74 static const krb5_address test_addr1 = {
75 KRB5_ADDRESS_INET, { 4, (void *) test_addr1_data }
76 };
77 static const krb5_address test_addr2 = {
78 KRB5_ADDRESS_INET, { 4, (void *) test_addr2_data }
79 };
80 static const krb5_address test_addr3 = {
81 KRB5_ADDRESS_INET6, { 16, (void *) test_addr3_data }
82 };
83 #endif
84
85 /*
86 * Holds Kerberos credential data that we can use in the test. We
87 * intentionally don't extract difficult things like the actual ticket. This
88 * is a separate struct so that we can write two different functions to
89 * extract the data from either MIT or Heimdal into a consistent structure.
90 */
91 struct cred_data {
92 krb5_context ctx;
93 char *client;
94 char *server;
95 time_t endtime;
96 time_t renew_till;
97 bool forwardable;
98 krb5_enctype enctype;
99 krb5_addresses addresses;
100 };
101
102 #define CHECK(ctx, s, m) check_status((ctx), (s), (m), __FILE__, __LINE__)
103 #define CHECK_BAIL(ctx, s) bail_status((ctx), (s), __FILE__, __LINE__)
104
105
106 /*
107 * Check the status of a WebAuth call.
108 */
109 static void
check_status(struct webauth_context * ctx,int s,const char * message,const char * file,unsigned long line)110 check_status(struct webauth_context *ctx, int s, const char *message,
111 const char *file, unsigned long line)
112 {
113 if (s != WA_ERR_NONE)
114 diag("webauth call failed at %s line %lu: %s (%d)\n", file, line,
115 webauth_error_message(ctx, s), s);
116 is_int(s, WA_ERR_NONE, "%s", message);
117 }
118
119
120 /*
121 * The same, but call bail if the WebAuth call fails.
122 */
123 static void
bail_status(struct webauth_context * ctx,int s,const char * file,unsigned long line)124 bail_status(struct webauth_context *ctx, int s, const char *file,
125 unsigned long line)
126 {
127 if (s != WA_ERR_NONE)
128 bail("webauth call failed at %s line %lu: %s (%d)\n", file, line,
129 webauth_error_message(ctx, s), s);
130 }
131
132
133 /*
134 * Extract implementation-specific data from a credential structure. There
135 * are some parts of the credential structure that are named differently based
136 * on whether the implementation is MIT or Heimdal.
137 */
138 #if defined(HAVE_KRB5_MIT)
139
140 static void
extract_cred_data_impl(krb5_context ctx,krb5_creds * cred,struct cred_data * data)141 extract_cred_data_impl(krb5_context ctx, krb5_creds *cred,
142 struct cred_data *data)
143 {
144 data->forwardable = (cred->ticket_flags & TKT_FLG_FORWARDABLE) != 0;
145 data->enctype = cred->keyblock.enctype;
146 if (cred->addresses != NULL && cred->addresses[0] != NULL)
147 krb5_copy_addresses(ctx, cred->addresses, &data->addresses);
148 }
149
150 #else
151
152 static void
extract_cred_data_impl(krb5_context ctx,krb5_creds * cred,struct cred_data * data)153 extract_cred_data_impl(krb5_context ctx, krb5_creds *cred,
154 struct cred_data *data)
155 {
156 data->forwardable = (cred->flags.i & KDC_OPT_FORWARDABLE) != 0;
157 data->enctype = cred->session.keytype;
158 if (cred->addresses.len > 0)
159 krb5_copy_addresses(ctx, &cred->addresses, &data->addresses);
160 }
161
162 #endif
163
164
165 /*
166 * Given a krb5_creds structure, extract data from it into a newly-allocated
167 * cred_data structure and return the new structure.
168 */
169 static struct cred_data *
extract_cred_data(krb5_context ctx,krb5_creds * cred)170 extract_cred_data(krb5_context ctx, krb5_creds *cred)
171 {
172 struct cred_data *data;
173 char *principal;
174
175 data = bcalloc(1, sizeof(struct cred_data));
176 data->ctx = ctx;
177 if (krb5_unparse_name(ctx, cred->client, &principal) == 0) {
178 data->client = bstrdup(principal);
179 krb5_free_unparsed_name(ctx, principal);
180 }
181 if (krb5_unparse_name(ctx, cred->server, &principal) == 0) {
182 data->server = bstrdup(principal);
183 krb5_free_unparsed_name(ctx, principal);
184 }
185 data->endtime = cred->times.endtime;
186 data->renew_till = cred->times.renew_till;
187 extract_cred_data_impl(ctx, cred, data);
188 return data;
189 }
190
191
192 /*
193 * Free a cred_data structure.
194 */
195 static void
free_cred_data(struct cred_data * data)196 free_cred_data(struct cred_data *data)
197 {
198 free(data->server);
199 free(data->client);
200 FREE_ADDRESSES(data);
201 krb5_free_context(data->ctx);
202 free(data);
203 }
204
205
206 /*
207 * Given the path to a test credential and the credential included in it,
208 * create a new WebAuth Kerberos context and initialize it from that test
209 * credential. Then, find the credential in the ticket cache and read the
210 * Kerberos credential from it. Returns the new WebAuth Kerberos context.
211 */
212 static struct cred_data *
import_cred(struct webauth_context * ctx,const char * file)213 import_cred(struct webauth_context *ctx, const char *file)
214 {
215 char *path, *tmpdir, *cache, *message;
216 FILE *input;
217 char buffer[BUFSIZ];
218 size_t size;
219 int s;
220 struct webauth_krb5 *kc;
221 krb5_context krb5_ctx;
222 krb5_ccache cc;
223 krb5_cc_cursor cursor;
224 krb5_creds cred;
225 krb5_error_code code;
226 struct cred_data *data;
227
228 /* Read the encoded token. */
229 path = test_file_path(file);
230 if (path == NULL)
231 sysbail("cannot find %s", file);
232 input = fopen(path, "r");
233 if (input == NULL)
234 sysbail("cannot open %s", path);
235 size = fread(buffer, 1, sizeof(buffer), input);
236 if (ferror(input))
237 sysbail("cannot read %s", path);
238 fclose(input);
239
240 /* Import the credential and create a ticket cache. */
241 tmpdir = test_tmpdir();
242 basprintf(&cache, "%s/krb5cc_import", tmpdir);
243 s = webauth_krb5_new(ctx, &kc);
244 CHECK_BAIL(ctx, s);
245 basprintf(&message, "import %s cred", file);
246 s = webauth_krb5_import_cred(ctx, kc, buffer, size, cache);
247 CHECK(ctx, s, message);
248 free(message);
249
250 /* Create a Kerberos context and pull the credential from the cache. */
251 memset(&cred, 0, sizeof(cred));
252 code = krb5_init_context(&krb5_ctx);
253 if (code != 0)
254 bail("cannot create Kerberos context");
255 code = krb5_cc_resolve(krb5_ctx, cache, &cc);
256 is_int(0, code, "... open Kerberos ticket cache");
257 code = krb5_cc_start_seq_get(krb5_ctx, cc, &cursor);
258 if (code != 0)
259 bail("cannot create cursor on ticket cache");
260 code = krb5_cc_next_cred(krb5_ctx, cc, &cursor, &cred);
261 is_int(0, code, "... read first ticket");
262 krb5_cc_end_seq_get(krb5_ctx, cc, &cursor);
263 krb5_cc_close(krb5_ctx, cc);
264
265 /* Extract credential data into our own struct. */
266 data = extract_cred_data(krb5_ctx, &cred);
267 krb5_free_cred_contents(krb5_ctx, &cred);
268
269 /* Clean up and return. */
270 test_file_path_free(path);
271 free(cache);
272 test_tmpdir_free(tmpdir);
273 return data;
274 }
275
276
277 int
main(void)278 main(void)
279 {
280 struct webauth_context *ctx;
281 struct cred_data *data;
282
283 if (webauth_context_init(&ctx, NULL) != WA_ERR_NONE)
284 bail("cannot initialize WebAuth context");
285
286 plan(63);
287
288 /* Basic credential with nothing special. */
289 data = import_cred(ctx, "data/creds/basic");
290 is_string("thoron@heimdal.stanford.edu", data->client,
291 "... client principal");
292 is_string("krbtgt/heimdal.stanford.edu@heimdal.stanford.edu",
293 data->server, "... server principal");
294 is_int(1355447711, data->endtime, "... end time");
295 is_int(0, data->renew_till, "... not renewable");
296 ok(!data->forwardable, "... not forwardable");
297 is_int(ENCTYPE_AES256_CTS_HMAC_SHA1_96, data->enctype,
298 "... session enctype");
299 ok(!HAS_ADDRESSES(data), "... no addresses");
300 free_cred_data(data);
301
302 /* Forwardable and renewable credential. */
303 data = import_cred(ctx, "data/creds/renewable");
304 is_string("thoron@heimdal.stanford.edu", data->client,
305 "... client principal");
306 is_string("krbtgt/heimdal.stanford.edu@heimdal.stanford.edu",
307 data->server, "... server principal");
308 is_int(1355529261, data->endtime, "... end time");
309 is_int(1356047658, data->renew_till, "... renew until time");
310 ok(data->forwardable, "... forwardable");
311 is_int(ENCTYPE_AES256_CTS_HMAC_SHA1_96, data->enctype,
312 "... session enctype");
313 ok(!HAS_ADDRESSES(data), "... no addresses");
314 free_cred_data(data);
315
316 /* Service ticket with restricted enctypes and address. */
317 data = import_cred(ctx, "data/creds/service");
318 is_string("thoron@heimdal.stanford.edu", data->client,
319 "... client principal");
320 is_string("host/example.stanford.edu@heimdal.stanford.edu",
321 data->server, "... server principal");
322 is_int(1355529505, data->endtime, "... end time");
323 is_int(1356047903, data->renew_till, "... renew until time");
324 ok(data->forwardable, "... forwardable");
325 is_int(ENCTYPE_DES3_CBC_SHA1, data->enctype, "... session enctype");
326 ok(HAS_ADDRESSES(data), "... addresses are present");
327 ok(krb5_address_search(data->ctx, &test_addr1, ADDRESSES(data)),
328 "... found expected IPv4 address");
329 free_cred_data(data);
330
331 /* Ticket with multiple addresses. */
332 data = import_cred(ctx, "data/creds/addresses");
333 is_string("thoron@heimdal.stanford.edu", data->client,
334 "... client principal");
335 is_string("krbtgt/heimdal.stanford.edu@heimdal.stanford.edu",
336 data->server, "... server principal");
337 is_int(1355532627, data->endtime, "... end time");
338 is_int(1356051022, data->renew_till, "... renew until time");
339 ok(data->forwardable, "... forwardable");
340 is_int(ENCTYPE_AES256_CTS_HMAC_SHA1_96, data->enctype,
341 "... session enctype");
342 ok(HAS_ADDRESSES(data), "... addresses are present");
343 ok(krb5_address_search(data->ctx, &test_addr2, ADDRESSES(data)),
344 "... found expected IPv4 address");
345 ok(krb5_address_search(data->ctx, &test_addr3, ADDRESSES(data)),
346 "... found expected IPv6 address");
347 free_cred_data(data);
348
349 /* Active Directory ticket encoded using Heimdal libraries. */
350 data = import_cred(ctx, "data/creds/ad-heimdal");
351 is_string("thoron@NT.STANFORD.EDU", data->client,
352 "... client principal");
353 is_string("krbtgt/NT.STANFORD.EDU@NT.STANFORD.EDU",
354 data->server, "... server principal");
355 is_int(1355559675, data->endtime, "... end time");
356 is_int(1356074475, data->renew_till, "... renew until time");
357 ok(data->forwardable, "... forwardable");
358 is_int(ENCTYPE_AES256_CTS_HMAC_SHA1_96, data->enctype,
359 "... session enctype");
360 ok(!HAS_ADDRESSES(data), "... no addresses");
361 free_cred_data(data);
362
363 /* Heimdal ticket with the old encoding (reversed flag bits). */
364 data = import_cred(ctx, "data/creds/old-heimdal");
365 is_string("thoron@heimdal.stanford.edu", data->client,
366 "... client principal");
367 is_string("krbtgt/heimdal.stanford.edu@heimdal.stanford.edu",
368 data->server, "... server principal");
369 is_int(1355556533, data->endtime, "... end time");
370 is_int(1356074930, data->renew_till, "... renew until time");
371 ok(data->forwardable, "... forwardable");
372 is_int(ENCTYPE_AES256_CTS_HMAC_SHA1_96, data->enctype,
373 "... session enctype");
374 ok(!HAS_ADDRESSES(data), "... no addresses");
375 free_cred_data(data);
376
377 /* Clean up. */
378 webauth_context_free(ctx);
379 return 0;
380 }
381