Merge pull request #3267 from Carbenium/crypto-hashing

Crypto: Implement a generic hashing API
This commit is contained in:
Isaac Connor 2021-05-30 18:07:58 -04:00 committed by GitHub
commit 4f2945bd57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 500 additions and 223 deletions

View File

@ -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)

View File

@ -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

View File

@ -1,6 +1,7 @@
#include "zm_crypt.h"
#include "zm_logger.h"
#include "zm_utils.h"
#include "BCrypt.hpp"
#include <algorithm>
#include <cstring>
@ -11,13 +12,6 @@
#include <jwt-cpp/jwt.h>
#endif
#if HAVE_LIBCRYPTO
#include <openssl/sha.h>
#elif HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#endif
// returns username if valid, "" if not
#if HAVE_LIBJWT
std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) {
@ -135,6 +129,8 @@ std::pair <std::string, unsigned int> 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] == '$')
&&

View File

@ -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 <string>
#include <utility>
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 <std::string, unsigned int> verifyToken(std::string token, std::string key);
#endif // ZM_CRYPT_H
std::pair<std::string, unsigned int> verifyToken(std::string token, std::string key);
namespace zm {
namespace crypto {
namespace impl {
#if defined(HAVE_LIBGNUTLS)
template<HashAlgorithms Algorithm>
using Hash = gnutls::GenericHashImpl<Algorithm>;
#elif defined(HAVE_LIBOPENSSL)
template<HashAlgorithms Algorithm>
using Hash = openssl::GenericHashImpl<Algorithm>;
#endif
}
}
}
namespace zm {
namespace crypto {
using MD5 = impl::Hash<impl::HashAlgorithms::kMD5>;
using SHA1 = impl::Hash<impl::HashAlgorithms::kSHA1>;
}
}
#endif // ZM_CRYPT_H

108
src/zm_crypto_generics.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_
#define ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_
#include "zm_define.h"
#include "zm_utils.h"
#include <array>
#include <cstring>
namespace zm {
namespace crypto {
namespace impl {
enum class HashAlgorithms {
kMD5,
kSHA1
};
template<HashAlgorithms Algorithm>
struct HashAlgorithm;
template<>
struct HashAlgorithm<HashAlgorithms::kMD5> {
static constexpr size_t digest_length = 16;
};
template<>
struct HashAlgorithm<HashAlgorithms::kSHA1> {
static constexpr size_t digest_length = 20;
};
template<typename Impl, HashAlgorithms Algorithm>
class GenericHash {
public:
static constexpr size_t DIGEST_LENGTH = HashAlgorithm<Algorithm>::digest_length;
using Digest = std::array<uint8, DIGEST_LENGTH>;
static Digest GetDigestOf(uint8 const *data, size_t len) {
Impl hash;
hash.UpdateData(data, len);
hash.Finalize();
return hash.GetDigest();
}
template<typename... Ts>
static Digest GetDigestOf(Ts &&... pack) {
Impl hash;
UpdateData(hash, std::forward<Ts>(pack)...);
hash.Finalize();
return hash.GetDigest();
}
void UpdateData(const uint8 *data, size_t length) {
static_cast<Impl &>(*this).DoUpdateData(data, length);
}
void UpdateData(const std::string &str) {
UpdateData(reinterpret_cast<const uint8 *>(str.c_str()), str.size());
}
void UpdateData(const char *str) {
UpdateData(reinterpret_cast<const uint8 *>(str), strlen(str));
}
template<typename Container>
void UpdateData(Container const &c) {
UpdateData(ZM::data(c), ZM::size(c));
}
void Finalize() {
static_cast<Impl &>(*this).DoFinalize();
}
const Digest &GetDigest() const { return digest_; }
protected:
Digest digest_ = {};
private:
template<typename T>
static void UpdateData(Impl &hash, T const &data) {
hash.UpdateData(data);
}
template<typename T, typename... TRest>
static void UpdateData(Impl &hash, T const &data, TRest &&... rest) {
hash.UpdateData(data);
UpdateData(hash, std::forward<TRest>(rest)...);
}
};
}
}
}
#endif //ZONEMINDER_SRC_ZM_CRYPTO_GENERICS_H_

75
src/zm_crypto_gnutls.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <gnutls/crypto.h>
namespace zm {
namespace crypto {
namespace impl {
namespace gnutls {
template<HashAlgorithms Algorithm>
struct HashAlgorithmMapper;
template<>
struct HashAlgorithmMapper<HashAlgorithms::kMD5> {
static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5;
};
template<>
struct HashAlgorithmMapper<HashAlgorithms::kSHA1> {
static constexpr gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_SHA1;
};
template<HashAlgorithms Algorithm>
class GenericHashImpl : public GenericHash<GenericHashImpl<Algorithm>, Algorithm> {
public:
GenericHashImpl() {
int32 ret = gnutls_hash_init(&handle_, HashAlgorithmMapper<Algorithm>::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<GenericHashImpl<Algorithm>, Algorithm>;
using Base::digest_;
};
}
}
}
}
#endif // HAVE_LIBGNUTLS
#endif // ZONEMINDER_SRC_ZM_CRYPTO_GNUTLS_H_

108
src/zm_crypto_openssl.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <openssl/evp.h>
namespace zm {
namespace crypto {
namespace impl {
namespace openssl {
typedef EVP_MD const *(*HashCreator)();
template<HashAlgorithms Algorithm>
struct HashAlgorithmMapper;
template<>
struct HashAlgorithmMapper<HashAlgorithms::kMD5> {
// 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<HashAlgorithms::kSHA1> {
// 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<HashAlgorithms Algorithm>
class GenericHashImpl : public GenericHash<GenericHashImpl<Algorithm>, 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<Algorithm>::hash_creator()(), nullptr);
#else
EVP_DigestInit_ex(ctx_, HashAlgorithmMapper<Algorithm>::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<Algorithm>::digest_length);
}
private:
EVP_MD_CTX *ctx_;
using Base = GenericHash<GenericHashImpl<Algorithm>, Algorithm>;
using Base::digest_;
};
}
}
}
}
#endif // HAVE_LIBOPENSSL
#endif // ZONEMINDER_SRC_ZM_CRYPTO_OPENSSL_H_

