1/*
2 * Copyright (c) 2011 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "krb5_locl.h"
35#include <heim-ipc.h>
36
37
38krb5_error_code
39_krb5_fast_cf2(krb5_context context,
40	       krb5_keyblock *key1,
41	       const char *pepper1,
42	       krb5_keyblock *key2,
43	       const char *pepper2,
44	       krb5_keyblock *armorkey,
45	       krb5_crypto *armor_crypto)
46{
47    krb5_crypto crypto1, crypto2;
48    krb5_data pa1, pa2;
49    krb5_error_code ret;
50
51    ret = krb5_crypto_init(context, key1, 0, &crypto1);
52    if (ret)
53	return ret;
54
55    ret = krb5_crypto_init(context, key2, 0, &crypto2);
56    if (ret) {
57	krb5_crypto_destroy(context, crypto1);
58	return ret;
59    }
60
61    pa1.data = rk_UNCONST(pepper1);
62    pa1.length = strlen(pepper1);
63    pa2.data = rk_UNCONST(pepper2);
64    pa2.length = strlen(pepper2);
65
66    ret = krb5_crypto_fx_cf2(context, crypto1, crypto2, &pa1, &pa2,
67			     key1->keytype, armorkey);
68    krb5_crypto_destroy(context, crypto1);
69    krb5_crypto_destroy(context, crypto2);
70    if (ret)
71	return ret;
72
73    if (armor_crypto) {
74	ret = krb5_crypto_init(context, armorkey, 0, armor_crypto);
75	if (ret)
76	    krb5_free_keyblock_contents(context, armorkey);
77    }
78
79    return ret;
80}
81
82krb5_error_code
83_krb5_fast_armor_key(krb5_context context,
84		     krb5_keyblock *subkey,
85		     krb5_keyblock *sessionkey,
86		     krb5_keyblock *armorkey,
87		     krb5_crypto *armor_crypto)
88{
89    return _krb5_fast_cf2(context,
90			  subkey,
91			  "subkeyarmor",
92			  sessionkey,
93			  "ticketarmor",
94			  armorkey,
95			  armor_crypto);
96}
97
98static krb5_error_code
99check_fast(krb5_context context, struct krb5_fast_state *state)
100{
101    if (state && (state->flags & KRB5_FAST_EXPECTED)) {
102	krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
103			       "Expected FAST, but no FAST "
104			       "was in the response from the KDC");
105	return KRB5KRB_AP_ERR_MODIFIED;
106    }
107    return 0;
108}
109
110krb5_error_code
111_krb5_make_fast_ap_fxarmor(krb5_context context,
112			   krb5_ccache armor_ccache,
113			   krb5_data *armor_value,
114			   krb5_keyblock *armor_key,
115			   krb5_crypto *armor_crypto)
116{
117    krb5_auth_context auth_context = NULL;
118    krb5_creds cred, *credp = NULL;
119    krb5_error_code ret;
120    krb5_data empty;
121
122    krb5_data_zero(&empty);
123    memset(&cred, 0, sizeof(cred));
124
125    ret = krb5_auth_con_init (context, &auth_context);
126    if (ret)
127	goto out;
128
129    ret = krb5_cc_get_principal(context, armor_ccache, &cred.client);
130    if (ret)
131	goto out;
132
133    ret = krb5_make_principal(context, &cred.server,
134			      cred.client->realm,
135			      KRB5_TGS_NAME,
136			      cred.client->realm,
137			      NULL);
138    if (ret) {
139	krb5_free_principal(context, cred.client);
140	goto out;
141    }
142
143    ret = krb5_get_credentials(context, 0, armor_ccache, &cred, &credp);
144    krb5_free_principal(context, cred.server);
145    krb5_free_principal(context, cred.client);
146    if (ret)
147	goto out;
148
149    ret = krb5_auth_con_add_AuthorizationData(context, auth_context,
150        KRB5_PADATA_FX_FAST_ARMOR, &empty);
151    if (ret)
152	goto out;
153
154    ret = krb5_mk_req_extended(context,
155			       &auth_context,
156			       AP_OPTS_USE_SUBKEY,
157			       NULL,
158			       credp,
159			       armor_value);
160    if (ret)
161	goto out;
162
163    ret = _krb5_fast_armor_key(context,
164			       auth_context->local_subkey,
165			       auth_context->keyblock,
166			       armor_key,
167			       armor_crypto);
168    if (ret)
169	goto out;
170
171 out:
172    if (auth_context)
173	krb5_auth_con_free(context, auth_context);
174    if (credp)
175	krb5_free_creds(context, credp);
176
177    return ret;
178}
179
180static heim_base_once_t armor_service_once = HEIM_BASE_ONCE_INIT;
181static heim_ipc armor_service = NULL;
182
183static void
184fast_armor_init_ipc(void *ctx)
185{
186    heim_ipc *ipc = ctx;
187    heim_ipc_init_context("ANY:org.h5l.armor-service", ipc);
188}
189
190
191static krb5_error_code
192make_fast_ap_fxarmor(krb5_context context,
193		     struct krb5_fast_state *state,
194		     const char *realm,
195		     KrbFastArmor **armor)
196{
197    KrbFastArmor *fxarmor = NULL;
198    krb5_error_code ret;
199
200    ALLOC(fxarmor, 1);
201    if (fxarmor == NULL) {
202	ret = ENOMEM;
203	goto out;
204    }
205
206    if (state->flags & KRB5_FAST_AP_ARMOR_SERVICE) {
207	KERB_ARMOR_SERVICE_REPLY msg;
208	krb5_data request, reply;
209
210	heim_base_once_f(&armor_service_once, &armor_service, fast_armor_init_ipc);
211	if (armor_service == NULL) {
212	    free(fxarmor);
213	    krb5_set_error_message(context, ENOENT, "Failed to open fast armor service");
214	    return ENOENT;
215	}
216
217	krb5_data_zero(&reply);
218
219	request.data = rk_UNCONST(realm);
220	request.length = strlen(realm);
221
222	ret = heim_ipc_call(armor_service, &request, &reply, NULL);
223	if (ret) {
224	    krb5_set_error_message(context, ret, "Failed to get armor service credential");
225	    goto out;
226	}
227
228	ret = decode_KERB_ARMOR_SERVICE_REPLY(reply.data, reply.length, &msg, NULL);
229	krb5_data_free(&reply);
230	if (ret)
231	    goto out;
232
233	ret = copy_KrbFastArmor(fxarmor, &msg.armor);
234	if (ret) {
235	    free_KERB_ARMOR_SERVICE_REPLY(&msg);
236	    goto out;
237	}
238
239	ret = krb5_copy_keyblock_contents(context, &msg.armor_key, &state->armor_key);
240	free_KERB_ARMOR_SERVICE_REPLY(&msg);
241	if (ret)
242	    goto out;
243
244	ret = krb5_crypto_init(context, &state->armor_key, 0, &state->armor_crypto);
245	if (ret)
246	    goto out;
247
248    } else {
249
250	fxarmor->armor_type = 1;
251
252	ret = _krb5_make_fast_ap_fxarmor(context,
253					 state->armor_ccache,
254					 &fxarmor->armor_value,
255					 &state->armor_key,
256					 &state->armor_crypto);
257	if (ret)
258	    goto out;
259    }
260
261
262    *armor = fxarmor;
263    fxarmor = NULL;
264 out:
265    if (fxarmor) {
266	free_KrbFastArmor(fxarmor);
267	free(fxarmor);
268    }
269    return ret;
270}
271
272static krb5_error_code
273unwrap_fast_rep(krb5_context context,
274		struct krb5_fast_state *state,
275		PA_DATA *pa,
276		KrbFastResponse *fastrep)
277{
278    PA_FX_FAST_REPLY fxfastrep;
279    krb5_error_code ret;
280
281    memset(&fxfastrep, 0, sizeof(fxfastrep));
282
283    ret = decode_PA_FX_FAST_REPLY(pa->padata_value.data, pa->padata_value.length, &fxfastrep, NULL);
284    if (ret)
285	return ret;
286
287    if (fxfastrep.element == choice_PA_FX_FAST_REPLY_armored_data) {
288	krb5_data data;
289
290	ret = krb5_decrypt_EncryptedData(context,
291					 state->armor_crypto,
292					 KRB5_KU_FAST_REP,
293					 &fxfastrep.u.armored_data.enc_fast_rep,
294					 &data);
295	if (ret)
296	    goto out;
297
298	ret = decode_KrbFastResponse(data.data, data.length, fastrep, NULL);
299	krb5_data_free(&data);
300	if (ret)
301	    goto out;
302
303    } else {
304	ret = KRB5KDC_ERR_PREAUTH_FAILED;
305	goto out;
306    }
307
308 out:
309    free_PA_FX_FAST_REPLY(&fxfastrep);
310
311    return ret;
312}
313
314static krb5_error_code
315set_anon_principal(krb5_context context, PrincipalName **p)
316{
317
318    ALLOC((*p), 1);
319    if (*p == NULL)
320	goto fail;
321
322    (*p)->name_type = KRB5_NT_PRINCIPAL;
323
324    ALLOC_SEQ(&(*p)->name_string, 2);
325    if ((*p)->name_string.val == NULL)
326	goto fail;
327
328    (*p)->name_string.val[0] = strdup(KRB5_WELLKNOWN_NAME);
329    if ((*p)->name_string.val[0] == NULL)
330	goto fail;
331
332    (*p)->name_string.val[1] = strdup(KRB5_ANON_NAME);
333    if ((*p)->name_string.val[1] == NULL)
334	goto fail;
335
336    return 0;
337 fail:
338    if (*p) {
339	if ((*p)->name_string.val) {
340	    free((*p)->name_string.val[0]);
341	    free((*p)->name_string.val[1]);
342	    free((*p)->name_string.val);
343	}
344	free(*p);
345    }
346
347    return krb5_enomem(context);
348}
349
350krb5_error_code
351_krb5_fast_create_armor(krb5_context context,
352			struct krb5_fast_state *state,
353			const char *realm)
354{
355    krb5_error_code ret;
356
357    if (state->armor_crypto == NULL) {
358	if (state->armor_ccache || state->armor_ac || (state->flags & KRB5_FAST_AP_ARMOR_SERVICE)) {
359	    /*
360	     * Instead of keeping state in FX_COOKIE in the KDC, we
361	     * rebuild a new armor key for every request, because this
362	     * is what the MIT KDC expect and RFC6113 is vage about
363	     * what the behavior should be.
364	     */
365	    state->type = choice_PA_FX_FAST_REQUEST_armored_data;
366	} else {
367	    return check_fast(context, state);
368	}
369    }
370
371    if (state->type == choice_PA_FX_FAST_REQUEST_armored_data) {
372
373	if (state->armor_crypto)
374	    krb5_crypto_destroy(context, state->armor_crypto);
375	krb5_free_keyblock_contents(context, &state->armor_key);
376
377	/*
378	 * If we have a armor auth context, its because the caller
379	 * wants us to do an implicit FAST armor (TGS-REQ).
380	 */
381	if (state->armor_ac) {
382	    heim_assert((state->flags & KRB5_FAST_AS_REQ) == 0, "FAST AS with AC");
383
384	    ret = _krb5_fast_armor_key(context,
385				       state->armor_ac->local_subkey,
386				       state->armor_ac->keyblock,
387				       &state->armor_key,
388				       &state->armor_crypto);
389	    if (ret)
390		goto out;
391
392	} else {
393	    heim_assert((state->flags & KRB5_FAST_AS_REQ) != 0, "FAST TGS without AC");
394
395	    if (state->armor_data) {
396		free_KrbFastArmor(state->armor_data);
397		free(state->armor_data);
398	    }
399	    ret = make_fast_ap_fxarmor(context, state, realm,
400				       &state->armor_data);
401	    if (ret)
402		goto out;
403	}
404    } else {
405	heim_abort("unknown state type: %d", (int)state->type);
406    }
407 out:
408    return ret;
409}
410
411
412krb5_error_code
413_krb5_fast_wrap_req(krb5_context context,
414		    struct krb5_fast_state *state,
415		    krb5_data *checksum_data,
416		    KDC_REQ *req)
417{
418    PA_FX_FAST_REQUEST fxreq;
419    krb5_error_code ret;
420    KrbFastReq fastreq;
421    krb5_data data, aschecksum_data;
422    size_t size = 0;
423
424    if (state->flags & KRB5_FAST_DISABLED) {
425	_krb5_debugx(context, 10, "fast disabled, not doing any fast wrapping");
426	return 0;
427    }
428
429    memset(&fxreq, 0, sizeof(fxreq));
430    memset(&fastreq, 0, sizeof(fastreq));
431    krb5_data_zero(&data);
432    krb5_data_zero(&aschecksum_data);
433
434    if (state->armor_crypto == NULL)
435	return check_fast(context, state);
436
437    state->flags |= KRB5_FAST_EXPECTED;
438
439    fastreq.fast_options.hide_client_names = 1;
440
441    ret = copy_KDC_REQ_BODY(&req->req_body, &fastreq.req_body);
442    if (ret)
443	goto out;
444
445    /*
446     * In the case of a AS-REQ, remove all account names. Want to this
447     * for TGS-REQ too, but due to layering this is tricky.
448     *
449     * 1. TGS-REQ need checksum of REQ-BODY
450     * 2. FAST needs checksum of TGS-REQ, so, FAST needs to happen after TGS-REQ
451     * 3. FAST privacy mangaling needs to happen before TGS-REQ does the checksum in 1.
452     *
453     * So lets not modify the bits for now for TGS-REQ
454     */
455    if (state->flags & KRB5_FAST_AS_REQ) {
456
457	free_KDC_REQ_BODY(&req->req_body);
458
459	req->req_body.realm = strdup(KRB5_ANON_REALM);
460	if (req->req_body.realm == NULL) {
461	    ret = krb5_enomem(context);
462	    goto out;
463	}
464
465	ret = set_anon_principal(context, &req->req_body.cname);
466	if (ret)
467	    goto out;
468
469	ALLOC(req->req_body.till, 1);
470	*req->req_body.till = 0;
471
472	heim_assert(checksum_data == NULL, "checksum data not NULL");
473
474	ASN1_MALLOC_ENCODE(KDC_REQ_BODY,
475			   aschecksum_data.data,
476			   aschecksum_data.length,
477			   &req->req_body,
478			   &size, ret);
479	if (ret)
480	    goto out;
481	heim_assert(aschecksum_data.length == size, "ASN.1 internal error");
482
483	checksum_data = &aschecksum_data;
484    }
485
486    if (req->padata) {
487	ret = copy_METHOD_DATA(req->padata, &fastreq.padata);
488	free_METHOD_DATA(req->padata);
489	if (ret)
490	    goto out;
491    } else {
492	ALLOC(req->padata, 1);
493	if (req->padata == NULL) {
494	    ret = krb5_enomem(context);
495	    goto out;
496	}
497    }
498
499
500    ASN1_MALLOC_ENCODE(KrbFastReq, data.data, data.length, &fastreq, &size, ret);
501    if (ret)
502	goto out;
503    heim_assert(data.length == size, "ASN.1 internal error");
504
505    fxreq.element = state->type;
506
507    if (state->type == choice_PA_FX_FAST_REQUEST_armored_data) {
508
509	fxreq.u.armored_data.armor = state->armor_data;
510	state->armor_data = NULL;
511	if (ret)
512	    goto out;
513
514	heim_assert(state->armor_crypto != NULL,
515		    "FAST armor key missing when FAST started");
516
517	ret = krb5_create_checksum(context, state->armor_crypto,
518				   KRB5_KU_FAST_REQ_CHKSUM, 0,
519				   checksum_data->data,
520				   checksum_data->length,
521				   &fxreq.u.armored_data.req_checksum);
522	if (ret)
523	    goto out;
524
525	ret = krb5_encrypt_EncryptedData(context, state->armor_crypto,
526					 KRB5_KU_FAST_ENC,
527					 data.data,
528					 data.length,
529					 0,
530					 &fxreq.u.armored_data.enc_fast_req);
531	krb5_data_free(&data);
532	if (ret)
533	    goto out;
534
535    } else {
536	krb5_data_free(&data);
537	heim_assert(false, "unknown FAST type, internal error");
538    }
539
540    ASN1_MALLOC_ENCODE(PA_FX_FAST_REQUEST, data.data, data.length, &fxreq, &size, ret);
541    if (ret)
542	goto out;
543    heim_assert(data.length == size, "ASN.1 internal error");
544
545
546    ret = krb5_padata_add(context, req->padata, KRB5_PADATA_FX_FAST, data.data, data.length);
547    if (ret)
548	goto out;
549    krb5_data_zero(&data);
550
551 out:
552    free_PA_FX_FAST_REQUEST(&fxreq);
553    krb5_data_free(&data);
554    krb5_data_free(&aschecksum_data);
555
556    return ret;
557}
558
559krb5_error_code
560_krb5_fast_unwrap_error(krb5_context context, struct krb5_fast_state *state,
561			METHOD_DATA *md, KRB_ERROR *error)
562{
563    KrbFastResponse fastrep;
564    krb5_error_code ret;
565    PA_DATA *pa;
566    int idx;
567
568    if (state->armor_crypto == NULL)
569	return check_fast(context, state);
570
571    memset(&fastrep, 0, sizeof(fastrep));
572
573    if (error->error_code != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
574	_krb5_debugx(context, 10, "using fast but no FAST error code ?");
575
576    idx = 0;
577    pa = krb5_find_padata(md->val, md->len, KRB5_PADATA_FX_FAST, &idx);
578    if (pa == NULL) {
579	ret = KRB5_KDCREP_MODIFIED;
580	krb5_set_error_message(context, ret, N_("FAST fast respons is missing fx-fast data", ""));
581	goto out;
582    }
583
584    ret = unwrap_fast_rep(context, state, pa, &fastrep);
585    if (ret)
586	goto out;
587
588    idx = 0;
589    pa = krb5_find_padata(fastrep.padata.val, fastrep.padata.len, KRB5_PADATA_FX_ERROR, &idx);
590    if (pa == NULL) {
591	ret = KRB5_KDCREP_MODIFIED;
592	krb5_set_error_message(context, ret, N_("No wrapped error", ""));
593	goto out;
594    }
595
596    free_KRB_ERROR(error);
597
598    ret = krb5_rd_error(context, &pa->padata_value, error);
599    if (ret)
600	goto out;
601
602    if (error->e_data)
603	_krb5_debugx(context, 10, "FAST wrapped KBB_ERROR contained e_data: %d",
604		     (int)error->e_data->length);
605
606    free_METHOD_DATA(md);
607    md->val = fastrep.padata.val;
608    md->len = fastrep.padata.len;
609
610    fastrep.padata.val = NULL;
611    fastrep.padata.len = 0;
612
613 out:
614    free_KrbFastResponse(&fastrep);
615    return ret;
616}
617
618krb5_error_code
619_krb5_fast_unwrap_kdc_rep(krb5_context context, int32_t nonce,
620			  krb5_data *chksumdata,
621			  struct krb5_fast_state *state, AS_REP *rep)
622{
623    KrbFastResponse fastrep;
624    krb5_error_code ret;
625    PA_DATA *pa = NULL;
626    int idx = 0;
627
628    if (state == NULL || state->armor_crypto == NULL || rep->padata == NULL)
629	return check_fast(context, state);
630
631    /* find PA_FX_FAST_REPLY */
632
633    pa = krb5_find_padata(rep->padata->val, rep->padata->len,
634			  KRB5_PADATA_FX_FAST, &idx);
635    if (pa == NULL)
636	return check_fast(context, state);
637
638    memset(&fastrep, 0, sizeof(fastrep));
639
640    ret = unwrap_fast_rep(context, state, pa, &fastrep);
641    if (ret)
642	goto out;
643
644    free_METHOD_DATA(rep->padata);
645    ret = copy_METHOD_DATA(&fastrep.padata, rep->padata);
646    if (ret)
647	goto out;
648
649    if (fastrep.strengthen_key) {
650	if (state->strengthen_key)
651	    krb5_free_keyblock(context, state->strengthen_key);
652
653	ret = krb5_copy_keyblock(context, fastrep.strengthen_key, &state->strengthen_key);
654	if (ret)
655	    goto out;
656    }
657
658    if (nonce != (int32_t)fastrep.nonce) {
659	ret = KRB5KDC_ERR_PREAUTH_FAILED;
660	goto out;
661    }
662    if (fastrep.finished) {
663	PrincipalName cname;
664	krb5_realm crealm = NULL;
665
666#if 0
667	if (chksumdata == NULL) {
668	    ret = KRB5KDC_ERR_PREAUTH_FAILED;
669	    goto out;
670	}
671
672	ret = krb5_verify_checksum(context, state->armor_crypto,
673				   KRB5_KU_FAST_FINISHED,
674				   chksumdata->data, chksumdata->length,
675				   &fastrep.finished->ticket_checksum);
676	if (ret)
677	    goto out;
678#endif
679
680	/* update */
681	ret = copy_Realm(&fastrep.finished->crealm, &crealm);
682	if (ret)
683	    goto out;
684	free_Realm(&rep->crealm);
685	rep->crealm = crealm;
686
687	ret = copy_PrincipalName(&fastrep.finished->cname, &cname);
688	if (ret)
689	    goto out;
690	free_PrincipalName(&rep->cname);
691	rep->cname = cname;
692
693#if 0 /* store authenticated checksum as kdc-offset */
694	fastrep->finished.timestamp;
695	fastrep->finished.usec = 0;
696#endif
697
698    } else if (chksumdata) {
699	/* expected fastrep.finish but didn't get it */
700	ret = KRB5KDC_ERR_PREAUTH_FAILED;
701    }
702
703 out:
704    return ret;
705}
706
707void
708_krb5_fast_free(krb5_context context, struct krb5_fast_state *state)
709{
710    if (state->armor_ccache)
711	krb5_cc_close(context, state->armor_ccache);
712    if (state->armor_service)
713	krb5_free_principal(context, state->armor_service);
714    if (state->armor_crypto)
715	krb5_crypto_destroy(context, state->armor_crypto);
716    if (state->strengthen_key)
717	krb5_free_keyblock(context, state->strengthen_key);
718    krb5_free_keyblock_contents(context, &state->armor_key);
719    if (state->armor_data) {
720	free_KrbFastArmor(state->armor_data);
721	free(state->armor_data);
722    }
723    memset(state, 0, sizeof(*state));
724}
725