Add new allocating a2d functions and use them.
[shishi.git] / lib / kdc.c
blob96cee0bee51aaef18df4f10feeb1c2919778e8ac
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, int *saltlen)
46 int len = *saltlen;
47 int tmplen;
48 char format[BUFSIZ];
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 sprintf (format, "padata.?%d.padata-type", i);
63 res = shishi_asn1_integer_field (handle, asrep, &patype, format);
64 if (res != SHISHI_OK)
65 return res;
67 if (patype == SHISHI_PA_PW_SALT)
69 sprintf (format, "padata.?%d.padata-value", i);
70 res = shishi_asn1_read (handle, asrep, format, salt, saltlen);
71 if (res != SHISHI_OK)
72 return res;
74 return SHISHI_OK;
78 len = *saltlen;
79 res =
80 shishi_asn1_read (handle, asreq, "req-body.realm", salt, &len);
81 if (res != SHISHI_OK)
82 return res;
84 res = shishi_asn1_number_of_elements (handle, asreq,
85 "req-body.cname.name-string",
86 &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 sprintf (format, "req-body.cname.name-string.?%d", i);
97 res = shishi_asn1_read (handle, asreq, format, salt + len, &tmplen);
98 if (res != SHISHI_OK)
99 return res;
101 len += tmplen;
104 *saltlen = len;
106 return SHISHI_OK;
110 shishi_kdcreq_sendrecv (Shishi * handle, Shishi_asn1 kdcreq,
111 Shishi_asn1 * kdcrep)
113 char *der;
114 int der_len, out_len;
115 char buffer[BUFSIZ]; /* XXX dynamically allocate this? */
116 char realm[BUFSIZ]; /* XXX dynamically allocate this */
117 int realmlen;
118 int res;
120 res = shishi_new_a2d (handle, kdcreq, &der, &der_len);
121 if (res != SHISHI_OK)
123 shishi_error_printf (handle, "Could not DER encode AS-REQ: %s\n",
124 shishi_strerror (res));
125 return res;
128 realmlen = sizeof (realm);
129 res = shishi_asn1_field (handle, kdcreq, realm, &realmlen,
130 "req-body.realm");
131 if (res != SHISHI_OK)
133 shishi_error_printf (handle, "Could not get realm: %s\n",
134 shishi_strerror_details (handle));
135 return res;
137 realm[realmlen] = '\0';
139 out_len = sizeof(buffer);
140 res = shishi_kdc_sendrecv (handle, realm, der, der_len, buffer, &out_len);
141 if (res != SHISHI_OK)
143 shishi_error_printf (handle, "Could not send to KDC: %s\n",
144 shishi_strerror_details (handle));
145 return res;
147 free(der);
149 if (VERBOSEASN1 (handle))
150 printf ("received %d bytes\n", out_len);
152 *kdcrep = shishi_der2asn1_asrep (handle, buffer, out_len);
153 if (*kdcrep == NULL)
155 *kdcrep = shishi_der2asn1_tgsrep (handle, buffer, out_len);
156 if (*kdcrep == NULL)
158 *kdcrep = shishi_der2asn1_kdcrep (handle, buffer, out_len);
159 if (*kdcrep == NULL)
161 *kdcrep = shishi_der2asn1_krberror (handle, buffer, out_len);
162 if (*kdcrep == NULL)
164 shishi_error_printf
165 (handle, "Could not DER decode AS-REP/KRB-ERROR: %s",
166 shishi_strerror_details (handle));
167 return SHISHI_ASN1_ERROR;
170 shishi_error_clear (handle);
171 return SHISHI_GOT_KRBERROR;
173 else
175 printf
176 ("Buggy server replied with KDC-REP instead of AS-REP\n");
181 return SHISHI_OK;
185 * shishi_kdc_copy_crealm:
186 * @handle: shishi handle as allocated by shishi_init().
187 * @kdcreq: KDC-REQ to read crealm from.
188 * @encticketpart: EncTicketPart to set crealm in.
190 * Set crealm in KDC-REP to value in EncTicketPart.
192 * Return value: Returns SHISHI_OK if successful.
195 shishi_kdc_copy_crealm (Shishi * handle,
196 Shishi_asn1 kdcrep, Shishi_asn1 encticketpart)
198 unsigned char buf[BUFSIZ];
199 int buflen;
200 int res;
202 buf[0] = '\0'; /* XXX if crealm is empty, buflen == 0 which
203 causes libtasn1 to strlen(buf)... */
204 buflen = BUFSIZ;
205 res = shishi_asn1_read (handle, encticketpart, "crealm",
206 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",
247 reprealm, &reprealmlen);
248 if (res != SHISHI_OK)
250 shishi_error_printf (handle, "Could not read reply realm: %s\n",
251 shishi_strerror (res));
252 return res;
255 reqrealm[reqrealmlen] = '\0';
256 reprealm[reprealmlen] = '\0';
258 if (VERBOSEASN1 (handle))
260 printf ("request realm: %s\n", reqrealm);
261 printf ("reply realm: %s\n", reprealm);
264 if (strcmp (reqrealm, reprealm) != 0)
265 return SHISHI_REALM_MISMATCH;
267 return SHISHI_OK;
271 * shishi_kdc_copy_crealm:
272 * @handle: shishi handle as allocated by shishi_init().
273 * @kdcreq: KDC-REQ to read cname from.
274 * @encticketpart: EncTicketPart to set cname in.
276 * Set cname in KDC-REP to value in EncTicketPart.
278 * Return value: Returns SHISHI_OK if successful.
281 shishi_kdc_copy_cname (Shishi * handle,
282 Shishi_asn1 kdcrep, Shishi_asn1 encticketpart)
284 unsigned char buf[BUFSIZ];
285 char format[BUFSIZ];
286 int buflen;
287 int res;
288 int i, n;
290 buflen = BUFSIZ;
291 res = shishi_asn1_read (handle, encticketpart,
292 "cname.name-type", buf, &buflen);
293 if (res != SHISHI_OK)
294 return res;
296 res = shishi_asn1_write (handle, kdcrep, "cname.name-type",
297 buf, buflen);
298 if (res != SHISHI_OK)
299 return res;
301 res = shishi_asn1_number_of_elements (handle, encticketpart,
302 "cname.name-string",
303 &n);
304 if (res != SHISHI_OK)
305 return res;
307 res = shishi_asn1_write (handle, kdcrep, "cname.name-string",
308 NULL, 0);
309 if (res != SHISHI_OK)
310 return res;
312 for (i = 1; i <= n; i++)
314 res = shishi_asn1_write (handle, kdcrep, "cname.name-string",
315 "NEW", 1);
316 if (res != SHISHI_OK)
317 return res;
319 sprintf (format, "cname.name-string.?%d", i);
320 buflen = BUFSIZ;
321 res = shishi_asn1_read (handle, encticketpart, format, buf, &buflen);
322 if (res != SHISHI_OK)
323 return res;
325 sprintf (format, "cname.name-string.?%d", i);
326 res = shishi_asn1_write (handle, kdcrep, format, buf, buflen);
327 if (res != SHISHI_OK)
328 return res;
331 return SHISHI_OK;
335 * shishi_as_check_cname:
336 * @handle: shishi handle as allocated by shishi_init().
337 * @kdcreq: AS-REQ to compare client name field in.
338 * @kdcrep: AS-REP to compare client name field in.
340 * Verify that AS-REQ.req-body.realm and AS-REP.crealm fields matches.
341 * This is one of the steps that has to be performed when processing a
342 * AS-REQ and AS-REP exchange, see shishi_kdc_process().
344 * Return value: Returns SHISHI_OK if successful,
345 * SHISHI_CNAME_MISMATCH if the values differ, or an error code.
348 shishi_as_check_cname (Shishi * handle, Shishi_asn1 asreq, Shishi_asn1 asrep)
350 char reqcname[BUFSIZ], repcname[BUFSIZ];
351 int reqcnamelen, repcnamelen;
352 char format[BUFSIZ];
353 int res;
354 int i, j;
356 /* We do not compare msg-type as recommended on the ietf-krb-wg list */
358 res = shishi_asn1_number_of_elements (handle, asreq,
359 "req-body.cname.name-string",
360 &i);
361 if (res != SHISHI_OK)
362 return res;
364 res = shishi_asn1_number_of_elements (handle, asrep,
365 "cname.name-string", &j);
366 if (res != SHISHI_OK)
367 return res;
369 if (i != j)
370 return SHISHI_CNAME_MISMATCH;
372 for (i = 1; i <= j; i++)
374 sprintf (format, "req-body.cname.name-string.?%d", i);
375 reqcnamelen = sizeof (reqcname);
376 res = shishi_asn1_read (handle, asreq, format, reqcname, &reqcnamelen);
377 if (res != SHISHI_OK)
378 return res;
380 sprintf (format, "cname.name-string.?%d", i);
381 repcnamelen = sizeof (repcname);
382 res = shishi_asn1_read (handle, asrep, format, repcname, &repcnamelen);
383 if (res != SHISHI_OK)
384 return res;
386 if (VERBOSEASN1 (handle))
388 reqcname[reqcnamelen] = '\0';
389 repcname[repcnamelen] = '\0';
390 printf ("request cname %d: %s\n", i, reqcname);
391 printf ("reply cname %d: %s\n", i, repcname);
394 if (reqcnamelen != repcnamelen)
395 return SHISHI_CNAME_MISMATCH;
397 if (memcmp (reqcname, repcname, reqcnamelen) != 0)
398 return SHISHI_CNAME_MISMATCH;
401 return SHISHI_OK;
405 * shishi_kdc_copy_nonce:
406 * @handle: shishi handle as allocated by shishi_init().
407 * @kdcreq: KDC-REQ to read nonce from.
408 * @enckdcreppart: EncKDCRepPart to set nonce in.
410 * Set nonce in EncKDCRepPart to value in KDC-REQ.
412 * Return value: Returns SHISHI_OK if successful.
415 shishi_kdc_copy_nonce (Shishi * handle,
416 Shishi_asn1 kdcreq, Shishi_asn1 enckdcreppart)
418 int res;
419 unsigned long nonce;
421 res = shishi_kdcreq_nonce (handle, kdcreq, &nonce);
422 if (res != SHISHI_OK)
423 return res;
425 res = shishi_enckdcreppart_nonce_set (handle, enckdcreppart, nonce);
426 if (res != SHISHI_OK)
427 return res;
429 return SHISHI_OK;
433 * shishi_kdc_check_nonce:
434 * @handle: shishi handle as allocated by shishi_init().
435 * @kdcreq: KDC-REQ to compare nonce field in.
436 * @enckdcreppart: Encrypted KDC-REP part to compare nonce field in.
438 * Verify that KDC-REQ.req-body.nonce and EncKDCRepPart.nonce fields
439 * matches. This is one of the steps that has to be performed when
440 * processing a KDC-REQ and KDC-REP exchange.
442 * Return value: Returns SHISHI_OK if successful,
443 * SHISHI_NONCE_LENGTH_MISMATCH if the nonces have different lengths
444 * (usually indicates that buggy server truncated nonce to 4 bytes),
445 * SHISHI_NONCE_MISMATCH if the values differ, or an error code.
448 shishi_kdc_check_nonce (Shishi * handle,
449 Shishi_asn1 kdcreq, Shishi_asn1 enckdcreppart)
451 unsigned char reqnonce[BUFSIZ];
452 unsigned char repnonce[BUFSIZ];
453 int reqnoncelen = BUFSIZ;
454 int repnoncelen = BUFSIZ;
455 int res;
457 res = shishi_asn1_read (handle, kdcreq, "req-body.nonce",
458 reqnonce, &reqnoncelen);
459 if (res != SHISHI_OK)
461 shishi_error_printf (handle, "Could not read request nonce: %s\n",
462 shishi_strerror (res));
463 return res;
466 res = shishi_asn1_read (handle, enckdcreppart, "nonce",
467 repnonce, &repnoncelen);
468 if (res != SHISHI_OK)
470 shishi_error_printf (handle, "Could not read reply nonce: %s\n",
471 shishi_strerror (res));
472 return res;
475 if (VERBOSEASN1 (handle))
477 int i;
479 printf ("request nonce (len=%d) ", reqnoncelen);
480 for (i = 0; i < reqnoncelen; i++)
481 printf ("%02X", reqnonce[i]);
482 printf ("\n");
483 printf ("reply nonce (len=%d) ", repnoncelen);
484 for (i = 0; i < repnoncelen; i++)
485 printf ("%02X", repnonce[i]);
486 printf ("\n");
489 if (reqnoncelen > 4 && repnoncelen == 4)
491 /* This case warrants some explanation.
493 * RFC 1510 didn't restrict nonce to 4 bytes, so the nonce field
494 * may be longer. There are KDCs that will accept longer nonces
495 * but truncated them to 4 bytes in the response. If we happen
496 * to parse such a KDC request, we consider it OK even though it
497 * isn't. I doubt this is a security problem, because you need
498 * to break the integrity protection of the encryption system
499 * as well as guess the nonce correctly. The nonce doesn't seem
500 * to serve any purpose at all, really.
504 if (memcmp (reqnonce + reqnoncelen - 4, repnonce, 4) != 0)
505 return SHISHI_NONCE_MISMATCH;
507 fprintf (stderr, "warning: server truncated long nonce to 4 bytes\n");
509 return SHISHI_OK;
512 if (reqnoncelen != repnoncelen ||
513 memcmp (reqnonce, repnonce, repnoncelen) != 0)
514 return SHISHI_NONCE_MISMATCH;
516 return SHISHI_OK;
520 * shishi_tgs_process:
521 * @handle: shishi handle as allocated by shishi_init().
522 * @kdcreq: input variable that holds the sent KDC-REQ.
523 * @kdcrep: input variable that holds the received KDC-REP.
524 * @oldenckdcreppart: input variable with EncKDCRepPart used in request.
525 * @enckdcreppart: output variable that holds new EncKDCRepPart.
527 * Process a TGS client exchange and output decrypted EncKDCRepPart
528 * which holds details for the new ticket received. This function
529 * simply derives the encryption key from the ticket used to construct
530 * the TGS request and calls shishi_kdc_process(), which see.
532 * Return value: Returns SHISHI_OK iff the TGS client exchange was
533 * successful.
536 shishi_tgs_process (Shishi * handle,
537 Shishi_asn1 tgsreq,
538 Shishi_asn1 tgsrep,
539 Shishi_asn1 oldenckdcreppart, Shishi_asn1 * enckdcreppart)
541 Shishi_key *key;
542 int etype;
543 int res;
545 res = shishi_kdcrep_get_enc_part_etype (handle, tgsrep, &etype);
546 if (res != SHISHI_OK)
547 return res;
549 res = shishi_enckdcreppart_get_key (handle, oldenckdcreppart, &key);
550 if (res != SHISHI_OK)
551 return res;
553 if (etype != shishi_key_type (key))
554 return SHISHI_TGSREP_BAD_KEYTYPE;
556 res = shishi_kdc_process (handle, tgsreq, tgsrep, key,
557 SHISHI_KEYUSAGE_ENCTGSREPPART_SESSION_KEY,
558 enckdcreppart);
560 return res;
564 * shishi_as_process:
565 * @handle: shishi handle as allocated by shishi_init().
566 * @kdcreq: input variable that holds the sent KDC-REQ.
567 * @kdcrep: input variable that holds the received KDC-REP.
568 * @string: input variable with zero terminated password.
569 * @enckdcreppart: output variable that holds new EncKDCRepPart.
571 * Process an AS client exchange and output decrypted EncKDCRepPart
572 * which holds details for the new ticket received. This function
573 * simply derives the encryption key from the password and calls
574 * shishi_kdc_process(), which see.
576 * Return value: Returns SHISHI_OK iff the AS client exchange was
577 * successful.
580 shishi_as_process (Shishi * handle,
581 Shishi_asn1 asreq,
582 Shishi_asn1 asrep,
583 const char *string, Shishi_asn1 * enckdcreppart)
585 unsigned char salt[BUFSIZ];
586 int saltlen;
587 int res;
588 Shishi_key *key;
589 int keytype;
591 saltlen = sizeof (salt);
592 res = shishi_as_derive_salt (handle, asreq, asrep, salt, &saltlen);
593 if (res != SHISHI_OK)
594 return res;
596 res = shishi_kdcrep_get_enc_part_etype (handle, asrep, &keytype);
597 if (res != SHISHI_OK)
598 return res;
600 res = shishi_key_from_string (handle, keytype,
601 string, strlen (string),
602 salt, saltlen, NULL, &key);
603 if (res != SHISHI_OK)
604 return res;
606 if (VERBOSENOICE (handle))
607 shishi_key_print (handle, stderr, key);
609 res = shishi_kdc_process (handle, asreq, asrep, key,
610 SHISHI_KEYUSAGE_ENCASREPPART, enckdcreppart);
612 return res;
616 * shishi_kdc_process:
617 * @handle: shishi handle as allocated by shishi_init().
618 * @kdcreq: input variable that holds the sent KDC-REQ.
619 * @kdcrep: input variable that holds the received KDC-REP.
620 * @keytype: input variable that holds type of key.
621 * @key: input array with key to decrypt encrypted part of KDC-REP with.
622 * @keylen: size of input array with key.
623 * @enckdcreppart: output variable that holds new EncKDCRepPart.
625 * Process a KDC client exchange and output decrypted EncKDCRepPart
626 * which holds details for the new ticket received. Use
627 * shishi_kdcrep_get_ticket() to extract the ticket. This function
628 * verifies the various conditions that must hold if the response is
629 * to be considered valid, specifically it compares nonces
630 * (shishi_check_nonces()) and if the exchange was a AS exchange, it
631 * also compares cname and crealm (shishi_check_cname() and
632 * shishi_check_crealm()).
634 * Usually the shishi_as_process() and shishi_tgs_process() functions
635 * should be used instead, since they simplify the decryption key
636 * computation.
638 * Return value: Returns SHISHI_OK iff the KDC client exchange was
639 * successful.
642 shishi_kdc_process (Shishi * handle,
643 Shishi_asn1 kdcreq,
644 Shishi_asn1 kdcrep,
645 Shishi_key * key, int keyusage,
646 Shishi_asn1 * enckdcreppart)
648 int res;
649 int msgtype;
652 If the reply message type is KRB_AS_REP, then the client verifies
653 that the cname and crealm fields in the cleartext portion of the
654 reply match what it requested. If any padata fields are present,
655 they may be used to derive the proper secret key to decrypt the
656 message. The client decrypts the encrypted part of the response
657 using its secret key, verifies that the nonce in the encrypted
658 part matches the nonce it supplied in its request (to detect
659 replays). It also verifies that the sname and srealm in the
660 response match those in the request (or are otherwise expected
661 values), and that the host address field is also correct. It then
662 stores the ticket, session key, start and expiration times, and
663 other information for later use. The key-expiration field from the
664 encrypted part of the response may be checked to notify the user
665 of impending key expiration (the client program could then suggest
666 remedial action, such as a password change).
669 msgtype = 0;
670 res = shishi_asn1_integer_field (handle, kdcrep,
671 &msgtype, "msg-type");
672 if (res != SHISHI_OK)
673 return res;
675 if (msgtype == SHISHI_MSGTYPE_AS_REP)
677 res = shishi_as_check_crealm (handle, kdcreq, kdcrep);
678 if (res != SHISHI_OK)
679 return res;
681 res = shishi_as_check_cname (handle, kdcreq, kdcrep);
682 if (res != SHISHI_OK)
683 return res;
686 res = shishi_kdcrep_decrypt (handle, kdcrep, key, keyusage, enckdcreppart);
687 if (res != SHISHI_OK)
688 return res;
690 res = shishi_kdc_check_nonce (handle, kdcreq, *enckdcreppart);
691 if (res != SHISHI_OK)
692 return res;
694 return SHISHI_OK;