Fixes.
[shishi.git] / lib / kdc.c
blobb2d4164be4cc8c91a2135a7451af7818abee59b3
1 /* kdc.c Key distribution (AS/TGS) functions
2 * Copyright (C) 2002, 2003 Simon Josefsson
4 * This file is part of Shishi.
6 * Shishi is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Shishi is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Shishi; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "internal.h"
24 /**
25 * shishi_as_derive_salt:
26 * @handle: shishi handle as allocated by shishi_init().
27 * @asrep: input AS-REP variable.
28 * @asrep: input AS-REP variable.
29 * @salt: output array with salt.
30 * @saltlen: on input, maximum size of output array with salt, on output,
31 * holds actual size of output array with salt.
33 * Derive the salt that should be used when deriving a key via
34 * shishi_string_to_key() for an AS exchange. Currently this searches
35 * for PA-DATA of type SHISHI_PA_PW_SALT in the AS-REP and returns it
36 * if found, otherwise the salt is derived from the client name and
37 * realm in AS-REQ.
39 * Return value: Returns SHISHI_OK iff successful.
40 **/
41 int
42 shishi_as_derive_salt (Shishi * handle,
43 Shishi_asn1 asreq,
44 Shishi_asn1 asrep, char *salt, size_t * saltlen)
46 int len = *saltlen;
47 int tmplen;
48 char *format;
49 int res;
50 int i, n;
52 res = shishi_asn1_number_of_elements (handle, asrep, "padata", &n);
53 if (res == SHISHI_ASN1_NO_ELEMENT)
54 n = 0;
55 else if (res != SHISHI_OK)
56 return res;
58 for (i = 1; i <= n; i++)
60 int patype;
62 asprintf (&format, "padata.?%d.padata-type", i);
63 res = shishi_asn1_read_int32 (handle, asrep, format, &patype);
64 free (format);
65 if (res != SHISHI_OK)
66 return res;
68 if (patype == SHISHI_PA_PW_SALT)
70 asprintf (&format, "padata.?%d.padata-value", i);
71 res = shishi_asn1_read (handle, asrep, format, salt, saltlen);
72 free (format);
73 if (res != SHISHI_OK)
74 return res;
76 return SHISHI_OK;
80 len = *saltlen;
81 res = shishi_asn1_read (handle, asreq, "req-body.realm", salt, &len);
82 if (res != SHISHI_OK)
83 return res;
85 res = shishi_asn1_number_of_elements (handle, asreq,
86 "req-body.cname.name-string", &n);
87 if (res != SHISHI_OK)
88 return res;
90 for (i = 1; i <= n; i++)
92 tmplen = *saltlen - len;
93 if (tmplen < 0)
94 return SHISHI_TOO_SMALL_BUFFER;
96 asprintf (&format, "req-body.cname.name-string.?%d", i);
97 res = shishi_asn1_read (handle, asreq, format, salt + len, &tmplen);
98 free (format);
99 if (res != SHISHI_OK)
100 return res;
102 len += tmplen;
105 *saltlen = len;
107 return SHISHI_OK;
111 shishi_kdcreq_sendrecv (Shishi * handle, Shishi_asn1 kdcreq,
112 Shishi_asn1 * kdcrep)
114 char *der;
115 int der_len, out_len;
116 char buffer[BUFSIZ]; /* XXX dynamically allocate this? */
117 char realm[BUFSIZ]; /* XXX dynamically allocate this */
118 int realmlen;
119 int res;
121 res = shishi_new_a2d (handle, kdcreq, &der, &der_len);
122 if (res != SHISHI_OK)
124 shishi_error_printf (handle, "Could not DER encode AS-REQ: %s\n",
125 shishi_strerror (res));
126 return res;
129 realmlen = sizeof (realm);
130 res = shishi_asn1_field (handle, kdcreq, realm, &realmlen,
131 "req-body.realm");
132 if (res != SHISHI_OK)
134 shishi_error_printf (handle, "Could not get realm: %s\n",
135 shishi_strerror_details (handle));
136 return res;
138 realm[realmlen] = '\0';
140 out_len = sizeof (buffer);
141 res = shishi_kdc_sendrecv (handle, realm, der, der_len, buffer, &out_len);
142 if (res != SHISHI_OK)
144 shishi_error_printf (handle, "Could not send to KDC: %s\n",
145 shishi_strerror_details (handle));
146 return res;
148 free (der);
150 if (VERBOSEASN1 (handle))
151 printf ("received %d bytes\n", out_len);
153 *kdcrep = shishi_der2asn1_asrep (handle, buffer, out_len);
154 if (*kdcrep == NULL)
156 *kdcrep = shishi_der2asn1_tgsrep (handle, buffer, out_len);
157 if (*kdcrep == NULL)
159 *kdcrep = shishi_der2asn1_kdcrep (handle, buffer, out_len);
160 if (*kdcrep == NULL)
162 *kdcrep = shishi_der2asn1_krberror (handle, buffer, out_len);
163 if (*kdcrep == NULL)
165 shishi_error_printf
166 (handle, "Could not DER decode AS-REP/KRB-ERROR: %s",
167 shishi_strerror_details (handle));
168 return SHISHI_ASN1_ERROR;
171 shishi_error_clear (handle);
172 return SHISHI_GOT_KRBERROR;
174 else
176 printf
177 ("Buggy server replied with KDC-REP instead of AS-REP\n");
182 return SHISHI_OK;
186 * shishi_kdc_copy_crealm:
187 * @handle: shishi handle as allocated by shishi_init().
188 * @kdcreq: KDC-REQ to read crealm from.
189 * @encticketpart: EncTicketPart to set crealm in.
191 * Set crealm in KDC-REP to value in EncTicketPart.
193 * Return value: Returns SHISHI_OK if successful.
196 shishi_kdc_copy_crealm (Shishi * handle,
197 Shishi_asn1 kdcrep, Shishi_asn1 encticketpart)
199 unsigned char buf[BUFSIZ];
200 int buflen;
201 int res;
203 buf[0] = '\0'; /* XXX if crealm is empty, buflen == 0 which
204 causes libtasn1 to strlen(buf)... */
205 buflen = BUFSIZ;
206 res = shishi_asn1_read (handle, encticketpart, "crealm", buf, &buflen);
207 if (res != SHISHI_OK)
208 return res;
210 res = shishi_asn1_write (handle, kdcrep, "crealm", buf, buflen);
211 if (res != SHISHI_OK)
212 return res;
214 return SHISHI_OK;
218 * shishi_as_check_crealm:
219 * @handle: shishi handle as allocated by shishi_init().
220 * @kdcreq: AS-REQ to compare realm field in.
221 * @kdcrep: AS-REP to compare realm field in.
223 * Verify that AS-REQ.req-body.realm and AS-REP.crealm fields matches.
224 * This is one of the steps that has to be performed when processing a
225 * AS-REQ and AS-REP exchange, see shishi_kdc_process().
227 * Return value: Returns SHISHI_OK if successful,
228 * SHISHI_REALM_MISMATCH if the values differ, or an error code.
231 shishi_as_check_crealm (Shishi * handle, Shishi_asn1 asreq, Shishi_asn1 asrep)
233 char reqrealm[BUFSIZ], reprealm[BUFSIZ];
234 int reqrealmlen = BUFSIZ, reprealmlen = BUFSIZ;
235 int res;
237 res = shishi_asn1_read (handle, asreq, "req-body.realm",
238 reqrealm, &reqrealmlen);
239 if (res != SHISHI_OK)
241 shishi_error_printf (handle, "Could not read request realm: %s\n",
242 shishi_strerror (res));
243 return res;
246 res = shishi_asn1_read (handle, asrep, "crealm", reprealm, &reprealmlen);
247 if (res != SHISHI_OK)
249 shishi_error_printf (handle, "Could not read reply realm: %s\n",
250 shishi_strerror (res));
251 return res;
254 reqrealm[reqrealmlen] = '\0';
255 reprealm[reprealmlen] = '\0';
257 if (VERBOSEASN1 (handle))
259 printf ("request realm: %s\n", reqrealm);
260 printf ("reply realm: %s\n", reprealm);
263 if (strcmp (reqrealm, reprealm) != 0)
264 return SHISHI_REALM_MISMATCH;
266 return SHISHI_OK;
270 * shishi_kdc_copy_crealm:
271 * @handle: shishi handle as allocated by shishi_init().
272 * @kdcreq: KDC-REQ to read cname from.
273 * @encticketpart: EncTicketPart to set cname in.
275 * Set cname in KDC-REP to value in EncTicketPart.
277 * Return value: Returns SHISHI_OK if successful.
280 shishi_kdc_copy_cname (Shishi * handle,
281 Shishi_asn1 kdcrep, Shishi_asn1 encticketpart)
283 unsigned char buf[BUFSIZ];
284 char format[BUFSIZ];
285 int buflen;
286 int res;
287 int i, n;
289 buflen = BUFSIZ;
290 res = shishi_asn1_read (handle, encticketpart,
291 "cname.name-type", buf, &buflen);
292 if (res != SHISHI_OK)
293 return res;
295 res = shishi_asn1_write (handle, kdcrep, "cname.name-type", buf, buflen);
296 if (res != SHISHI_OK)
297 return res;
299 res = shishi_asn1_number_of_elements (handle, encticketpart,
300 "cname.name-string", &n);
301 if (res != SHISHI_OK)
302 return res;
304 res = shishi_asn1_write (handle, kdcrep, "cname.name-string", NULL, 0);
305 if (res != SHISHI_OK)
306 return res;
308 for (i = 1; i <= n; i++)
310 res = shishi_asn1_write (handle, kdcrep, "cname.name-string", "NEW", 1);
311 if (res != SHISHI_OK)
312 return res;
314 sprintf (format, "cname.name-string.?%d", i);
315 buflen = BUFSIZ;
316 res = shishi_asn1_read (handle, encticketpart, format, buf, &buflen);
317 if (res != SHISHI_OK)
318 return res;
320 sprintf (format, "cname.name-string.?%d", i);
321 res = shishi_asn1_write (handle, kdcrep, format, buf, buflen);
322 if (res != SHISHI_OK)
323 return res;
326 return SHISHI_OK;
330 * shishi_as_check_cname:
331 * @handle: shishi handle as allocated by shishi_init().
332 * @kdcreq: AS-REQ to compare client name field in.
333 * @kdcrep: AS-REP to compare client name field in.
335 * Verify that AS-REQ.req-body.realm and AS-REP.crealm fields matches.
336 * This is one of the steps that has to be performed when processing a
337 * AS-REQ and AS-REP exchange, see shishi_kdc_process().
339 * Return value: Returns SHISHI_OK if successful,
340 * SHISHI_CNAME_MISMATCH if the values differ, or an error code.
343 shishi_as_check_cname (Shishi * handle, Shishi_asn1 asreq, Shishi_asn1 asrep)
345 char reqcname[BUFSIZ], repcname[BUFSIZ];
346 int reqcnamelen, repcnamelen;
347 char format[BUFSIZ];
348 int res;
349 int i, j;
351 /* We do not compare msg-type as recommended on the ietf-krb-wg list */
353 res = shishi_asn1_number_of_elements (handle, asreq,
354 "req-body.cname.name-string", &i);
355 if (res != SHISHI_OK)
356 return res;
358 res = shishi_asn1_number_of_elements (handle, asrep,
359 "cname.name-string", &j);
360 if (res != SHISHI_OK)
361 return res;
363 if (i != j)
364 return SHISHI_CNAME_MISMATCH;
366 for (i = 1; i <= j; i++)
368 sprintf (format, "req-body.cname.name-string.?%d", i);
369 reqcnamelen = sizeof (reqcname);
370 res = shishi_asn1_read (handle, asreq, format, reqcname, &reqcnamelen);
371 if (res != SHISHI_OK)
372 return res;
374 sprintf (format, "cname.name-string.?%d", i);
375 repcnamelen = sizeof (repcname);
376 res = shishi_asn1_read (handle, asrep, format, repcname, &repcnamelen);
377 if (res != SHISHI_OK)
378 return res;
380 if (VERBOSEASN1 (handle))
382 reqcname[reqcnamelen] = '\0';
383 repcname[repcnamelen] = '\0';
384 printf ("request cname %d: %s\n", i, reqcname);
385 printf ("reply cname %d: %s\n", i, repcname);
388 if (reqcnamelen != repcnamelen)
389 return SHISHI_CNAME_MISMATCH;
391 if (memcmp (reqcname, repcname, reqcnamelen) != 0)
392 return SHISHI_CNAME_MISMATCH;
395 return SHISHI_OK;
399 * shishi_kdc_copy_nonce:
400 * @handle: shishi handle as allocated by shishi_init().
401 * @kdcreq: KDC-REQ to read nonce from.
402 * @enckdcreppart: EncKDCRepPart to set nonce in.
404 * Set nonce in EncKDCRepPart to value in KDC-REQ.
406 * Return value: Returns SHISHI_OK if successful.
409 shishi_kdc_copy_nonce (Shishi * handle,
410 Shishi_asn1 kdcreq, Shishi_asn1 enckdcreppart)
412 int res;
413 uint32_t nonce;
415 res = shishi_kdcreq_nonce (handle, kdcreq, &nonce);
416 if (res != SHISHI_OK)
417 return res;
419 res = shishi_enckdcreppart_nonce_set (handle, enckdcreppart, nonce);
420 if (res != SHISHI_OK)
421 return res;
423 return SHISHI_OK;
427 * shishi_kdc_check_nonce:
428 * @handle: shishi handle as allocated by shishi_init().
429 * @kdcreq: KDC-REQ to compare nonce field in.
430 * @enckdcreppart: Encrypted KDC-REP part to compare nonce field in.
432 * Verify that KDC-REQ.req-body.nonce and EncKDCRepPart.nonce fields
433 * matches. This is one of the steps that has to be performed when
434 * processing a KDC-REQ and KDC-REP exchange.
436 * Return value: Returns SHISHI_OK if successful,
437 * SHISHI_NONCE_LENGTH_MISMATCH if the nonces have different lengths
438 * (usually indicates that buggy server truncated nonce to 4 bytes),
439 * SHISHI_NONCE_MISMATCH if the values differ, or an error code.
442 shishi_kdc_check_nonce (Shishi * handle,
443 Shishi_asn1 kdcreq, Shishi_asn1 enckdcreppart)
445 unsigned char reqnonce[BUFSIZ];
446 unsigned char repnonce[BUFSIZ];
447 int reqnoncelen = BUFSIZ;
448 int repnoncelen = BUFSIZ;
449 int res;
451 res = shishi_asn1_read (handle, kdcreq, "req-body.nonce",
452 reqnonce, &reqnoncelen);
453 if (res != SHISHI_OK)
455 shishi_error_printf (handle, "Could not read request nonce: %s\n",
456 shishi_strerror (res));
457 return res;
460 res = shishi_asn1_read (handle, enckdcreppart, "nonce",
461 repnonce, &repnoncelen);
462 if (res != SHISHI_OK)
464 shishi_error_printf (handle, "Could not read reply nonce: %s\n",
465 shishi_strerror (res));
466 return res;
469 if (VERBOSEASN1 (handle))
471 int i;
473 printf ("request nonce (len=%d) ", reqnoncelen);
474 for (i = 0; i < reqnoncelen; i++)
475 printf ("%02X", reqnonce[i]);
476 printf ("\n");
477 printf ("reply nonce (len=%d) ", repnoncelen);
478 for (i = 0; i < repnoncelen; i++)
479 printf ("%02X", repnonce[i]);
480 printf ("\n");
483 if (reqnoncelen > 4 && repnoncelen == 4)
485 /* This case warrants some explanation.
487 * RFC 1510 didn't restrict nonce to 4 bytes, so the nonce field
488 * may be longer. There are KDCs that will accept longer nonces
489 * but truncated them to 4 bytes in the response. If we happen
490 * to parse such a KDC request, we consider it OK even though it
491 * isn't. I doubt this is a security problem, because you need
492 * to break the integrity protection of the encryption system
493 * as well as guess the nonce correctly. The nonce doesn't seem
494 * to serve any purpose at all, really.
498 if (memcmp (reqnonce + reqnoncelen - 4, repnonce, 4) != 0)
499 return SHISHI_NONCE_MISMATCH;
501 fprintf (stderr, "warning: server truncated long nonce to 4 bytes\n");
503 return SHISHI_OK;
506 if (reqnoncelen != repnoncelen ||
507 memcmp (reqnonce, repnonce, repnoncelen) != 0)
508 return SHISHI_NONCE_MISMATCH;
510 return SHISHI_OK;
514 * shishi_tgs_process:
515 * @handle: shishi handle as allocated by shishi_init().
516 * @kdcreq: input variable that holds the sent KDC-REQ.
517 * @kdcrep: input variable that holds the received KDC-REP.
518 * @oldenckdcreppart: input variable with EncKDCRepPart used in request.
519 * @enckdcreppart: output variable that holds new EncKDCRepPart.
521 * Process a TGS client exchange and output decrypted EncKDCRepPart
522 * which holds details for the new ticket received. This function
523 * simply derives the encryption key from the ticket used to construct
524 * the TGS request and calls shishi_kdc_process(), which see.
526 * Return value: Returns SHISHI_OK iff the TGS client exchange was
527 * successful.
530 shishi_tgs_process (Shishi * handle,
531 Shishi_asn1 tgsreq,
532 Shishi_asn1 tgsrep,
533 Shishi_asn1 oldenckdcreppart, Shishi_asn1 * enckdcreppart)
535 Shishi_key *key;
536 int etype;
537 int res;
539 res = shishi_kdcrep_get_enc_part_etype (handle, tgsrep, &etype);
540 if (res != SHISHI_OK)
541 return res;
543 res = shishi_enckdcreppart_get_key (handle, oldenckdcreppart, &key);
544 if (res != SHISHI_OK)
545 return res;
547 if (etype != shishi_key_type (key))
548 return SHISHI_TGSREP_BAD_KEYTYPE;
550 res = shishi_kdc_process (handle, tgsreq, tgsrep, key,
551 SHISHI_KEYUSAGE_ENCTGSREPPART_SESSION_KEY,
552 enckdcreppart);
554 return res;
558 * shishi_as_process:
559 * @handle: shishi handle as allocated by shishi_init().
560 * @kdcreq: input variable that holds the sent KDC-REQ.
561 * @kdcrep: input variable that holds the received KDC-REP.
562 * @string: input variable with zero terminated password.
563 * @enckdcreppart: output variable that holds new EncKDCRepPart.
565 * Process an AS client exchange and output decrypted EncKDCRepPart
566 * which holds details for the new ticket received. This function
567 * simply derives the encryption key from the password and calls
568 * shishi_kdc_process(), which see.
570 * Return value: Returns SHISHI_OK iff the AS client exchange was
571 * successful.
574 shishi_as_process (Shishi * handle,
575 Shishi_asn1 asreq,
576 Shishi_asn1 asrep,
577 const char *string, Shishi_asn1 * enckdcreppart)
579 unsigned char salt[BUFSIZ];
580 int saltlen;
581 int res;
582 Shishi_key *key;
583 int keytype;
585 saltlen = sizeof (salt);
586 res = shishi_as_derive_salt (handle, asreq, asrep, salt, &saltlen);
587 if (res != SHISHI_OK)
588 return res;
590 res = shishi_kdcrep_get_enc_part_etype (handle, asrep, &keytype);
591 if (res != SHISHI_OK)
592 return res;
594 res = shishi_key_from_string (handle, keytype,
595 string, strlen (string),
596 salt, saltlen, NULL, &key);
597 if (res != SHISHI_OK)
598 return res;
600 if (VERBOSENOICE (handle))
601 shishi_key_print (handle, stderr, key);
603 res = shishi_kdc_process (handle, asreq, asrep, key,
604 SHISHI_KEYUSAGE_ENCASREPPART, enckdcreppart);
606 return res;
610 * shishi_kdc_process:
611 * @handle: shishi handle as allocated by shishi_init().
612 * @kdcreq: input variable that holds the sent KDC-REQ.
613 * @kdcrep: input variable that holds the received KDC-REP.
614 * @keytype: input variable that holds type of key.
615 * @key: input array with key to decrypt encrypted part of KDC-REP with.
616 * @keylen: size of input array with key.
617 * @enckdcreppart: output variable that holds new EncKDCRepPart.
619 * Process a KDC client exchange and output decrypted EncKDCRepPart
620 * which holds details for the new ticket received. Use
621 * shishi_kdcrep_get_ticket() to extract the ticket. This function
622 * verifies the various conditions that must hold if the response is
623 * to be considered valid, specifically it compares nonces
624 * (shishi_check_nonces()) and if the exchange was a AS exchange, it
625 * also compares cname and crealm (shishi_check_cname() and
626 * shishi_check_crealm()).
628 * Usually the shishi_as_process() and shishi_tgs_process() functions
629 * should be used instead, since they simplify the decryption key
630 * computation.
632 * Return value: Returns SHISHI_OK iff the KDC client exchange was
633 * successful.
636 shishi_kdc_process (Shishi * handle,
637 Shishi_asn1 kdcreq,
638 Shishi_asn1 kdcrep,
639 Shishi_key * key, int keyusage,
640 Shishi_asn1 * enckdcreppart)
642 int res;
643 int msgtype;
646 If the reply message type is KRB_AS_REP, then the client verifies
647 that the cname and crealm fields in the cleartext portion of the
648 reply match what it requested. If any padata fields are present,
649 they may be used to derive the proper secret key to decrypt the
650 message. The client decrypts the encrypted part of the response
651 using its secret key, verifies that the nonce in the encrypted
652 part matches the nonce it supplied in its request (to detect
653 replays). It also verifies that the sname and srealm in the
654 response match those in the request (or are otherwise expected
655 values), and that the host address field is also correct. It then
656 stores the ticket, session key, start and expiration times, and
657 other information for later use. The key-expiration field from the
658 encrypted part of the response may be checked to notify the user
659 of impending key expiration (the client program could then suggest
660 remedial action, such as a password change).
663 msgtype = 0;
664 res = shishi_asn1_read_integer (handle, kdcrep, &msgtype, "msg-type");
665 if (res != SHISHI_OK)
666 return res;
668 if (msgtype == SHISHI_MSGTYPE_AS_REP)
670 res = shishi_as_check_crealm (handle, kdcreq, kdcrep);
671 if (res != SHISHI_OK)
672 return res;
674 res = shishi_as_check_cname (handle, kdcreq, kdcrep);
675 if (res != SHISHI_OK)
676 return res;
679 res = shishi_kdcrep_decrypt (handle, kdcrep, key, keyusage, enckdcreppart);
680 if (res != SHISHI_OK)
681 return res;
683 res = shishi_kdc_check_nonce (handle, kdcreq, *enckdcreppart);
684 if (res != SHISHI_OK)
685 return res;
687 return SHISHI_OK;