diff options
author | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-02 17:06:34 +0300 |
---|---|---|
committer | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2022-05-04 15:58:47 +0300 |
commit | f2aa7b8853b03cf0e74afb38d1a8a5b7011de9db (patch) | |
tree | e75625f993014ae6850d665a2001c49a6a2d311b /src/microhttpd/digestauth.c | |
parent | 46554d2c55a9bacad6881fbec705d0ca7d962684 (diff) | |
download | libmicrohttpd-f2aa7b8853b03cf0e74afb38d1a8a5b7011de9db.tar.gz libmicrohttpd-f2aa7b8853b03cf0e74afb38d1a8a5b7011de9db.zip |
digestauth: added management of nonce-nc map array slots
Diffstat (limited to 'src/microhttpd/digestauth.c')
-rw-r--r-- | src/microhttpd/digestauth.c | 291 |
1 files changed, 239 insertions, 52 deletions
diff --git a/src/microhttpd/digestauth.c b/src/microhttpd/digestauth.c index 5fc4374b..bc47a2d3 100644 --- a/src/microhttpd/digestauth.c +++ b/src/microhttpd/digestauth.c | |||
@@ -41,6 +41,16 @@ | |||
41 | #include <windows.h> | 41 | #include <windows.h> |
42 | #endif /* MHD_W32_MUTEX_ */ | 42 | #endif /* MHD_W32_MUTEX_ */ |
43 | 43 | ||
44 | |||
45 | /** | ||
46 | * Allow re-use of the nonce-nc map array slot after #REUSE_TIMEOUT seconds, | ||
47 | * if this slot is needed for the new nonce, while the old nonce was not used | ||
48 | * even one time by the client. | ||
49 | * Typically clients immediately use generated nonce for new request. | ||
50 | */ | ||
51 | #define REUSE_TIMEOUT 30 | ||
52 | |||
53 | |||
44 | /** | 54 | /** |
45 | * 48 bit value in bytes | 55 | * 48 bit value in bytes |
46 | */ | 56 | */ |
@@ -564,42 +574,6 @@ get_nonce_nc_idx (size_t arr_size, | |||
564 | 574 | ||
565 | 575 | ||
566 | /** | 576 | /** |
567 | * Add the new nonce to the nonce-nc map array. | ||
568 | * | ||
569 | * @param connection The MHD connection structure | ||
570 | * @param nonce the pointer that referenced a zero-terminated array of nonce | ||
571 | * @param noncelen the lenth of @a nonce, in characters | ||
572 | * @return #MHD_YES if successful, #MHD_NO if invalid (or we have no NC array) | ||
573 | */ | ||
574 | static bool | ||
575 | add_nonce (struct MHD_Connection *connection, | ||
576 | const char *nonce, | ||
577 | size_t noncelen) | ||
578 | { | ||
579 | struct MHD_Daemon *const daemon = connection->daemon; | ||
580 | struct MHD_NonceNc *nn; | ||
581 | |||
582 | mhd_assert (MAX_NONCE_LENGTH >= noncelen); | ||
583 | if (0 == daemon->nonce_nc_size) | ||
584 | return false; | ||
585 | |||
586 | nn = &daemon->nnc[get_nonce_nc_idx (daemon->nonce_nc_size, | ||
587 | nonce, | ||
588 | noncelen)]; | ||
589 | |||
590 | MHD_mutex_lock_chk_ (&daemon->nnc_lock); | ||
591 | memcpy (nn->nonce, | ||
592 | nonce, | ||
593 | noncelen); | ||
594 | nn->nonce[noncelen] = 0; | ||
595 | nn->nc = 0; | ||
596 | nn->nmask = 0; | ||
597 | MHD_mutex_unlock_chk_ (&daemon->nnc_lock); | ||
598 | return true; | ||
599 | } | ||
600 | |||
601 | |||
602 | /** | ||
603 | * Check nonce-nc map array with either new nonce counter | 577 | * Check nonce-nc map array with either new nonce counter |
604 | * or a whole new nonce. | 578 | * or a whole new nonce. |
605 | * | 579 | * |
@@ -814,6 +788,219 @@ calculate_nonce (uint64_t nonce_time, | |||
814 | 788 | ||
815 | 789 | ||
816 | /** | 790 | /** |
791 | * Extract timestamp from the given nonce. | ||
792 | * @param nonce the nonce to check | ||
793 | * @param noncelen the lenght of the nonce, zero for autodetect | ||
794 | * @param[out] ptimestamp the pointer to store extracted timestamp | ||
795 | * @return true if timestamp was extracted, | ||
796 | * false if nonce does not have valid timestamp. | ||
797 | */ | ||
798 | static bool | ||
799 | get_nonce_timestamp (const char *const nonce, | ||
800 | size_t noncelen, | ||
801 | uint64_t *const ptimestamp) | ||
802 | { | ||
803 | mhd_assert ((0 == noncelen) || (strlen (nonce) == noncelen)); | ||
804 | if (0 == noncelen) | ||
805 | noncelen = strlen (nonce); | ||
806 | |||
807 | if ( (NONCE_STD_LEN (SHA256_DIGEST_SIZE) != noncelen) && | ||
808 | (NONCE_STD_LEN (MD5_DIGEST_SIZE) != noncelen) ) | ||
809 | return false; | ||
810 | |||
811 | if (TIMESTAMP_CHARS_LEN != | ||
812 | MHD_strx_to_uint64_n_ (nonce + noncelen - TIMESTAMP_CHARS_LEN, | ||
813 | TIMESTAMP_CHARS_LEN, | ||
814 | ptimestamp)) | ||
815 | return false; | ||
816 | return true; | ||
817 | } | ||
818 | |||
819 | |||
820 | /** | ||
821 | * Check whether it is possible to use slot in nonce-nc map array. | ||
822 | * | ||
823 | * Should be called with mutex held to avoid external modification of | ||
824 | * the slot data. | ||
825 | * | ||
826 | * @param nn the pointer to the nonce-nc slot | ||
827 | * @param now the current time | ||
828 | * @param new_nonce the new nonce supposed to be stored in this slot, | ||
829 | * zero-terminated | ||
830 | * @param new_nonce_len the length of the @a new_nonce in chars, not including | ||
831 | * the terminating zero. | ||
832 | * @return true if the slot can be used to store the new nonce, | ||
833 | * false otherwise. | ||
834 | */ | ||
835 | static bool | ||
836 | is_slot_available (const struct MHD_NonceNc *const nn, | ||
837 | const uint64_t now, | ||
838 | const char *const new_nonce, | ||
839 | size_t new_nonce_len) | ||
840 | { | ||
841 | uint64_t timestamp; | ||
842 | bool timestamp_valid; | ||
843 | mhd_assert (new_nonce_len <= NONCE_STD_LEN (MAX_DIGEST)); | ||
844 | mhd_assert (NONCE_STD_LEN (MAX_DIGEST) < MAX_NONCE_LENGTH); | ||
845 | if (0 == nn->nonce[0]) | ||
846 | return true; /* The slot is empty */ | ||
847 | |||
848 | if (0 != nn->nc) | ||
849 | return true; /* Client already used the nonce in this slot at least | ||
850 | one time, re-use the slot */ | ||
851 | |||
852 | if (0 == memcmp (nn->nonce, new_nonce, new_nonce_len + 1)) | ||
853 | { | ||
854 | /* The slot has the same nonce already, the same nonce was already generated | ||
855 | * and used, this slot cannot be used with the same nonce as it would | ||
856 | * just reset received 'nc' values. */ | ||
857 | return false; | ||
858 | } | ||
859 | |||
860 | timestamp_valid = get_nonce_timestamp (nn->nonce, 0, ×tamp); | ||
861 | mhd_assert (timestamp_valid); | ||
862 | if (! timestamp_valid) | ||
863 | return true; /* Invalid timestamp in nonce-nc, should not be possible */ | ||
864 | |||
865 | if ((REUSE_TIMEOUT * 1000) < TRIM_TO_TIMESTAMP (now - timestamp)) | ||
866 | return true; | ||
867 | |||
868 | return false; | ||
869 | } | ||
870 | |||
871 | |||
872 | /** | ||
873 | * Calculate the server nonce so that it mitigates replay attacks and add | ||
874 | * the new nonce to the nonce-nc map array. | ||
875 | * | ||
876 | * @param connection the MHD connection structure | ||
877 | * @param timestamp the current timestamp | ||
878 | * @param realm the string of characters that describes the realm of auth | ||
879 | * @param da the digest algorithm to use | ||
880 | * @param[out] nonce the pointer to a character array for the nonce to put in, | ||
881 | * must provide NONCE_STD_LEN(da->digest_size)+1 bytes | ||
882 | * @return true if the new nonce has been added to the nonce-nc map array, | ||
883 | * false otherwise. | ||
884 | */ | ||
885 | static bool | ||
886 | calculate_add_nonce (struct MHD_Connection *const connection, | ||
887 | uint64_t timestamp, | ||
888 | const char *realm, | ||
889 | struct DigestAlgorithm *da, | ||
890 | char *nonce) | ||
891 | { | ||
892 | struct MHD_Daemon *const daemon = connection->daemon; | ||
893 | struct MHD_NonceNc *nn; | ||
894 | const size_t nonce_size = NONCE_STD_LEN (da->digest_size); | ||
895 | bool ret; | ||
896 | |||
897 | mhd_assert (MAX_NONCE_LENGTH >= nonce_size); | ||
898 | mhd_assert (0 != nonce_size); | ||
899 | |||
900 | calculate_nonce (timestamp, | ||
901 | connection->method, | ||
902 | connection->daemon->digest_auth_random, | ||
903 | connection->daemon->digest_auth_rand_size, | ||
904 | connection->url, | ||
905 | realm, | ||
906 | da, | ||
907 | nonce); | ||
908 | |||
909 | if (0 == daemon->nonce_nc_size) | ||
910 | return false; | ||
911 | |||
912 | nn = daemon->nnc + get_nonce_nc_idx (daemon->nonce_nc_size, | ||
913 | nonce, | ||
914 | nonce_size); | ||
915 | |||
916 | MHD_mutex_lock_chk_ (&daemon->nnc_lock); | ||
917 | if (is_slot_available (nn, timestamp, nonce, nonce_size)) | ||
918 | { | ||
919 | memcpy (nn->nonce, | ||
920 | nonce, | ||
921 | nonce_size); | ||
922 | nn->nonce[nonce_size] = 0; /* With terminating zero */ | ||
923 | nn->nc = 0; | ||
924 | nn->nmask = 0; | ||
925 | ret = true; | ||
926 | } | ||
927 | else | ||
928 | ret = false; | ||
929 | MHD_mutex_unlock_chk_ (&daemon->nnc_lock); | ||
930 | |||
931 | return ret; | ||
932 | } | ||
933 | |||
934 | |||
935 | /** | ||
936 | * Calculate the server nonce so that it mitigates replay attacks and add | ||
937 | * the new nonce to the nonce-nc map array. | ||
938 | * | ||
939 | * @param connection the MHD connection structure | ||
940 | * @param realm A string of characters that describes the realm of auth. | ||
941 | * @param da digest algorithm to use | ||
942 | * @param[out] nonce A pointer to a character array for the nonce to put in, | ||
943 | * must provide NONCE_STD_LEN(da->digest_size)+1 bytes | ||
944 | */ | ||
945 | static bool | ||
946 | calculate_add_nonce_with_retry (struct MHD_Connection *const connection, | ||
947 | const char *realm, | ||
948 | struct DigestAlgorithm *da, | ||
949 | char *nonce) | ||
950 | { | ||
951 | const uint64_t timestamp1 = MHD_monotonic_msec_counter (); | ||
952 | |||
953 | if (! calculate_add_nonce (connection, timestamp1, realm, da, nonce)) | ||
954 | { | ||
955 | /* Either: | ||
956 | * 1. The same nonce was already generated. If it will be used then one | ||
957 | * of the clients will fail (as no initial 'nc' value could be given to | ||
958 | * the client, the second client which will use 'nc=00000001' will fail). | ||
959 | * 2. Another nonce uses the same slot, and this nonce never has been | ||
960 | * used by the client and this nonce is still fresh enough. | ||
961 | */ | ||
962 | const size_t digest_size = da->digest_size; | ||
963 | char nonce2[NONCE_STD_LEN (VLA_ARRAY_LEN_DIGEST (digest_size)) + 1]; | ||
964 | uint64_t timestamp2; | ||
965 | if (0 == connection->daemon->nonce_nc_size) | ||
966 | return false; /* No need to re-try */ | ||
967 | |||
968 | timestamp2 = MHD_monotonic_msec_counter (); | ||
969 | if (timestamp1 == timestamp2) | ||
970 | { | ||
971 | /* The timestamps are equal, need to generate some arbitrary | ||
972 | * difference for nonce. */ | ||
973 | uint64_t base1; | ||
974 | uint32_t base2; | ||
975 | uint16_t base3; | ||
976 | uint8_t base4; | ||
977 | base1 = (uint64_t) (uintptr_t) nonce2; | ||
978 | base2 = ((uint32_t) (base1 >> 32)) ^ ((uint32_t) base1); | ||
979 | base2 = _MHD_ROTL32 (base2, 4); | ||
980 | base3 = ((uint16_t) (base2 >> 16)) ^ ((uint16_t) base2); | ||
981 | base4 = ((uint8_t) (base3 >> 8)) ^ ((uint8_t) base3); | ||
982 | base1 = (uint64_t) (uintptr_t) connection; | ||
983 | base2 = ((uint32_t) (base1 >> 32)) ^ ((uint32_t) base1); | ||
984 | base2 = _MHD_ROTL32 (base2, (((base1 >> 4) ^ base1) % 32)); | ||
985 | base3 = ((uint16_t) (base2 >> 16)) ^ ((uint16_t) base2); | ||
986 | base4 = ((uint8_t) (base3 >> 8)) ^ ((uint8_t) base3); | ||
987 | timestamp2 -= (base4 & 0x7f); /* Use up to 127 ms difference */ | ||
988 | } | ||
989 | if (! calculate_add_nonce (connection, timestamp2, realm, da, nonce2)) | ||
990 | { | ||
991 | /* No free slot has been found. Re-tries are expensive, just use | ||
992 | * the generated nonce. As it is not stored in nonce-nc map array, | ||
993 | * the next request of the client will be recognized as valid, but 'stale' | ||
994 | * so client should re-try automatically. */ | ||
995 | return false; | ||
996 | } | ||
997 | memcpy (nonce, nonce2, NONCE_STD_LEN (digest_size) + 1); | ||
998 | } | ||
999 | return true; | ||
1000 | } | ||
1001 | |||
1002 | |||
1003 | /** | ||
817 | * Test if the given key-value pair is in the headers for the | 1004 | * Test if the given key-value pair is in the headers for the |
818 | * given connection. | 1005 | * given connection. |
819 | * | 1006 | * |
@@ -1461,30 +1648,30 @@ MHD_queue_auth_fail_response2 (struct MHD_Connection *connection, | |||
1461 | if (NULL == response) | 1648 | if (NULL == response) |
1462 | return MHD_NO; | 1649 | return MHD_NO; |
1463 | 1650 | ||
1651 | if (0 == connection->daemon->nonce_nc_size) | ||
1652 | { | ||
1653 | #ifdef HAVE_MESSAGES | ||
1654 | MHD_DLOG (connection->daemon, | ||
1655 | _ ("The nonce array size is zero.\n")); | ||
1656 | #endif /* HAVE_MESSAGES */ | ||
1657 | return MHD_NO; | ||
1658 | } | ||
1659 | |||
1464 | if (1) | 1660 | if (1) |
1465 | { | 1661 | { |
1466 | char nonce[NONCE_STD_LEN (VLA_ARRAY_LEN_DIGEST (da.digest_size)) + 1]; | 1662 | char nonce[NONCE_STD_LEN (VLA_ARRAY_LEN_DIGEST (da.digest_size)) + 1]; |
1467 | 1663 | ||
1468 | VLA_CHECK_LEN_DIGEST (da.digest_size); | 1664 | VLA_CHECK_LEN_DIGEST (da.digest_size); |
1469 | /* Generating the server nonce */ | 1665 | if (! calculate_add_nonce_with_retry (connection, realm, &da, nonce)) |
1470 | calculate_nonce (MHD_monotonic_msec_counter (), | ||
1471 | connection->method, | ||
1472 | connection->daemon->digest_auth_random, | ||
1473 | connection->daemon->digest_auth_rand_size, | ||
1474 | connection->url, | ||
1475 | realm, | ||
1476 | &da, | ||
1477 | nonce); | ||
1478 | if (! add_nonce (connection, | ||
1479 | nonce, | ||
1480 | NONCE_STD_LEN (da.digest_size))) | ||
1481 | { | 1666 | { |
1482 | #ifdef HAVE_MESSAGES | 1667 | #ifdef HAVE_MESSAGES |
1483 | MHD_DLOG (connection->daemon, | 1668 | MHD_DLOG (connection->daemon, |
1484 | _ ( | 1669 | _ ("Could not register nonce. Client's requests with this " |
1485 | "Could not register nonce (is the nonce array size zero?).\n")); | 1670 | "nonce will be always 'stale'. Probably clients' requests " |
1486 | #endif | 1671 | "are too intensive.\n")); |
1487 | return MHD_NO; | 1672 | #else /* ! HAVE_MESSAGES */ |
1673 | (void) 0; | ||
1674 | #endif /* ! HAVE_MESSAGES */ | ||
1488 | } | 1675 | } |
1489 | /* Building the authentication header */ | 1676 | /* Building the authentication header */ |
1490 | hlen = MHD_snprintf_ (NULL, | 1677 | hlen = MHD_snprintf_ (NULL, |