View File

@ -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" ) {

View File

@ -20,7 +20,6 @@
#include "zm_define.h"
#include <array>
#include <cassert>
#include "span.hpp"
#include <string>
#include <vector>

View File

@ -18,9 +18,9 @@
#include "zm_rtsp_auth.h"
#include "zm_crypt.h"
#include "zm_logger.h"
#include "zm_utils.h"
#include <cassert>
#include <cstring>
#include <utility>
@ -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(<username>:<realm>:<password>):<nonce>:md5(<cmd>:<url>))
constexpr size_t md5len = 16;
uint8 md5buf[md5len];
char md5HexBuf[md5len * 2 + 1];
// Step 1: md5(<username>:<realm>:<password>)
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(<cmd>:<url>)
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:<nonce>: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) {

View File

@ -22,14 +22,6 @@
#include "zm_config.h"
#include <string>
#if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#endif
#if HAVE_LIBCRYPTO
#include <openssl/md5.h>
#endif // HAVE_LIBCRYPTO
namespace zm {
enum AuthMethod { AUTH_UNDEFINED = 0, AUTH_BASIC = 1, AUTH_DIGEST = 2 };

View File

@ -22,17 +22,8 @@
#include "zm_crypt.h"
#include "zm_logger.h"
#include "zm_utils.h"
#include <cassert>
#include <cstring>
#if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#endif
#if HAVE_LIBCRYPTO
#include <openssl/md5.h>
#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<int64>(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 )

View File

@ -116,6 +116,23 @@ std::string Join(const StringVector &values, const std::string &delim) {
return ss.str();
}
std::string ByteArrayToHexString(nonstd::span<const uint8> 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'};

View File

@ -20,23 +20,36 @@
#ifndef ZM_UTILS_H
#define ZM_UTILS_H
#include "zm_define.h"
#include <algorithm>
#include <chrono>
#include <ctime>
#include <functional>
#include <map>
#include <memory>
#include "span.hpp"
#include <stdexcept>
#include <string>
#include <sys/time.h>
#include <vector>
#ifdef NDEBUG
#define ASSERT(x) do { (void) sizeof(x); } while (0)
#else
#include <cassert>
#define ASSERT(x) assert(x)
#endif
typedef std::vector<std::string> 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<const uint8> 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<typename T, typename ...Args>
inline auto make_unique(Args &&...args) ->
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<typename T>
inline auto make_unique(std::size_t size) ->
typename std::enable_if<std::is_array<T>::value && std::extent<T>::value == 0, std::unique_ptr<T>>::type {
return std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]());
}
template<typename T, typename... Args>
inline auto make_unique(Args &&...) ->
typename std::enable_if<std::extent<T>::value != 0, void>::type = delete;
// C++17 std::clamp (TODO: remove this once C++17 is supported)
template<class T, class Compare>
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<class T>
constexpr const T &clamp(const T &v, const T &lo, const T &hi) {
return ZM::clamp(v, lo, hi, std::less<T>{});
}
// C++17 std::data (TODO: remove this once C++17 is supported)
template<typename C>
constexpr auto data(C &c) -> decltype(c.data()) { return c.data(); }
template<typename C>
constexpr auto data(C const &c) -> decltype(c.data()) { return c.data(); }
template<typename T, std::size_t N>
constexpr T *data(T(&a)[N]) noexcept { return a; }
template<typename T, std::size_t N>
constexpr T const *data(const T(&a)[N]) noexcept { return a; }
template<typename T>
constexpr T const *data(std::initializer_list<T> l) noexcept { return l.begin(); }
// C++17 std::size (TODO: remove this once C++17 is supported)
template<typename C>
constexpr auto size(const C &c) -> decltype(c.size()) { return c.size(); }
template<typename T, std::size_t N>
constexpr std::size_t size(const T(&)[N]) noexcept { return N; }
}
typedef std::chrono::microseconds Microseconds;
@ -146,4 +182,5 @@ class QueryString {
std::map<std::string, std::unique_ptr<QueryParameter>> parameters_;
};
#endif // ZM_UTILS_H

View File

@ -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<uint8, 3> data = {'a', 'b', 'c'};
SECTION("data and len") {
MD5::Digest digest = MD5::GetDigestOf(reinterpret_cast<const uint8 *>(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<uint8, 3> 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});
}

View File

@ -140,6 +140,18 @@ TEST_CASE("Join") {
REQUIRE(Join({"a", "b"}, "") == "ab");
}
TEST_CASE("ByteArrayToHexString") {
std::vector<uint8> 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==");

View File

@ -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