Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / crypto / dist / heimdal / kdc / digest.c
blob15bd45452bd9cb09bf69a8664996f99f6cb58ccd
1 /*
2 * Copyright (c) 2006 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
34 #include "kdc_locl.h"
35 #include <hex.h>
37 __RCSID("$Heimdal: digest.c 22374 2007-12-28 18:36:52Z lha $"
38 "$NetBSD: digest.c,v 1.1 2008/03/22 08:37:03 mlelstv Exp $");
40 #define MS_CHAP_V2 0x20
41 #define CHAP_MD5 0x10
42 #define DIGEST_MD5 0x08
43 #define NTLM_V2 0x04
44 #define NTLM_V1_SESSION 0x02
45 #define NTLM_V1 0x01
47 const struct units _kdc_digestunits[] = {
48 {"ms-chap-v2", 1U << 5},
49 {"chap-md5", 1U << 4},
50 {"digest-md5", 1U << 3},
51 {"ntlm-v2", 1U << 2},
52 {"ntlm-v1-session", 1U << 1},
53 {"ntlm-v1", 1U << 0},
54 {NULL, 0}
58 static krb5_error_code
59 get_digest_key(krb5_context context,
60 krb5_kdc_configuration *config,
61 hdb_entry_ex *server,
62 krb5_crypto *crypto)
64 krb5_error_code ret;
65 krb5_enctype enctype;
66 Key *key;
68 ret = _kdc_get_preferred_key(context,
69 config,
70 server,
71 "digest-service",
72 &enctype,
73 &key);
74 if (ret)
75 return ret;
76 return krb5_crypto_init(context, &key->key, 0, crypto);
83 static char *
84 get_ntlm_targetname(krb5_context context,
85 hdb_entry_ex *client)
87 char *targetname, *p;
89 targetname = strdup(krb5_principal_get_realm(context,
90 client->entry.principal));
91 if (targetname == NULL)
92 return NULL;
94 p = strchr(targetname, '.');
95 if (p)
96 *p = '\0';
98 strupr(targetname);
99 return targetname;
102 static krb5_error_code
103 fill_targetinfo(krb5_context context,
104 char *targetname,
105 hdb_entry_ex *client,
106 krb5_data *data)
108 struct ntlm_targetinfo ti;
109 krb5_error_code ret;
110 struct ntlm_buf d;
111 krb5_principal p;
112 const char *str;
114 memset(&ti, 0, sizeof(ti));
116 ti.domainname = targetname;
117 p = client->entry.principal;
118 str = krb5_principal_get_comp_string(context, p, 0);
119 if (str != NULL &&
120 (strcmp("host", str) == 0 ||
121 strcmp("ftp", str) == 0 ||
122 strcmp("imap", str) == 0 ||
123 strcmp("pop", str) == 0 ||
124 strcmp("smtp", str)))
126 str = krb5_principal_get_comp_string(context, p, 1);
127 ti.dnsservername = rk_UNCONST(str);
130 ret = heim_ntlm_encode_targetinfo(&ti, 1, &d);
131 if (ret)
132 return ret;
134 data->data = d.data;
135 data->length = d.length;
137 return 0;
141 static const unsigned char ms_chap_v2_magic1[39] = {
142 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
143 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
144 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
145 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
147 static const unsigned char ms_chap_v2_magic2[41] = {
148 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
149 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
150 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
151 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
152 0x6E
154 static const unsigned char ms_rfc3079_magic1[27] = {
155 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
156 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
157 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79
164 static krb5_error_code
165 get_password_entry(krb5_context context,
166 krb5_kdc_configuration *config,
167 const char *username,
168 char **password)
170 krb5_principal clientprincipal;
171 krb5_error_code ret;
172 hdb_entry_ex *user;
173 HDB *db;
175 /* get username */
176 ret = krb5_parse_name(context, username, &clientprincipal);
177 if (ret)
178 return ret;
180 ret = _kdc_db_fetch(context, config, clientprincipal,
181 HDB_F_GET_CLIENT, &db, &user);
182 krb5_free_principal(context, clientprincipal);
183 if (ret)
184 return ret;
186 ret = hdb_entry_get_password(context, db, &user->entry, password);
187 if (ret || password == NULL) {
188 if (ret == 0) {
189 ret = EINVAL;
190 krb5_set_error_string(context, "password missing");
192 memset(user, 0, sizeof(*user));
194 _kdc_free_ent (context, user);
195 return ret;
202 krb5_error_code
203 _kdc_do_digest(krb5_context context,
204 krb5_kdc_configuration *config,
205 const DigestREQ *req, krb5_data *reply,
206 const char *from, struct sockaddr *addr)
208 krb5_error_code ret = 0;
209 krb5_ticket *ticket = NULL;
210 krb5_auth_context ac = NULL;
211 krb5_keytab id = NULL;
212 krb5_crypto crypto = NULL;
213 DigestReqInner ireq;
214 DigestRepInner r;
215 DigestREP rep;
216 krb5_flags ap_req_options;
217 krb5_data buf;
218 size_t size;
219 krb5_storage *sp = NULL;
220 Checksum res;
221 hdb_entry_ex *server = NULL, *user = NULL;
222 hdb_entry_ex *client = NULL;
223 char *client_name = NULL, *password = NULL;
224 krb5_data serverNonce;
226 if(!config->enable_digest) {
227 kdc_log(context, config, 0,
228 "Rejected digest request (disabled) from %s", from);
229 return KRB5KDC_ERR_POLICY;
232 krb5_data_zero(&buf);
233 krb5_data_zero(reply);
234 krb5_data_zero(&serverNonce);
235 memset(&ireq, 0, sizeof(ireq));
236 memset(&r, 0, sizeof(r));
237 memset(&rep, 0, sizeof(rep));
239 kdc_log(context, config, 0, "Digest request from %s", from);
241 ret = krb5_kt_resolve(context, "HDB:", &id);
242 if (ret) {
243 kdc_log(context, config, 0, "Can't open database for digest");
244 goto out;
247 ret = krb5_rd_req(context,
248 &ac,
249 &req->apReq,
250 NULL,
252 &ap_req_options,
253 &ticket);
254 if (ret)
255 goto out;
257 /* check the server principal in the ticket matches digest/R@R */
259 krb5_principal principal = NULL;
260 const char *p, *r;
262 ret = krb5_ticket_get_server(context, ticket, &principal);
263 if (ret)
264 goto out;
266 ret = EINVAL;
267 krb5_set_error_string(context, "Wrong digest server principal used");
268 p = krb5_principal_get_comp_string(context, principal, 0);
269 if (p == NULL) {
270 krb5_free_principal(context, principal);
271 goto out;
273 if (strcmp(p, KRB5_DIGEST_NAME) != 0) {
274 krb5_free_principal(context, principal);
275 goto out;
278 p = krb5_principal_get_comp_string(context, principal, 1);
279 if (p == NULL) {
280 krb5_free_principal(context, principal);
281 goto out;
283 r = krb5_principal_get_realm(context, principal);
284 if (r == NULL) {
285 krb5_free_principal(context, principal);
286 goto out;
288 if (strcmp(p, r) != 0) {
289 krb5_free_principal(context, principal);
290 goto out;
292 krb5_clear_error_string(context);
294 ret = _kdc_db_fetch(context, config, principal,
295 HDB_F_GET_SERVER, NULL, &server);
296 if (ret)
297 goto out;
299 krb5_free_principal(context, principal);
302 /* check the client is allowed to do digest auth */
304 krb5_principal principal = NULL;
306 ret = krb5_ticket_get_client(context, ticket, &principal);
307 if (ret)
308 goto out;
310 ret = krb5_unparse_name(context, principal, &client_name);
311 if (ret) {
312 krb5_free_principal(context, principal);
313 goto out;
316 ret = _kdc_db_fetch(context, config, principal,
317 HDB_F_GET_CLIENT, NULL, &client);
318 krb5_free_principal(context, principal);
319 if (ret)
320 goto out;
322 if (client->entry.flags.allow_digest == 0) {
323 kdc_log(context, config, 0,
324 "Client %s tried to use digest "
325 "but is not allowed to",
326 client_name);
327 krb5_set_error_string(context,
328 "Client is not permitted to use digest");
329 ret = KRB5KDC_ERR_POLICY;
330 goto out;
334 /* unpack request */
336 krb5_keyblock *key;
338 ret = krb5_auth_con_getremotesubkey(context, ac, &key);
339 if (ret)
340 goto out;
341 if (key == NULL) {
342 krb5_set_error_string(context, "digest: remote subkey not found");
343 ret = EINVAL;
344 goto out;
347 ret = krb5_crypto_init(context, key, 0, &crypto);
348 krb5_free_keyblock (context, key);
349 if (ret)
350 goto out;
353 ret = krb5_decrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
354 &req->innerReq, &buf);
355 krb5_crypto_destroy(context, crypto);
356 crypto = NULL;
357 if (ret)
358 goto out;
360 ret = decode_DigestReqInner(buf.data, buf.length, &ireq, NULL);
361 krb5_data_free(&buf);
362 if (ret) {
363 krb5_set_error_string(context, "Failed to decode digest inner request");
364 goto out;
367 kdc_log(context, config, 0, "Valid digest request from %s (%s)",
368 client_name, from);
371 * Process the inner request
374 switch (ireq.element) {
375 case choice_DigestReqInner_init: {
376 unsigned char server_nonce[16], identifier;
378 RAND_pseudo_bytes(&identifier, sizeof(identifier));
379 RAND_pseudo_bytes(server_nonce, sizeof(server_nonce));
381 server_nonce[0] = kdc_time & 0xff;
382 server_nonce[1] = (kdc_time >> 8) & 0xff;
383 server_nonce[2] = (kdc_time >> 16) & 0xff;
384 server_nonce[3] = (kdc_time >> 24) & 0xff;
386 r.element = choice_DigestRepInner_initReply;
388 hex_encode(server_nonce, sizeof(server_nonce), &r.u.initReply.nonce);
389 if (r.u.initReply.nonce == NULL) {
390 krb5_set_error_string(context, "Failed to decode server nonce");
391 ret = ENOMEM;
392 goto out;
395 sp = krb5_storage_emem();
396 if (sp == NULL) {
397 ret = ENOMEM;
398 krb5_set_error_string(context, "out of memory");
399 goto out;
401 ret = krb5_store_stringz(sp, ireq.u.init.type);
402 if (ret) {
403 krb5_clear_error_string(context);
404 goto out;
407 if (ireq.u.init.channel) {
408 char *s;
410 asprintf(&s, "%s-%s:%s", r.u.initReply.nonce,
411 ireq.u.init.channel->cb_type,
412 ireq.u.init.channel->cb_binding);
413 if (s == NULL) {
414 krb5_set_error_string(context, "Failed to allocate "
415 "channel binding");
416 ret = ENOMEM;
417 goto out;
419 free(r.u.initReply.nonce);
420 r.u.initReply.nonce = s;
423 ret = krb5_store_stringz(sp, r.u.initReply.nonce);
424 if (ret) {
425 krb5_clear_error_string(context);
426 goto out;
429 if (strcasecmp(ireq.u.init.type, "CHAP") == 0) {
430 r.u.initReply.identifier =
431 malloc(sizeof(*r.u.initReply.identifier));
432 if (r.u.initReply.identifier == NULL) {
433 krb5_set_error_string(context, "out of memory");
434 ret = ENOMEM;
435 goto out;
438 asprintf(r.u.initReply.identifier, "%02X", identifier & 0xff);
439 if (*r.u.initReply.identifier == NULL) {
440 krb5_set_error_string(context, "out of memory");
441 ret = ENOMEM;
442 goto out;
445 } else
446 r.u.initReply.identifier = NULL;
448 if (ireq.u.init.hostname) {
449 ret = krb5_store_stringz(sp, *ireq.u.init.hostname);
450 if (ret) {
451 krb5_clear_error_string(context);
452 goto out;
456 ret = krb5_storage_to_data(sp, &buf);
457 if (ret) {
458 krb5_clear_error_string(context);
459 goto out;
462 ret = get_digest_key(context, config, server, &crypto);
463 if (ret)
464 goto out;
466 ret = krb5_create_checksum(context,
467 crypto,
468 KRB5_KU_DIGEST_OPAQUE,
470 buf.data,
471 buf.length,
472 &res);
473 krb5_crypto_destroy(context, crypto);
474 crypto = NULL;
475 krb5_data_free(&buf);
476 if (ret)
477 goto out;
479 ASN1_MALLOC_ENCODE(Checksum, buf.data, buf.length, &res, &size, ret);
480 free_Checksum(&res);
481 if (ret) {
482 krb5_set_error_string(context, "Failed to encode "
483 "checksum in digest request");
484 goto out;
486 if (size != buf.length)
487 krb5_abortx(context, "ASN1 internal error");
489 hex_encode(buf.data, buf.length, &r.u.initReply.opaque);
490 free(buf.data);
491 buf.data = NULL;
492 if (r.u.initReply.opaque == NULL) {
493 krb5_clear_error_string(context);
494 ret = ENOMEM;
495 goto out;
498 kdc_log(context, config, 0, "Digest %s init request successful from %s",
499 ireq.u.init.type, from);
501 break;
503 case choice_DigestReqInner_digestRequest: {
504 sp = krb5_storage_emem();
505 if (sp == NULL) {
506 ret = ENOMEM;
507 krb5_set_error_string(context, "out of memory");
508 goto out;
510 ret = krb5_store_stringz(sp, ireq.u.digestRequest.type);
511 if (ret) {
512 krb5_clear_error_string(context);
513 goto out;
516 krb5_store_stringz(sp, ireq.u.digestRequest.serverNonce);
518 if (ireq.u.digestRequest.hostname) {
519 ret = krb5_store_stringz(sp, *ireq.u.digestRequest.hostname);
520 if (ret) {
521 krb5_clear_error_string(context);
522 goto out;
526 buf.length = strlen(ireq.u.digestRequest.opaque);
527 buf.data = malloc(buf.length);
528 if (buf.data == NULL) {
529 krb5_set_error_string(context, "out of memory");
530 ret = ENOMEM;
531 goto out;
534 ret = hex_decode(ireq.u.digestRequest.opaque, buf.data, buf.length);
535 if (ret <= 0) {
536 krb5_set_error_string(context, "Failed to decode opaque");
537 ret = ENOMEM;
538 goto out;
540 buf.length = ret;
542 ret = decode_Checksum(buf.data, buf.length, &res, NULL);
543 free(buf.data);
544 if (ret) {
545 krb5_set_error_string(context, "Failed to decode digest Checksum");
546 goto out;
549 ret = krb5_storage_to_data(sp, &buf);
550 if (ret) {
551 krb5_clear_error_string(context);
552 goto out;
555 serverNonce.length = strlen(ireq.u.digestRequest.serverNonce);
556 serverNonce.data = malloc(serverNonce.length);
557 if (serverNonce.data == NULL) {
558 krb5_set_error_string(context, "out of memory");
559 ret = ENOMEM;
560 goto out;
564 * CHAP does the checksum of the raw nonce, but do it for all
565 * types, since we need to check the timestamp.
568 ssize_t ssize;
570 ssize = hex_decode(ireq.u.digestRequest.serverNonce,
571 serverNonce.data, serverNonce.length);
572 if (ssize <= 0) {
573 krb5_set_error_string(context, "Failed to decode serverNonce");
574 ret = ENOMEM;
575 goto out;
577 serverNonce.length = ssize;
580 ret = get_digest_key(context, config, server, &crypto);
581 if (ret)
582 goto out;
584 ret = krb5_verify_checksum(context, crypto,
585 KRB5_KU_DIGEST_OPAQUE,
586 buf.data, buf.length, &res);
587 krb5_crypto_destroy(context, crypto);
588 crypto = NULL;
589 if (ret)
590 goto out;
592 /* verify time */
594 unsigned char *p = serverNonce.data;
595 uint32_t t;
597 if (serverNonce.length < 4) {
598 krb5_set_error_string(context, "server nonce too short");
599 ret = EINVAL;
600 goto out;
602 t = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
604 if (abs((kdc_time & 0xffffffff) - t) > context->max_skew) {
605 krb5_set_error_string(context, "time screw in server nonce ");
606 ret = EINVAL;
607 goto out;
611 if (strcasecmp(ireq.u.digestRequest.type, "CHAP") == 0) {
612 MD5_CTX ctx;
613 unsigned char md[MD5_DIGEST_LENGTH];
614 char *mdx;
615 char id;
617 if ((config->digests_allowed & CHAP_MD5) == 0) {
618 kdc_log(context, config, 0, "Digest CHAP MD5 not allowed");
619 goto out;
622 if (ireq.u.digestRequest.identifier == NULL) {
623 krb5_set_error_string(context, "Identifier missing "
624 "from CHAP request");
625 ret = EINVAL;
626 goto out;
629 if (hex_decode(*ireq.u.digestRequest.identifier, &id, 1) != 1) {
630 krb5_set_error_string(context, "failed to decode identifier");
631 ret = EINVAL;
632 goto out;
635 ret = get_password_entry(context, config,
636 ireq.u.digestRequest.username,
637 &password);
638 if (ret)
639 goto out;
641 MD5_Init(&ctx);
642 MD5_Update(&ctx, &id, 1);
643 MD5_Update(&ctx, password, strlen(password));
644 MD5_Update(&ctx, serverNonce.data, serverNonce.length);
645 MD5_Final(md, &ctx);
647 hex_encode(md, sizeof(md), &mdx);
648 if (mdx == NULL) {
649 krb5_clear_error_string(context);
650 ret = ENOMEM;
651 goto out;
654 r.element = choice_DigestRepInner_response;
656 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
657 free(mdx);
658 if (ret == 0) {
659 r.u.response.success = TRUE;
660 } else {
661 kdc_log(context, config, 0,
662 "CHAP reply mismatch for %s",
663 ireq.u.digestRequest.username);
664 r.u.response.success = FALSE;
667 } else if (strcasecmp(ireq.u.digestRequest.type, "SASL-DIGEST-MD5") == 0) {
668 MD5_CTX ctx;
669 unsigned char md[MD5_DIGEST_LENGTH];
670 char *mdx;
671 char *A1, *A2;
673 if ((config->digests_allowed & DIGEST_MD5) == 0) {
674 kdc_log(context, config, 0, "Digest SASL MD5 not allowed");
675 goto out;
678 if (ireq.u.digestRequest.nonceCount == NULL)
679 goto out;
680 if (ireq.u.digestRequest.clientNonce == NULL)
681 goto out;
682 if (ireq.u.digestRequest.qop == NULL)
683 goto out;
684 if (ireq.u.digestRequest.realm == NULL)
685 goto out;
687 ret = get_password_entry(context, config,
688 ireq.u.digestRequest.username,
689 &password);
690 if (ret)
691 goto failed;
693 MD5_Init(&ctx);
694 MD5_Update(&ctx, ireq.u.digestRequest.username,
695 strlen(ireq.u.digestRequest.username));
696 MD5_Update(&ctx, ":", 1);
697 MD5_Update(&ctx, *ireq.u.digestRequest.realm,
698 strlen(*ireq.u.digestRequest.realm));
699 MD5_Update(&ctx, ":", 1);
700 MD5_Update(&ctx, password, strlen(password));
701 MD5_Final(md, &ctx);
703 MD5_Init(&ctx);
704 MD5_Update(&ctx, md, sizeof(md));
705 MD5_Update(&ctx, ":", 1);
706 MD5_Update(&ctx, ireq.u.digestRequest.serverNonce,
707 strlen(ireq.u.digestRequest.serverNonce));
708 MD5_Update(&ctx, ":", 1);
709 MD5_Update(&ctx, *ireq.u.digestRequest.nonceCount,
710 strlen(*ireq.u.digestRequest.nonceCount));
711 if (ireq.u.digestRequest.authid) {
712 MD5_Update(&ctx, ":", 1);
713 MD5_Update(&ctx, *ireq.u.digestRequest.authid,
714 strlen(*ireq.u.digestRequest.authid));
716 MD5_Final(md, &ctx);
717 hex_encode(md, sizeof(md), &A1);
718 if (A1 == NULL) {
719 krb5_set_error_string(context, "out of memory");
720 ret = ENOMEM;
721 goto failed;
724 MD5_Init(&ctx);
725 MD5_Update(&ctx, "AUTHENTICATE:", sizeof("AUTHENTICATE:") - 1);
726 MD5_Update(&ctx, *ireq.u.digestRequest.uri,
727 strlen(*ireq.u.digestRequest.uri));
729 /* conf|int */
730 if (strcmp(ireq.u.digestRequest.digest, "clear") != 0) {
731 static char conf_zeros[] = ":00000000000000000000000000000000";
732 MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1);
735 MD5_Final(md, &ctx);
736 hex_encode(md, sizeof(md), &A2);
737 if (A2 == NULL) {
738 krb5_set_error_string(context, "out of memory");
739 ret = ENOMEM;
740 free(A1);
741 goto failed;
744 MD5_Init(&ctx);
745 MD5_Update(&ctx, A1, strlen(A2));
746 MD5_Update(&ctx, ":", 1);
747 MD5_Update(&ctx, ireq.u.digestRequest.serverNonce,
748 strlen(ireq.u.digestRequest.serverNonce));
749 MD5_Update(&ctx, ":", 1);
750 MD5_Update(&ctx, *ireq.u.digestRequest.nonceCount,
751 strlen(*ireq.u.digestRequest.nonceCount));
752 MD5_Update(&ctx, ":", 1);
753 MD5_Update(&ctx, *ireq.u.digestRequest.clientNonce,
754 strlen(*ireq.u.digestRequest.clientNonce));
755 MD5_Update(&ctx, ":", 1);
756 MD5_Update(&ctx, *ireq.u.digestRequest.qop,
757 strlen(*ireq.u.digestRequest.qop));
758 MD5_Update(&ctx, ":", 1);
759 MD5_Update(&ctx, A2, strlen(A2));
761 MD5_Final(md, &ctx);
763 free(A1);
764 free(A2);
766 hex_encode(md, sizeof(md), &mdx);
767 if (mdx == NULL) {
768 krb5_clear_error_string(context);
769 ret = ENOMEM;
770 goto out;
773 r.element = choice_DigestRepInner_response;
774 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
775 free(mdx);
776 if (ret == 0) {
777 r.u.response.success = TRUE;
778 } else {
779 kdc_log(context, config, 0,
780 "DIGEST-MD5 reply mismatch for %s",
781 ireq.u.digestRequest.username);
782 r.u.response.success = FALSE;
785 } else if (strcasecmp(ireq.u.digestRequest.type, "MS-CHAP-V2") == 0) {
786 unsigned char md[SHA_DIGEST_LENGTH], challange[SHA_DIGEST_LENGTH];
787 krb5_principal clientprincipal = NULL;
788 char *mdx;
789 const char *username;
790 struct ntlm_buf answer;
791 Key *key = NULL;
792 SHA_CTX ctx;
794 if ((config->digests_allowed & MS_CHAP_V2) == 0) {
795 kdc_log(context, config, 0, "MS-CHAP-V2 not allowed");
796 goto failed;
799 if (ireq.u.digestRequest.clientNonce == NULL) {
800 krb5_set_error_string(context,
801 "MS-CHAP-V2 clientNonce missing");
802 ret = EINVAL;
803 goto failed;
805 if (serverNonce.length != 16) {
806 krb5_set_error_string(context,
807 "MS-CHAP-V2 serverNonce wrong length");
808 ret = EINVAL;
809 goto failed;
812 /* strip of the domain component */
813 username = strchr(ireq.u.digestRequest.username, '\\');
814 if (username == NULL)
815 username = ireq.u.digestRequest.username;
816 else
817 username++;
819 /* ChallangeHash */
820 SHA1_Init(&ctx);
822 ssize_t ssize;
823 krb5_data clientNonce;
825 clientNonce.length = strlen(*ireq.u.digestRequest.clientNonce);
826 clientNonce.data = malloc(clientNonce.length);
827 if (clientNonce.data == NULL) {
828 ret = ENOMEM;
829 krb5_set_error_string(context, "out of memory");
830 goto out;
833 ssize = hex_decode(*ireq.u.digestRequest.clientNonce,
834 clientNonce.data, clientNonce.length);
835 if (ssize != 16) {
836 krb5_set_error_string(context,
837 "Failed to decode clientNonce");
838 ret = ENOMEM;
839 goto out;
841 SHA1_Update(&ctx, clientNonce.data, ssize);
842 free(clientNonce.data);
844 SHA1_Update(&ctx, serverNonce.data, serverNonce.length);
845 SHA1_Update(&ctx, username, strlen(username));
846 SHA1_Final(challange, &ctx);
848 /* NtPasswordHash */
849 ret = krb5_parse_name(context, username, &clientprincipal);
850 if (ret)
851 goto failed;
853 ret = _kdc_db_fetch(context, config, clientprincipal,
854 HDB_F_GET_CLIENT, NULL, &user);
855 krb5_free_principal(context, clientprincipal);
856 if (ret) {
857 krb5_set_error_string(context,
858 "MS-CHAP-V2 user %s not in database",
859 username);
860 goto failed;
863 ret = hdb_enctype2key(context, &user->entry,
864 ETYPE_ARCFOUR_HMAC_MD5, &key);
865 if (ret) {
866 krb5_set_error_string(context,
867 "MS-CHAP-V2 missing arcfour key %s",
868 username);
869 goto failed;
872 /* ChallengeResponse */
873 ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
874 key->key.keyvalue.length,
875 challange, &answer);
876 if (ret) {
877 krb5_set_error_string(context, "NTLM missing arcfour key");
878 goto failed;
881 hex_encode(answer.data, answer.length, &mdx);
882 if (mdx == NULL) {
883 free(answer.data);
884 krb5_clear_error_string(context);
885 ret = ENOMEM;
886 goto out;
889 r.element = choice_DigestRepInner_response;
890 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
891 if (ret == 0) {
892 r.u.response.success = TRUE;
893 } else {
894 kdc_log(context, config, 0,
895 "MS-CHAP-V2 hash mismatch for %s",
896 ireq.u.digestRequest.username);
897 r.u.response.success = FALSE;
899 free(mdx);
901 if (r.u.response.success) {
902 unsigned char hashhash[MD4_DIGEST_LENGTH];
904 /* hashhash */
906 MD4_CTX hctx;
908 MD4_Init(&hctx);
909 MD4_Update(&hctx, key->key.keyvalue.data,
910 key->key.keyvalue.length);
911 MD4_Final(hashhash, &hctx);
914 /* GenerateAuthenticatorResponse */
915 SHA1_Init(&ctx);
916 SHA1_Update(&ctx, hashhash, sizeof(hashhash));
917 SHA1_Update(&ctx, answer.data, answer.length);
918 SHA1_Update(&ctx, ms_chap_v2_magic1,sizeof(ms_chap_v2_magic1));
919 SHA1_Final(md, &ctx);
921 SHA1_Init(&ctx);
922 SHA1_Update(&ctx, md, sizeof(md));
923 SHA1_Update(&ctx, challange, 8);
924 SHA1_Update(&ctx, ms_chap_v2_magic2, sizeof(ms_chap_v2_magic2));
925 SHA1_Final(md, &ctx);
927 r.u.response.rsp = calloc(1, sizeof(*r.u.response.rsp));
928 if (r.u.response.rsp == NULL) {
929 free(answer.data);
930 krb5_clear_error_string(context);
931 ret = ENOMEM;
932 goto out;
935 hex_encode(md, sizeof(md), r.u.response.rsp);
936 if (r.u.response.rsp == NULL) {
937 free(answer.data);
938 krb5_clear_error_string(context);
939 ret = ENOMEM;
940 goto out;
943 /* get_master, rfc 3079 3.4 */
944 SHA1_Init(&ctx);
945 SHA1_Update(&ctx, hashhash, 16); /* md4(hash) */
946 SHA1_Update(&ctx, answer.data, answer.length);
947 SHA1_Update(&ctx, ms_rfc3079_magic1, sizeof(ms_rfc3079_magic1));
948 SHA1_Final(md, &ctx);
950 free(answer.data);
952 r.u.response.session_key =
953 calloc(1, sizeof(*r.u.response.session_key));
954 if (r.u.response.session_key == NULL) {
955 krb5_clear_error_string(context);
956 ret = ENOMEM;
957 goto out;
960 ret = krb5_data_copy(r.u.response.session_key, md, 16);
961 if (ret) {
962 krb5_clear_error_string(context);
963 goto out;
967 } else {
968 r.element = choice_DigestRepInner_error;
969 asprintf(&r.u.error.reason, "Unsupported digest type %s",
970 ireq.u.digestRequest.type);
971 if (r.u.error.reason == NULL) {
972 krb5_set_error_string(context, "out of memory");
973 ret = ENOMEM;
974 goto out;
976 r.u.error.code = EINVAL;
979 kdc_log(context, config, 0, "Digest %s request successful %s",
980 ireq.u.digestRequest.type, ireq.u.digestRequest.username);
982 break;
984 case choice_DigestReqInner_ntlmInit:
986 if ((config->digests_allowed & (NTLM_V1|NTLM_V1_SESSION|NTLM_V2)) == 0) {
987 kdc_log(context, config, 0, "NTLM not allowed");
988 goto failed;
991 r.element = choice_DigestRepInner_ntlmInitReply;
993 r.u.ntlmInitReply.flags = NTLM_NEG_UNICODE;
995 if ((ireq.u.ntlmInit.flags & NTLM_NEG_UNICODE) == 0) {
996 kdc_log(context, config, 0, "NTLM client have no unicode");
997 goto failed;
1000 if (ireq.u.ntlmInit.flags & NTLM_NEG_NTLM)
1001 r.u.ntlmInitReply.flags |= NTLM_NEG_NTLM;
1002 else {
1003 kdc_log(context, config, 0, "NTLM client doesn't support NTLM");
1004 goto failed;
1007 r.u.ntlmInitReply.flags |=
1008 NTLM_NEG_TARGET |
1009 NTLM_TARGET_DOMAIN |
1010 NTLM_ENC_128;
1012 #define ALL \
1013 NTLM_NEG_SIGN| \
1014 NTLM_NEG_SEAL| \
1015 NTLM_NEG_ALWAYS_SIGN| \
1016 NTLM_NEG_NTLM2_SESSION| \
1017 NTLM_NEG_KEYEX
1019 r.u.ntlmInitReply.flags |= (ireq.u.ntlmInit.flags & (ALL));
1021 #undef ALL
1023 r.u.ntlmInitReply.targetname =
1024 get_ntlm_targetname(context, client);
1025 if (r.u.ntlmInitReply.targetname == NULL) {
1026 krb5_set_error_string(context, "out of memory");
1027 ret = ENOMEM;
1028 goto out;
1030 r.u.ntlmInitReply.challange.data = malloc(8);
1031 if (r.u.ntlmInitReply.challange.data == NULL) {
1032 krb5_set_error_string(context, "out of memory");
1033 ret = ENOMEM;
1034 goto out;
1036 r.u.ntlmInitReply.challange.length = 8;
1037 if (RAND_bytes(r.u.ntlmInitReply.challange.data,
1038 r.u.ntlmInitReply.challange.length) != 1)
1040 krb5_set_error_string(context, "out of random error");
1041 ret = ENOMEM;
1042 goto out;
1044 /* XXX fix targetinfo */
1045 ALLOC(r.u.ntlmInitReply.targetinfo);
1046 if (r.u.ntlmInitReply.targetinfo == NULL) {
1047 krb5_set_error_string(context, "out of memory");
1048 ret = ENOMEM;
1049 goto out;
1052 ret = fill_targetinfo(context,
1053 r.u.ntlmInitReply.targetname,
1054 client,
1055 r.u.ntlmInitReply.targetinfo);
1056 if (ret) {
1057 krb5_set_error_string(context, "out of memory");
1058 ret = ENOMEM;
1059 goto out;
1063 * Save data encryted in opaque for the second part of the
1064 * ntlm authentication
1066 sp = krb5_storage_emem();
1067 if (sp == NULL) {
1068 ret = ENOMEM;
1069 krb5_set_error_string(context, "out of memory");
1070 goto out;
1073 ret = krb5_storage_write(sp, r.u.ntlmInitReply.challange.data, 8);
1074 if (ret != 8) {
1075 ret = ENOMEM;
1076 krb5_set_error_string(context, "storage write challange");
1077 goto out;
1079 ret = krb5_store_uint32(sp, r.u.ntlmInitReply.flags);
1080 if (ret) {
1081 krb5_clear_error_string(context);
1082 goto out;
1085 ret = krb5_storage_to_data(sp, &buf);
1086 if (ret) {
1087 krb5_clear_error_string(context);
1088 goto out;
1091 ret = get_digest_key(context, config, server, &crypto);
1092 if (ret)
1093 goto out;
1095 ret = krb5_encrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1096 buf.data, buf.length, &r.u.ntlmInitReply.opaque);
1097 krb5_data_free(&buf);
1098 krb5_crypto_destroy(context, crypto);
1099 crypto = NULL;
1100 if (ret)
1101 goto out;
1103 kdc_log(context, config, 0, "NTLM init from %s", from);
1105 break;
1107 case choice_DigestReqInner_ntlmRequest: {
1108 krb5_principal clientprincipal;
1109 unsigned char sessionkey[16];
1110 unsigned char challange[8];
1111 uint32_t flags;
1112 Key *key = NULL;
1113 int version;
1115 r.element = choice_DigestRepInner_ntlmResponse;
1116 r.u.ntlmResponse.success = 0;
1117 r.u.ntlmResponse.flags = 0;
1118 r.u.ntlmResponse.sessionkey = NULL;
1119 r.u.ntlmResponse.tickets = NULL;
1121 /* get username */
1122 ret = krb5_parse_name(context,
1123 ireq.u.ntlmRequest.username,
1124 &clientprincipal);
1125 if (ret)
1126 goto failed;
1128 ret = _kdc_db_fetch(context, config, clientprincipal,
1129 HDB_F_GET_CLIENT, NULL, &user);
1130 krb5_free_principal(context, clientprincipal);
1131 if (ret) {
1132 krb5_set_error_string(context, "NTLM user %s not in database",
1133 ireq.u.ntlmRequest.username);
1134 goto failed;
1137 ret = get_digest_key(context, config, server, &crypto);
1138 if (ret)
1139 goto failed;
1141 ret = krb5_decrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1142 ireq.u.ntlmRequest.opaque.data,
1143 ireq.u.ntlmRequest.opaque.length, &buf);
1144 krb5_crypto_destroy(context, crypto);
1145 crypto = NULL;
1146 if (ret) {
1147 kdc_log(context, config, 0,
1148 "Failed to decrypt nonce from %s", from);
1149 goto failed;
1152 sp = krb5_storage_from_data(&buf);
1153 if (sp == NULL) {
1154 ret = ENOMEM;
1155 krb5_set_error_string(context, "out of memory");
1156 goto out;
1159 ret = krb5_storage_read(sp, challange, sizeof(challange));
1160 if (ret != sizeof(challange)) {
1161 krb5_set_error_string(context, "NTLM storage read challange");
1162 ret = ENOMEM;
1163 goto out;
1165 ret = krb5_ret_uint32(sp, &flags);
1166 if (ret) {
1167 krb5_set_error_string(context, "NTLM storage read flags");
1168 goto out;
1170 krb5_data_free(&buf);
1172 if ((flags & NTLM_NEG_NTLM) == 0) {
1173 ret = EINVAL;
1174 krb5_set_error_string(context, "NTLM not negotiated");
1175 goto out;
1178 ret = hdb_enctype2key(context, &user->entry,
1179 ETYPE_ARCFOUR_HMAC_MD5, &key);
1180 if (ret) {
1181 krb5_set_error_string(context, "NTLM missing arcfour key");
1182 goto out;
1185 /* check if this is NTLMv2 */
1186 if (ireq.u.ntlmRequest.ntlm.length != 24) {
1187 struct ntlm_buf infotarget, answer;
1188 char *targetname;
1190 if ((config->digests_allowed & NTLM_V2) == 0) {
1191 kdc_log(context, config, 0, "NTLM v2 not allowed");
1192 goto out;
1195 version = 2;
1197 targetname = get_ntlm_targetname(context, client);
1198 if (targetname == NULL) {
1199 krb5_set_error_string(context, "out of memory");
1200 ret = ENOMEM;
1201 goto out;
1204 answer.length = ireq.u.ntlmRequest.ntlm.length;
1205 answer.data = ireq.u.ntlmRequest.ntlm.data;
1207 ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
1208 key->key.keyvalue.length,
1209 ireq.u.ntlmRequest.username,
1210 targetname,
1212 challange,
1213 &answer,
1214 &infotarget,
1215 sessionkey);
1216 free(targetname);
1217 if (ret) {
1218 krb5_set_error_string(context, "NTLM v2 verify failed");
1219 goto failed;
1222 /* XXX verify infotarget matches client (checksum ?) */
1224 free(infotarget.data);
1225 /* */
1227 } else {
1228 struct ntlm_buf answer;
1230 version = 1;
1232 if (flags & NTLM_NEG_NTLM2_SESSION) {
1233 unsigned char sessionhash[MD5_DIGEST_LENGTH];
1234 MD5_CTX md5ctx;
1236 if ((config->digests_allowed & NTLM_V1_SESSION) == 0) {
1237 kdc_log(context, config, 0, "NTLM v1-session not allowed");
1238 ret = EINVAL;
1239 goto failed;
1242 if (ireq.u.ntlmRequest.lm.length != 24) {
1243 krb5_set_error_string(context, "LM hash have wrong length "
1244 "for NTLM session key");
1245 ret = EINVAL;
1246 goto failed;
1249 MD5_Init(&md5ctx);
1250 MD5_Update(&md5ctx, challange, sizeof(challange));
1251 MD5_Update(&md5ctx, ireq.u.ntlmRequest.lm.data, 8);
1252 MD5_Final(sessionhash, &md5ctx);
1253 memcpy(challange, sessionhash, sizeof(challange));
1254 } else {
1255 if ((config->digests_allowed & NTLM_V1) == 0) {
1256 kdc_log(context, config, 0, "NTLM v1 not allowed");
1257 goto failed;
1261 ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
1262 key->key.keyvalue.length,
1263 challange, &answer);
1264 if (ret) {
1265 krb5_set_error_string(context, "NTLM missing arcfour key");
1266 goto failed;
1269 if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
1270 memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
1272 free(answer.data);
1273 ret = EINVAL;
1274 krb5_set_error_string(context, "NTLM hash mismatch");
1275 goto failed;
1277 free(answer.data);
1280 MD4_CTX ctx;
1282 MD4_Init(&ctx);
1283 MD4_Update(&ctx,
1284 key->key.keyvalue.data, key->key.keyvalue.length);
1285 MD4_Final(sessionkey, &ctx);
1289 if (ireq.u.ntlmRequest.sessionkey) {
1290 unsigned char masterkey[MD4_DIGEST_LENGTH];
1291 RC4_KEY rc4;
1292 size_t len;
1294 if ((flags & NTLM_NEG_KEYEX) == 0) {
1295 krb5_set_error_string(context,
1296 "NTLM client failed to neg key "
1297 "exchange but still sent key");
1298 ret = EINVAL;
1299 goto failed;
1302 len = ireq.u.ntlmRequest.sessionkey->length;
1303 if (len != sizeof(masterkey)){
1304 krb5_set_error_string(context,
1305 "NTLM master key wrong length: %lu",
1306 (unsigned long)len);
1307 goto failed;
1310 RC4_set_key(&rc4, sizeof(sessionkey), sessionkey);
1312 RC4(&rc4, sizeof(masterkey),
1313 ireq.u.ntlmRequest.sessionkey->data,
1314 masterkey);
1315 memset(&rc4, 0, sizeof(rc4));
1317 r.u.ntlmResponse.sessionkey =
1318 malloc(sizeof(*r.u.ntlmResponse.sessionkey));
1319 if (r.u.ntlmResponse.sessionkey == NULL) {
1320 krb5_set_error_string(context, "out of memory");
1321 goto out;
1324 ret = krb5_data_copy(r.u.ntlmResponse.sessionkey,
1325 masterkey, sizeof(masterkey));
1326 if (ret) {
1327 krb5_set_error_string(context, "out of memory");
1328 goto out;
1332 r.u.ntlmResponse.success = 1;
1333 kdc_log(context, config, 0, "NTLM version %d successful for %s",
1334 version, ireq.u.ntlmRequest.username);
1335 break;
1337 case choice_DigestReqInner_supportedMechs:
1339 kdc_log(context, config, 0, "digest supportedMechs from %s", from);
1341 r.element = choice_DigestRepInner_supportedMechs;
1342 memset(&r.u.supportedMechs, 0, sizeof(r.u.supportedMechs));
1344 if (config->digests_allowed & NTLM_V1)
1345 r.u.supportedMechs.ntlm_v1 = 1;
1346 if (config->digests_allowed & NTLM_V1_SESSION)
1347 r.u.supportedMechs.ntlm_v1_session = 1;
1348 if (config->digests_allowed & NTLM_V2)
1349 r.u.supportedMechs.ntlm_v2 = 1;
1350 if (config->digests_allowed & DIGEST_MD5)
1351 r.u.supportedMechs.digest_md5 = 1;
1352 if (config->digests_allowed & CHAP_MD5)
1353 r.u.supportedMechs.chap_md5 = 1;
1354 if (config->digests_allowed & MS_CHAP_V2)
1355 r.u.supportedMechs.ms_chap_v2 = 1;
1356 break;
1358 default: {
1359 char *s;
1360 krb5_set_error_string(context, "unknown operation to digest");
1361 ret = EINVAL;
1363 failed:
1365 s = krb5_get_error_message(context, ret);
1366 if (s == NULL) {
1367 krb5_clear_error_string(context);
1368 goto out;
1371 kdc_log(context, config, 0, "Digest failed with: %s", s);
1373 r.element = choice_DigestRepInner_error;
1374 r.u.error.reason = strdup("unknown error");
1375 krb5_free_error_string(context, s);
1376 if (r.u.error.reason == NULL) {
1377 krb5_set_error_string(context, "out of memory");
1378 ret = ENOMEM;
1379 goto out;
1381 r.u.error.code = EINVAL;
1382 break;
1386 ASN1_MALLOC_ENCODE(DigestRepInner, buf.data, buf.length, &r, &size, ret);
1387 if (ret) {
1388 krb5_set_error_string(context, "Failed to encode inner digest reply");
1389 goto out;
1391 if (size != buf.length)
1392 krb5_abortx(context, "ASN1 internal error");
1394 krb5_auth_con_addflags(context, ac, KRB5_AUTH_CONTEXT_USE_SUBKEY, NULL);
1396 ret = krb5_mk_rep (context, ac, &rep.apRep);
1397 if (ret)
1398 goto out;
1401 krb5_keyblock *key;
1403 ret = krb5_auth_con_getlocalsubkey(context, ac, &key);
1404 if (ret)
1405 goto out;
1407 ret = krb5_crypto_init(context, key, 0, &crypto);
1408 krb5_free_keyblock (context, key);
1409 if (ret)
1410 goto out;
1413 ret = krb5_encrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
1414 buf.data, buf.length, 0,
1415 &rep.innerRep);
1417 ASN1_MALLOC_ENCODE(DigestREP, reply->data, reply->length, &rep, &size, ret);
1418 if (ret) {
1419 krb5_set_error_string(context, "Failed to encode digest reply");
1420 goto out;
1422 if (size != reply->length)
1423 krb5_abortx(context, "ASN1 internal error");
1426 out:
1427 if (ac)
1428 krb5_auth_con_free(context, ac);
1429 if (ret)
1430 krb5_warn(context, ret, "Digest request from %s failed", from);
1431 if (ticket)
1432 krb5_free_ticket(context, ticket);
1433 if (id)
1434 krb5_kt_close(context, id);
1435 if (crypto)
1436 krb5_crypto_destroy(context, crypto);
1437 if (sp)
1438 krb5_storage_free(sp);
1439 if (user)
1440 _kdc_free_ent (context, user);
1441 if (server)
1442 _kdc_free_ent (context, server);
1443 if (client)
1444 _kdc_free_ent (context, client);
1445 if (password) {
1446 memset(password, 0, strlen(password));
1447 free (password);
1449 if (client_name)
1450 free (client_name);
1451 krb5_data_free(&buf);
1452 krb5_data_free(&serverNonce);
1453 free_DigestREP(&rep);
1454 free_DigestRepInner(&r);
1455 free_DigestReqInner(&ireq);
1457 return ret;