test debug
[heimdal.git] / kdc / digest.c
blob11b9f421a012d91518f2abfcaae2187af5cceb8f
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("$Id$");
39 #define MS_CHAP_V2 0x20
40 #define CHAP_MD5 0x10
41 #define DIGEST_MD5 0x08
42 #define NTLM_V2 0x04
43 #define NTLM_V1_SESSION 0x02
44 #define NTLM_V1 0x01
46 const struct units _kdc_digestunits[] = {
47 {"ms-chap-v2", 1U << 5},
48 {"chap-md5", 1U << 4},
49 {"digest-md5", 1U << 3},
50 {"ntlm-v2", 1U << 2},
51 {"ntlm-v1-session", 1U << 1},
52 {"ntlm-v1", 1U << 0},
53 {NULL, 0}
57 static krb5_error_code
58 get_digest_key(krb5_context context,
59 krb5_kdc_configuration *config,
60 hdb_entry_ex *server,
61 krb5_crypto *crypto)
63 krb5_error_code ret;
64 krb5_enctype enctype;
65 Key *key;
67 ret = _kdc_get_preferred_key(context,
68 config,
69 server,
70 "digest-service",
71 &enctype,
72 &key);
73 if (ret)
74 return ret;
75 return krb5_crypto_init(context, &key->key, 0, crypto);
82 static char *
83 get_ntlm_targetname(krb5_context context,
84 hdb_entry_ex *client)
86 char *targetname, *p;
88 targetname = strdup(krb5_principal_get_realm(context,
89 client->entry.principal));
90 if (targetname == NULL)
91 return NULL;
93 p = strchr(targetname, '.');
94 if (p)
95 *p = '\0';
97 strupr(targetname);
98 return targetname;
101 static krb5_error_code
102 fill_targetinfo(krb5_context context,
103 char *targetname,
104 hdb_entry_ex *client,
105 krb5_data *data)
107 struct ntlm_targetinfo ti;
108 krb5_error_code ret;
109 struct ntlm_buf d;
110 krb5_principal p;
111 const char *str;
113 memset(&ti, 0, sizeof(ti));
115 ti.domainname = targetname;
116 p = client->entry.principal;
117 str = krb5_principal_get_comp_string(context, p, 0);
118 if (str != NULL &&
119 (strcmp("host", str) == 0 ||
120 strcmp("ftp", str) == 0 ||
121 strcmp("imap", str) == 0 ||
122 strcmp("pop", str) == 0 ||
123 strcmp("smtp", str)))
125 str = krb5_principal_get_comp_string(context, p, 1);
126 ti.dnsservername = rk_UNCONST(str);
129 ret = heim_ntlm_encode_targetinfo(&ti, 1, &d);
130 if (ret)
131 return ret;
133 data->data = d.data;
134 data->length = d.length;
136 return 0;
140 static const unsigned char ms_chap_v2_magic1[39] = {
141 0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
142 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
143 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
144 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
146 static const unsigned char ms_chap_v2_magic2[41] = {
147 0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
148 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
149 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
150 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
151 0x6E
153 static const unsigned char ms_rfc3079_magic1[27] = {
154 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
155 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
156 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79
163 static krb5_error_code
164 get_password_entry(krb5_context context,
165 krb5_kdc_configuration *config,
166 const char *username,
167 char **password)
169 krb5_principal clientprincipal;
170 krb5_error_code ret;
171 hdb_entry_ex *user;
172 HDB *db;
174 /* get username */
175 ret = krb5_parse_name(context, username, &clientprincipal);
176 if (ret)
177 return ret;
179 ret = _kdc_db_fetch(context, config, clientprincipal,
180 HDB_F_GET_CLIENT, &db, &user);
181 krb5_free_principal(context, clientprincipal);
182 if (ret)
183 return ret;
185 ret = hdb_entry_get_password(context, db, &user->entry, password);
186 if (ret || password == NULL) {
187 if (ret == 0) {
188 ret = EINVAL;
189 krb5_set_error_string(context, "password missing");
191 memset(user, 0, sizeof(*user));
193 _kdc_free_ent (context, user);
194 return ret;
201 krb5_error_code
202 _kdc_do_digest(krb5_context context,
203 krb5_kdc_configuration *config,
204 const DigestREQ *req, krb5_data *reply,
205 const char *from, struct sockaddr *addr)
207 krb5_error_code ret = 0;
208 krb5_ticket *ticket = NULL;
209 krb5_auth_context ac = NULL;
210 krb5_keytab id = NULL;
211 krb5_crypto crypto = NULL;
212 DigestReqInner ireq;
213 DigestRepInner r;
214 DigestREP rep;
215 krb5_flags ap_req_options;
216 krb5_data buf;
217 size_t size;
218 krb5_storage *sp = NULL;
219 Checksum res;
220 hdb_entry_ex *server = NULL, *user = NULL;
221 hdb_entry_ex *client = NULL;
222 char *client_name = NULL, *password = NULL;
223 krb5_data serverNonce;
225 if(!config->enable_digest) {
226 kdc_log(context, config, 0,
227 "Rejected digest request (disabled) from %s", from);
228 return KRB5KDC_ERR_POLICY;
231 krb5_data_zero(&buf);
232 krb5_data_zero(reply);
233 krb5_data_zero(&serverNonce);
234 memset(&ireq, 0, sizeof(ireq));
235 memset(&r, 0, sizeof(r));
236 memset(&rep, 0, sizeof(rep));
238 kdc_log(context, config, 0, "Digest request from %s", from);
240 ret = krb5_kt_resolve(context, "HDB:", &id);
241 if (ret) {
242 kdc_log(context, config, 0, "Can't open database for digest");
243 goto out;
246 ret = krb5_rd_req(context,
247 &ac,
248 &req->apReq,
249 NULL,
251 &ap_req_options,
252 &ticket);
253 if (ret)
254 goto out;
256 /* check the server principal in the ticket matches digest/R@R */
258 krb5_principal principal = NULL;
259 const char *p, *r;
261 ret = krb5_ticket_get_server(context, ticket, &principal);
262 if (ret)
263 goto out;
265 ret = EINVAL;
266 krb5_set_error_string(context, "Wrong digest server principal used");
267 p = krb5_principal_get_comp_string(context, principal, 0);
268 if (p == NULL) {
269 krb5_free_principal(context, principal);
270 goto out;
272 if (strcmp(p, KRB5_DIGEST_NAME) != 0) {
273 krb5_free_principal(context, principal);
274 goto out;
277 p = krb5_principal_get_comp_string(context, principal, 1);
278 if (p == NULL) {
279 krb5_free_principal(context, principal);
280 goto out;
282 r = krb5_principal_get_realm(context, principal);
283 if (r == NULL) {
284 krb5_free_principal(context, principal);
285 goto out;
287 if (strcmp(p, r) != 0) {
288 krb5_free_principal(context, principal);
289 goto out;
291 krb5_clear_error_string(context);
293 ret = _kdc_db_fetch(context, config, principal,
294 HDB_F_GET_SERVER, NULL, &server);
295 if (ret)
296 goto out;
298 krb5_free_principal(context, principal);
301 /* check the client is allowed to do digest auth */
303 krb5_principal principal = NULL;
305 ret = krb5_ticket_get_client(context, ticket, &principal);
306 if (ret)
307 goto out;
309 ret = krb5_unparse_name(context, principal, &client_name);
310 if (ret) {
311 krb5_free_principal(context, principal);
312 goto out;
315 ret = _kdc_db_fetch(context, config, principal,
316 HDB_F_GET_CLIENT, NULL, &client);
317 krb5_free_principal(context, principal);
318 if (ret)
319 goto out;
321 if (client->entry.flags.allow_digest == 0) {
322 kdc_log(context, config, 0,
323 "Client %s tried to use digest "
324 "but is not allowed to",
325 client_name);
326 krb5_set_error_string(context,
327 "Client is not permitted to use digest");
328 ret = KRB5KDC_ERR_POLICY;
329 goto out;
333 /* unpack request */
335 krb5_keyblock *key;
337 ret = krb5_auth_con_getremotesubkey(context, ac, &key);
338 if (ret)
339 goto out;
340 if (key == NULL) {
341 krb5_set_error_string(context, "digest: remote subkey not found");
342 ret = EINVAL;
343 goto out;
346 ret = krb5_crypto_init(context, key, 0, &crypto);
347 krb5_free_keyblock (context, key);
348 if (ret)
349 goto out;
352 ret = krb5_decrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
353 &req->innerReq, &buf);
354 krb5_crypto_destroy(context, crypto);
355 crypto = NULL;
356 if (ret)
357 goto out;
359 ret = decode_DigestReqInner(buf.data, buf.length, &ireq, NULL);
360 krb5_data_free(&buf);
361 if (ret) {
362 krb5_set_error_string(context, "Failed to decode digest inner request");
363 goto out;
366 kdc_log(context, config, 0, "Valid digest request from %s (%s)",
367 client_name, from);
370 * Process the inner request
373 switch (ireq.element) {
374 case choice_DigestReqInner_init: {
375 unsigned char server_nonce[16], identifier;
377 RAND_pseudo_bytes(&identifier, sizeof(identifier));
378 RAND_pseudo_bytes(server_nonce, sizeof(server_nonce));
380 server_nonce[0] = kdc_time & 0xff;
381 server_nonce[1] = (kdc_time >> 8) & 0xff;
382 server_nonce[2] = (kdc_time >> 16) & 0xff;
383 server_nonce[3] = (kdc_time >> 24) & 0xff;
385 r.element = choice_DigestRepInner_initReply;
387 hex_encode(server_nonce, sizeof(server_nonce), &r.u.initReply.nonce);
388 if (r.u.initReply.nonce == NULL) {
389 krb5_set_error_string(context, "Failed to decode server nonce");
390 ret = ENOMEM;
391 goto out;
394 sp = krb5_storage_emem();
395 if (sp == NULL) {
396 ret = ENOMEM;
397 krb5_set_error_string(context, "out of memory");
398 goto out;
400 ret = krb5_store_stringz(sp, ireq.u.init.type);
401 if (ret) {
402 krb5_clear_error_string(context);
403 goto out;
406 if (ireq.u.init.channel) {
407 char *s;
409 asprintf(&s, "%s-%s:%s", r.u.initReply.nonce,
410 ireq.u.init.channel->cb_type,
411 ireq.u.init.channel->cb_binding);
412 if (s == NULL) {
413 krb5_set_error_string(context, "Failed to allocate "
414 "channel binding");
415 ret = ENOMEM;
416 goto out;
418 free(r.u.initReply.nonce);
419 r.u.initReply.nonce = s;
422 ret = krb5_store_stringz(sp, r.u.initReply.nonce);
423 if (ret) {
424 krb5_clear_error_string(context);
425 goto out;
428 if (strcasecmp(ireq.u.init.type, "CHAP") == 0) {
429 r.u.initReply.identifier =
430 malloc(sizeof(*r.u.initReply.identifier));
431 if (r.u.initReply.identifier == NULL) {
432 krb5_set_error_string(context, "out of memory");
433 ret = ENOMEM;
434 goto out;
437 asprintf(r.u.initReply.identifier, "%02X", identifier & 0xff);
438 if (*r.u.initReply.identifier == NULL) {
439 krb5_set_error_string(context, "out of memory");
440 ret = ENOMEM;
441 goto out;
444 } else
445 r.u.initReply.identifier = NULL;
447 if (ireq.u.init.hostname) {
448 ret = krb5_store_stringz(sp, *ireq.u.init.hostname);
449 if (ret) {
450 krb5_clear_error_string(context);
451 goto out;
455 ret = krb5_storage_to_data(sp, &buf);
456 if (ret) {
457 krb5_clear_error_string(context);
458 goto out;
461 ret = get_digest_key(context, config, server, &crypto);
462 if (ret)
463 goto out;
465 ret = krb5_create_checksum(context,
466 crypto,
467 KRB5_KU_DIGEST_OPAQUE,
469 buf.data,
470 buf.length,
471 &res);
472 krb5_crypto_destroy(context, crypto);
473 crypto = NULL;
474 krb5_data_free(&buf);
475 if (ret)
476 goto out;
478 ASN1_MALLOC_ENCODE(Checksum, buf.data, buf.length, &res, &size, ret);
479 free_Checksum(&res);
480 if (ret) {
481 krb5_set_error_string(context, "Failed to encode "
482 "checksum in digest request");
483 goto out;
485 if (size != buf.length)
486 krb5_abortx(context, "ASN1 internal error");
488 hex_encode(buf.data, buf.length, &r.u.initReply.opaque);
489 free(buf.data);
490 if (r.u.initReply.opaque == NULL) {
491 krb5_clear_error_string(context);
492 ret = ENOMEM;
493 goto out;
496 kdc_log(context, config, 0, "Digest %s init request successful from %s",
497 ireq.u.init.type, from);
499 break;
501 case choice_DigestReqInner_digestRequest: {
502 sp = krb5_storage_emem();
503 if (sp == NULL) {
504 ret = ENOMEM;
505 krb5_set_error_string(context, "out of memory");
506 goto out;
508 ret = krb5_store_stringz(sp, ireq.u.digestRequest.type);
509 if (ret) {
510 krb5_clear_error_string(context);
511 goto out;
514 krb5_store_stringz(sp, ireq.u.digestRequest.serverNonce);
516 if (ireq.u.digestRequest.hostname) {
517 ret = krb5_store_stringz(sp, *ireq.u.digestRequest.hostname);
518 if (ret) {
519 krb5_clear_error_string(context);
520 goto out;
524 buf.length = strlen(ireq.u.digestRequest.opaque);
525 buf.data = malloc(buf.length);
526 if (buf.data == NULL) {
527 krb5_set_error_string(context, "out of memory");
528 ret = ENOMEM;
529 goto out;
532 ret = hex_decode(ireq.u.digestRequest.opaque, buf.data, buf.length);
533 if (ret <= 0) {
534 krb5_set_error_string(context, "Failed to decode opaque");
535 ret = ENOMEM;
536 goto out;
538 buf.length = ret;
540 ret = decode_Checksum(buf.data, buf.length, &res, NULL);
541 free(buf.data);
542 if (ret) {
543 krb5_set_error_string(context, "Failed to decode digest Checksum");
544 goto out;
547 ret = krb5_storage_to_data(sp, &buf);
548 if (ret) {
549 krb5_clear_error_string(context);
550 goto out;
553 serverNonce.length = strlen(ireq.u.digestRequest.serverNonce);
554 serverNonce.data = malloc(serverNonce.length);
555 if (serverNonce.data == NULL) {
556 krb5_set_error_string(context, "out of memory");
557 ret = ENOMEM;
558 goto out;
562 * CHAP does the checksum of the raw nonce, but do it for all
563 * types, since we need to check the timestamp.
566 ssize_t ssize;
568 ssize = hex_decode(ireq.u.digestRequest.serverNonce,
569 serverNonce.data, serverNonce.length);
570 if (ssize <= 0) {
571 krb5_set_error_string(context, "Failed to decode serverNonce");
572 ret = ENOMEM;
573 goto out;
575 serverNonce.length = ssize;
578 ret = get_digest_key(context, config, server, &crypto);
579 if (ret)
580 goto out;
582 ret = krb5_verify_checksum(context, crypto,
583 KRB5_KU_DIGEST_OPAQUE,
584 buf.data, buf.length, &res);
585 krb5_crypto_destroy(context, crypto);
586 crypto = NULL;
587 if (ret)
588 goto out;
590 /* verify time */
592 unsigned char *p = serverNonce.data;
593 uint32_t t;
595 if (serverNonce.length < 4) {
596 krb5_set_error_string(context, "server nonce too short");
597 ret = EINVAL;
598 goto out;
600 t = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
602 if (abs((kdc_time & 0xffffffff) - t) > context->max_skew) {
603 krb5_set_error_string(context, "time screw in server nonce ");
604 ret = EINVAL;
605 goto out;
609 if (strcasecmp(ireq.u.digestRequest.type, "CHAP") == 0) {
610 MD5_CTX ctx;
611 unsigned char md[MD5_DIGEST_LENGTH];
612 char *mdx;
613 char id;
615 if ((config->digests_allowed & CHAP_MD5) == 0) {
616 kdc_log(context, config, 0, "Digest CHAP MD5 not allowed");
617 goto out;
620 if (ireq.u.digestRequest.identifier == NULL) {
621 krb5_set_error_string(context, "Identifier missing "
622 "from CHAP request");
623 ret = EINVAL;
624 goto out;
627 if (hex_decode(*ireq.u.digestRequest.identifier, &id, 1) != 1) {
628 krb5_set_error_string(context, "failed to decode identifier");
629 ret = EINVAL;
630 goto out;
633 ret = get_password_entry(context, config,
634 ireq.u.digestRequest.username,
635 &password);
636 if (ret)
637 goto out;
639 MD5_Init(&ctx);
640 MD5_Update(&ctx, &id, 1);
641 MD5_Update(&ctx, password, strlen(password));
642 MD5_Update(&ctx, serverNonce.data, serverNonce.length);
643 MD5_Final(md, &ctx);
645 hex_encode(md, sizeof(md), &mdx);
646 if (mdx == NULL) {
647 krb5_clear_error_string(context);
648 ret = ENOMEM;
649 goto out;
652 r.element = choice_DigestRepInner_response;
654 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
655 free(mdx);
656 if (ret == 0) {
657 r.u.response.success = TRUE;
658 } else {
659 kdc_log(context, config, 0,
660 "CHAP reply mismatch for %s",
661 ireq.u.digestRequest.username);
662 r.u.response.success = FALSE;
665 } else if (strcasecmp(ireq.u.digestRequest.type, "SASL-DIGEST-MD5") == 0) {
666 MD5_CTX ctx;
667 unsigned char md[MD5_DIGEST_LENGTH];
668 char *mdx;
669 char *A1, *A2;
671 if ((config->digests_allowed & DIGEST_MD5) == 0) {
672 kdc_log(context, config, 0, "Digest SASL MD5 not allowed");
673 goto out;
676 if (ireq.u.digestRequest.nonceCount == NULL)
677 goto out;
678 if (ireq.u.digestRequest.clientNonce == NULL)
679 goto out;
680 if (ireq.u.digestRequest.qop == NULL)
681 goto out;
682 if (ireq.u.digestRequest.realm == NULL)
683 goto out;
685 ret = get_password_entry(context, config,
686 ireq.u.digestRequest.username,
687 &password);
688 if (ret)
689 goto failed;
691 MD5_Init(&ctx);
692 MD5_Update(&ctx, ireq.u.digestRequest.username,
693 strlen(ireq.u.digestRequest.username));
694 MD5_Update(&ctx, ":", 1);
695 MD5_Update(&ctx, *ireq.u.digestRequest.realm,
696 strlen(*ireq.u.digestRequest.realm));
697 MD5_Update(&ctx, ":", 1);
698 MD5_Update(&ctx, password, strlen(password));
699 MD5_Final(md, &ctx);
701 MD5_Init(&ctx);
702 MD5_Update(&ctx, md, sizeof(md));
703 MD5_Update(&ctx, ":", 1);
704 MD5_Update(&ctx, ireq.u.digestRequest.serverNonce,
705 strlen(ireq.u.digestRequest.serverNonce));
706 MD5_Update(&ctx, ":", 1);
707 MD5_Update(&ctx, *ireq.u.digestRequest.nonceCount,
708 strlen(*ireq.u.digestRequest.nonceCount));
709 if (ireq.u.digestRequest.authid) {
710 MD5_Update(&ctx, ":", 1);
711 MD5_Update(&ctx, *ireq.u.digestRequest.authid,
712 strlen(*ireq.u.digestRequest.authid));
714 MD5_Final(md, &ctx);
715 hex_encode(md, sizeof(md), &A1);
716 if (A1 == NULL) {
717 krb5_set_error_string(context, "out of memory");
718 ret = ENOMEM;
719 goto failed;
722 MD5_Init(&ctx);
723 MD5_Update(&ctx, "AUTHENTICATE:", sizeof("AUTHENTICATE:") - 1);
724 MD5_Update(&ctx, *ireq.u.digestRequest.uri,
725 strlen(*ireq.u.digestRequest.uri));
727 /* conf|int */
728 if (strcmp(ireq.u.digestRequest.digest, "clear") != 0) {
729 static char conf_zeros[] = ":00000000000000000000000000000000";
730 MD5_Update(&ctx, conf_zeros, sizeof(conf_zeros) - 1);
733 MD5_Final(md, &ctx);
734 hex_encode(md, sizeof(md), &A2);
735 if (A2 == NULL) {
736 krb5_set_error_string(context, "out of memory");
737 ret = ENOMEM;
738 free(A1);
739 goto failed;
742 MD5_Init(&ctx);
743 MD5_Update(&ctx, A1, strlen(A2));
744 MD5_Update(&ctx, ":", 1);
745 MD5_Update(&ctx, ireq.u.digestRequest.serverNonce,
746 strlen(ireq.u.digestRequest.serverNonce));
747 MD5_Update(&ctx, ":", 1);
748 MD5_Update(&ctx, *ireq.u.digestRequest.nonceCount,
749 strlen(*ireq.u.digestRequest.nonceCount));
750 MD5_Update(&ctx, ":", 1);
751 MD5_Update(&ctx, *ireq.u.digestRequest.clientNonce,
752 strlen(*ireq.u.digestRequest.clientNonce));
753 MD5_Update(&ctx, ":", 1);
754 MD5_Update(&ctx, *ireq.u.digestRequest.qop,
755 strlen(*ireq.u.digestRequest.qop));
756 MD5_Update(&ctx, ":", 1);
757 MD5_Update(&ctx, A2, strlen(A2));
759 MD5_Final(md, &ctx);
761 free(A1);
762 free(A2);
764 hex_encode(md, sizeof(md), &mdx);
765 if (mdx == NULL) {
766 krb5_clear_error_string(context);
767 ret = ENOMEM;
768 goto out;
771 r.element = choice_DigestRepInner_response;
772 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
773 free(mdx);
774 if (ret == 0) {
775 r.u.response.success = TRUE;
776 } else {
777 kdc_log(context, config, 0,
778 "DIGEST-MD5 reply mismatch for %s",
779 ireq.u.digestRequest.username);
780 r.u.response.success = FALSE;
783 } else if (strcasecmp(ireq.u.digestRequest.type, "MS-CHAP-V2") == 0) {
784 unsigned char md[SHA_DIGEST_LENGTH], challange[SHA_DIGEST_LENGTH];
785 krb5_principal clientprincipal = NULL;
786 char *mdx;
787 const char *username;
788 struct ntlm_buf answer;
789 Key *key = NULL;
790 SHA_CTX ctx;
792 if ((config->digests_allowed & MS_CHAP_V2) == 0) {
793 kdc_log(context, config, 0, "MS-CHAP-V2 not allowed");
794 goto failed;
797 if (ireq.u.digestRequest.clientNonce == NULL) {
798 krb5_set_error_string(context,
799 "MS-CHAP-V2 clientNonce missing");
800 ret = EINVAL;
801 goto failed;
803 if (serverNonce.length != 16) {
804 krb5_set_error_string(context,
805 "MS-CHAP-V2 serverNonce wrong length");
806 ret = EINVAL;
807 goto failed;
810 /* strip of the domain component */
811 username = strchr(ireq.u.digestRequest.username, '\\');
812 if (username == NULL)
813 username = ireq.u.digestRequest.username;
814 else
815 username++;
817 /* ChallangeHash */
818 SHA1_Init(&ctx);
820 ssize_t ssize;
821 krb5_data clientNonce;
823 clientNonce.length = strlen(*ireq.u.digestRequest.clientNonce);
824 clientNonce.data = malloc(clientNonce.length);
825 if (clientNonce.data == NULL) {
826 ret = ENOMEM;
827 krb5_set_error_string(context, "out of memory");
828 goto out;
831 ssize = hex_decode(*ireq.u.digestRequest.clientNonce,
832 clientNonce.data, clientNonce.length);
833 if (ssize != 16) {
834 krb5_set_error_string(context,
835 "Failed to decode clientNonce");
836 ret = ENOMEM;
837 goto out;
839 SHA1_Update(&ctx, clientNonce.data, ssize);
840 free(clientNonce.data);
842 SHA1_Update(&ctx, serverNonce.data, serverNonce.length);
843 SHA1_Update(&ctx, username, strlen(username));
844 SHA1_Final(challange, &ctx);
846 /* NtPasswordHash */
847 ret = krb5_parse_name(context, username, &clientprincipal);
848 if (ret)
849 goto failed;
851 ret = _kdc_db_fetch(context, config, clientprincipal,
852 HDB_F_GET_CLIENT, NULL, &user);
853 krb5_free_principal(context, clientprincipal);
854 if (ret) {
855 krb5_set_error_string(context,
856 "MS-CHAP-V2 user %s not in database",
857 username);
858 goto failed;
861 ret = hdb_enctype2key(context, &user->entry,
862 ETYPE_ARCFOUR_HMAC_MD5, &key);
863 if (ret) {
864 krb5_set_error_string(context,
865 "MS-CHAP-V2 missing arcfour key %s",
866 username);
867 goto failed;
870 /* ChallengeResponse */
871 ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
872 key->key.keyvalue.length,
873 challange, &answer);
874 if (ret) {
875 krb5_set_error_string(context, "NTLM missing arcfour key");
876 goto failed;
879 hex_encode(answer.data, answer.length, &mdx);
880 if (mdx == NULL) {
881 free(answer.data);
882 krb5_clear_error_string(context);
883 ret = ENOMEM;
884 goto out;
887 r.element = choice_DigestRepInner_response;
888 ret = strcasecmp(mdx, ireq.u.digestRequest.responseData);
889 if (ret == 0) {
890 r.u.response.success = TRUE;
891 } else {
892 kdc_log(context, config, 0,
893 "MS-CHAP-V2 hash mismatch for %s",
894 ireq.u.digestRequest.username);
895 r.u.response.success = FALSE;
897 free(mdx);
899 if (r.u.response.success) {
900 unsigned char hashhash[MD4_DIGEST_LENGTH];
902 /* hashhash */
904 MD4_CTX hctx;
906 MD4_Init(&hctx);
907 MD4_Update(&hctx, key->key.keyvalue.data,
908 key->key.keyvalue.length);
909 MD4_Final(hashhash, &hctx);
912 /* GenerateAuthenticatorResponse */
913 SHA1_Init(&ctx);
914 SHA1_Update(&ctx, hashhash, sizeof(hashhash));
915 SHA1_Update(&ctx, answer.data, answer.length);
916 SHA1_Update(&ctx, ms_chap_v2_magic1,sizeof(ms_chap_v2_magic1));
917 SHA1_Final(md, &ctx);
919 SHA1_Init(&ctx);
920 SHA1_Update(&ctx, md, sizeof(md));
921 SHA1_Update(&ctx, challange, 8);
922 SHA1_Update(&ctx, ms_chap_v2_magic2, sizeof(ms_chap_v2_magic2));
923 SHA1_Final(md, &ctx);
925 r.u.response.rsp = calloc(1, sizeof(*r.u.response.rsp));
926 if (r.u.response.rsp == NULL) {
927 free(answer.data);
928 krb5_clear_error_string(context);
929 ret = ENOMEM;
930 goto out;
933 hex_encode(md, sizeof(md), r.u.response.rsp);
934 if (r.u.response.rsp == NULL) {
935 free(answer.data);
936 krb5_clear_error_string(context);
937 ret = ENOMEM;
938 goto out;
941 /* get_master, rfc 3079 3.4 */
942 SHA1_Init(&ctx);
943 SHA1_Update(&ctx, hashhash, 16); /* md4(hash) */
944 SHA1_Update(&ctx, answer.data, answer.length);
945 SHA1_Update(&ctx, ms_rfc3079_magic1, sizeof(ms_rfc3079_magic1));
946 SHA1_Final(md, &ctx);
948 free(answer.data);
950 r.u.response.session_key =
951 calloc(1, sizeof(*r.u.response.session_key));
952 if (r.u.response.session_key == NULL) {
953 krb5_clear_error_string(context);
954 ret = ENOMEM;
955 goto out;
958 ret = krb5_data_copy(r.u.response.session_key, md, 16);
959 if (ret) {
960 krb5_clear_error_string(context);
961 goto out;
965 } else {
966 r.element = choice_DigestRepInner_error;
967 asprintf(&r.u.error.reason, "Unsupported digest type %s",
968 ireq.u.digestRequest.type);
969 if (r.u.error.reason == NULL) {
970 krb5_set_error_string(context, "out of memory");
971 ret = ENOMEM;
972 goto out;
974 r.u.error.code = EINVAL;
977 kdc_log(context, config, 0, "Digest %s request successful %s",
978 ireq.u.digestRequest.type, ireq.u.digestRequest.username);
980 break;
982 case choice_DigestReqInner_ntlmInit:
984 if ((config->digests_allowed & (NTLM_V1|NTLM_V1_SESSION|NTLM_V2)) == 0) {
985 kdc_log(context, config, 0, "NTLM not allowed");
986 goto failed;
989 r.element = choice_DigestRepInner_ntlmInitReply;
991 r.u.ntlmInitReply.flags = NTLM_NEG_UNICODE;
993 if ((ireq.u.ntlmInit.flags & NTLM_NEG_UNICODE) == 0) {
994 kdc_log(context, config, 0, "NTLM client have no unicode");
995 goto failed;
998 if (ireq.u.ntlmInit.flags & NTLM_NEG_NTLM)
999 r.u.ntlmInitReply.flags |= NTLM_NEG_NTLM;
1000 else {
1001 kdc_log(context, config, 0, "NTLM client doesn't support NTLM");
1002 goto failed;
1005 r.u.ntlmInitReply.flags |=
1006 NTLM_NEG_TARGET |
1007 NTLM_TARGET_DOMAIN |
1008 NTLM_ENC_128;
1010 #define ALL \
1011 NTLM_NEG_SIGN| \
1012 NTLM_NEG_SEAL| \
1013 NTLM_NEG_ALWAYS_SIGN| \
1014 NTLM_NEG_NTLM2_SESSION| \
1015 NTLM_NEG_KEYEX
1017 r.u.ntlmInitReply.flags |= (ireq.u.ntlmInit.flags & (ALL));
1019 #undef ALL
1021 r.u.ntlmInitReply.targetname =
1022 get_ntlm_targetname(context, client);
1023 if (r.u.ntlmInitReply.targetname == NULL) {
1024 krb5_set_error_string(context, "out of memory");
1025 ret = ENOMEM;
1026 goto out;
1028 r.u.ntlmInitReply.challange.data = malloc(8);
1029 if (r.u.ntlmInitReply.challange.data == NULL) {
1030 krb5_set_error_string(context, "out of memory");
1031 ret = ENOMEM;
1032 goto out;
1034 r.u.ntlmInitReply.challange.length = 8;
1035 if (RAND_bytes(r.u.ntlmInitReply.challange.data,
1036 r.u.ntlmInitReply.challange.length) != 1)
1038 krb5_set_error_string(context, "out of random error");
1039 ret = ENOMEM;
1040 goto out;
1042 /* XXX fix targetinfo */
1043 ALLOC(r.u.ntlmInitReply.targetinfo);
1044 if (r.u.ntlmInitReply.targetinfo == NULL) {
1045 krb5_set_error_string(context, "out of memory");
1046 ret = ENOMEM;
1047 goto out;
1050 ret = fill_targetinfo(context,
1051 r.u.ntlmInitReply.targetname,
1052 client,
1053 r.u.ntlmInitReply.targetinfo);
1054 if (ret) {
1055 krb5_set_error_string(context, "out of memory");
1056 ret = ENOMEM;
1057 goto out;
1061 * Save data encryted in opaque for the second part of the
1062 * ntlm authentication
1064 sp = krb5_storage_emem();
1065 if (sp == NULL) {
1066 ret = ENOMEM;
1067 krb5_set_error_string(context, "out of memory");
1068 goto out;
1071 ret = krb5_storage_write(sp, r.u.ntlmInitReply.challange.data, 8);
1072 if (ret != 8) {
1073 ret = ENOMEM;
1074 krb5_set_error_string(context, "storage write challange");
1075 goto out;
1077 ret = krb5_store_uint32(sp, r.u.ntlmInitReply.flags);
1078 if (ret) {
1079 krb5_clear_error_string(context);
1080 goto out;
1083 ret = krb5_storage_to_data(sp, &buf);
1084 if (ret) {
1085 krb5_clear_error_string(context);
1086 goto out;
1089 ret = get_digest_key(context, config, server, &crypto);
1090 if (ret)
1091 goto out;
1093 ret = krb5_encrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1094 buf.data, buf.length, &r.u.ntlmInitReply.opaque);
1095 krb5_data_free(&buf);
1096 krb5_crypto_destroy(context, crypto);
1097 crypto = NULL;
1098 if (ret)
1099 goto out;
1101 kdc_log(context, config, 0, "NTLM init from %s", from);
1103 break;
1105 case choice_DigestReqInner_ntlmRequest: {
1106 krb5_principal clientprincipal;
1107 unsigned char sessionkey[16];
1108 unsigned char challange[8];
1109 uint32_t flags;
1110 Key *key = NULL;
1111 int version;
1113 r.element = choice_DigestRepInner_ntlmResponse;
1114 r.u.ntlmResponse.success = 0;
1115 r.u.ntlmResponse.flags = 0;
1116 r.u.ntlmResponse.sessionkey = NULL;
1117 r.u.ntlmResponse.tickets = NULL;
1119 /* get username */
1120 ret = krb5_parse_name(context,
1121 ireq.u.ntlmRequest.username,
1122 &clientprincipal);
1123 if (ret)
1124 goto failed;
1126 ret = _kdc_db_fetch(context, config, clientprincipal,
1127 HDB_F_GET_CLIENT, NULL, &user);
1128 krb5_free_principal(context, clientprincipal);
1129 if (ret) {
1130 krb5_set_error_string(context, "NTLM user %s not in database",
1131 ireq.u.ntlmRequest.username);
1132 goto failed;
1135 ret = get_digest_key(context, config, server, &crypto);
1136 if (ret)
1137 goto failed;
1139 ret = krb5_decrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1140 ireq.u.ntlmRequest.opaque.data,
1141 ireq.u.ntlmRequest.opaque.length, &buf);
1142 krb5_crypto_destroy(context, crypto);
1143 crypto = NULL;
1144 if (ret) {
1145 kdc_log(context, config, 0,
1146 "Failed to decrypt nonce from %s", from);
1147 goto failed;
1150 sp = krb5_storage_from_data(&buf);
1151 if (sp == NULL) {
1152 ret = ENOMEM;
1153 krb5_set_error_string(context, "out of memory");
1154 goto out;
1157 ret = krb5_storage_read(sp, challange, sizeof(challange));
1158 if (ret != sizeof(challange)) {
1159 krb5_set_error_string(context, "NTLM storage read challange");
1160 ret = ENOMEM;
1161 goto out;
1163 ret = krb5_ret_uint32(sp, &flags);
1164 if (ret) {
1165 krb5_set_error_string(context, "NTLM storage read flags");
1166 goto out;
1168 krb5_data_free(&buf);
1170 if ((flags & NTLM_NEG_NTLM) == 0) {
1171 ret = EINVAL;
1172 krb5_set_error_string(context, "NTLM not negotiated");
1173 goto out;
1176 ret = hdb_enctype2key(context, &user->entry,
1177 ETYPE_ARCFOUR_HMAC_MD5, &key);
1178 if (ret) {
1179 krb5_set_error_string(context, "NTLM missing arcfour key");
1180 goto out;
1183 /* check if this is NTLMv2 */
1184 if (ireq.u.ntlmRequest.ntlm.length != 24) {
1185 struct ntlm_buf infotarget, answer;
1186 char *targetname;
1188 if ((config->digests_allowed & NTLM_V2) == 0) {
1189 kdc_log(context, config, 0, "NTLM v2 not allowed");
1190 goto out;
1193 version = 2;
1195 targetname = get_ntlm_targetname(context, client);
1196 if (targetname == NULL) {
1197 krb5_set_error_string(context, "out of memory");
1198 ret = ENOMEM;
1199 goto out;
1202 answer.length = ireq.u.ntlmRequest.ntlm.length;
1203 answer.data = ireq.u.ntlmRequest.ntlm.data;
1205 ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
1206 key->key.keyvalue.length,
1207 ireq.u.ntlmRequest.username,
1208 targetname,
1210 challange,
1211 &answer,
1212 &infotarget,
1213 sessionkey);
1214 free(targetname);
1215 if (ret) {
1216 krb5_set_error_string(context, "NTLM v2 verify failed");
1217 goto failed;
1220 /* XXX verify infotarget matches client (checksum ?) */
1222 free(infotarget.data);
1223 /* */
1225 } else {
1226 struct ntlm_buf answer;
1228 version = 1;
1230 if (flags & NTLM_NEG_NTLM2_SESSION) {
1231 unsigned char sessionhash[MD5_DIGEST_LENGTH];
1232 MD5_CTX md5ctx;
1234 if ((config->digests_allowed & NTLM_V1_SESSION) == 0) {
1235 kdc_log(context, config, 0, "NTLM v1-session not allowed");
1236 ret = EINVAL;
1237 goto failed;
1240 if (ireq.u.ntlmRequest.lm.length != 24) {
1241 krb5_set_error_string(context, "LM hash have wrong length "
1242 "for NTLM session key");
1243 ret = EINVAL;
1244 goto failed;
1247 MD5_Init(&md5ctx);
1248 MD5_Update(&md5ctx, challange, sizeof(challange));
1249 MD5_Update(&md5ctx, ireq.u.ntlmRequest.lm.data, 8);
1250 MD5_Final(sessionhash, &md5ctx);
1251 memcpy(challange, sessionhash, sizeof(challange));
1252 } else {
1253 if ((config->digests_allowed & NTLM_V1) == 0) {
1254 kdc_log(context, config, 0, "NTLM v1 not allowed");
1255 goto failed;
1259 ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
1260 key->key.keyvalue.length,
1261 challange, &answer);
1262 if (ret) {
1263 krb5_set_error_string(context, "NTLM missing arcfour key");
1264 goto failed;
1267 if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
1268 memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
1270 free(answer.data);
1271 ret = EINVAL;
1272 krb5_set_error_string(context, "NTLM hash mismatch");
1273 goto failed;
1275 free(answer.data);
1278 MD4_CTX ctx;
1280 MD4_Init(&ctx);
1281 MD4_Update(&ctx,
1282 key->key.keyvalue.data, key->key.keyvalue.length);
1283 MD4_Final(sessionkey, &ctx);
1287 if (ireq.u.ntlmRequest.sessionkey) {
1288 unsigned char masterkey[MD4_DIGEST_LENGTH];
1289 RC4_KEY rc4;
1290 size_t len;
1292 if ((flags & NTLM_NEG_KEYEX) == 0) {
1293 krb5_set_error_string(context,
1294 "NTLM client failed to neg key "
1295 "exchange but still sent key");
1296 ret = EINVAL;
1297 goto failed;
1300 len = ireq.u.ntlmRequest.sessionkey->length;
1301 if (len != sizeof(masterkey)){
1302 krb5_set_error_string(context,
1303 "NTLM master key wrong length: %lu",
1304 (unsigned long)len);
1305 goto failed;
1308 RC4_set_key(&rc4, sizeof(sessionkey), sessionkey);
1310 RC4(&rc4, sizeof(masterkey),
1311 ireq.u.ntlmRequest.sessionkey->data,
1312 masterkey);
1313 memset(&rc4, 0, sizeof(rc4));
1315 r.u.ntlmResponse.sessionkey =
1316 malloc(sizeof(*r.u.ntlmResponse.sessionkey));
1317 if (r.u.ntlmResponse.sessionkey == NULL) {
1318 krb5_set_error_string(context, "out of memory");
1319 goto out;
1322 ret = krb5_data_copy(r.u.ntlmResponse.sessionkey,
1323 masterkey, sizeof(masterkey));
1324 if (ret) {
1325 krb5_set_error_string(context, "out of memory");
1326 goto out;
1330 r.u.ntlmResponse.success = 1;
1331 kdc_log(context, config, 0, "NTLM version %d successful for %s",
1332 version, ireq.u.ntlmRequest.username);
1333 break;
1335 case choice_DigestReqInner_supportedMechs:
1337 kdc_log(context, config, 0, "digest supportedMechs from %s", from);
1339 r.element = choice_DigestRepInner_supportedMechs;
1340 memset(&r.u.supportedMechs, 0, sizeof(r.u.supportedMechs));
1342 if (config->digests_allowed & NTLM_V1)
1343 r.u.supportedMechs.ntlm_v1 = 1;
1344 if (config->digests_allowed & NTLM_V1_SESSION)
1345 r.u.supportedMechs.ntlm_v1_session = 1;
1346 if (config->digests_allowed & NTLM_V2)
1347 r.u.supportedMechs.ntlm_v2 = 1;
1348 if (config->digests_allowed & DIGEST_MD5)
1349 r.u.supportedMechs.digest_md5 = 1;
1350 if (config->digests_allowed & CHAP_MD5)
1351 r.u.supportedMechs.chap_md5 = 1;
1352 if (config->digests_allowed & MS_CHAP_V2)
1353 r.u.supportedMechs.ms_chap_v2 = 1;
1354 break;
1356 default: {
1357 char *s;
1358 krb5_set_error_string(context, "unknown operation to digest");
1359 ret = EINVAL;
1361 failed:
1363 s = krb5_get_error_message(context, ret);
1364 if (s == NULL) {
1365 krb5_clear_error_string(context);
1366 goto out;
1369 kdc_log(context, config, 0, "Digest failed with: %s", s);
1371 r.element = choice_DigestRepInner_error;
1372 r.u.error.reason = strdup("unknown error");
1373 krb5_free_error_string(context, s);
1374 if (r.u.error.reason == NULL) {
1375 krb5_set_error_string(context, "out of memory");
1376 ret = ENOMEM;
1377 goto out;
1379 r.u.error.code = EINVAL;
1380 break;
1384 ASN1_MALLOC_ENCODE(DigestRepInner, buf.data, buf.length, &r, &size, ret);
1385 if (ret) {
1386 krb5_set_error_string(context, "Failed to encode inner digest reply");
1387 goto out;
1389 if (size != buf.length)
1390 krb5_abortx(context, "ASN1 internal error");
1392 krb5_auth_con_addflags(context, ac, KRB5_AUTH_CONTEXT_USE_SUBKEY, NULL);
1394 ret = krb5_mk_rep (context, ac, &rep.apRep);
1395 if (ret)
1396 goto out;
1399 krb5_keyblock *key;
1401 ret = krb5_auth_con_getlocalsubkey(context, ac, &key);
1402 if (ret)
1403 goto out;
1405 ret = krb5_crypto_init(context, key, 0, &crypto);
1406 krb5_free_keyblock (context, key);
1407 if (ret)
1408 goto out;
1411 ret = krb5_encrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
1412 buf.data, buf.length, 0,
1413 &rep.innerRep);
1415 ASN1_MALLOC_ENCODE(DigestREP, reply->data, reply->length, &rep, &size, ret);
1416 if (ret) {
1417 krb5_set_error_string(context, "Failed to encode digest reply");
1418 goto out;
1420 if (size != reply->length)
1421 krb5_abortx(context, "ASN1 internal error");
1424 out:
1425 if (ac)
1426 krb5_auth_con_free(context, ac);
1427 if (ret)
1428 krb5_warn(context, ret, "Digest request from %s failed", from);
1429 if (ticket)
1430 krb5_free_ticket(context, ticket);
1431 if (id)
1432 krb5_kt_close(context, id);
1433 if (crypto)
1434 krb5_crypto_destroy(context, crypto);
1435 if (sp)
1436 krb5_storage_free(sp);
1437 if (user)
1438 _kdc_free_ent (context, user);
1439 if (server)
1440 _kdc_free_ent (context, server);
1441 if (client)
1442 _kdc_free_ent (context, client);
1443 if (password) {
1444 memset(password, 0, strlen(password));
1445 free (password);
1447 if (client_name)
1448 free (client_name);
1449 krb5_data_free(&buf);
1450 krb5_data_free(&serverNonce);
1451 free_DigestREP(&rep);
1452 free_DigestRepInner(&r);
1453 free_DigestReqInner(&ireq);
1455 return ret;