diff --git a/CMakeLists.txt b/CMakeLists.txt index 36e821a54..2bb232486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,7 +371,6 @@ if (${ZM_CRYPTO_BACKEND} STREQUAL "gnutls") set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") endif() mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) - check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) set(optlibsfound "${optlibsfound} GnuTLS") else() set(optlibsnotfound "${optlibsnotfound} GnuTLS") @@ -381,11 +380,9 @@ elseif (${ZM_CRYPTO_BACKEND} STREQUAL "openssl") find_package(OpenSSL REQUIRED) if(OPENSSL_FOUND) set(HAVE_LIBOPENSSL 1) - set(HAVE_LIBCRYPTO 1) list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") include_directories("${OPENSSL_INCLUDE_DIR}") set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) set(optlibsfound "${optlibsfound} OpenSSL") else() set(optlibsnotfound "${optlibsnotfound} OpenSSL") @@ -679,41 +676,6 @@ if(ZM_ONVIF) set(ZM_HAS_ONVIF 1) endif() -# Check for authentication functions -if(HAVE_OPENSSL_MD5_H) - set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" - HAVE_MD5_OPENSSL) -endif() - -if(HAVE_GNUTLS_GNUTLS_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - gnutls_fingerprint - "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" - HAVE_DECL_GNUTLS_FINGERPRINT) -endif() - -if(NOT HAVE_DECL_GNUTLS_FINGERPRINT AND HAVE_MD5_OPENSSL) - set(HAVE_DECL_MD5 1) -endif() - -if((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) - message(AUTHOR_WARNING - "ZoneMinder requires a working MD5 function for hashed authentication but - none were found - hashed authentication will not be available") -endif() - -# Dirty fix for zm_user only using openssl's md5 if gnutls is not available. -# This needs to be fixed in zm_user.[h,cpp] but such fix will also require changes to configure.ac -if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) - set(HAVE_GNUTLS_OPENSSL_H 0) -endif() - # Check for Perl find_package(Perl) if(NOT PERL_FOUND) diff --git a/distros/ubuntu1504_cmake_split_packages/rules b/distros/ubuntu1504_cmake_split_packages/rules index 4cf1e0c58..218ab3c17 100755 --- a/distros/ubuntu1504_cmake_split_packages/rules +++ b/distros/ubuntu1504_cmake_split_packages/rules @@ -15,7 +15,6 @@ DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) CFLAGS = -Wall -CXXFLAGS = -DHAVE_LIBCRYPTO ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) DEBOPT = --enable-debug diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 583f5d8c2..5f4139b61 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,6 +1,7 @@ #include "zm_crypt.h" #include "zm_logger.h" +#include "zm_utils.h" #include "BCrypt.hpp" #include #include @@ -11,13 +12,6 @@ #include #endif -#if HAVE_LIBCRYPTO -#include -#elif HAVE_GNUTLS_GNUTLS_H -#include -#include -#endif - // returns username if valid, "" if not #if HAVE_LIBJWT std::pair verifyToken(std::string jwt_token_str, std::string key) { @@ -135,6 +129,8 @@ std::pair verifyToken(std::string jwt_token_str, std #endif // HAVE_LIBJWT bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) { + using namespace zm::crypto; + bool password_correct = false; if ( strlen(db_password_hash) < 4 ) { // actually, shoud be more, but this is min. for next code @@ -143,47 +139,13 @@ bool verifyPassword(const char *username, const char *input_password, const char } if ( db_password_hash[0] == '*' ) { // MYSQL PASSWORD - Debug(1, "%s is using an MD5 encoded password", username); - - #ifndef SHA_DIGEST_LENGTH - #define SHA_DIGEST_LENGTH 20 - #endif - -#if HAVE_LIBCRYPTO - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; - SHA_CTX ctx1, ctx2; - - //get first iteration - SHA1_Init(&ctx1); - SHA1_Update(&ctx1, input_password, strlen(input_password)); - SHA1_Final(digest_interim, &ctx1); + Debug(1, "%s is using an SHA1 encoded password", username); - //2nd iteration - SHA1_Init(&ctx2); - SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); - SHA1_Final(digest_final, &ctx2); -#elif HAVE_GNUTLS_GNUTLS_H - unsigned char digest_interim[SHA_DIGEST_LENGTH]; - unsigned char digest_final[SHA_DIGEST_LENGTH]; - //get first iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, input_password, strlen(input_password), digest_interim); - //2nd iteration - gnutls_hash_fast(GNUTLS_DIG_SHA1, digest_interim, SHA_DIGEST_LENGTH, digest_final); -#else - Error("Authentication Error. ZoneMinder not built with GnuTLS or Openssl"); - return false; -#endif + SHA1::Digest digest = SHA1::GetDigestOf(SHA1::GetDigestOf(input_password)); + std::string hex_digest = '*' + StringToUpper(ByteArrayToHexString(digest)); - char final_hash[SHA_DIGEST_LENGTH * 2 +2]; - final_hash[0] = '*'; - //convert to hex - for ( int i = 0; i < SHA_DIGEST_LENGTH; i++ ) - sprintf(&final_hash[i*2]+1, "%02X", (unsigned int)digest_final[i]); - final_hash[SHA_DIGEST_LENGTH *2 + 1] = 0; - - Debug(1, "Computed password_hash:%s, stored password_hash:%s", final_hash, db_password_hash); - password_correct = (strcmp(db_password_hash, final_hash)==0); + Debug(1, "Computed password_hash: %s, stored password_hash: %s", hex_digest.c_str(), db_password_hash); + password_correct = (strcmp(db_password_hash, hex_digest.c_str()) == 0); } else if ( (db_password_hash[0] == '$') && diff --git a/src/zm_crypt.h b/src/zm_crypt.h index 15dbc9332..b495dd536 100644 --- a/src/zm_crypt.h +++ b/src/zm_crypt.h @@ -20,10 +20,36 @@ #ifndef ZM_CRYPT_H #define ZM_CRYPT_H +#include "zm_config.h" +#include "zm_crypto_gnutls.h" +#include "zm_crypto_openssl.h" +#include "zm_define.h" #include -#include -bool verifyPassword( const char *username, const char *input_password, const char *db_password_hash); +bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash); -std::pair verifyToken(std::string token, std::string key); -#endif // ZM_CRYPT_H \ No newline at end of file +std::pair verifyToken(std::string token, std::string key); + +namespace zm { +namespace crypto { +namespace impl { + +#if defined(HAVE_LIBGNUTLS) +template +using Hash = gnutls::GenericHashImpl; +#elif defined(HAVE_LIBOPENSSL) +template +using Hash = openssl::GenericHashImpl; +#endif +} +} +} + +namespace zm { +namespace crypto { +using MD5 = impl::Hash; +using SHA1 = impl::Hash; +} +} + +#endif // ZM_CRYPT_H diff --git a/src/zm_crypto_generics.h b/src/zm_crypto_generics.h new file mode 100644 index 000000000..ce5fb090f --- /dev/null +++ b/src/zm_crypto_generics.h @@ -0,0 +1,108 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ + +#include "zm_define.h" +#include "zm_utils.h" +#include +#include + +namespace zm { +namespace crypto { +namespace impl { + +enum class HashAlgorithms { + kMD5, + kSHA1 +}; + +template +struct HashAlgorithm; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 16; +}; + +template<> +struct HashAlgorithm { + static constexpr size_t digest_length = 20; +}; + +template +class GenericHash { + public: + static constexpr size_t DIGEST_LENGTH = HashAlgorithm::digest_length; + using Digest = std::array; + + static Digest GetDigestOf(uint8 const *data, size_t len) { + Impl hash; + hash.UpdateData(data, len); + hash.Finalize(); + return hash.GetDigest(); + } + + template + static Digest GetDigestOf(Ts &&... pack) { + Impl hash; + UpdateData(hash, std::forward(pack)...); + hash.Finalize(); + return hash.GetDigest(); + } + + void UpdateData(const uint8 *data, size_t length) { + static_cast(*this).DoUpdateData(data, length); + } + void UpdateData(const std::string &str) { + UpdateData(reinterpret_cast(str.c_str()), str.size()); + } + void UpdateData(const char *str) { + UpdateData(reinterpret_cast(str), strlen(str)); + } + template + void UpdateData(Container const &c) { + UpdateData(ZM::data(c), ZM::size(c)); + } + + void Finalize() { + static_cast(*this).DoFinalize(); + } + + const Digest &GetDigest() const { return digest_; } + + protected: + Digest digest_ = {}; + + private: + template + static void UpdateData(Impl &hash, T const &data) { + hash.UpdateData(data); + } + + template + static void UpdateData(Impl &hash, T const &data, TRest &&... rest) { + hash.UpdateData(data); + UpdateData(hash, std::forward(rest)...); + } +}; +} +} +} + +#endif //ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_ diff --git a/src/zm_crypto_gnutls.h b/src/zm_crypto_gnutls.h new file mode 100644 index 000000000..d2c51f35d --- /dev/null +++ b/src/zm_crypto_gnutls.h @@ -0,0 +1,75 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ + +#ifdef HAVE_LIBGNUTLS + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace gnutls { + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5; +}; + +template<> +struct HashAlgorithmMapper { + static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_SHA1; +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + int32 ret = gnutls_hash_init(&handle_, HashAlgorithmMapper::algorithm); + ASSERT(ret == 0); + }; + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = gnutls_hash(handle_, data, length); + ASSERT(res == 0); + } + + void DoFinalize() { + gnutls_hash_deinit(handle_, digest_.data()); + } + + private: + gnutls_hash_hd_t handle_ = {}; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBGNUTLS + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_ diff --git a/src/zm_crypto_openssl.h b/src/zm_crypto_openssl.h new file mode 100644 index 000000000..667142531 --- /dev/null +++ b/src/zm_crypto_openssl.h @@ -0,0 +1,108 @@ +/* + * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#ifndef ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ +#define ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ + +#ifdef HAVE_LIBOPENSSL + +#include "zm_crypto_generics.h" +#include "zm_utils.h" +#include + +namespace zm { +namespace crypto { +namespace impl { +namespace openssl { + +typedef EVP_MD const *(*HashCreator)(); + +template +struct HashAlgorithmMapper; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_md5; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_md5; +#endif +}; + +template<> +struct HashAlgorithmMapper { +// TODO: Remove conditional once Jessie and CentOS 7 are deprecated +// This is needed since GCC 4.8 is faulty (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60199) +#if defined(__GNUC__) && __GNUC__ < 5 + static HashCreator hash_creator() { + static constexpr HashCreator creator = EVP_sha1; + return creator; + } +#else + static constexpr HashCreator hash_creator = EVP_sha1; +#endif +}; + +template +class GenericHashImpl : public GenericHash, Algorithm> { + public: + GenericHashImpl() { + // TODO: Use EVP_MD_CTX_new once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + ctx_ = EVP_MD_CTX_create(); +#if defined(__GNUC__) && __GNUC__ < 5 + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator()(), nullptr); +#else + EVP_DigestInit_ex(ctx_, HashAlgorithmMapper::hash_creator(), nullptr); +#endif + }; + + ~GenericHashImpl() { + // TODO: Use EVP_MD_CTX_free once we drop support for Jessie and CentOS 7 (OpenSSL > 1.1.0) + EVP_MD_CTX_destroy(ctx_); + } + + void DoUpdateData(const uint8 *data, size_t length) { + int32 res = EVP_DigestUpdate(ctx_, data, length); + ASSERT(res == 1); + } + + void DoFinalize() { + uint32 length = 0; + int32 res = EVP_DigestFinal_ex(ctx_, digest_.data(), &length); + ASSERT(res == 1); + ASSERT(length == HashAlgorithm::digest_length); + } + + private: + EVP_MD_CTX *ctx_; + + using Base = GenericHash, Algorithm>; + using Base::digest_; +}; +} +} +} +} + +#endif // HAVE_LIBOPENSSL + +#endif // ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_ diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 3c3bc160e..74a5c7293 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -285,7 +285,7 @@ int FfmpegCamera::OpenFfmpeg() { // Set transport method as specified by method field, rtpUni is default std::string protocol = mPath.substr(0, 4); - StringToUpper(protocol); + protocol = StringToUpper(protocol); if ( protocol == "RTSP" ) { const std::string method = Method(); if ( method == "rtpMulti" ) { diff --git a/src/zm_font.h b/src/zm_font.h index b606108b2..e1f03d7d1 100644 --- a/src/zm_font.h +++ b/src/zm_font.h @@ -20,7 +20,6 @@ #include "zm_define.h" #include -#include #include "span.hpp" #include #include diff --git a/src/zm_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 39b498d81..cf2be15f4 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -18,9 +18,9 @@ #include "zm_rtsp_auth.h" +#include "zm_crypt.h" #include "zm_logger.h" #include "zm_utils.h" -#include #include #include @@ -119,74 +119,36 @@ std::string Authenticator::getAuthHeader(const std::string &method, const std::s } std::string Authenticator::computeDigestResponse(const std::string &method, const std::string &uri) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT // The "response" field is computed as: // md5(md5(::)::md5(:)) - constexpr size_t md5len = 16; - uint8 md5buf[md5len]; - char md5HexBuf[md5len * 2 + 1]; - + // Step 1: md5(::) std::string ha1Data = username() + ":" + realm() + ":" + password(); - Debug( 2, "HA1 pre-md5: %s", ha1Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha1 = {(unsigned char *) ha1Data.c_str(), (unsigned int) ha1Data.length()}; - size_t md5_len_tmp = md5len; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5_len_tmp); - assert(md5_len_tmp == md5len); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf(&md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha1Hash = md5HexBuf; - + Debug(2, "HA1 pre-md5: %s", ha1Data.c_str()); + + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(ha1Data); + std::string ha1Hash = ByteArrayToHexString(md5_digest); + // Step 2: md5(:) std::string ha2Data = method + ":" + uri; - Debug( 2, "HA2 pre-md5: %s", ha2Data.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha2 = {(unsigned char *) ha2Data.c_str(), (unsigned int) ha2Data.length()}; - md5_len_tmp = md5len; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5_len_tmp); - assert(md5_len_tmp == md5len); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - std::string ha2Hash = md5HexBuf; + Debug(2, "HA2 pre-md5: %s", ha2Data.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(ha2Data); + std::string ha2Hash = ByteArrayToHexString(md5_digest); // Step 3: md5(ha1::ha2) std::string digestData = ha1Hash + ":" + nonce(); - if ( ! fQop.empty() ) { - digestData += ":" + stringtf("%08x", nc) + ":"+fCnonce + ":" + fQop; - nc ++; + if (!fQop.empty()) { + digestData += ":" + stringtf("%08x", nc) + ":" + fCnonce + ":" + fQop; + nc++; // if qop was specified, then we have to include t and a cnonce and an nccount } digestData += ":" + ha2Hash; - Debug( 2, "pre-md5: %s", digestData.c_str() ); -#if HAVE_DECL_MD5 - MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5datadigest = {(unsigned char *) digestData.c_str(), (unsigned int) digestData.length()}; - md5_len_tmp = md5len; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5_len_tmp); - assert(md5_len_tmp == md5len); -#endif - for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &md5HexBuf[2*j], "%02x", md5buf[j] ); - } - md5HexBuf[md5len*2]='\0'; - - return md5HexBuf; -#else // HAVE_DECL_MD5 - Error("You need to build with gnutls or openssl installed to use digest authentication"); - return 0; -#endif // HAVE_DECL_MD5 + Debug(2, "pre-md5: %s", digestData.c_str()); + + md5_digest = zm::crypto::MD5::GetDigestOf(digestData); + + return ByteArrayToHexString(md5_digest); } void Authenticator::checkAuthResponse(const std::string &response) { diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index b5a4959f9..52d186d26 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -22,14 +22,6 @@ #include "zm_config.h" #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_LIBCRYPTO -#include -#endif // HAVE_LIBCRYPTO - namespace zm { enum AuthMethod { AUTH_UNDEFINED = 0, AUTH_BASIC = 1, AUTH_DIGEST = 2 }; diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 8b475ca23..0b761c44f 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -22,17 +22,8 @@ #include "zm_crypt.h" #include "zm_logger.h" #include "zm_utils.h" -#include #include -#if HAVE_GNUTLS_GNUTLS_H -#include -#endif - -#if HAVE_LIBCRYPTO -#include -#endif // HAVE_LIBCRYPTO - User::User() { id = 0; username[0] = password[0] = 0; @@ -189,11 +180,10 @@ User *zmLoadTokenUser(const std::string &jwt_token_str, bool use_remote_addr) { // Function to validate an authentication string User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { -#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT const char *remote_addr = ""; - if ( use_remote_addr ) { + if (use_remote_addr) { remote_addr = getenv("REMOTE_ADDR"); - if ( !remote_addr ) { + if (!remote_addr) { Warning("Can't determine remote address, using null"); remote_addr = ""; } @@ -209,7 +199,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { return nullptr; int n_users = mysql_num_rows(result); - if ( n_users < 1 ) { + if (n_users < 1) { mysql_free_result(result); Warning("Unable to authenticate user"); return nullptr; @@ -218,58 +208,38 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { // getting the time is expensive, so only do it once. time_t now = time(nullptr); unsigned int hours = config.auth_hash_ttl; - if ( !hours ) { + if (!hours) { Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); hours = 2; } else { Debug(1, "AUTH_HASH_TTL is %d, time is %" PRIi64, hours, static_cast(now)); } - char auth_key[512] = ""; - char auth_md5[32+1] = ""; - constexpr size_t md5len = 16; - uint8 md5sum[md5len]; - const char * hex = "0123456789abcdef"; - while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { + while (MYSQL_ROW dbrow = mysql_fetch_row(result)) { const char *username = dbrow[1]; const char *password = dbrow[2]; time_t our_now = now; tm now_tm = {}; - for ( unsigned int i = 0; i < hours; i++, our_now -= 3600 ) { + for (unsigned int i = 0; i < hours; i++, our_now -= 3600) { localtime_r(&our_now, &now_tm); - snprintf(auth_key, sizeof(auth_key)-1, "%s%s%s%s%d%d%d%d", - config.auth_hash_secret, - username, - password, - remote_addr, - now_tm.tm_hour, - now_tm.tm_mday, - now_tm.tm_mon, - now_tm.tm_year); + std::string auth_key = stringtf("%s%s%s%s%d%d%d%d", + config.auth_hash_secret, + username, + password, + remote_addr, + now_tm.tm_hour, + now_tm.tm_mday, + now_tm.tm_mon, + now_tm.tm_year); -#if HAVE_DECL_MD5 - MD5((unsigned char *)auth_key, strlen(auth_key), md5sum); -#elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5data = {(unsigned char *) auth_key, (unsigned int) strlen(auth_key)}; - size_t md5_len_tmp = md5len; - gnutls_fingerprint(GNUTLS_DIG_MD5, &md5data, md5sum, &md5_len_tmp); - assert(md5_len_tmp == md5len); -#endif - unsigned char *md5sum_ptr = md5sum; - char *auth_md5_ptr = auth_md5; + zm::crypto::MD5::Digest md5_digest = zm::crypto::MD5::GetDigestOf(auth_key); + std::string auth_md5 = ByteArrayToHexString(md5_digest); - for ( unsigned int j = 0; j < md5len; j++ ) { - *auth_md5_ptr++ = hex[(*md5sum_ptr>>4)&0xf]; - *auth_md5_ptr++ = hex[(*md5sum_ptr++)&0xf]; - } - *auth_md5_ptr = 0; + Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key.c_str(), auth_md5.c_str(), auth); - Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", - auth_key, auth_md5, auth); - - if ( !strcmp(auth, auth_md5) ) { + if (!strcmp(auth, auth_md5.c_str())) { // We have a match User *user = new User(dbrow); Debug(1, "Authenticated user '%s'", user->getUsername()); @@ -281,9 +251,7 @@ User *zmLoadAuthUser(const char *auth, bool use_remote_addr) { } // end foreach hour } // end foreach user mysql_free_result(result); -#else // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT - Error("You need to build with gnutls or openssl to use hash based auth"); -#endif // HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT + Debug(1, "No user found for auth_key %s", auth); return nullptr; } // end User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 7d2bbc888..890b288e4 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -116,6 +116,23 @@ std::string Join(const StringVector &values, const std::string &delim) { return ss.str(); } +std::string ByteArrayToHexString(nonstd::span bytes) { + static constexpr char lowercase_table[] = "0123456789abcdef"; + std::string buf; + buf.resize(2 * bytes.size()); + + const uint8 *srcPtr = bytes.data(); + char *dstPtr = &buf[0]; + + for (size_t i = 0; i < bytes.size(); ++i) { + uint8 c = *srcPtr++; + *dstPtr++ = lowercase_table[c >> 4]; + *dstPtr++ = lowercase_table[c & 0x0f]; + } + + return buf; +} + std::string Base64Encode(const std::string &str) { static char base64_table[64] = {'\0'}; diff --git a/src/zm_utils.h b/src/zm_utils.h index 13bbc2dcf..8ca58a22b 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -20,23 +20,36 @@ #ifndef ZM_UTILS_H #define ZM_UTILS_H +#include "zm_define.h" #include #include #include #include #include #include +#include "span.hpp" #include #include #include #include + +#ifdef NDEBUG +#define ASSERT(x) do { (void) sizeof(x); } while (0) +#else +#include +#define ASSERT(x) assert(x) +#endif + typedef std::vector StringVector; std::string Trim(const std::string &str, const std::string &char_set); inline std::string TrimSpaces(const std::string &str) { return Trim(str, " \t"); } -std::string ReplaceAll(std::string str, const std::string& old_value, const std::string& new_value); -inline void StringToUpper(std::string &str) { std::transform(str.begin(), str.end(), str.begin(), ::toupper); } +std::string ReplaceAll(std::string str, const std::string &old_value, const std::string &new_value); +inline std::string StringToUpper(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; +} StringVector Split(const std::string &str, char delim); StringVector Split(const std::string &str, const std::string &delim, size_t limit = 0); @@ -59,6 +72,8 @@ std::string stringtf(const std::string &format, Args... args) { return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside } +std::string ByteArrayToHexString(nonstd::span bytes); + std::string Base64Encode(const std::string &str); void TimespecDiff(timespec *start, timespec *end, timespec *diff); @@ -72,32 +87,53 @@ void *sse2_aligned_memcpy(void *dest, const void *src, size_t bytes); void touch(const char *pathname); namespace ZM { -//! std::make_unique implementation (TODO: remove this once C++14 is supported) +// C++14 std::make_unique (TODO: remove this once C++14 is supported) template inline auto make_unique(Args &&...args) -> typename std::enable_if::value, std::unique_ptr>::type { return std::unique_ptr(new T(std::forward(args)...)); } - template inline auto make_unique(std::size_t size) -> typename std::enable_if::value && std::extent::value == 0, std::unique_ptr>::type { return std::unique_ptr(new typename std::remove_extent::type[size]()); } - template inline auto make_unique(Args &&...) -> typename std::enable_if::value != 0, void>::type = delete; +// C++17 std::clamp (TODO: remove this once C++17 is supported) template constexpr const T &clamp(const T &v, const T &lo, const T &hi, Compare comp) { return comp(v, lo) ? lo : comp(hi, v) ? hi : v; } - template constexpr const T &clamp(const T &v, const T &lo, const T &hi) { return ZM::clamp(v, lo, hi, std::less{}); } + +// C++17 std::data (TODO: remove this once C++17 is supported) +template +constexpr auto data(C &c) -> decltype(c.data()) { return c.data(); } + +template +constexpr auto data(C const &c) -> decltype(c.data()) { return c.data(); } + +template +constexpr T *data(T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(const T(&a)[N]) noexcept { return a; } + +template +constexpr T const *data(std::initializer_list l) noexcept { return l.begin(); } + +// C++17 std::size (TODO: remove this once C++17 is supported) +template +constexpr auto size(const C &c) -> decltype(c.size()) { return c.size(); } + +template +constexpr std::size_t size(const T(&)[N]) noexcept { return N; } } typedef std::chrono::microseconds Microseconds; @@ -146,4 +182,5 @@ class QueryString { std::map> parameters_; }; + #endif // ZM_UTILS_H diff --git a/tests/zm_crypt.cpp b/tests/zm_crypt.cpp index 0d68c1e3c..94004527c 100644 --- a/tests/zm_crypt.cpp +++ b/tests/zm_crypt.cpp @@ -76,3 +76,62 @@ TEST_CASE("JWT validation") { REQUIRE(result.second == 0); } } + +TEST_CASE("zm::crypto::MD5") { + using namespace zm::crypto; + MD5 md5; + + REQUIRE(md5.GetDigest() == MD5::Digest()); + + SECTION("hash from const char*") { + md5.UpdateData("abcdefghijklmnopqrstuvwxyz"); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } + + SECTION("hash from std::string") { + md5.UpdateData(std::string("abcdefghijklmnopqrstuvwxyz")); + md5.Finalize(); + + REQUIRE(md5.GetDigest() == MD5::Digest{0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, + 0x67, 0xe1, 0x3b}); + } +} + +TEST_CASE("zm::crypto::MD5::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SECTION("data and len") { + MD5::Digest digest = MD5::GetDigestOf(reinterpret_cast(data.data()), data.size()); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("container") { + MD5::Digest digest = MD5::GetDigestOf(data); + + REQUIRE(digest == MD5::Digest{0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1, + 0x7f, 0x72}); + } + + SECTION("multiple containers") { + MD5::Digest digest = MD5::GetDigestOf(data, data); + + REQUIRE(digest == MD5::Digest{0x44, 0x0a, 0xc8, 0x58, 0x92, 0xca, 0x43, 0xad, 0x26, 0xd4, 0x4c, 0x7a, 0xd9, 0xd4, + 0x7d, 0x3e}); + } +} + +TEST_CASE("zm::crypto::SHA1::GetDigestOf") { + using namespace zm::crypto; + std::array data = {'a', 'b', 'c'}; + + SHA1::Digest digest = SHA1::GetDigestOf(data); + + REQUIRE(digest == SHA1::Digest{0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, + 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d}); +} diff --git a/tests/zm_utils.cpp b/tests/zm_utils.cpp index 4745ad60b..dcb2a6d07 100644 --- a/tests/zm_utils.cpp +++ b/tests/zm_utils.cpp @@ -140,6 +140,18 @@ TEST_CASE("Join") { REQUIRE(Join({"a", "b"}, "") == "ab"); } +TEST_CASE("ByteArrayToHexString") { + std::vector bytes; + + REQUIRE(ByteArrayToHexString(bytes) == ""); + + bytes = {0x00}; + REQUIRE(ByteArrayToHexString(bytes) == "00"); + + bytes = {0x00, 0x01, 0x02, 0xff}; + REQUIRE(ByteArrayToHexString(bytes) == "000102ff"); +} + TEST_CASE("Base64Encode") { REQUIRE(Base64Encode("") == ""); REQUIRE(Base64Encode("f") == "Zg=="); diff --git a/zoneminder-config.cmake b/zoneminder-config.cmake index b9a446205..c9cc2ef88 100644 --- a/zoneminder-config.cmake +++ b/zoneminder-config.cmake @@ -29,14 +29,11 @@ #cmakedefine HAVE_LIBJPEG 1 #cmakedefine HAVE_JPEGLIB_H 1 #cmakedefine HAVE_LIBOPENSSL 1 -#cmakedefine HAVE_OPENSSL_MD5_H 1 -#cmakedefine HAVE_LIBCRYPTO 1 #cmakedefine HAVE_LIBPTHREAD 1 #cmakedefine HAVE_PTHREAD_H #cmakedefine HAVE_LIBPCRE 1 #cmakedefine HAVE_PCRE_H 1 #cmakedefine HAVE_LIBGNUTLS 1 -#cmakedefine HAVE_GNUTLS_GNUTLS_H 1 #cmakedefine HAVE_LIBMYSQLCLIENT 1 #cmakedefine HAVE_MYSQL_H 1 #cmakedefine HAVE_LIBAVFORMAT 1 @@ -68,12 +65,6 @@ #cmakedefine HAVE_LIBJWT 1 #cmakedefine HAVE_RTSP_SERVER 1 -/* Authenication checks */ -#cmakedefine HAVE_MD5_OPENSSL 1 -#cmakedefine HAVE_MD5_GNUTLS 1 -#cmakedefine HAVE_DECL_MD5 1 -#cmakedefine HAVE_DECL_GNUTLS_FINGERPRINT 1 - /* Few ZM options that are needed by the source code */ #cmakedefine ZM_MEM_MAPPED 1