DH is a subpage
[heimdal.git] / kdc / digest.c
blob0e793414445df435ca3ad08a23847550507f6094
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_DOMAIN |
1007 NTLM_ENC_128;
1009 #define ALL \
1010 NTLM_NEG_SIGN| \
1011 NTLM_NEG_SEAL| \
1012 NTLM_NEG_ALWAYS_SIGN| \
1013 NTLM_NEG_NTLM2_SESSION| \
1014 NTLM_NEG_KEYEX
1016 r.u.ntlmInitReply.flags |= (ireq.u.ntlmInit.flags & (ALL));
1018 #undef ALL
1020 r.u.ntlmInitReply.targetname =
1021 get_ntlm_targetname(context, client);
1022 if (r.u.ntlmInitReply.targetname == NULL) {
1023 krb5_set_error_string(context, "out of memory");
1024 ret = ENOMEM;
1025 goto out;
1027 r.u.ntlmInitReply.challange.data = malloc(8);
1028 if (r.u.ntlmInitReply.challange.data == NULL) {
1029 krb5_set_error_string(context, "out of memory");
1030 ret = ENOMEM;
1031 goto out;
1033 r.u.ntlmInitReply.challange.length = 8;
1034 if (RAND_bytes(r.u.ntlmInitReply.challange.data,
1035 r.u.ntlmInitReply.challange.length) != 1)
1037 krb5_set_error_string(context, "out of random error");
1038 ret = ENOMEM;
1039 goto out;
1041 /* XXX fix targetinfo */
1042 ALLOC(r.u.ntlmInitReply.targetinfo);
1043 if (r.u.ntlmInitReply.targetinfo == NULL) {
1044 krb5_set_error_string(context, "out of memory");
1045 ret = ENOMEM;
1046 goto out;
1049 ret = fill_targetinfo(context,
1050 r.u.ntlmInitReply.targetname,
1051 client,
1052 r.u.ntlmInitReply.targetinfo);
1053 if (ret) {
1054 krb5_set_error_string(context, "out of memory");
1055 ret = ENOMEM;
1056 goto out;
1060 * Save data encryted in opaque for the second part of the
1061 * ntlm authentication
1063 sp = krb5_storage_emem();
1064 if (sp == NULL) {
1065 ret = ENOMEM;
1066 krb5_set_error_string(context, "out of memory");
1067 goto out;
1070 ret = krb5_storage_write(sp, r.u.ntlmInitReply.challange.data, 8);
1071 if (ret != 8) {
1072 ret = ENOMEM;
1073 krb5_set_error_string(context, "storage write challange");
1074 goto out;
1076 ret = krb5_store_uint32(sp, r.u.ntlmInitReply.flags);
1077 if (ret) {
1078 krb5_clear_error_string(context);
1079 goto out;
1082 ret = krb5_storage_to_data(sp, &buf);
1083 if (ret) {
1084 krb5_clear_error_string(context);
1085 goto out;
1088 ret = get_digest_key(context, config, server, &crypto);
1089 if (ret)
1090 goto out;
1092 ret = krb5_encrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1093 buf.data, buf.length, &r.u.ntlmInitReply.opaque);
1094 krb5_data_free(&buf);
1095 krb5_crypto_destroy(context, crypto);
1096 crypto = NULL;
1097 if (ret)
1098 goto out;
1100 kdc_log(context, config, 0, "NTLM init from %s", from);
1102 break;
1104 case choice_DigestReqInner_ntlmRequest: {
1105 krb5_principal clientprincipal;
1106 unsigned char sessionkey[16];
1107 unsigned char challange[8];
1108 uint32_t flags;
1109 Key *key = NULL;
1110 int version;
1112 r.element = choice_DigestRepInner_ntlmResponse;
1113 r.u.ntlmResponse.success = 0;
1114 r.u.ntlmResponse.flags = 0;
1115 r.u.ntlmResponse.sessionkey = NULL;
1116 r.u.ntlmResponse.tickets = NULL;
1118 /* get username */
1119 ret = krb5_parse_name(context,
1120 ireq.u.ntlmRequest.username,
1121 &clientprincipal);
1122 if (ret)
1123 goto failed;
1125 ret = _kdc_db_fetch(context, config, clientprincipal,
1126 HDB_F_GET_CLIENT, NULL, &user);
1127 krb5_free_principal(context, clientprincipal);
1128 if (ret) {
1129 krb5_set_error_string(context, "NTLM user %s not in database",
1130 ireq.u.ntlmRequest.username);
1131 goto failed;
1134 ret = get_digest_key(context, config, server, &crypto);
1135 if (ret)
1136 goto failed;
1138 ret = krb5_decrypt(context, crypto, KRB5_KU_DIGEST_OPAQUE,
1139 ireq.u.ntlmRequest.opaque.data,
1140 ireq.u.ntlmRequest.opaque.length, &buf);
1141 krb5_crypto_destroy(context, crypto);
1142 crypto = NULL;
1143 if (ret) {
1144 kdc_log(context, config, 0,
1145 "Failed to decrypt nonce from %s", from);
1146 goto failed;
1149 sp = krb5_storage_from_data(&buf);
1150 if (sp == NULL) {
1151 ret = ENOMEM;
1152 krb5_set_error_string(context, "out of memory");
1153 goto out;
1156 ret = krb5_storage_read(sp, challange, sizeof(challange));
1157 if (ret != sizeof(challange)) {
1158 krb5_set_error_string(context, "NTLM storage read challange");
1159 ret = ENOMEM;
1160 goto out;
1162 ret = krb5_ret_uint32(sp, &flags);
1163 if (ret) {
1164 krb5_set_error_string(context, "NTLM storage read flags");
1165 goto out;
1167 krb5_data_free(&buf);
1169 if ((flags & NTLM_NEG_NTLM) == 0) {
1170 ret = EINVAL;
1171 krb5_set_error_string(context, "NTLM not negotiated");
1172 goto out;
1175 ret = hdb_enctype2key(context, &user->entry,
1176 ETYPE_ARCFOUR_HMAC_MD5, &key);
1177 if (ret) {
1178 krb5_set_error_string(context, "NTLM missing arcfour key");
1179 goto out;
1182 /* check if this is NTLMv2 */
1183 if (ireq.u.ntlmRequest.ntlm.length != 24) {
1184 struct ntlm_buf infotarget, answer;
1185 char *targetname;
1187 if ((config->digests_allowed & NTLM_V2) == 0) {
1188 kdc_log(context, config, 0, "NTLM v2 not allowed");
1189 goto out;
1192 version = 2;
1194 targetname = get_ntlm_targetname(context, client);
1195 if (targetname == NULL) {
1196 krb5_set_error_string(context, "out of memory");
1197 ret = ENOMEM;
1198 goto out;
1201 answer.length = ireq.u.ntlmRequest.ntlm.length;
1202 answer.data = ireq.u.ntlmRequest.ntlm.data;
1204 ret = heim_ntlm_verify_ntlm2(key->key.keyvalue.data,
1205 key->key.keyvalue.length,
1206 ireq.u.ntlmRequest.username,
1207 targetname,
1209 challange,
1210 &answer,
1211 &infotarget,
1212 sessionkey);
1213 free(targetname);
1214 if (ret) {
1215 krb5_set_error_string(context, "NTLM v2 verify failed");
1216 goto failed;
1219 /* XXX verify infotarget matches client (checksum ?) */
1221 free(infotarget.data);
1222 /* */
1224 } else {
1225 struct ntlm_buf answer;
1227 version = 1;
1229 if (flags & NTLM_NEG_NTLM2_SESSION) {
1230 unsigned char sessionhash[MD5_DIGEST_LENGTH];
1231 MD5_CTX md5ctx;
1233 if ((config->digests_allowed & NTLM_V1_SESSION) == 0) {
1234 kdc_log(context, config, 0, "NTLM v1-session not allowed");
1235 ret = EINVAL;
1236 goto failed;
1239 if (ireq.u.ntlmRequest.lm.length != 24) {
1240 krb5_set_error_string(context, "LM hash have wrong length "
1241 "for NTLM session key");
1242 ret = EINVAL;
1243 goto failed;
1246 MD5_Init(&md5ctx);
1247 MD5_Update(&md5ctx, challange, sizeof(challange));
1248 MD5_Update(&md5ctx, ireq.u.ntlmRequest.lm.data, 8);
1249 MD5_Final(sessionhash, &md5ctx);
1250 memcpy(challange, sessionhash, sizeof(challange));
1251 } else {
1252 if ((config->digests_allowed & NTLM_V1) == 0) {
1253 kdc_log(context, config, 0, "NTLM v1 not allowed");
1254 goto failed;
1258 ret = heim_ntlm_calculate_ntlm1(key->key.keyvalue.data,
1259 key->key.keyvalue.length,
1260 challange, &answer);
1261 if (ret) {
1262 krb5_set_error_string(context, "NTLM missing arcfour key");
1263 goto failed;
1266 if (ireq.u.ntlmRequest.ntlm.length != answer.length ||
1267 memcmp(ireq.u.ntlmRequest.ntlm.data, answer.data, answer.length) != 0)
1269 free(answer.data);
1270 ret = EINVAL;
1271 krb5_set_error_string(context, "NTLM hash mismatch");
1272 goto failed;
1274 free(answer.data);
1277 MD4_CTX ctx;
1279 MD4_Init(&ctx);
1280 MD4_Update(&ctx,
1281 key->key.keyvalue.data, key->key.keyvalue.length);
1282 MD4_Final(sessionkey, &ctx);
1286 if (ireq.u.ntlmRequest.sessionkey) {
1287 unsigned char masterkey[MD4_DIGEST_LENGTH];
1288 RC4_KEY rc4;
1289 size_t len;
1291 if ((flags & NTLM_NEG_KEYEX) == 0) {
1292 krb5_set_error_string(context,
1293 "NTLM client failed to neg key "
1294 "exchange but still sent key");
1295 ret = EINVAL;
1296 goto failed;
1299 len = ireq.u.ntlmRequest.sessionkey->length;
1300 if (len != sizeof(masterkey)){
1301 krb5_set_error_string(context,
1302 "NTLM master key wrong length: %lu",
1303 (unsigned long)len);
1304 goto failed;
1307 RC4_set_key(&rc4, sizeof(sessionkey), sessionkey);
1309 RC4(&rc4, sizeof(masterkey),
1310 ireq.u.ntlmRequest.sessionkey->data,
1311 masterkey);
1312 memset(&rc4, 0, sizeof(rc4));
1314 r.u.ntlmResponse.sessionkey =
1315 malloc(sizeof(*r.u.ntlmResponse.sessionkey));
1316 if (r.u.ntlmResponse.sessionkey == NULL) {
1317 krb5_set_error_string(context, "out of memory");
1318 goto out;
1321 ret = krb5_data_copy(r.u.ntlmResponse.sessionkey,
1322 masterkey, sizeof(masterkey));
1323 if (ret) {
1324 krb5_set_error_string(context, "out of memory");
1325 goto out;
1329 r.u.ntlmResponse.success = 1;
1330 kdc_log(context, config, 0, "NTLM version %d successful for %s",
1331 version, ireq.u.ntlmRequest.username);
1332 break;
1334 case choice_DigestReqInner_supportedMechs:
1336 r.element = choice_DigestRepInner_supportedMechs;
1337 memset(&r.u.supportedMechs, 0, sizeof(r.u.supportedMechs));
1339 if (config->digests_allowed & NTLM_V1)
1340 r.u.supportedMechs.ntlm_v1 = 1;
1341 if (config->digests_allowed & NTLM_V1_SESSION)
1342 r.u.supportedMechs.ntlm_v1_session = 1;
1343 if (config->digests_allowed & NTLM_V2)
1344 r.u.supportedMechs.ntlm_v2 = 1;
1345 if (config->digests_allowed & DIGEST_MD5)
1346 r.u.supportedMechs.digest_md5 = 1;
1347 if (config->digests_allowed & CHAP_MD5)
1348 r.u.supportedMechs.chap_md5 = 1;
1349 if (config->digests_allowed & MS_CHAP_V2)
1350 r.u.supportedMechs.ms_chap_v2 = 1;
1351 break;
1353 default: {
1354 char *s;
1355 krb5_set_error_string(context, "unknown operation to digest");
1356 ret = EINVAL;
1358 failed:
1360 s = krb5_get_error_message(context, ret);
1361 if (s == NULL) {
1362 krb5_clear_error_string(context);
1363 goto out;
1366 kdc_log(context, config, 0, "Digest failed with: %s", s);
1368 r.element = choice_DigestRepInner_error;
1369 r.u.error.reason = strdup("unknown error");
1370 krb5_free_error_string(context, s);
1371 if (r.u.error.reason == NULL) {
1372 krb5_set_error_string(context, "out of memory");
1373 ret = ENOMEM;
1374 goto out;
1376 r.u.error.code = EINVAL;
1377 break;
1381 ASN1_MALLOC_ENCODE(DigestRepInner, buf.data, buf.length, &r, &size, ret);
1382 if (ret) {
1383 krb5_set_error_string(context, "Failed to encode inner digest reply");
1384 goto out;
1386 if (size != buf.length)
1387 krb5_abortx(context, "ASN1 internal error");
1389 krb5_auth_con_addflags(context, ac, KRB5_AUTH_CONTEXT_USE_SUBKEY, NULL);
1391 ret = krb5_mk_rep (context, ac, &rep.apRep);
1392 if (ret)
1393 goto out;
1396 krb5_keyblock *key;
1398 ret = krb5_auth_con_getlocalsubkey(context, ac, &key);
1399 if (ret)
1400 goto out;
1402 ret = krb5_crypto_init(context, key, 0, &crypto);
1403 krb5_free_keyblock (context, key);
1404 if (ret)
1405 goto out;
1408 ret = krb5_encrypt_EncryptedData(context, crypto, KRB5_KU_DIGEST_ENCRYPT,
1409 buf.data, buf.length, 0,
1410 &rep.innerRep);
1412 ASN1_MALLOC_ENCODE(DigestREP, reply->data, reply->length, &rep, &size, ret);
1413 if (ret) {
1414 krb5_set_error_string(context, "Failed to encode digest reply");
1415 goto out;
1417 if (size != reply->length)
1418 krb5_abortx(context, "ASN1 internal error");
1421 out:
1422 if (ac)
1423 krb5_auth_con_free(context, ac);
1424 if (ret)
1425 krb5_warn(context, ret, "Digest request from %s failed", from);
1426 if (ticket)
1427 krb5_free_ticket(context, ticket);
1428 if (id)
1429 krb5_kt_close(context, id);
1430 if (crypto)
1431 krb5_crypto_destroy(context, crypto);
1432 if (sp)
1433 krb5_storage_free(sp);
1434 if (user)
1435 _kdc_free_ent (context, user);
1436 if (server)
1437 _kdc_free_ent (context, server);
1438 if (client)
1439 _kdc_free_ent (context, client);
1440 if (password) {
1441 memset(password, 0, strlen(password));
1442 free (password);
1444 if (client_name)
1445 free (client_name);
1446 krb5_data_free(&buf);
1447 krb5_data_free(&serverNonce);
1448 free_DigestREP(&rep);
1449 free_DigestRepInner(&r);
1450 free_DigestReqInner(&ireq);
1452 return ret;