*
* crypt.cpp
* Look into the password file and check the encrypted password with
* the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/common/backend/libpq/crypt.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#include "catalog/pg_authid.h"
#include "commands/user.h"
#include "libpq/crypt.h"
#include "libpq/md5.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "cipher.h"
#include "openssl/evp.h"
#define IAM_ROLE_WITH_DWSDB_PRIV "dws_db_acc"
#define DWS_IAMAUTH_CONFIG_PATH "/opt/dws/iamauth/"
#define CLUSTERID_LEN 36
#define TENANTID_LEN 32
static bool GetValidPeriod(const char *role, password_info *passInfo);
*Funcation : get_password_stored_method
*Description: get user password stored method : MD5 SHA256 COMBINED or PLAIN
*/
int32 get_password_stored_method(const char* role, char* encrypted_string, int len)
{
char* shadow_pass = NULL;
password_info pass_info = {NULL, 0, 0, false, false};
int32 stored_method;
errno_t rc = EOK;
if (encrypted_string == NULL)
return BAD_MEM_ADDR;
if (get_stored_password(role, &pass_info)) {
shadow_pass = pass_info.shadow_pass;
} else {
pfree_ext(pass_info.shadow_pass);
return ERROR_PASSWORD;
}
if (isMD5(shadow_pass)) {
stored_method = MD5_PASSWORD;
} else if (isSHA256(shadow_pass)) {
rc = strncpy_s((char*)encrypted_string, len, shadow_pass + SHA256_LENGTH, ENCRYPTED_STRING_LENGTH);
securec_check(rc, "\0", "\0");
stored_method = SHA256_PASSWORD;
} else if (isSM3(shadow_pass)) {
rc = strncpy_s((char*)encrypted_string, len, shadow_pass + SM3_LENGTH, ENCRYPTED_STRING_LENGTH);
securec_check(rc, "\0", "\0");
stored_method = SM3_PASSWORD;
} else if (isCOMBINED(shadow_pass)) {
rc = strncpy_s((char*)encrypted_string, len, shadow_pass + SHA256_LENGTH, ENCRYPTED_STRING_LENGTH);
securec_check(rc, "\0", "\0");
stored_method = COMBINED_PASSWORD;
} else {
stored_method = PLAIN_PASSWORD;
}
pfree_ext(pass_info.shadow_pass);
return stored_method;
}
*Funcation : get_stored_password
*Description: get user password information
*/
bool get_stored_password(const char* role, password_info* pass_info)
{
HeapTuple roleTup = NULL;
Datum datum1;
Datum datum2;
bool save_ImmediateInterruptOK = t_thrd.int_cxt.ImmediateInterruptOK;
* Disable immediate interrupts while doing database access. (Note we
* don't bother to turn this back on if we hit one of the failure
* conditions, since we can expect we'll just exit right away anyway.)
*/
t_thrd.int_cxt.ImmediateInterruptOK = false;
if (strchr(role, '@') && !OidIsValid(u_sess->proc_cxt.MyDatabaseId))
ereport(ERROR,(errcode(ERRCODE_INVALID_NAME),errmsg("@ can't be allowed in username")));
roleTup = SearchUserHostName(role, NULL);
if (!HeapTupleIsValid(roleTup)) {
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
return false;
}
* Since pass_info->isnull_begin will be reset later, it is only
* used as a tag here, with no real meaning.
*/
datum1 = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolpassword, &(pass_info->isnull_begin));
if (pass_info->isnull_begin) {
ReleaseSysCache(roleTup);
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
return false;
}
pass_info->shadow_pass = TextDatumGetCString(datum1);
datum1 = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolvalidbegin, &(pass_info->isnull_begin));
if (!(pass_info->isnull_begin))
pass_info->vbegin = DatumGetTimestampTz(datum1);
datum2 = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_rolvaliduntil, &(pass_info->isnull_until));
if (!(pass_info->isnull_until))
pass_info->vuntil = DatumGetTimestampTz(datum2);
ReleaseSysCache(roleTup);
if (*(pass_info->shadow_pass) == '\0') {
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
return false;
}
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
CHECK_FOR_INTERRUPTS();
return true;
}
*Funcation : GetValidPeriod
*Description: ONLY get user valid peroid information
*/
static bool GetValidPeriod(const char *role, password_info *passInfo)
{
bool save_ImmediateInterruptOK = t_thrd.int_cxt.ImmediateInterruptOK;
* Disable immediate interrupts while doing database access. (Note we
* don't bother to turn this back on if we hit one of the failure
* conditions, since we can expect we'll just exit right away anyway.)
*/
t_thrd.int_cxt.ImmediateInterruptOK = false;
if (strchr(role, '@') && !OidIsValid(u_sess->proc_cxt.MyDatabaseId))
ereport(ERROR,(errcode(ERRCODE_INVALID_NAME),errmsg("@ can't be allowed in username")));
HeapTuple roleTup = SearchUserHostName(role, NULL);
if (!HeapTupleIsValid(roleTup)) {
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
return false;
}
Datum datum1 = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvalidbegin, &(passInfo->isnull_begin));
if (!(passInfo->isnull_begin)) {
passInfo->vbegin = DatumGetTimestampTz(datum1);
}
Datum datum2 = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &(passInfo->isnull_until));
if (!(passInfo->isnull_until)) {
passInfo->vuntil = DatumGetTimestampTz(datum2);
}
ReleaseSysCache(roleTup);
t_thrd.int_cxt.ImmediateInterruptOK = save_ImmediateInterruptOK;
CHECK_FOR_INTERRUPTS();
return true;
}
int CheckUserValid(Port* port, const char* role)
{
TimestampTz vbegin = 0;
TimestampTz vuntil = 0;
bool isnull_begin = false;
bool isnull_until = false;
password_info pass_info = {NULL, 0, 0, false, false};
int retval = STATUS_ERROR;
if (GetValidPeriod(role, &pass_info)) {
isnull_begin = pass_info.isnull_begin;
vbegin = pass_info.vbegin;
isnull_until = pass_info.isnull_until;
vuntil = pass_info.vuntil;
} else {
return STATUS_ERROR;
}
if (isnull_begin && isnull_until) {
retval = STATUS_OK;
} else if ((!isnull_begin && vbegin > GetCurrentTimestamp()) || (!isnull_until && vuntil < GetCurrentTimestamp())) {
retval = STATUS_EXPIRED;
} else {
retval = STATUS_OK;
}
return retval;
}
* Brief : whether the digest of passwd equal to the passDigest
* Description : the compare mainly contains the digest of password
* Notes :
*/
bool VerifyPasswdDigest(const char* rolename, char* passwd, char* passDigest)
{
char encrypted_md5_password[MD5_PASSWD_LEN + 1] = {0};
char encrypted_sha256_password[SHA256_PASSWD_LEN + 1] = {0};
char encryptedSm3Password[SM3_PASSWD_LEN + 1] = {0};
char encrypted_combined_password[MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1] = {0};
char salt[SALT_LENGTH * 2 + 1] = {0};
int iteration_count = 0;
errno_t rc = EOK;
if (passwd == NULL || passDigest == NULL || strlen(passDigest) == 0) {
return false;
}
if (isMD5(passDigest)) {
if (!pg_md5_encrypt(passwd, rolename, strlen(rolename), encrypted_md5_password)) {
str_reset(passwd);
str_reset(passDigest);
ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("md5-password encryption failed.")));
}
if (strncmp(passDigest, encrypted_md5_password, MD5_PASSWD_LEN + 1) == 0) {
rc = memset_s(encrypted_md5_password, MD5_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
return true;
}
} else if (isSHA256(passDigest)) {
rc = strncpy_s(salt, sizeof(salt), &passDigest[SHA256_LENGTH], sizeof(salt) - 1);
securec_check(rc, "\0", "\0");
salt[sizeof(salt) - 1] = '\0';
iteration_count = get_stored_iteration(rolename);
if (iteration_count == -1) {
iteration_count = ITERATION_COUNT;
}
if (!pg_sha256_encrypt(passwd, salt, strlen(salt), encrypted_sha256_password, NULL, iteration_count)) {
rc = memset_s(encrypted_sha256_password, SHA256_PASSWD_LEN + 1, 0, SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
str_reset(passwd);
str_reset(passDigest);
ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("sha256-password encryption failed.")));
}
if (strncmp(passDigest, encrypted_sha256_password, SHA256_LENGTH + SALT_STRING_LENGTH) == 0 &&
strncmp(passDigest + (SHA256_PASSWD_LEN - STORED_KEY_STRING_LENGTH), encrypted_sha256_password +
(SHA256_PASSWD_LEN - STORED_KEY_STRING_LENGTH), STORED_KEY_STRING_LENGTH) == 0) {
rc = memset_s(encrypted_sha256_password, SHA256_PASSWD_LEN + 1, 0, SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
return true;
}
} else if (isSM3(passDigest)) {
rc = strncpy_s(salt, sizeof(salt), &passDigest[SM3_LENGTH], sizeof(salt) - 1);
securec_check(rc, "\0", "\0");
salt[sizeof(salt) - 1] = '\0';
iteration_count = get_stored_iteration(rolename);
if (!GsSm3Encrypt(passwd, salt, strlen(salt), encryptedSm3Password, NULL, iteration_count)) {
rc = memset_s(encryptedSm3Password, SM3_PASSWD_LEN + 1, 0, SM3_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("sm3-password encryption failed.")));
}
if (strncmp(passDigest, encryptedSm3Password, SM3_PASSWD_LEN) == 0) {
rc = memset_s(encryptedSm3Password, SM3_PASSWD_LEN + 1, 0, SM3_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
return true;
}
} else if (isCOMBINED(passDigest)) {
rc = strncpy_s(salt, sizeof(salt), &passDigest[SHA256_LENGTH], sizeof(salt) - 1);
securec_check(rc, "\0", "\0");
salt[sizeof(salt) - 1] = '\0';
iteration_count = get_stored_iteration(rolename);
if (iteration_count == -1) {
iteration_count = ITERATION_COUNT;
}
if (!pg_sha256_encrypt(passwd, salt, strlen(salt), encrypted_sha256_password, NULL, iteration_count)) {
rc = memset_s(encrypted_sha256_password, SHA256_PASSWD_LEN + 1, 0, SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
str_reset(passwd);
str_reset(passDigest);
ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("first stage encryption password failed")));
}
if (!pg_md5_encrypt(passwd, rolename, strlen(rolename), encrypted_md5_password)) {
str_reset(passwd);
str_reset(passDigest);
ereport(ERROR, (errcode(ERRCODE_INVALID_PASSWORD), errmsg("second stage encryption password failed")));
}
rc = snprintf_s(encrypted_combined_password,
MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1,
MD5_PASSWD_LEN + SHA256_PASSWD_LEN,
"%s%s",
encrypted_sha256_password,
encrypted_md5_password);
securec_check_ss(rc, "\0", "\0");
* When alter user's password:
* 1. No need to compare the new iteration and old iteration.
* 2. No need to compare MD5 password, just compare SHA256 is enough.
*/
if (strncmp(passDigest, encrypted_combined_password, SHA256_LENGTH + SALT_STRING_LENGTH) == 0 &&
strncmp(passDigest + (SHA256_PASSWD_LEN - STORED_KEY_STRING_LENGTH), encrypted_combined_password +
(SHA256_PASSWD_LEN - STORED_KEY_STRING_LENGTH), STORED_KEY_STRING_LENGTH) == 0) {
rc = memset_s(encrypted_md5_password, MD5_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(encrypted_sha256_password, SHA256_PASSWD_LEN + 1, 0, SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(encrypted_combined_password,
MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1,
0,
MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
return true;
}
} else {
if (strcmp(passwd, passDigest) == 0) {
return true;
}
}
rc = memset_s(encrypted_md5_password, MD5_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(encrypted_sha256_password, SHA256_PASSWD_LEN + 1, 0, SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(encryptedSm3Password, SM3_PASSWD_LEN + 1, 0, SM3_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
rc = memset_s(
encrypted_combined_password, MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1, 0, MD5_PASSWD_LEN + SHA256_PASSWD_LEN + 1);
securec_check(rc, "\0", "\0");
return false;
}
int crypt_verify(const Port* port, const char* role, char* client_pass)
{
int retval = STATUS_ERROR;
char* shadow_pass = NULL;
char* crypt_pwd = NULL;
char combined_shadow_pass[SHA256_PASSWD_LEN + MD5_PASSWD_LEN + ITERATION_STRING_LEN + 1] = {0};
char* crypt_client_pass = client_pass;
int CRYPT_hmac_ret;
int CRYPT_digest_ret;
bool RFC5802_pass = false;
password_info pass_info = {NULL, 0, 0, false, false};
errno_t errorno = EOK;
if (get_stored_password(role, &pass_info)) {
* We just extract appropriate password encrypted information in upper layer
* according to the authentication method. And there is no need to check
* whether it is combined password later.
*/
if (isCOMBINED(pass_info.shadow_pass)) {
if (port->hba->auth_method == uaMD5) {
errorno = memcpy_s(combined_shadow_pass,
SHA256_PASSWD_LEN + MD5_PASSWD_LEN + ITERATION_STRING_LEN + 1,
&pass_info.shadow_pass[SHA256_PASSWD_LEN],
MD5_PASSWD_LEN);
securec_check(errorno, "\0", "\0");
}
if (port->hba->auth_method == uaSHA256) {
errorno = memcpy_s(combined_shadow_pass,
SHA256_PASSWD_LEN + MD5_PASSWD_LEN + ITERATION_STRING_LEN + 1,
pass_info.shadow_pass,
SHA256_PASSWD_LEN);
securec_check(errorno, "\0", "\0");
errorno = memcpy_s(combined_shadow_pass + SHA256_PASSWD_LEN,
MD5_PASSWD_LEN + ITERATION_STRING_LEN + 1,
&pass_info.shadow_pass[SHA256_PASSWD_LEN + MD5_PASSWD_LEN],
ITERATION_STRING_LEN);
securec_check(errorno, "\0", "\0");
}
shadow_pass = combined_shadow_pass;
} else {
shadow_pass = pass_info.shadow_pass;
}
} else {
return STATUS_ERROR;
}
* Don't allow an empty password. Libpq treats an empty password the same
* as no password at all, and won't even try to authenticate. But other
* clients might, so allowing it would be confusing.
*
* For a plaintext password, we can simply check that it's not an empty
* string. For an encrypted password, check that it does not match the MD5
* hash of an empty string.
*/
if (shadow_pass == NULL || *shadow_pass == '\0')
return STATUS_ERROR;
if (VerifyPasswdDigest(port->user_name, "\0", shadow_pass)) {
return STATUS_ERROR;
}
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection.
*/
switch (port->hba->auth_method) {
case uaMD5:
crypt_pwd = (char*)palloc(MD5_PASSWD_LEN + 1);
if (crypt_pwd != NULL) {
errorno = memset_s(crypt_pwd, (MD5_PASSWD_LEN + 1), 0, (MD5_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
}else {
return STATUS_ERROR;
}
if (isMD5(shadow_pass)) {
if (!pg_md5_encrypt(shadow_pass + strlen("md5"), port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
}
else if (isSHA256(shadow_pass)) {
if (!pg_md5_encrypt(shadow_pass + SHA256_LENGTH, port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
} else {
char* crypt_pwd2 = (char*)palloc(MD5_PASSWD_LEN + 1);
errorno = memset_s(crypt_pwd2, (MD5_PASSWD_LEN + 1), 0, (MD5_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
if (!pg_md5_encrypt(shadow_pass, port->user_name, strlen(port->user_name), crypt_pwd2)) {
pfree_ext(crypt_pwd);
pfree_ext(crypt_pwd2);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) {
pfree_ext(crypt_pwd);
pfree_ext(crypt_pwd2);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
pfree_ext(crypt_pwd2);
}
break;
case uaSHA256: {
crypt_pwd = (char*)palloc(SHA256_MD5_ENCRY_PASSWD_LEN + 1);
if (crypt_pwd == NULL) {
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
errorno = memset_s(crypt_pwd, (SHA256_MD5_ENCRY_PASSWD_LEN + 1), 0, (SHA256_MD5_ENCRY_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
if (isMD5(shadow_pass)) {
if (!pg_sha256_encrypt_for_md5(
shadow_pass + strlen("md5"), port->md5Salt, sizeof(port->md5Salt), crypt_pwd)) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
} else if (isSHA256(shadow_pass)) {
char stored_key[STORED_KEY_BYTES_LENGTH + 1] = {0};
char stored_key_string[STORED_KEY_STRING_LENGTH + 1] = {0};
char hmac_result[HMAC_LENGTH + 1] = {0};
char xor_result[HMAC_LENGTH + 1] = {0};
char token[TOKEN_LENGTH + 1] = {0};
char hash_result[HMAC_LENGTH + 1] = {0};
char hash_result_string[HMAC_LENGTH * 2 + 1] = {0};
char client_pass_bytes[HMAC_LENGTH + 1] = {0};
int hmac_length = HMAC_LENGTH;
if (H_LENGTH != strlen(client_pass)) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_WRONG_PASSWORD;
}
errorno = strncpy_s(stored_key_string,
sizeof(stored_key_string),
&shadow_pass[SHA256_LENGTH + SALT_STRING_LENGTH + HMAC_STRING_LENGTH],
sizeof(stored_key_string) - 1);
securec_check(errorno, "\0", "\0");
stored_key_string[sizeof(stored_key_string) - 1] = '\0';
sha_hex_to_bytes32(stored_key, stored_key_string);
sha_hex_to_bytes4(token, (char*)port->token);
CRYPT_hmac_ret = CRYPT_hmac(NID_hmacWithSHA256,
(GS_UCHAR*)stored_key,
STORED_KEY_LENGTH,
(GS_UCHAR*)token,
TOKEN_LENGTH,
(GS_UCHAR*)hmac_result,
(GS_UINT32*)&hmac_length);
if (CRYPT_hmac_ret) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
sha_hex_to_bytes32(client_pass_bytes, client_pass);
if (XOR_between_password(hmac_result, client_pass_bytes, xor_result, HMAC_LENGTH)) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
CRYPT_digest_ret = EVP_Digest((GS_UCHAR*)xor_result,
HMAC_LENGTH,
(GS_UCHAR*)hash_result,
(GS_UINT32*)&hmac_length,
EVP_sha256(),
NULL);
if (!CRYPT_digest_ret) {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
sha_bytes_to_hex64((uint8*)hash_result, hash_result_string);
if (strncmp(hash_result_string, stored_key_string, HMAC_LENGTH * 2) == 0) {
RFC5802_pass = true;
} else {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_WRONG_PASSWORD;
}
} else {
pfree_ext(crypt_pwd);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
break;
}
case uaSM3: {
char stored_key_sm3[STORED_KEY_BYTES_LENGTH + 1] = {0};
char stored_key_string_sm3[STORED_KEY_STRING_LENGTH + 1] = {0};
char hmac_result_sm3[HMAC_LENGTH + 1] = {0};
char xor_result_sm3[HMAC_LENGTH + 1] = {0};
char token_sm3[TOKEN_LENGTH + 1] = {0};
char hash_result_sm3[HMAC_LENGTH + 1] = {0};
char hash_result_string_sm3[HMAC_LENGTH * 2 + 1] = {0};
char client_pass_bytes_sm3[HMAC_LENGTH + 1] = {0};
int hmac_length_sm3 = HMAC_LENGTH;
if (H_LENGTH != strlen(client_pass)) {
pfree_ext(pass_info.shadow_pass);
return STATUS_WRONG_PASSWORD;
}
errorno = strncpy_s(stored_key_string_sm3,
sizeof(stored_key_string_sm3),
&shadow_pass[SM3_LENGTH + SALT_STRING_LENGTH + HMAC_STRING_LENGTH],
sizeof(stored_key_string_sm3) - 1);
securec_check(errorno, "\0", "\0");
stored_key_string_sm3[sizeof(stored_key_string_sm3) - 1] = '\0';
sha_hex_to_bytes32(stored_key_sm3, stored_key_string_sm3);
sha_hex_to_bytes4(token_sm3, (char*)port->token);
CRYPT_hmac_ret = CRYPT_hmac(NID_hmacWithSHA256,
(GS_UCHAR*)stored_key_sm3,
STORED_KEY_LENGTH,
(GS_UCHAR*)token_sm3,
TOKEN_LENGTH,
(GS_UCHAR*)hmac_result_sm3,
(GS_UINT32*)&hmac_length_sm3);
if (CRYPT_hmac_ret) {
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
sha_hex_to_bytes32(client_pass_bytes_sm3, client_pass);
if (XOR_between_password(hmac_result_sm3, client_pass_bytes_sm3, xor_result_sm3, HMAC_LENGTH)) {
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
CRYPT_digest_ret = EVP_Digest((GS_UCHAR*)xor_result_sm3,
HMAC_LENGTH,
(GS_UCHAR*)hash_result_sm3,
(GS_UINT32*)&hmac_length_sm3,
EVP_sm3(),
NULL);
#endif
if (!CRYPT_digest_ret) {
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
sha_bytes_to_hex64((uint8*)hash_result_sm3, hash_result_string_sm3);
if (strncmp(hash_result_string_sm3, stored_key_string_sm3, HMAC_LENGTH * 2) == 0) {
RFC5802_pass = true;
} else {
pfree_ext(pass_info.shadow_pass);
return STATUS_WRONG_PASSWORD;
}
break;
}
default:
if (isMD5(shadow_pass)) {
crypt_client_pass = (char*)palloc(MD5_PASSWD_LEN + 1);
if (crypt_client_pass != NULL) {
errorno = memset_s(crypt_client_pass, (MD5_PASSWD_LEN + 1), 0, (MD5_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
}
if (!pg_md5_encrypt(client_pass, port->user_name, strlen(port->user_name), crypt_client_pass)) {
pfree_ext(crypt_client_pass);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
}
if (isSHA256(shadow_pass)) {
crypt_client_pass = (char*)palloc(SHA256_PASSWD_LEN + 1);
if (crypt_client_pass != NULL) {
errorno = memset_s(crypt_client_pass, (SHA256_PASSWD_LEN + 1), 0, (SHA256_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
}
if (!pg_sha256_encrypt(
client_pass, port->user_name, strlen(port->user_name), crypt_client_pass, NULL)) {
pfree_ext(crypt_client_pass);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
}
if (isSM3(shadow_pass)) {
crypt_client_pass = (char*)palloc(SM3_PASSWD_LEN + 1);
if (crypt_client_pass != NULL) {
errorno = memset_s(crypt_client_pass, (SM3_PASSWD_LEN + 1), 0, (SM3_PASSWD_LEN + 1));
securec_check(errorno, "\0", "\0");
}
if (!GsSm3Encrypt(
client_pass, port->user_name, strlen(port->user_name), crypt_client_pass, NULL)) {
pfree_ext(crypt_client_pass);
pfree_ext(pass_info.shadow_pass);
return STATUS_ERROR;
}
}
crypt_pwd = shadow_pass;
break;
}
if (RFC5802_pass || strcmp(crypt_client_pass, crypt_pwd) == 0) {
retval = STATUS_OK;
} else {
retval = STATUS_WRONG_PASSWORD;
}
if (port->hba->auth_method == uaMD5 || port->hba->auth_method == uaSHA256) {
pfree_ext(crypt_pwd);
}
if (crypt_client_pass != client_pass) {
pfree_ext(crypt_client_pass);
}
pfree_ext(pass_info.shadow_pass);
return retval;
}
* Target :get iteration from the stored password.
* Description :under the latest password storage format, iteration is stored in pg_authid.
* we get the iteration if there is ,if not we return the default iteration value.
* Input :role name.
* Return :auth iteration integer.
*/
int get_stored_iteration(const char* role)
{
password_info pass_info = {NULL, 0, 0, false, false};
int iteration_count = 0;
if (!get_stored_password(role, &pass_info)) {
pfree_ext(pass_info.shadow_pass);
return -1;
}
iteration_count = get_iteration_by_password(pass_info.shadow_pass);
pfree_ext(pass_info.shadow_pass);
return iteration_count;
}
int get_iteration_by_password(char* encrypted_password)
{
int iteration_count = 0;
if (isCOMBINED(encrypted_password)) {
iteration_count = decode_iteration(&encrypted_password[SHA256_PASSWD_LEN + MD5_PASSWD_LEN]);
} else if (isSHA256(encrypted_password)) {
iteration_count = decode_iteration(&encrypted_password[SHA256_PASSWD_LEN]);
} else if (isSM3(encrypted_password)) {
iteration_count = decode_iteration(&encrypted_password[SM3_PASSWD_LEN]);
} else {
iteration_count = ITERATION_COUNT;
}
return iteration_count;
}
#ifdef USE_IAM
* @Description: read auth file to buffer for iam token authenication.
* @in file_path : the file path with filename.
* @return : string buffer contained the contents of the file.
* @NOTICE : the return buffer need be free'd by caller.
*/
char* read_auth_file(const char* file_path)
{
char* file_buf = NULL;
int file_len = 0;
int fd = -1;
struct stat statbuf;
fd = open(file_path, O_RDONLY, S_IRUSR | S_IWUSR);
if (fd < 0) {
ereport(LOG, (errmsg("could not open file : %m")));
return NULL;
}
if (fstat(fd, &statbuf) < 0) {
(void)close(fd);
ereport(LOG, (errmsg("could not stat file : %m")));
return NULL;
}
if (!S_ISREG(statbuf.st_mode) || (statbuf.st_mode & (S_IRWXG | S_IRWXO))) {
(void)close(fd);
ereport(LOG, (errmsg("file permissions can't be more than 600.")));
return NULL;
}
file_buf = (char*)palloc0(statbuf.st_size + 1);
file_len = (int)read(fd, file_buf, statbuf.st_size);
if (file_len != statbuf.st_size) {
(void)close(fd);
pfree_ext(file_buf);
ereport(LOG, (errmsg("could not read file : %m")));
return NULL;
}
if (close(fd) != 0)
ereport(LOG, (errmsg("close file failed : %m")));
return file_buf;
}
* @Description: convert the token to cms format for verify.
* @in token_string : the token need be converted.
* @return : the filename stored the cms token.
* @NOTICE : the return buffer need be free'd by caller.
*/
char* convert_cms_token(const char* token_string)
{
#define ROW_LENGTH 64
char* temp_string = NULL;
char* source_string = NULL;
char* cms_file = NULL;
FILE* fp = NULL;
int token_length = 0;
int multiple_num = 0;
int remainder_num = 0;
int i = 0;
errno_t rc = 0;
token_length = strlen(token_string);
multiple_num = token_length / ROW_LENGTH;
remainder_num = token_length % ROW_LENGTH;
temp_string = pstrdup(token_string);
cms_file = (char*)palloc0(MAXPGPATH);
source_string = temp_string;
while (*temp_string != '\0') {
if (*temp_string == '-') {
*temp_string = '/';
}
temp_string++;
}
rc = snprintf_s(cms_file,
MAXPGPATH,
MAXPGPATH - 1,
"%sgaussdb_cms_token_%d_%ld",
DWS_IAMAUTH_CONFIG_PATH,
gs_random(),
GetCurrentTimestamp());
securec_check_ss(rc, "\0", "\0");
if ((fp = fopen(cms_file, "w")) == NULL) {
ereport(LOG, (errmsg("create cms file failed.")));
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
pfree_ext(cms_file);
return NULL;
}
#ifdef WIN32
rc = _chmod(cms_file, 0600);
#else
rc = fchmod(fp->_fileno, 0600);
#endif
if (rc != 0) {
ereport(LOG, (errmsg("could not set permissions of file.")));
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
pfree_ext(cms_file);
(void)fclose(fp);
return NULL;
}
if (fputs("-----BEGIN CMS-----\n", fp) < 0) {
ereport(LOG, (errmsg("add cms head failed.")));
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
pfree_ext(cms_file);
(void)fclose(fp);
return NULL;
}
for (i = 0; i <= multiple_num; i++) {
int data_size;
if (i < multiple_num) {
data_size = ROW_LENGTH;
} else {
data_size = remainder_num;
}
if (i == multiple_num && remainder_num == 0) {
break;
}
if (fwrite(source_string + ROW_LENGTH * i, data_size, 1, fp) != 1) {
ereport(LOG, (errmsg("add cms body failed.")));
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
pfree_ext(cms_file);
(void)fclose(fp);
return NULL;
}
(void)fputs("\n", fp);
}
if (fputs("-----END CMS-----\n", fp) < 0) {
ereport(LOG, (errmsg("add cms end failed.")));
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
pfree_ext(cms_file);
(void)fclose(fp);
return NULL;
}
rc = memset_s(source_string, strlen(source_string), 0, strlen(source_string));
securec_check(rc, "\0", "\0");
pfree_ext(source_string);
(void)fclose(fp);
return cms_file;
}
* @Description: verify the token used signature cert and ca.
* @in token_string : the token need be verified.
* @return : string of the verified token.
* @NOTICE : the return buffer need be free'd by caller.
*/
char* verify_cms_token(char* token_string)
{
#define TOKEN_FILE_MAX_LEN 256
char command[MAXPGPATH] = {0};
char* cms_token_file = NULL;
char* verified_token_file = NULL;
const char* cert_file = "signing_cert.pem";
const char* ca_file = "ca.pem";
errno_t rc = EOK;
cms_token_file = convert_cms_token(token_string);
if (cms_token_file == NULL) {
ereport(LOG, (errmsg("convert to cms failed.")));
return NULL;
}
verified_token_file = (char*)palloc0(TOKEN_FILE_MAX_LEN);
rc = snprintf_s(verified_token_file,
TOKEN_FILE_MAX_LEN,
TOKEN_FILE_MAX_LEN - 1,
"%s_%ld",
cms_token_file,
GetCurrentTimestamp());
securec_check_ss(rc, "\0", "\0");
rc = snprintf_s(command,
MAXPGPATH,
MAXPGPATH - 1,
"openssl cms -verify -certfile %s%s -CAfile %s%s -inform PEM -nosmimecap -nodetach -nocerts -noattr < %s 1> %s "
"2> /dev/null",
DWS_IAMAUTH_CONFIG_PATH,
cert_file,
DWS_IAMAUTH_CONFIG_PATH,
ca_file,
cms_token_file,
verified_token_file);
securec_check_ss(rc, "\0", "\0");
rc = system(command);
if (rc != 0) {
ereport(LOG, (errmsg("openssl cms command failed : [%d]", rc)));
(void)unlink(cms_token_file);
(void)unlink(verified_token_file);
pfree_ext(cms_token_file);
pfree_ext(verified_token_file);
return NULL;
}
(void)unlink(cms_token_file);
pfree_ext(cms_token_file);
return verified_token_file;
}
* @Description: parse the roles array in json body and check whether include dws privilege role.
* @in array_object : the role array object in json body need be parsed .
* @return : false for not including the dws_db_acc role privileges.
*/
bool parse_token_roles(cJSON* array_object)
{
int obj_size = 0;
int i = 0;
cJSON* arr_object = NULL;
cJSON* object = NULL;
char* rol_name = NULL;
obj_size = cJSON_GetArraySize(array_object);
for (i = 0; i < obj_size; i++) {
arr_object = cJSON_GetArrayItem(array_object, i);
object = cJSON_GetObjectItem(arr_object, "name");
if (object == NULL) {
ereport(LOG, (errmsg("get name in token failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
}
rol_name = object->valuestring;
if (rol_name == NULL) {
ereport(LOG, (errmsg("get role in token failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
}
if (0 == strcmp(rol_name, IAM_ROLE_WITH_DWSDB_PRIV))
return true;
}
return false;
}
* @Description: parse the token in json format.
* @in token_string : the token string need be parsed.
* @out token : the token messages after parsed.
* @return : false for parse failed.
* @NOTICE : the return token and messages in token need be free'd by caller.
*/
bool parse_token(const char* token_string, iam_token* token)
{
char* verified_token_file = NULL;
char* clusterid = NULL;
char* real_token = NULL;
char* json_string = NULL;
cJSON* root_obj = NULL;
cJSON* first_level_obj = NULL;
cJSON* second_level_obj = NULL;
cJSON* third_level_obj = NULL;
cJSON* fourth_level_obj = NULL;
int token_str_len = 0;
int token_len = 0;
errno_t rc = EOK;
token_str_len = strlen(token_string);
if (token_str_len <= CLUSTERID_LEN) {
return false;
}
token_len = token_str_len - CLUSTERID_LEN;
clusterid = (char*)palloc0(CLUSTERID_LEN + 1);
real_token = (char*)palloc0(token_len + 1);
rc = memcpy_s(real_token, token_len + 1, token_string, token_len);
securec_check(rc, "\0", "\0");
rc = memcpy_s(clusterid, CLUSTERID_LEN + 1, token_string + token_str_len - CLUSTERID_LEN, CLUSTERID_LEN);
securec_check(rc, "\0", "\0");
token->cluster_id = clusterid;
verified_token_file = verify_cms_token(real_token);
pfree_ext(real_token);
if (verified_token_file == NULL) {
return false;
}
json_string = read_auth_file(verified_token_file);
if (json_string == NULL) {
ereport(LOG, (errmsg("read verified token file failed.")));
(void)unlink(verified_token_file);
pfree_ext(verified_token_file);
return false;
}
(void)unlink(verified_token_file);
pfree_ext(verified_token_file);
root_obj = cJSON_Parse(json_string);
if (root_obj == NULL) {
ereport(LOG, (errmsg("get root object failed : [%s].", (char*)cJSON_GetErrorPtr)));
pfree_ext(json_string);
return false;
}
pfree_ext(json_string);
first_level_obj = cJSON_GetObjectItem(root_obj, "token");
if (first_level_obj == NULL) {
ereport(LOG, (errmsg("get first level object failed : [%s].", (char*)cJSON_GetErrorPtr)));
cJSON_Delete(root_obj);
return false;
}
second_level_obj = cJSON_GetObjectItem(first_level_obj, "expires_at");
if (second_level_obj == NULL) {
ereport(LOG, (errmsg("get token expires_at failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
} else {
token->expires_at = second_level_obj->valuestring;
ereport(DEBUG5,
(errmsg("token expires object messages: type=%d, key=%s, value=%s.",
second_level_obj->type,
second_level_obj->string,
second_level_obj->valuestring)));
}
second_level_obj = cJSON_GetObjectItem(first_level_obj, "user");
if (second_level_obj == NULL) {
ereport(LOG, (errmsg("get token user failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
} else {
third_level_obj = cJSON_GetObjectItem(second_level_obj, "name");
if (third_level_obj == NULL) {
ereport(LOG, (errmsg("get token user name failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
}
token->username = third_level_obj->valuestring;
ereport(DEBUG5,
(errmsg("token user object messages: type=%d, key=%s, value=%s.",
third_level_obj->type,
third_level_obj->string,
third_level_obj->valuestring)));
third_level_obj = cJSON_GetObjectItem(second_level_obj, "domain");
if (third_level_obj == NULL) {
ereport(LOG, (errmsg("get token user domain failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
}
fourth_level_obj = cJSON_GetObjectItem(third_level_obj, "id");
if (fourth_level_obj == NULL) {
ereport(LOG, (errmsg("get token user tenantid failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
}
token->tenant_id = fourth_level_obj->valuestring;
}
second_level_obj = cJSON_GetObjectItem(first_level_obj, "roles");
if (second_level_obj == NULL) {
ereport(LOG, (errmsg("get token roles failed : [%s].", (char*)cJSON_GetErrorPtr)));
return false;
} else {
token->role_priv = parse_token_roles(second_level_obj);
ereport(DEBUG5, (errmsg("token role messages: %d.", token->role_priv)));
}
cJSON_Delete(root_obj);
return true;
}
* @Description: check whether the token is expired.
* @in token_expired_time : the expired time of the token.
* @return : false for token expired.
*/
bool check_token_expired(char* token_expired_time)
{
struct tm token_time;
struct tm now_time;
time_t now = time(NULL);
if (NULL == gmtime_r(&now, &now_time)) {
ereport(LOG, (errmsg("Call gmtime_r failed.")));
return false;
}
(void)strptime(token_expired_time, "%Y-%m-%dT%H:%M:%SZ", &token_time);
if (mktime(&token_time) <= mktime(&now_time)) {
ereport(LOG, (errmsg("Invalid token for expired : %s.", token_expired_time)));
return false;
} else {
return true;
}
}
* @Description: check the clusterid and tenantId to determine unique cluster information.
* @in token : the token need to be check.
* @return : false for wrong properties.
*/
bool check_cluster_properties(iam_token token)
{
char* cluster_properties = NULL;
char* clusterid_position = NULL;
char* tenantid_position = NULL;
char clusterid_value[CLUSTERID_LEN + 1] = {0};
char tenantid_value[TENANTID_LEN + 1] = {0};
const char* cluserid_file = "cluster.properties";
char cluserid_file_path[MAXPGPATH] = {0};
errno_t rc = EOK;
rc = snprintf_s(cluserid_file_path, MAXPGPATH, MAXPGPATH - 1, "%s%s", DWS_IAMAUTH_CONFIG_PATH, cluserid_file);
securec_check_ss(rc, "\0", "\0");
cluster_properties = read_auth_file(cluserid_file_path);
if (cluster_properties == NULL) {
ereport(LOG, (errmsg("read file cluster.properties failed.")));
return false;
}
clusterid_position = strstr(cluster_properties, "cluterId=");
tenantid_position = strstr(cluster_properties, "tenantId=");
if (clusterid_position == NULL || tenantid_position == NULL) {
ereport(LOG, (errmsg("wrong content for cluster.properties.")));
pfree_ext(cluster_properties);
return false;
}
rc = memcpy_s(clusterid_value, CLUSTERID_LEN + 1, clusterid_position + strlen("cluterId="), CLUSTERID_LEN);
securec_check(rc, "\0", "\0");
rc = memcpy_s(tenantid_value, TENANTID_LEN + 1, tenantid_position + strlen("tenantId="), TENANTID_LEN);
securec_check(rc, "\0", "\0");
pfree_ext(cluster_properties);
if (token.cluster_id == NULL || token.tenant_id == NULL) {
ereport(LOG, (errmsg("wrong clusterid or tenantid in token.")));
return false;
}
if (0 != strcmp(clusterid_value, token.cluster_id) || 0 != strcmp(tenantid_value, token.tenant_id)) {
ereport(LOG, (errmsg("check clusterid and tenantid failed.")));
return false;
} else {
return true;
}
}
* @Description: check the token from iam.
* @in token : the token messages.
* @in rolname : the db role name used for login.
* @return : false for wrong token.
*/
bool check_token(iam_token token, char* rolname)
{
if (!check_cluster_properties(token))
return false;
if (!check_token_expired(token.expires_at))
return false;
if (0 != strcmp(token.username, rolname)) {
ereport(LOG, (errmsg("Invalid iam username %s for %s.", token.username, rolname)));
return false;
}
if (!token.role_priv) {
ereport(LOG, (errmsg("Invalid iam role for login.")));
return false;
}
return true;
}
#endif