diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/libpq/auth-scram.c | 51 | ||||
-rw-r--r-- | src/backend/libpq/crypt.c | 2 | ||||
-rw-r--r-- | src/bin/psql/command.c | 4 | ||||
-rw-r--r-- | src/bin/scripts/createuser.c | 9 | ||||
-rw-r--r-- | src/common/scram-common.c | 64 | ||||
-rw-r--r-- | src/include/common/scram-common.h | 3 | ||||
-rw-r--r-- | src/include/libpq/scram.h | 4 | ||||
-rw-r--r-- | src/interfaces/libpq/exports.txt | 1 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-auth-scram.c | 35 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-auth.c | 125 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-auth.h | 1 | ||||
-rw-r--r-- | src/interfaces/libpq/libpq-fe.h | 1 |
12 files changed, 235 insertions, 65 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 5c85af943cd..6e7a1405826 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -207,7 +207,7 @@ pg_be_scram_init(const char *username, const char *shadow_pass) */ char *verifier; - verifier = scram_build_verifier(username, shadow_pass, 0); + verifier = pg_be_scram_build_verifier(shadow_pass); (void) parse_scram_verifier(verifier, &state->iterations, &state->salt, state->StoredKey, state->ServerKey); @@ -387,22 +387,14 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen, /* * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword. * - * If iterations is 0, default number of iterations is used. The result is - * palloc'd, so caller is responsible for freeing it. + * The result is palloc'd, so caller is responsible for freeing it. */ char * -scram_build_verifier(const char *username, const char *password, - int iterations) +pg_be_scram_build_verifier(const char *password) { char *prep_password = NULL; pg_saslprep_rc rc; char saltbuf[SCRAM_DEFAULT_SALT_LEN]; - uint8 salted_password[SCRAM_KEY_LEN]; - uint8 keybuf[SCRAM_KEY_LEN]; - char *encoded_salt; - char *encoded_storedkey; - char *encoded_serverkey; - int encoded_len; char *result; /* @@ -414,10 +406,7 @@ scram_build_verifier(const char *username, const char *password, if (rc == SASLPREP_SUCCESS) password = (const char *) prep_password; - if (iterations <= 0) - iterations = SCRAM_DEFAULT_ITERATIONS; - - /* Generate salt, and encode it in base64 */ + /* Generate random salt */ if (!pg_backend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) { ereport(LOG, @@ -426,37 +415,11 @@ scram_build_verifier(const char *username, const char *password, return NULL; } - encoded_salt = palloc(pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN) + 1); - encoded_len = pg_b64_encode(saltbuf, SCRAM_DEFAULT_SALT_LEN, encoded_salt); - encoded_salt[encoded_len] = '\0'; - - /* Calculate StoredKey, and encode it in base64 */ - scram_SaltedPassword(password, saltbuf, SCRAM_DEFAULT_SALT_LEN, - iterations, salted_password); - scram_ClientKey(salted_password, keybuf); - scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */ - - encoded_storedkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1); - encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, - encoded_storedkey); - encoded_storedkey[encoded_len] = '\0'; - - /* And same for ServerKey */ - scram_ServerKey(salted_password, keybuf); - - encoded_serverkey = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1); - encoded_len = pg_b64_encode((const char *) keybuf, SCRAM_KEY_LEN, - encoded_serverkey); - encoded_serverkey[encoded_len] = '\0'; - - result = psprintf("SCRAM-SHA-256$%d:%s$%s:%s", iterations, encoded_salt, - encoded_storedkey, encoded_serverkey); + result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, + SCRAM_DEFAULT_ITERATIONS, password); if (prep_password) pfree(prep_password); - pfree(encoded_salt); - pfree(encoded_storedkey); - pfree(encoded_serverkey); return result; } @@ -1194,7 +1157,7 @@ scram_MockSalt(const char *username) * Generate salt using a SHA256 hash of the username and the cluster's * mock authentication nonce. (This works as long as the salt length is * not larger the SHA256 digest length. If the salt is smaller, the caller - * will just ignore the extra data)) + * will just ignore the extra data.) */ StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN, "salt length greater than SHA256 digest length"); diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index d0030f2b6d8..9fe79b48946 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -156,7 +156,7 @@ encrypt_password(PasswordType target_type, const char *role, switch (guessed_type) { case PASSWORD_TYPE_PLAINTEXT: - return scram_build_verifier(role, password, 0); + return pg_be_scram_build_verifier(password); case PASSWORD_TYPE_MD5: diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 859ded71f61..b3263a9570a 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -1878,11 +1878,11 @@ exec_command_password(PsqlScanState scan_state, bool active_branch) else user = PQuser(pset.db); - encrypted_password = PQencryptPassword(pw1, user); + encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL); if (!encrypted_password) { - psql_error("Password encryption failed.\n"); + psql_error("%s", PQerrorMessage(pset.db)); success = false; } else diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c index 3d74797a8f5..35a53bf2064 100644 --- a/src/bin/scripts/createuser.c +++ b/src/bin/scripts/createuser.c @@ -274,11 +274,14 @@ main(int argc, char *argv[]) { char *encrypted_password; - encrypted_password = PQencryptPassword(newpassword, - newuser); + encrypted_password = PQencryptPasswordConn(conn, + newpassword, + newuser, + NULL); if (!encrypted_password) { - fprintf(stderr, _("Password encryption failed.\n")); + fprintf(stderr, _("%s: password encryption failed: %s"), + progname, PQerrorMessage(conn)); exit(1); } appendStringLiteralConn(&sql, encrypted_password, conn); diff --git a/src/common/scram-common.c b/src/common/scram-common.c index a8ea44944c4..77b54c8a5e7 100644 --- a/src/common/scram-common.c +++ b/src/common/scram-common.c @@ -23,6 +23,7 @@ #include <netinet/in.h> #include <arpa/inet.h> +#include "common/base64.h" #include "common/scram-common.h" #define HMAC_IPAD 0x36 @@ -180,3 +181,66 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result) scram_HMAC_update(&ctx, "Server Key", strlen("Server Key")); scram_HMAC_final(result, &ctx); } + + +/* + * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword. + * + * The password should already have been processed with SASLprep, if necessary! + * + * If iterations is 0, default number of iterations is used. The result is + * palloc'd or malloc'd, so caller is responsible for freeing it. + */ +char * +scram_build_verifier(const char *salt, int saltlen, int iterations, + const char *password) +{ + uint8 salted_password[SCRAM_KEY_LEN]; + uint8 stored_key[SCRAM_KEY_LEN]; + uint8 server_key[SCRAM_KEY_LEN]; + char *result; + char *p; + int maxlen; + + if (iterations <= 0) + iterations = SCRAM_DEFAULT_ITERATIONS; + + /* Calculate StoredKey and ServerKey */ + scram_SaltedPassword(password, salt, saltlen, iterations, + salted_password); + scram_ClientKey(salted_password, stored_key); + scram_H(stored_key, SCRAM_KEY_LEN, stored_key); + + scram_ServerKey(salted_password, server_key); + + /* + * The format is: + * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey> + */ + maxlen = strlen("SCRAM-SHA-256") + 1 + + 10 + 1 /* iteration count */ + + pg_b64_enc_len(saltlen) + 1 /* Base64-encoded salt */ + + pg_b64_enc_len(SCRAM_KEY_LEN) + 1 /* Base64-encoded StoredKey */ + + pg_b64_enc_len(SCRAM_KEY_LEN) + 1; /* Base64-encoded ServerKey */ + +#ifdef FRONTEND + result = malloc(maxlen); + if (!result) + return NULL; +#else + result = palloc(maxlen); +#endif + + p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations); + + p += pg_b64_encode(salt, saltlen, p); + *(p++) = '$'; + p += pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p); + *(p++) = ':'; + p += pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p); + *(p++) = '\0'; + + Assert(p - result <= maxlen); + + return result; +} diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h index 656d9e1e6b1..307f92b54a4 100644 --- a/src/include/common/scram-common.h +++ b/src/include/common/scram-common.h @@ -53,4 +53,7 @@ extern void scram_H(const uint8 *str, int len, uint8 *result); extern void scram_ClientKey(const uint8 *salted_password, uint8 *result); extern void scram_ServerKey(const uint8 *salted_password, uint8 *result); +extern char *scram_build_verifier(const char *salt, int saltlen, int iterations, + const char *password); + #endif /* SCRAM_COMMON_H */ diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h index e373f0c07e8..060b8af69e3 100644 --- a/src/include/libpq/scram.h +++ b/src/include/libpq/scram.h @@ -27,9 +27,7 @@ extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen, char **output, int *outputlen, char **logdetail); /* Routines to handle and check SCRAM-SHA-256 verifier */ -extern char *scram_build_verifier(const char *username, - const char *password, - int iterations); +extern char *pg_be_scram_build_verifier(const char *password); extern bool is_scram_verifier(const char *verifier); extern bool scram_verify_plain_password(const char *username, const char *password, const char *verifier); diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 21dd772ca91..d6a38d0df85 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -171,3 +171,4 @@ PQsslAttributeNames 168 PQsslAttribute 169 PQsetErrorContextVisibility 170 PQresultVerboseErrorMessage 171 +PQencryptPasswordConn 172 diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c index be271ce8ac0..52dae49abf6 100644 --- a/src/interfaces/libpq/fe-auth-scram.c +++ b/src/interfaces/libpq/fe-auth-scram.c @@ -615,6 +615,41 @@ verify_server_signature(fe_scram_state *state) } /* + * Build a new SCRAM verifier. + */ +char * +pg_fe_scram_build_verifier(const char *password) +{ + char *prep_password = NULL; + pg_saslprep_rc rc; + char saltbuf[SCRAM_DEFAULT_SALT_LEN]; + char *result; + + /* + * Normalize the password with SASLprep. If that doesn't work, because + * the password isn't valid UTF-8 or contains prohibited characters, just + * proceed with the original password. (See comments at top of file.) + */ + rc = pg_saslprep(password, &prep_password); + if (rc == SASLPREP_OOM) + return NULL; + if (rc == SASLPREP_SUCCESS) + password = (const char *) prep_password; + + /* Generate a random salt */ + if (!pg_frontend_random(saltbuf, SCRAM_DEFAULT_SALT_LEN)) + return NULL; + + result = scram_build_verifier(saltbuf, SCRAM_DEFAULT_SALT_LEN, + SCRAM_DEFAULT_ITERATIONS, password); + + if (prep_password) + free(prep_password); + + return result; +} + +/* * Random number generator. */ static bool diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index d81ee4f9447..daa7cc95858 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -1077,7 +1077,33 @@ pg_fe_getauthname(PQExpBuffer errorMessage) /* - * PQencryptPassword -- exported routine to encrypt a password + * PQencryptPassword -- exported routine to encrypt a password with MD5 + * + * This function is equivalent to calling PQencryptPasswordConn with + * "md5" as the encryption method, except that this doesn't require + * a connection object. This function is deprecated, use + * PQencryptPasswordConn instead. + */ +char * +PQencryptPassword(const char *passwd, const char *user) +{ + char *crypt_pwd; + + crypt_pwd = malloc(MD5_PASSWD_LEN + 1); + if (!crypt_pwd) + return NULL; + + if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + { + free(crypt_pwd); + return NULL; + } + + return crypt_pwd; +} + +/* + * PQencryptPasswordConn -- exported routine to encrypt a password * * This is intended to be used by client applications that wish to send * commands like ALTER USER joe PASSWORD 'pwd'. The password need not @@ -1087,27 +1113,102 @@ pg_fe_getauthname(PQExpBuffer errorMessage) * be dependent on low-level details like whether the encryption is MD5 * or something else. * - * Arguments are the cleartext password, and the SQL name of the user it - * is for. + * Arguments are a connection object, the cleartext password, the SQL + * name of the user it is for, and a string indicating the algorithm to + * use for encrypting the password. If algorithm is NULL, this queries + * the server for the current 'password_encryption' value. If you wish + * to avoid that, e.g. to avoid blocking, you can execute + * 'show password_encryption' yourself before calling this function, and + * pass it as the algorithm. * - * Return value is a malloc'd string, or NULL if out-of-memory. The client - * may assume the string doesn't contain any special characters that would - * require escaping. + * Return value is a malloc'd string. The client may assume the string + * doesn't contain any special characters that would require escaping. + * On error, an error message is stored in the connection object, and + * returns NULL. */ char * -PQencryptPassword(const char *passwd, const char *user) +PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, + const char *algorithm) { - char *crypt_pwd; +#define MAX_ALGORITHM_NAME_LEN 50 + char algobuf[MAX_ALGORITHM_NAME_LEN + 1]; + char *crypt_pwd = NULL; - crypt_pwd = malloc(MD5_PASSWD_LEN + 1); - if (!crypt_pwd) + if (!conn) return NULL; - if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + /* If no algorithm was given, ask the server. */ + if (algorithm == NULL) { - free(crypt_pwd); + PGresult *res; + char *val; + + res = PQexec(conn, "show password_encryption"); + if (res == NULL) + { + /* PQexec() should've set conn->errorMessage already */ + return NULL; + } + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + /* PQexec() should've set conn->errorMessage already */ + PQclear(res); + return NULL; + } + if (PQntuples(res) != 1 || PQnfields(res) != 1) + { + PQclear(res); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unexpected shape of result set returned for SHOW\n")); + return NULL; + } + val = PQgetvalue(res, 0, 0); + + if (strlen(val) > MAX_ALGORITHM_NAME_LEN) + { + PQclear(res); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("password_encryption value too long\n")); + return NULL; + } + strcpy(algobuf, val); + PQclear(res); + + algorithm = algobuf; + } + + /* Ok, now we know what algorithm to use */ + + if (strcmp(algorithm, "scram-sha-256") == 0) + { + crypt_pwd = pg_fe_scram_build_verifier(passwd); + } + else if (strcmp(algorithm, "md5") == 0) + { + crypt_pwd = malloc(MD5_PASSWD_LEN + 1); + if (crypt_pwd) + { + if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd)) + { + free(crypt_pwd); + crypt_pwd = NULL; + } + } + } + else if (strcmp(algorithm, "plain") == 0) + { + crypt_pwd = strdup(passwd); + } + else + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("unknown password encryption algorithm\n")); return NULL; } + if (!crypt_pwd) + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return crypt_pwd; } diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h index a5c739f01a3..9f4c2a50d8d 100644 --- a/src/interfaces/libpq/fe-auth.h +++ b/src/interfaces/libpq/fe-auth.h @@ -28,5 +28,6 @@ extern void pg_fe_scram_free(void *opaq); extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen, char **output, int *outputlen, bool *done, bool *success, PQExpBuffer errorMessage); +extern char *pg_fe_scram_build_verifier(const char *password); #endif /* FE_AUTH_H */ diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 635af5b50e3..093c4986d8c 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -597,6 +597,7 @@ extern int PQenv2encoding(void); /* === in fe-auth.c === */ extern char *PQencryptPassword(const char *passwd, const char *user); +extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm); /* === in encnames.c === */ |