2019-05-03 23:40:35 +08:00
|
|
|
#include "zm.h"
|
|
|
|
# include "zm_crypt.h"
|
2019-05-09 04:45:28 +08:00
|
|
|
#include "BCrypt.hpp"
|
|
|
|
#include "jwt.h"
|
2019-05-03 23:40:35 +08:00
|
|
|
#include <algorithm>
|
2019-05-09 07:17:31 +08:00
|
|
|
#include <openssl/sha.h>
|
2019-05-03 23:40:35 +08:00
|
|
|
|
|
|
|
|
2019-05-09 04:45:28 +08:00
|
|
|
// returns username if valid, "" if not
|
2019-05-12 01:39:40 +08:00
|
|
|
std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) {
|
2019-05-09 04:45:28 +08:00
|
|
|
std::string username = "";
|
2019-05-12 17:03:16 +08:00
|
|
|
unsigned int token_issued_at = 0;
|
2019-05-09 04:45:28 +08:00
|
|
|
try {
|
|
|
|
// is it decodable?
|
|
|
|
auto decoded = jwt::decode(jwt_token_str);
|
|
|
|
auto verifier = jwt::verify()
|
|
|
|
.allow_algorithm(jwt::algorithm::hs256{ key })
|
|
|
|
.with_issuer("ZoneMinder");
|
|
|
|
|
|
|
|
// signature verified?
|
|
|
|
verifier.verify(decoded);
|
2019-05-03 23:40:35 +08:00
|
|
|
|
2019-05-09 04:45:28 +08:00
|
|
|
// make sure it has fields we need
|
|
|
|
if (decoded.has_payload_claim("type")) {
|
|
|
|
std::string type = decoded.get_payload_claim("type").as_string();
|
|
|
|
if (type != "access") {
|
|
|
|
Error ("Only access tokens are allowed. Please do not use refresh tokens");
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair("",0);
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// something is wrong. All ZM tokens have type
|
|
|
|
Error ("Missing token type. This should not happen");
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair("",0);
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
|
|
|
if (decoded.has_payload_claim("user")) {
|
|
|
|
username = decoded.get_payload_claim("user").as_string();
|
2019-05-13 03:45:39 +08:00
|
|
|
Debug (1, "Got %s as user claim from token", username.c_str());
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
Error ("User not found in claim");
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair("",0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (decoded.has_payload_claim("iat")) {
|
2019-05-12 17:03:16 +08:00
|
|
|
token_issued_at = (unsigned int) (decoded.get_payload_claim("iat").as_int());
|
2019-05-13 03:45:39 +08:00
|
|
|
Debug (1,"Got IAT token=%u", token_issued_at);
|
2019-05-12 17:03:16 +08:00
|
|
|
|
2019-05-12 01:39:40 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
Error ("IAT not found in claim. This should not happen");
|
|
|
|
return std::make_pair("",0);
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
|
|
|
} // try
|
|
|
|
catch (const std::exception &e) {
|
|
|
|
Error("Unable to verify token: %s", e.what());
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair("",0);
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
Error ("unknown exception");
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair("",0);
|
2019-05-03 23:40:35 +08:00
|
|
|
|
2019-05-09 04:45:28 +08:00
|
|
|
}
|
2019-05-12 01:39:40 +08:00
|
|
|
return std::make_pair(username,token_issued_at);
|
2019-05-03 23:40:35 +08:00
|
|
|
}
|
|
|
|
|
2019-05-04 00:01:13 +08:00
|
|
|
bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) {
|
2019-05-03 23:40:35 +08:00
|
|
|
bool password_correct = false;
|
2019-06-21 03:14:20 +08:00
|
|
|
if ( strlen(db_password_hash) < 4 ) {
|
2019-05-03 23:40:35 +08:00
|
|
|
// actually, shoud be more, but this is min. for next code
|
2019-06-21 03:14:20 +08:00
|
|
|
Error("DB Password is too short or invalid to check");
|
2019-05-03 23:40:35 +08:00
|
|
|
return false;
|
|
|
|
}
|
2019-06-21 03:14:20 +08:00
|
|
|
if ( db_password_hash[0] == '*' ) {
|
2019-05-03 23:40:35 +08:00
|
|
|
// MYSQL PASSWORD
|
2019-06-21 03:14:20 +08:00
|
|
|
Debug(1, "%s is using an MD5 encoded password", username);
|
2019-05-05 03:20:31 +08:00
|
|
|
|
|
|
|
SHA_CTX ctx1, ctx2;
|
2019-05-04 23:52:53 +08:00
|
|
|
unsigned char digest_interim[SHA_DIGEST_LENGTH];
|
|
|
|
unsigned char digest_final[SHA_DIGEST_LENGTH];
|
2019-05-05 03:20:31 +08:00
|
|
|
|
|
|
|
//get first iteration
|
|
|
|
SHA1_Init(&ctx1);
|
|
|
|
SHA1_Update(&ctx1, input_password, strlen(input_password));
|
|
|
|
SHA1_Final(digest_interim, &ctx1);
|
|
|
|
|
|
|
|
//2nd iteration
|
|
|
|
SHA1_Init(&ctx2);
|
|
|
|
SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH);
|
2019-05-05 03:27:00 +08:00
|
|
|
SHA1_Final (digest_final, &ctx2);
|
2019-05-05 03:20:31 +08:00
|
|
|
|
2019-05-04 23:52:53 +08:00
|
|
|
char final_hash[SHA_DIGEST_LENGTH * 2 +2];
|
2019-06-21 03:14:20 +08:00
|
|
|
final_hash[0] = '*';
|
2019-05-05 03:20:31 +08:00
|
|
|
//convert to hex
|
2019-06-21 03:14:20 +08:00
|
|
|
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;
|
2019-05-04 23:52:53 +08:00
|
|
|
|
2019-06-21 03:14:20 +08:00
|
|
|
Debug(1, "Computed password_hash:%s, stored password_hash:%s", final_hash, db_password_hash);
|
2019-05-04 23:52:53 +08:00
|
|
|
password_correct = (strcmp(db_password_hash, final_hash)==0);
|
2019-06-21 03:14:20 +08:00
|
|
|
} else if (
|
|
|
|
(db_password_hash[0] == '$')
|
|
|
|
&&
|
|
|
|
(db_password_hash[1]== '2')
|
|
|
|
&&
|
|
|
|
(db_password_hash[3] == '$')
|
|
|
|
) {
|
2019-05-03 23:40:35 +08:00
|
|
|
// BCRYPT
|
2019-06-21 03:14:20 +08:00
|
|
|
Debug(1, "%s is using a bcrypt encoded password", username);
|
2019-05-03 23:40:35 +08:00
|
|
|
BCrypt bcrypt;
|
|
|
|
std::string input_hash = bcrypt.generateHash(std::string(input_password));
|
|
|
|
password_correct = bcrypt.validatePassword(std::string(input_password), std::string(db_password_hash));
|
2019-06-21 03:14:20 +08:00
|
|
|
} else {
|
2019-05-03 23:40:35 +08:00
|
|
|
// plain
|
2019-06-21 03:14:20 +08:00
|
|
|
Warning("%s is using a plain text password, please do not use plain text", username);
|
2019-05-03 23:40:35 +08:00
|
|
|
password_correct = (strcmp(input_password, db_password_hash) == 0);
|
|
|
|
}
|
|
|
|
return password_correct;
|
2019-06-21 03:14:20 +08:00
|
|
|
}
|