diff options
author | Christian Grothoff <christian@grothoff.org> | 2015-05-29 10:20:53 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2015-05-29 10:20:53 +0000 |
commit | f2f5b555500c4f965c330fcd6e5e6c428d21bcf0 (patch) | |
tree | bf77543636a9866e5d586f2c4c21f438cc8ed123 | |
parent | dd80b6137d38a9e01c37daea18e11542ec2edd05 (diff) | |
download | libmicrohttpd-f2f5b555500c4f965c330fcd6e5e6c428d21bcf0.tar.gz libmicrohttpd-f2f5b555500c4f965c330fcd6e5e6c428d21bcf0.zip |
fix digest authentication with escaped urls, as reported on mailinglist
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | src/microhttpd/digestauth.c | 207 | ||||
-rw-r--r-- | src/testcurl/test_digestauth.c | 77 |
3 files changed, 149 insertions, 139 deletions
@@ -1,3 +1,7 @@ | |||
1 | Fri May 29 12:23:01 CEST 2015 | ||
2 | Fixing digest authentication when used in combination | ||
3 | with escaped characters in URLs. -CG/AW | ||
4 | |||
1 | Wed May 13 11:49:09 CEST 2015 | 5 | Wed May 13 11:49:09 CEST 2015 |
2 | Releasing libmicrohttpd 0.9.42. -CG | 6 | Releasing libmicrohttpd 0.9.42. -CG |
3 | 7 | ||
diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c index 9c3fe8c5..4cc7f61b 100644 --- a/src/microhttpd/digestauth.c +++ b/src/microhttpd/digestauth.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | This file is part of libmicrohttpd | 2 | This file is part of libmicrohttpd |
3 | Copyright (C) 2010, 2011, 2012 Daniel Pittman and Christian Grothoff | 3 | Copyright (C) 2010, 2011, 2012, 2015 Daniel Pittman and Christian Grothoff |
4 | 4 | ||
5 | This library is free software; you can redistribute it and/or | 5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public | 6 | modify it under the terms of the GNU Lesser General Public |
@@ -472,8 +472,8 @@ test_header (struct MHD_Connection *connection, | |||
472 | * | 472 | * |
473 | * @param connection connections with headers to compare against | 473 | * @param connection connections with headers to compare against |
474 | * @param args argument URI string (after "?" in URI) | 474 | * @param args argument URI string (after "?" in URI) |
475 | * @return MHD_YES if the arguments match, | 475 | * @return #MHD_YES if the arguments match, |
476 | * MHD_NO if not | 476 | * #MHD_NO if not |
477 | */ | 477 | */ |
478 | static int | 478 | static int |
479 | check_argument_match (struct MHD_Connection *connection, | 479 | check_argument_match (struct MHD_Connection *connection, |
@@ -632,10 +632,83 @@ MHD_digest_auth_check (struct MHD_Connection *connection, | |||
632 | header value. */ | 632 | header value. */ |
633 | return MHD_NO; | 633 | return MHD_NO; |
634 | } | 634 | } |
635 | /* 8 = 4 hexadecimal numbers for the timestamp */ | ||
636 | nonce_time = strtoul (nonce + len - 8, (char **)NULL, 16); | ||
637 | t = (uint32_t) MHD_monotonic_time(); | ||
638 | /* | ||
639 | * First level vetting for the nonce validity: if the timestamp | ||
640 | * attached to the nonce exceeds `nonce_timeout', then the nonce is | ||
641 | * invalid. | ||
642 | */ | ||
643 | if ( (t > nonce_time + nonce_timeout) || | ||
644 | (nonce_time + nonce_timeout < nonce_time) ) | ||
645 | { | ||
646 | /* too old */ | ||
647 | return MHD_INVALID_NONCE; | ||
648 | } | ||
649 | |||
650 | calculate_nonce (nonce_time, | ||
651 | connection->method, | ||
652 | connection->daemon->digest_auth_random, | ||
653 | connection->daemon->digest_auth_rand_size, | ||
654 | connection->url, | ||
655 | realm, | ||
656 | noncehashexp); | ||
657 | /* | ||
658 | * Second level vetting for the nonce validity | ||
659 | * if the timestamp attached to the nonce is valid | ||
660 | * and possibly fabricated (in case of an attack) | ||
661 | * the attacker must also know the random seed to be | ||
662 | * able to generate a "sane" nonce, which if he does | ||
663 | * not, the nonce fabrication process going to be | ||
664 | * very hard to achieve. | ||
665 | */ | ||
666 | |||
667 | if (0 != strcmp (nonce, noncehashexp)) | ||
668 | { | ||
669 | return MHD_INVALID_NONCE; | ||
670 | } | ||
671 | if ( (0 == lookup_sub_value (cnonce, | ||
672 | sizeof (cnonce), | ||
673 | header, "cnonce")) || | ||
674 | (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) || | ||
675 | ( (0 != strcmp (qop, "auth")) && | ||
676 | (0 != strcmp (qop, "")) ) || | ||
677 | (0 == lookup_sub_value (nc, sizeof (nc), header, "nc")) || | ||
678 | (0 == lookup_sub_value (response, sizeof (response), header, "response")) ) | ||
679 | { | ||
680 | #if HAVE_MESSAGES | ||
681 | MHD_DLOG (connection->daemon, | ||
682 | "Authentication failed, invalid format.\n"); | ||
683 | #endif | ||
684 | return MHD_NO; | ||
685 | } | ||
686 | nci = strtoul (nc, &end, 16); | ||
687 | if ( ('\0' != *end) || | ||
688 | ( (LONG_MAX == nci) && | ||
689 | (ERANGE == errno) ) ) | ||
690 | { | ||
691 | #if HAVE_MESSAGES | ||
692 | MHD_DLOG (connection->daemon, | ||
693 | "Authentication failed, invalid format.\n"); | ||
694 | #endif | ||
695 | return MHD_NO; /* invalid nonce format */ | ||
696 | } | ||
697 | /* | ||
698 | * Checking if that combination of nonce and nc is sound | ||
699 | * and not a replay attack attempt. Also adds the nonce | ||
700 | * to the nonce-nc map if it does not exist there. | ||
701 | */ | ||
702 | |||
703 | if (MHD_YES != check_nonce_nc (connection, nonce, nci)) | ||
704 | { | ||
705 | return MHD_NO; | ||
706 | } | ||
707 | |||
635 | { | 708 | { |
636 | char *uri; | 709 | char *uri; |
637 | 710 | ||
638 | uri = malloc(left + 1); | 711 | uri = malloc (left + 1); |
639 | if (NULL == uri) | 712 | if (NULL == uri) |
640 | { | 713 | { |
641 | #if HAVE_MESSAGES | 714 | #if HAVE_MESSAGES |
@@ -648,24 +721,31 @@ MHD_digest_auth_check (struct MHD_Connection *connection, | |||
648 | left + 1, | 721 | left + 1, |
649 | header, "uri")) | 722 | header, "uri")) |
650 | { | 723 | { |
651 | free(uri); | 724 | free (uri); |
652 | return MHD_NO; | 725 | return MHD_NO; |
653 | } | 726 | } |
654 | 727 | ||
655 | /* 8 = 4 hexadecimal numbers for the timestamp */ | 728 | digest_calc_ha1("md5", |
656 | nonce_time = strtoul (nonce + len - 8, (char **)NULL, 16); | 729 | username, |
657 | t = (uint32_t) MHD_monotonic_time(); | 730 | realm, |
658 | /* | 731 | password, |
659 | * First level vetting for the nonce validity: if the timestamp | 732 | nonce, |
660 | * attached to the nonce exceeds `nonce_timeout', then the nonce is | 733 | cnonce, |
661 | * invalid. | 734 | ha1); |
662 | */ | 735 | digest_calc_response (ha1, |
663 | if ( (t > nonce_time + nonce_timeout) || | 736 | nonce, |
664 | (nonce_time + nonce_timeout < nonce_time) ) | 737 | nc, |
665 | { | 738 | cnonce, |
666 | free(uri); | 739 | qop, |
667 | return MHD_INVALID_NONCE; | 740 | connection->method, |
668 | } | 741 | uri, |
742 | hentity, | ||
743 | respexp); | ||
744 | |||
745 | /* Need to unescape URI before comparing with connection->url */ | ||
746 | connection->daemon->unescape_callback (connection->daemon->unescape_callback_cls, | ||
747 | connection, | ||
748 | uri); | ||
669 | if (0 != strncmp (uri, | 749 | if (0 != strncmp (uri, |
670 | connection->url, | 750 | connection->url, |
671 | strlen (connection->url))) | 751 | strlen (connection->url))) |
@@ -674,9 +754,10 @@ MHD_digest_auth_check (struct MHD_Connection *connection, | |||
674 | MHD_DLOG (connection->daemon, | 754 | MHD_DLOG (connection->daemon, |
675 | "Authentication failed, URI does not match.\n"); | 755 | "Authentication failed, URI does not match.\n"); |
676 | #endif | 756 | #endif |
677 | free(uri); | 757 | free (uri); |
678 | return MHD_NO; | 758 | return MHD_NO; |
679 | } | 759 | } |
760 | |||
680 | { | 761 | { |
681 | const char *args = strchr (uri, '?'); | 762 | const char *args = strchr (uri, '?'); |
682 | 763 | ||
@@ -692,89 +773,11 @@ MHD_digest_auth_check (struct MHD_Connection *connection, | |||
692 | MHD_DLOG (connection->daemon, | 773 | MHD_DLOG (connection->daemon, |
693 | "Authentication failed, arguments do not match.\n"); | 774 | "Authentication failed, arguments do not match.\n"); |
694 | #endif | 775 | #endif |
695 | free(uri); | 776 | free (uri); |
696 | return MHD_NO; | 777 | return MHD_NO; |
697 | } | 778 | } |
698 | } | 779 | } |
699 | calculate_nonce (nonce_time, | 780 | free (uri); |
700 | connection->method, | ||
701 | connection->daemon->digest_auth_random, | ||
702 | connection->daemon->digest_auth_rand_size, | ||
703 | connection->url, | ||
704 | realm, | ||
705 | noncehashexp); | ||
706 | /* | ||
707 | * Second level vetting for the nonce validity | ||
708 | * if the timestamp attached to the nonce is valid | ||
709 | * and possibly fabricated (in case of an attack) | ||
710 | * the attacker must also know the random seed to be | ||
711 | * able to generate a "sane" nonce, which if he does | ||
712 | * not, the nonce fabrication process going to be | ||
713 | * very hard to achieve. | ||
714 | */ | ||
715 | |||
716 | if (0 != strcmp (nonce, noncehashexp)) | ||
717 | { | ||
718 | free(uri); | ||
719 | return MHD_INVALID_NONCE; | ||
720 | } | ||
721 | if ( (0 == lookup_sub_value (cnonce, | ||
722 | sizeof (cnonce), | ||
723 | header, "cnonce")) || | ||
724 | (0 == lookup_sub_value (qop, sizeof (qop), header, "qop")) || | ||
725 | ( (0 != strcmp (qop, "auth")) && | ||
726 | (0 != strcmp (qop, "")) ) || | ||
727 | (0 == lookup_sub_value (nc, sizeof (nc), header, "nc")) || | ||
728 | (0 == lookup_sub_value (response, sizeof (response), header, "response")) ) | ||
729 | { | ||
730 | #if HAVE_MESSAGES | ||
731 | MHD_DLOG (connection->daemon, | ||
732 | "Authentication failed, invalid format.\n"); | ||
733 | #endif | ||
734 | free(uri); | ||
735 | return MHD_NO; | ||
736 | } | ||
737 | nci = strtoul (nc, &end, 16); | ||
738 | if ( ('\0' != *end) || | ||
739 | ( (LONG_MAX == nci) && | ||
740 | (ERANGE == errno) ) ) | ||
741 | { | ||
742 | #if HAVE_MESSAGES | ||
743 | MHD_DLOG (connection->daemon, | ||
744 | "Authentication failed, invalid format.\n"); | ||
745 | #endif | ||
746 | free(uri); | ||
747 | return MHD_NO; /* invalid nonce format */ | ||
748 | } | ||
749 | /* | ||
750 | * Checking if that combination of nonce and nc is sound | ||
751 | * and not a replay attack attempt. Also adds the nonce | ||
752 | * to the nonce-nc map if it does not exist there. | ||
753 | */ | ||
754 | |||
755 | if (MHD_YES != check_nonce_nc (connection, nonce, nci)) | ||
756 | { | ||
757 | free(uri); | ||
758 | return MHD_NO; | ||
759 | } | ||
760 | |||
761 | digest_calc_ha1("md5", | ||
762 | username, | ||
763 | realm, | ||
764 | password, | ||
765 | nonce, | ||
766 | cnonce, | ||
767 | ha1); | ||
768 | digest_calc_response (ha1, | ||
769 | nonce, | ||
770 | nc, | ||
771 | cnonce, | ||
772 | qop, | ||
773 | connection->method, | ||
774 | uri, | ||
775 | hentity, | ||
776 | respexp); | ||
777 | free(uri); | ||
778 | return (0 == strcmp(response, respexp)) | 781 | return (0 == strcmp(response, respexp)) |
779 | ? MHD_YES | 782 | ? MHD_YES |
780 | : MHD_NO; | 783 | : MHD_NO; |
@@ -835,7 +838,7 @@ MHD_queue_auth_fail_response (struct MHD_Connection *connection, | |||
835 | : ""); | 838 | : ""); |
836 | { | 839 | { |
837 | char *header; | 840 | char *header; |
838 | 841 | ||
839 | header = malloc(hlen + 1); | 842 | header = malloc(hlen + 1); |
840 | if (NULL == header) | 843 | if (NULL == header) |
841 | { | 844 | { |
diff --git a/src/testcurl/test_digestauth.c b/src/testcurl/test_digestauth.c index 255e086a..fd579196 100644 --- a/src/testcurl/test_digestauth.c +++ b/src/testcurl/test_digestauth.c | |||
@@ -73,7 +73,8 @@ ahc_echo (void *cls, | |||
73 | const char *url, | 73 | const char *url, |
74 | const char *method, | 74 | const char *method, |
75 | const char *version, | 75 | const char *version, |
76 | const char *upload_data, size_t *upload_data_size, | 76 | const char *upload_data, |
77 | size_t *upload_data_size, | ||
77 | void **unused) | 78 | void **unused) |
78 | { | 79 | { |
79 | struct MHD_Response *response; | 80 | struct MHD_Response *response; |
@@ -82,44 +83,47 @@ ahc_echo (void *cls, | |||
82 | const char *realm = "test@example.com"; | 83 | const char *realm = "test@example.com"; |
83 | int ret; | 84 | int ret; |
84 | 85 | ||
85 | username = MHD_digest_auth_get_username(connection); | 86 | username = MHD_digest_auth_get_username (connection); |
86 | if ( (username == NULL) || | 87 | if ( (username == NULL) || |
87 | (0 != strcmp (username, "testuser")) ) | 88 | (0 != strcmp (username, "testuser")) ) |
88 | { | 89 | { |
89 | response = MHD_create_response_from_buffer(strlen (DENIED), | 90 | response = MHD_create_response_from_buffer (strlen (DENIED), |
90 | DENIED, | 91 | DENIED, |
91 | MHD_RESPMEM_PERSISTENT); | 92 | MHD_RESPMEM_PERSISTENT); |
92 | ret = MHD_queue_auth_fail_response(connection, realm, | 93 | ret = MHD_queue_auth_fail_response(connection, realm, |
93 | MY_OPAQUE, | 94 | MY_OPAQUE, |
94 | response, | 95 | response, |
95 | MHD_NO); | 96 | MHD_NO); |
96 | MHD_destroy_response(response); | 97 | MHD_destroy_response(response); |
97 | return ret; | 98 | return ret; |
98 | } | 99 | } |
99 | ret = MHD_digest_auth_check(connection, realm, | 100 | ret = MHD_digest_auth_check(connection, realm, |
100 | username, | 101 | username, |
101 | password, | 102 | password, |
102 | 300); | 103 | 300); |
103 | free(username); | 104 | free(username); |
104 | if ( (ret == MHD_INVALID_NONCE) || | 105 | if ( (ret == MHD_INVALID_NONCE) || |
105 | (ret == MHD_NO) ) | 106 | (ret == MHD_NO) ) |
106 | { | 107 | { |
107 | response = MHD_create_response_from_buffer(strlen (DENIED), | 108 | response = MHD_create_response_from_buffer(strlen (DENIED), |
108 | DENIED, | 109 | DENIED, |
109 | MHD_RESPMEM_PERSISTENT); | 110 | MHD_RESPMEM_PERSISTENT); |
110 | if (NULL == response) | 111 | if (NULL == response) |
111 | return MHD_NO; | 112 | return MHD_NO; |
112 | ret = MHD_queue_auth_fail_response(connection, realm, | 113 | ret = MHD_queue_auth_fail_response(connection, realm, |
113 | MY_OPAQUE, | 114 | MY_OPAQUE, |
114 | response, | 115 | response, |
115 | (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO); | 116 | (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO); |
116 | MHD_destroy_response(response); | 117 | MHD_destroy_response(response); |
117 | return ret; | 118 | return ret; |
118 | } | 119 | } |
119 | response = MHD_create_response_from_buffer(strlen(PAGE), PAGE, | 120 | response = MHD_create_response_from_buffer (strlen(PAGE), |
120 | MHD_RESPMEM_PERSISTENT); | 121 | PAGE, |
121 | ret = MHD_queue_response(connection, MHD_HTTP_OK, response); | 122 | MHD_RESPMEM_PERSISTENT); |
122 | MHD_destroy_response(response); | 123 | ret = MHD_queue_response (connection, |
124 | MHD_HTTP_OK, | ||
125 | response); | ||
126 | MHD_destroy_response (response); | ||
123 | return ret; | 127 | return ret; |
124 | } | 128 | } |
125 | 129 | ||
@@ -144,24 +148,24 @@ testDigestAuth () | |||
144 | fd = open("/dev/urandom", O_RDONLY); | 148 | fd = open("/dev/urandom", O_RDONLY); |
145 | if (-1 == fd) | 149 | if (-1 == fd) |
146 | { | 150 | { |
147 | fprintf(stderr, "Failed to open `%s': %s\n", | 151 | fprintf(stderr, "Failed to open `%s': %s\n", |
148 | "/dev/urandom", | 152 | "/dev/urandom", |
149 | strerror(errno)); | 153 | strerror(errno)); |
150 | return 1; | 154 | return 1; |
151 | } | 155 | } |
152 | while (off < 8) | 156 | while (off < 8) |
153 | { | 157 | { |
154 | len = read(fd, rnd, 8); | 158 | len = read(fd, rnd, 8); |
155 | if (len == -1) | 159 | if (len == -1) |
156 | { | 160 | { |
157 | fprintf(stderr, "Failed to read `%s': %s\n", | 161 | fprintf(stderr, "Failed to read `%s': %s\n", |
158 | "/dev/urandom", | 162 | "/dev/urandom", |
159 | strerror(errno)); | 163 | strerror(errno)); |
160 | (void) close(fd); | 164 | (void) close(fd); |
161 | return 1; | 165 | return 1; |
162 | } | 166 | } |
163 | off += len; | 167 | off += len; |
164 | } | 168 | } |
165 | (void) close(fd); | 169 | (void) close(fd); |
166 | #else | 170 | #else |
167 | { | 171 | { |
@@ -193,7 +197,7 @@ testDigestAuth () | |||
193 | if (d == NULL) | 197 | if (d == NULL) |
194 | return 1; | 198 | return 1; |
195 | c = curl_easy_init (); | 199 | c = curl_easy_init (); |
196 | curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1337/"); | 200 | curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1337/bar%20 foo?a=bü%20"); |
197 | curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); | 201 | curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); |
198 | curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); | 202 | curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); |
199 | curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); | 203 | curl_easy_setopt (c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); |
@@ -225,7 +229,6 @@ testDigestAuth () | |||
225 | } | 229 | } |
226 | 230 | ||
227 | 231 | ||
228 | |||
229 | int | 232 | int |
230 | main (int argc, char *const *argv) | 233 | main (int argc, char *const *argv) |
231 | { | 234 | { |