mirror of https://github.com/postgres/postgres
This introduces a new generic SASL authentication method, similar to the GSS and SSPI methods. The server first tells the client which SASL authentication mechanism to use, and then the mechanism-specific SASL messages are exchanged in AuthenticationSASLcontinue and PasswordMessage messages. Only SCRAM-SHA-256 is supported at the moment, but this allows adding more SASL mechanisms in the future, without changing the overall protocol. Support for channel binding, aka SCRAM-SHA-256-PLUS is left for later. The SASLPrep algorithm, for pre-processing the password, is not yet implemented. That could cause trouble, if you use a password with non-ASCII characters, and a client library that does implement SASLprep. That will hopefully be added later. Authorization identities, as specified in the SCRAM-SHA-256 specification, are ignored. SET SESSION AUTHORIZATION provides more or less the same functionality, anyway. If a user doesn't exist, perform a "mock" authentication, by constructing an authentic-looking challenge on the fly. The challenge is derived from a new system-wide random value, "mock authentication nonce", which is created at initdb, and stored in the control file. We go through these motions, in order to not give away the information on whether the user exists, to unauthenticated users. Bumps PG_CONTROL_VERSION, because of the new field in control file. Patch by Michael Paquier and Heikki Linnakangas, reviewed at different stages by Robert Haas, Stephen Frost, David Steele, Aleksander Alekseev, and many others. Discussion: https://www.postgresql.org/message-id/CAB7nPqRbR3GmFYdedCAhzukfKrgBLTLtMvENOmPrVWREsZkF8g%40mail.gmail.com Discussion: https://www.postgresql.org/message-id/CAB7nPqSMXU35g%3DW9X74HVeQp0uvgJxvYOuA4A-A3M%2B0wfEBv-w%40mail.gmail.com Discussion: https://www.postgresql.org/message-id/55192AFE.6080106@iki.fipull/17/merge
parent
273c458a2b
commit
818fd4a67d
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,199 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* base64.c |
||||
* Encoding and decoding routines for base64 without whitespace. |
||||
* |
||||
* Copyright (c) 2001-2016, PostgreSQL Global Development Group |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/common/base64.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
#ifndef FRONTEND |
||||
#include "postgres.h" |
||||
#else |
||||
#include "postgres_fe.h" |
||||
#endif |
||||
|
||||
#include "common/base64.h" |
||||
|
||||
/*
|
||||
* BASE64 |
||||
*/ |
||||
|
||||
static const char _base64[] = |
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
||||
|
||||
static const int8 b64lookup[128] = { |
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, |
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, |
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, |
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, |
||||
}; |
||||
|
||||
/*
|
||||
* pg_b64_encode |
||||
* |
||||
* Encode into base64 the given string. Returns the length of the encoded |
||||
* string. |
||||
*/ |
||||
int |
||||
pg_b64_encode(const char *src, int len, char *dst) |
||||
{ |
||||
char *p; |
||||
const char *s, |
||||
*end = src + len; |
||||
int pos = 2; |
||||
uint32 buf = 0; |
||||
|
||||
s = src; |
||||
p = dst; |
||||
|
||||
while (s < end) |
||||
{ |
||||
buf |= (unsigned char) *s << (pos << 3); |
||||
pos--; |
||||
s++; |
||||
|
||||
/* write it out */ |
||||
if (pos < 0) |
||||
{ |
||||
*p++ = _base64[(buf >> 18) & 0x3f]; |
||||
*p++ = _base64[(buf >> 12) & 0x3f]; |
||||
*p++ = _base64[(buf >> 6) & 0x3f]; |
||||
*p++ = _base64[buf & 0x3f]; |
||||
|
||||
pos = 2; |
||||
buf = 0; |
||||
} |
||||
} |
||||
if (pos != 2) |
||||
{ |
||||
*p++ = _base64[(buf >> 18) & 0x3f]; |
||||
*p++ = _base64[(buf >> 12) & 0x3f]; |
||||
*p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '='; |
||||
*p++ = '='; |
||||
} |
||||
|
||||
return p - dst; |
||||
} |
||||
|
||||
/*
|
||||
* pg_b64_decode |
||||
* |
||||
* Decode the given base64 string. Returns the length of the decoded |
||||
* string on success, and -1 in the event of an error. |
||||
*/ |
||||
int |
||||
pg_b64_decode(const char *src, int len, char *dst) |
||||
{ |
||||
const char *srcend = src + len, |
||||
*s = src; |
||||
char *p = dst; |
||||
char c; |
||||
int b = 0; |
||||
uint32 buf = 0; |
||||
int pos = 0, |
||||
end = 0; |
||||
|
||||
while (s < srcend) |
||||
{ |
||||
c = *s++; |
||||
|
||||
/* Leave if a whitespace is found */ |
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') |
||||
return -1; |
||||
|
||||
if (c == '=') |
||||
{ |
||||
/* end sequence */ |
||||
if (!end) |
||||
{ |
||||
if (pos == 2) |
||||
end = 1; |
||||
else if (pos == 3) |
||||
end = 2; |
||||
else |
||||
{ |
||||
/*
|
||||
* Unexpected "=" character found while decoding base64 |
||||
* sequence. |
||||
*/ |
||||
return -1; |
||||
} |
||||
} |
||||
b = 0; |
||||
} |
||||
else |
||||
{ |
||||
b = -1; |
||||
if (c > 0 && c < 127) |
||||
b = b64lookup[(unsigned char) c]; |
||||
if (b < 0) |
||||
{ |
||||
/* invalid symbol found */ |
||||
return -1; |
||||
} |
||||
} |
||||
/* add it to buffer */ |
||||
buf = (buf << 6) + b; |
||||
pos++; |
||||
if (pos == 4) |
||||
{ |
||||
*p++ = (buf >> 16) & 255; |
||||
if (end == 0 || end > 1) |
||||
*p++ = (buf >> 8) & 255; |
||||
if (end == 0 || end > 2) |
||||
*p++ = buf & 255; |
||||
buf = 0; |
||||
pos = 0; |
||||
} |
||||
} |
||||
|
||||
if (pos != 0) |
||||
{ |
||||
/*
|
||||
* base64 end sequence is invalid. Input data is missing padding, is |
||||
* truncated or is otherwise corrupted. |
||||
*/ |
||||
return -1; |
||||
} |
||||
|
||||
return p - dst; |
||||
} |
||||
|
||||
/*
|
||||
* pg_b64_enc_len |
||||
* |
||||
* Returns to caller the length of the string if it were encoded with |
||||
* base64 based on the length provided by caller. This is useful to |
||||
* estimate how large a buffer allocation needs to be done before doing |
||||
* the actual encoding. |
||||
*/ |
||||
int |
||||
pg_b64_enc_len(int srclen) |
||||
{ |
||||
/* 3 bytes will be converted to 4 */ |
||||
return (srclen + 2) * 4 / 3; |
||||
} |
||||
|
||||
/*
|
||||
* pg_b64_dec_len |
||||
* |
||||
* Returns to caller the length of the string if it were to be decoded |
||||
* with base64, based on the length given by caller. This is useful to |
||||
* estimate how large a buffer allocation needs to be done before doing |
||||
* the actual decoding. |
||||
*/ |
||||
int |
||||
pg_b64_dec_len(int srclen) |
||||
{ |
||||
return (srclen * 3) >> 2; |
||||
} |
@ -0,0 +1,196 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* scram-common.c |
||||
* Shared frontend/backend code for SCRAM authentication |
||||
* |
||||
* This contains the common low-level functions needed in both frontend and |
||||
* backend, for implement the Salted Challenge Response Authentication |
||||
* Mechanism (SCRAM), per IETF's RFC 5802. |
||||
* |
||||
* Portions Copyright (c) 2016, PostgreSQL Global Development Group |
||||
* |
||||
* IDENTIFICATION |
||||
* src/common/scram-common.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef FRONTEND |
||||
#include "postgres.h" |
||||
#include "utils/memutils.h" |
||||
#else |
||||
#include "postgres_fe.h" |
||||
#endif |
||||
|
||||
#include "common/scram-common.h" |
||||
|
||||
#define HMAC_IPAD 0x36 |
||||
#define HMAC_OPAD 0x5C |
||||
|
||||
/*
|
||||
* Calculate HMAC per RFC2104. |
||||
* |
||||
* The hash function used is SHA-256. |
||||
*/ |
||||
void |
||||
scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen) |
||||
{ |
||||
uint8 k_ipad[SHA256_HMAC_B]; |
||||
int i; |
||||
uint8 keybuf[SCRAM_KEY_LEN]; |
||||
|
||||
/*
|
||||
* If the key is longer than the block size (64 bytes for SHA-256), pass |
||||
* it through SHA-256 once to shrink it down. |
||||
*/ |
||||
if (keylen > SHA256_HMAC_B) |
||||
{ |
||||
pg_sha256_ctx sha256_ctx; |
||||
|
||||
pg_sha256_init(&sha256_ctx); |
||||
pg_sha256_update(&sha256_ctx, key, keylen); |
||||
pg_sha256_final(&sha256_ctx, keybuf); |
||||
key = keybuf; |
||||
keylen = SCRAM_KEY_LEN; |
||||
} |
||||
|
||||
memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B); |
||||
memset(ctx->k_opad, HMAC_OPAD, SHA256_HMAC_B); |
||||
|
||||
for (i = 0; i < keylen; i++) |
||||
{ |
||||
k_ipad[i] ^= key[i]; |
||||
ctx->k_opad[i] ^= key[i]; |
||||
} |
||||
|
||||
/* tmp = H(K XOR ipad, text) */ |
||||
pg_sha256_init(&ctx->sha256ctx); |
||||
pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B); |
||||
} |
||||
|
||||
/*
|
||||
* Update HMAC calculation |
||||
* The hash function used is SHA-256. |
||||
*/ |
||||
void |
||||
scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen) |
||||
{ |
||||
pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen); |
||||
} |
||||
|
||||
/*
|
||||
* Finalize HMAC calculation. |
||||
* The hash function used is SHA-256. |
||||
*/ |
||||
void |
||||
scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx) |
||||
{ |
||||
uint8 h[SCRAM_KEY_LEN]; |
||||
|
||||
pg_sha256_final(&ctx->sha256ctx, h); |
||||
|
||||
/* H(K XOR opad, tmp) */ |
||||
pg_sha256_init(&ctx->sha256ctx); |
||||
pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B); |
||||
pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN); |
||||
pg_sha256_final(&ctx->sha256ctx, result); |
||||
} |
||||
|
||||
/*
|
||||
* Iterate hash calculation of HMAC entry using given salt. |
||||
* scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the |
||||
* pseudorandom function. |
||||
*/ |
||||
static void |
||||
scram_Hi(const char *str, const char *salt, int saltlen, int iterations, uint8 *result) |
||||
{ |
||||
int str_len = strlen(str); |
||||
uint32 one = htonl(1); |
||||
int i, |
||||
j; |
||||
uint8 Ui[SCRAM_KEY_LEN]; |
||||
uint8 Ui_prev[SCRAM_KEY_LEN]; |
||||
scram_HMAC_ctx hmac_ctx; |
||||
|
||||
/* First iteration */ |
||||
scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len); |
||||
scram_HMAC_update(&hmac_ctx, salt, saltlen); |
||||
scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32)); |
||||
scram_HMAC_final(Ui_prev, &hmac_ctx); |
||||
memcpy(result, Ui_prev, SCRAM_KEY_LEN); |
||||
|
||||
/* Subsequent iterations */ |
||||
for (i = 2; i <= iterations; i++) |
||||
{ |
||||
scram_HMAC_init(&hmac_ctx, (uint8 *) str, str_len); |
||||
scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN); |
||||
scram_HMAC_final(Ui, &hmac_ctx); |
||||
for (j = 0; j < SCRAM_KEY_LEN; j++) |
||||
result[j] ^= Ui[j]; |
||||
memcpy(Ui_prev, Ui, SCRAM_KEY_LEN); |
||||
} |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is |
||||
* not included in the hash). |
||||
*/ |
||||
void |
||||
scram_H(const uint8 *input, int len, uint8 *result) |
||||
{ |
||||
pg_sha256_ctx ctx; |
||||
|
||||
pg_sha256_init(&ctx); |
||||
pg_sha256_update(&ctx, input, len); |
||||
pg_sha256_final(&ctx, result); |
||||
} |
||||
|
||||
/*
|
||||
* Normalize a password for SCRAM authentication. |
||||
*/ |
||||
static void |
||||
scram_Normalize(const char *password, char *result) |
||||
{ |
||||
/*
|
||||
* XXX: Here SASLprep should be applied on password. However, per RFC5802, |
||||
* it is required that the password is encoded in UTF-8, something that is |
||||
* not guaranteed in this protocol. We may want to revisit this |
||||
* normalization function once encoding functions are available as well in |
||||
* the frontend in order to be able to encode properly this string, and |
||||
* then apply SASLprep on it. |
||||
*/ |
||||
memcpy(result, password, strlen(password) + 1); |
||||
} |
||||
|
||||
/*
|
||||
* Encrypt password for SCRAM authentication. This basically applies the |
||||
* normalization of the password and a hash calculation using the salt |
||||
* value given by caller. |
||||
*/ |
||||
static void |
||||
scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations, |
||||
uint8 *result) |
||||
{ |
||||
char *pwbuf; |
||||
|
||||
pwbuf = (char *) malloc(strlen(password) + 1); |
||||
scram_Normalize(password, pwbuf); |
||||
scram_Hi(pwbuf, salt, saltlen, iterations, result); |
||||
free(pwbuf); |
||||
} |
||||
|
||||
/*
|
||||
* Calculate ClientKey or ServerKey. |
||||
*/ |
||||
void |
||||
scram_ClientOrServerKey(const char *password, |
||||
const char *salt, int saltlen, int iterations, |
||||
const char *keystr, uint8 *result) |
||||
{ |
||||
uint8 keybuf[SCRAM_KEY_LEN]; |
||||
scram_HMAC_ctx ctx; |
||||
|
||||
scram_SaltedPassword(password, salt, saltlen, iterations, keybuf); |
||||
scram_HMAC_init(&ctx, keybuf, SCRAM_KEY_LEN); |
||||
scram_HMAC_update(&ctx, keystr, strlen(keystr)); |
||||
scram_HMAC_final(result, &ctx); |
||||
} |
@ -0,0 +1,19 @@ |
||||
/*
|
||||
* base64.h |
||||
* Encoding and decoding routines for base64 without whitespace |
||||
* support. |
||||
* |
||||
* Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group |
||||
* |
||||
* src/include/common/base64.h |
||||
*/ |
||||
#ifndef BASE64_H |
||||
#define BASE64_H |
||||
|
||||
/* base 64 */ |
||||
extern int pg_b64_encode(const char *src, int len, char *dst); |
||||
extern int pg_b64_decode(const char *src, int len, char *dst); |
||||
extern int pg_b64_enc_len(int srclen); |
||||
extern int pg_b64_dec_len(int srclen); |
||||
|
||||
#endif /* BASE64_H */ |
@ -0,0 +1,62 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* scram-common.h |
||||
* Declarations for helper functions used for SCRAM authentication |
||||
* |
||||
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/include/common/relpath.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef SCRAM_COMMON_H |
||||
#define SCRAM_COMMON_H |
||||
|
||||
#include "common/sha2.h" |
||||
|
||||
/* Length of SCRAM keys (client and server) */ |
||||
#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH |
||||
|
||||
/* length of HMAC */ |
||||
#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH |
||||
|
||||
/*
|
||||
* Size of random nonce generated in the authentication exchange. This |
||||
* is in "raw" number of bytes, the actual nonces sent over the wire are |
||||
* encoded using only ASCII-printable characters. |
||||
*/ |
||||
#define SCRAM_RAW_NONCE_LEN 10 |
||||
|
||||
/* length of salt when generating new verifiers */ |
||||
#define SCRAM_SALT_LEN 10 |
||||
|
||||
/* number of bytes used when sending iteration number during exchange */ |
||||
#define SCRAM_ITERATION_LEN 10 |
||||
|
||||
/* default number of iterations when generating verifier */ |
||||
#define SCRAM_ITERATIONS_DEFAULT 4096 |
||||
|
||||
/* Base name of keys used for proof generation */ |
||||
#define SCRAM_SERVER_KEY_NAME "Server Key" |
||||
#define SCRAM_CLIENT_KEY_NAME "Client Key" |
||||
|
||||
/*
|
||||
* Context data for HMAC used in SCRAM authentication. |
||||
*/ |
||||
typedef struct |
||||
{ |
||||
pg_sha256_ctx sha256ctx; |
||||
uint8 k_opad[SHA256_HMAC_B]; |
||||
} scram_HMAC_ctx; |
||||
|
||||
extern void scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen); |
||||
extern void scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen); |
||||
extern void scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx); |
||||
|
||||
extern void scram_H(const uint8 *str, int len, uint8 *result); |
||||
extern void scram_ClientOrServerKey(const char *password, const char *salt, |
||||
int saltlen, int iterations, |
||||
const char *keystr, uint8 *result); |
||||
|
||||
#endif /* SCRAM_COMMON_H */ |
@ -0,0 +1,35 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* scram.h |
||||
* Interface to libpq/scram.c |
||||
* |
||||
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/include/libpq/scram.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef PG_SCRAM_H |
||||
#define PG_SCRAM_H |
||||
|
||||
/* Name of SCRAM-SHA-256 per IANA */ |
||||
#define SCRAM_SHA256_NAME "SCRAM-SHA-256" |
||||
|
||||
/* Status codes for message exchange */ |
||||
#define SASL_EXCHANGE_CONTINUE 0 |
||||
#define SASL_EXCHANGE_SUCCESS 1 |
||||
#define SASL_EXCHANGE_FAILURE 2 |
||||
|
||||
/* Routines dedicated to authentication */ |
||||
extern void *pg_be_scram_init(const char *username, const char *shadow_pass, bool doomed); |
||||
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 bool is_scram_verifier(const char *verifier); |
||||
|
||||
#endif /* PG_SCRAM_H */ |
@ -0,0 +1,640 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* fe-auth-scram.c |
||||
* The front-end (client) implementation of SCRAM authentication. |
||||
* |
||||
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/interfaces/libpq/fe-auth-scram.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
#include "postgres_fe.h" |
||||
|
||||
#include "common/base64.h" |
||||
#include "common/scram-common.h" |
||||
#include "fe-auth.h" |
||||
|
||||
/* These are needed for getpid(), in the fallback implementation */ |
||||
#ifndef HAVE_STRONG_RANDOM |
||||
#include <sys/types.h> |
||||
#include <unistd.h> |
||||
#endif |
||||
|
||||
/*
|
||||
* Status of exchange messages used for SCRAM authentication via the |
||||
* SASL protocol. |
||||
*/ |
||||
typedef enum |
||||
{ |
||||
FE_SCRAM_INIT, |
||||
FE_SCRAM_NONCE_SENT, |
||||
FE_SCRAM_PROOF_SENT, |
||||
FE_SCRAM_FINISHED |
||||
} fe_scram_state_enum; |
||||
|
||||
typedef struct |
||||
{ |
||||
fe_scram_state_enum state; |
||||
|
||||
/* These are supplied by the user */ |
||||
const char *username; |
||||
const char *password; |
||||
|
||||
/* We construct these */ |
||||
char *client_nonce; |
||||
char *client_first_message_bare; |
||||
char *client_final_message_without_proof; |
||||
|
||||
/* These come from the server-first message */ |
||||
char *server_first_message; |
||||
char *salt; |
||||
int saltlen; |
||||
int iterations; |
||||
char *nonce; |
||||
|
||||
/* These come from the server-final message */ |
||||
char *server_final_message; |
||||
char ServerProof[SCRAM_KEY_LEN]; |
||||
} fe_scram_state; |
||||
|
||||
static bool read_server_first_message(fe_scram_state *state, char *input, |
||||
PQExpBuffer errormessage); |
||||
static bool read_server_final_message(fe_scram_state *state, char *input, |
||||
PQExpBuffer errormessage); |
||||
static char *build_client_first_message(fe_scram_state *state, |
||||
PQExpBuffer errormessage); |
||||
static char *build_client_final_message(fe_scram_state *state, |
||||
PQExpBuffer errormessage); |
||||
static bool verify_server_proof(fe_scram_state *state); |
||||
static void calculate_client_proof(fe_scram_state *state, |
||||
const char *client_final_message_without_proof, |
||||
uint8 *result); |
||||
static bool pg_frontend_random(char *dst, int len); |
||||
|
||||
/*
|
||||
* Initialize SCRAM exchange status. |
||||
*/ |
||||
void * |
||||
pg_fe_scram_init(const char *username, const char *password) |
||||
{ |
||||
fe_scram_state *state; |
||||
|
||||
state = (fe_scram_state *) malloc(sizeof(fe_scram_state)); |
||||
if (!state) |
||||
return NULL; |
||||
memset(state, 0, sizeof(fe_scram_state)); |
||||
state->state = FE_SCRAM_INIT; |
||||
state->username = username; |
||||
state->password = password; |
||||
|
||||
return state; |
||||
} |
||||
|
||||
/*
|
||||
* Free SCRAM exchange status |
||||
*/ |
||||
void |
||||
pg_fe_scram_free(void *opaq) |
||||
{ |
||||
fe_scram_state *state = (fe_scram_state *) opaq; |
||||
|
||||
/* client messages */ |
||||
if (state->client_nonce) |
||||
free(state->client_nonce); |
||||
if (state->client_first_message_bare) |
||||
free(state->client_first_message_bare); |
||||
if (state->client_final_message_without_proof) |
||||
free(state->client_final_message_without_proof); |
||||
|
||||
/* first message from server */ |
||||
if (state->server_first_message) |
||||
free(state->server_first_message); |
||||
if (state->salt) |
||||
free(state->salt); |
||||
if (state->nonce) |
||||
free(state->nonce); |
||||
|
||||
/* final message from server */ |
||||
if (state->server_final_message) |
||||
free(state->server_final_message); |
||||
|
||||
free(state); |
||||
} |
||||
|
||||
/*
|
||||
* Exchange a SCRAM message with backend. |
||||
*/ |
||||
void |
||||
pg_fe_scram_exchange(void *opaq, char *input, int inputlen, |
||||
char **output, int *outputlen, |
||||
bool *done, bool *success, PQExpBuffer errorMessage) |
||||
{ |
||||
fe_scram_state *state = (fe_scram_state *) opaq; |
||||
|
||||
*done = false; |
||||
*success = false; |
||||
*output = NULL; |
||||
*outputlen = 0; |
||||
|
||||
/*
|
||||
* Check that the input length agrees with the string length of the input. |
||||
* We can ignore inputlen after this. |
||||
*/ |
||||
if (state->state != FE_SCRAM_INIT) |
||||
{ |
||||
if (inputlen == 0) |
||||
{ |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("malformed SCRAM message (empty message)\n")); |
||||
goto error; |
||||
} |
||||
if (inputlen != strlen(input)) |
||||
{ |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("malformed SCRAM message (length mismatch)\n")); |
||||
goto error; |
||||
} |
||||
} |
||||
|
||||
switch (state->state) |
||||
{ |
||||
case FE_SCRAM_INIT: |
||||
/* Begin the SCRAM handshake, by sending client nonce */ |
||||
*output = build_client_first_message(state, errorMessage); |
||||
if (*output == NULL) |
||||
goto error; |
||||
|
||||
*outputlen = strlen(*output); |
||||
*done = false; |
||||
state->state = FE_SCRAM_NONCE_SENT; |
||||
break; |
||||
|
||||
case FE_SCRAM_NONCE_SENT: |
||||
/* Receive salt and server nonce, send response. */ |
||||
if (!read_server_first_message(state, input, errorMessage)) |
||||
goto error; |
||||
|
||||
*output = build_client_final_message(state, errorMessage); |
||||
if (*output == NULL) |
||||
goto error; |
||||
|
||||
*outputlen = strlen(*output); |
||||
*done = false; |
||||
state->state = FE_SCRAM_PROOF_SENT; |
||||
break; |
||||
|
||||
case FE_SCRAM_PROOF_SENT: |
||||
/* Receive server proof */ |
||||
if (!read_server_final_message(state, input, errorMessage)) |
||||
goto error; |
||||
|
||||
/*
|
||||
* Verify server proof, to make sure we're talking to the genuine |
||||
* server. XXX: A fake server could simply not require |
||||
* authentication, though. There is currently no option in libpq |
||||
* to reject a connection, if SCRAM authentication did not happen. |
||||
*/ |
||||
if (verify_server_proof(state)) |
||||
*success = true; |
||||
else |
||||
{ |
||||
*success = false; |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("invalid server proof\n")); |
||||
} |
||||
*done = true; |
||||
state->state = FE_SCRAM_FINISHED; |
||||
break; |
||||
|
||||
default: |
||||
/* shouldn't happen */ |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("invalid SCRAM exchange state\n")); |
||||
goto error; |
||||
} |
||||
return; |
||||
|
||||
error: |
||||
*done = true; |
||||
*success = false; |
||||
return; |
||||
} |
||||
|
||||
/*
|
||||
* Read value for an attribute part of a SASL message. |
||||
*/ |
||||
static char * |
||||
read_attr_value(char **input, char attr, PQExpBuffer errorMessage) |
||||
{ |
||||
char *begin = *input; |
||||
char *end; |
||||
|
||||
if (*begin != attr) |
||||
{ |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("malformed SCRAM message (%c expected)\n"), |
||||
attr); |
||||
return NULL; |
||||
} |
||||
begin++; |
||||
|
||||
if (*begin != '=') |
||||
{ |
||||
printfPQExpBuffer(errorMessage, |
||||
libpq_gettext("malformed SCRAM message (expected = in attr '%c')\n"), |
||||
attr); |
||||
return NULL; |
||||
} |
||||
begin++; |
||||
|
||||
end = begin; |
||||
while (*end && *end != ',') |
||||
end++; |
||||
|
||||
if (*end) |
||||
{ |
||||
*end = '\0'; |
||||
*input = end + 1; |
||||
} |
||||
else |
||||
*input = end; |
||||
|
||||
return begin; |
||||
} |
||||
|
||||
/*
|
||||
* Build the first exchange message sent by the client. |
||||
*/ |
||||
static char * |
||||
build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage) |
||||
{ |
||||
char raw_nonce[SCRAM_RAW_NONCE_LEN + 1]; |
||||
char *buf; |
||||
char buflen; |
||||
int encoded_len; |
||||
|
||||
/*
|
||||
* Generate a "raw" nonce. This is converted to ASCII-printable form by |
||||
* base64-encoding it. |
||||
*/ |
||||
if (!pg_frontend_random(raw_nonce, SCRAM_RAW_NONCE_LEN)) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("failed to generate nonce\n")); |
||||
return NULL; |
||||
} |
||||
|
||||
state->client_nonce = malloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1); |
||||
if (state->client_nonce == NULL) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return NULL; |
||||
} |
||||
encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->client_nonce); |
||||
state->client_nonce[encoded_len] = '\0'; |
||||
|
||||
/*
|
||||
* Generate message. The username is left empty as the backend uses the |
||||
* value provided by the startup packet. Also, as this username is not |
||||
* prepared with SASLprep, the message parsing would fail if it includes |
||||
* '=' or ',' characters. |
||||
*/ |
||||
buflen = 8 + strlen(state->client_nonce) + 1; |
||||
buf = malloc(buflen); |
||||
if (buf == NULL) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return NULL; |
||||
} |
||||
snprintf(buf, buflen, "n,,n=,r=%s", state->client_nonce); |
||||
|
||||
state->client_first_message_bare = strdup(buf + 3); |
||||
if (!state->client_first_message_bare) |
||||
{ |
||||
free(buf); |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return NULL; |
||||
} |
||||
|
||||
return buf; |
||||
} |
||||
|
||||
/*
|
||||
* Build the final exchange message sent from the client. |
||||
*/ |
||||
static char * |
||||
build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage) |
||||
{ |
||||
PQExpBufferData buf; |
||||
uint8 client_proof[SCRAM_KEY_LEN]; |
||||
char *result; |
||||
|
||||
initPQExpBuffer(&buf); |
||||
|
||||
/*
|
||||
* Construct client-final-message-without-proof. We need to remember it |
||||
* for verifying the server proof in the final step of authentication. |
||||
*/ |
||||
appendPQExpBuffer(&buf, "c=biws,r=%s", state->nonce); |
||||
if (PQExpBufferDataBroken(buf)) |
||||
goto oom_error; |
||||
|
||||
state->client_final_message_without_proof = strdup(buf.data); |
||||
if (state->client_final_message_without_proof == NULL) |
||||
goto oom_error; |
||||
|
||||
/* Append proof to it, to form client-final-message. */ |
||||
calculate_client_proof(state, |
||||
state->client_final_message_without_proof, |
||||
client_proof); |
||||
|
||||
appendPQExpBuffer(&buf, ",p="); |
||||
if (!enlargePQExpBuffer(&buf, pg_b64_enc_len(SCRAM_KEY_LEN))) |
||||
goto oom_error; |
||||
buf.len += pg_b64_encode((char *) client_proof, |
||||
SCRAM_KEY_LEN, |
||||
buf.data + buf.len); |
||||
buf.data[buf.len] = '\0'; |
||||
|
||||
result = strdup(buf.data); |
||||
if (result == NULL) |
||||
goto oom_error; |
||||
|
||||
termPQExpBuffer(&buf); |
||||
return result; |
||||
|
||||
oom_error: |
||||
termPQExpBuffer(&buf); |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return NULL; |
||||
} |
||||
|
||||
/*
|
||||
* Read the first exchange message coming from the server. |
||||
*/ |
||||
static bool |
||||
read_server_first_message(fe_scram_state *state, char *input, |
||||
PQExpBuffer errormessage) |
||||
{ |
||||
char *iterations_str; |
||||
char *endptr; |
||||
char *encoded_salt; |
||||
char *nonce; |
||||
|
||||
state->server_first_message = strdup(input); |
||||
if (state->server_first_message == NULL) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return false; |
||||
} |
||||
|
||||
/* parse the message */ |
||||
nonce = read_attr_value(&input, 'r', errormessage); |
||||
if (nonce == NULL) |
||||
{ |
||||
/* read_attr_value() has generated an error string */ |
||||
return false; |
||||
} |
||||
|
||||
/* Verify immediately that the server used our part of the nonce */ |
||||
if (strncmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("invalid SCRAM response (nonce mismatch)\n")); |
||||
return false; |
||||
} |
||||
|
||||
state->nonce = strdup(nonce); |
||||
if (state->nonce == NULL) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return false; |
||||
} |
||||
|
||||
encoded_salt = read_attr_value(&input, 's', errormessage); |
||||
if (encoded_salt == NULL) |
||||
{ |
||||
/* read_attr_value() has generated an error string */ |
||||
return false; |
||||
} |
||||
state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt))); |
||||
if (state->salt == NULL) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return false; |
||||
} |
||||
state->saltlen = pg_b64_decode(encoded_salt, |
||||
strlen(encoded_salt), |
||||
state->salt); |
||||
|
||||
iterations_str = read_attr_value(&input, 'i', errormessage); |
||||
if (iterations_str == NULL) |
||||
{ |
||||
/* read_attr_value() has generated an error string */ |
||||
return false; |
||||
} |
||||
state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN); |
||||
if (*endptr != '\0' || state->iterations < 1) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("malformed SCRAM message (invalid iteration count)\n")); |
||||
return false; |
||||
} |
||||
|
||||
if (*input != '\0') |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("malformed SCRAM message (garbage at end of server-first-message)\n")); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* Read the final exchange message coming from the server. |
||||
*/ |
||||
static bool |
||||
read_server_final_message(fe_scram_state *state, |
||||
char *input, |
||||
PQExpBuffer errormessage) |
||||
{ |
||||
char *encoded_server_proof; |
||||
int server_proof_len; |
||||
|
||||
state->server_final_message = strdup(input); |
||||
if (!state->server_final_message) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("out of memory\n")); |
||||
return false; |
||||
} |
||||
|
||||
/* Check for error result. */ |
||||
if (*input == 'e') |
||||
{ |
||||
char *errmsg = read_attr_value(&input, 'e', errormessage); |
||||
|
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("error received from server in SASL exchange: %s\n"), |
||||
errmsg); |
||||
return false; |
||||
} |
||||
|
||||
/* Parse the message. */ |
||||
encoded_server_proof = read_attr_value(&input, 'v', errormessage); |
||||
if (encoded_server_proof == NULL) |
||||
{ |
||||
/* read_attr_value() has generated an error message */ |
||||
return false; |
||||
} |
||||
|
||||
if (*input != '\0') |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("malformed SCRAM message (garbage at end of server-final-message)\n")); |
||||
|
||||
server_proof_len = pg_b64_decode(encoded_server_proof, |
||||
strlen(encoded_server_proof), |
||||
state->ServerProof); |
||||
if (server_proof_len != SCRAM_KEY_LEN) |
||||
{ |
||||
printfPQExpBuffer(errormessage, |
||||
libpq_gettext("malformed SCRAM message (invalid server proof)\n")); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* Calculate the client proof, part of the final exchange message sent |
||||
* by the client. |
||||
*/ |
||||
static void |
||||
calculate_client_proof(fe_scram_state *state, |
||||
const char *client_final_message_without_proof, |
||||
uint8 *result) |
||||
{ |
||||
uint8 StoredKey[SCRAM_KEY_LEN]; |
||||
uint8 ClientKey[SCRAM_KEY_LEN]; |
||||
uint8 ClientSignature[SCRAM_KEY_LEN]; |
||||
int i; |
||||
scram_HMAC_ctx ctx; |
||||
|
||||
scram_ClientOrServerKey(state->password, state->salt, state->saltlen, |
||||
state->iterations, SCRAM_CLIENT_KEY_NAME, ClientKey); |
||||
scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey); |
||||
|
||||
scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN); |
||||
scram_HMAC_update(&ctx, |
||||
state->client_first_message_bare, |
||||
strlen(state->client_first_message_bare)); |
||||
scram_HMAC_update(&ctx, ",", 1); |
||||
scram_HMAC_update(&ctx, |
||||
state->server_first_message, |
||||
strlen(state->server_first_message)); |
||||
scram_HMAC_update(&ctx, ",", 1); |
||||
scram_HMAC_update(&ctx, |
||||
client_final_message_without_proof, |
||||
strlen(client_final_message_without_proof)); |
||||
scram_HMAC_final(ClientSignature, &ctx); |
||||
|
||||
for (i = 0; i < SCRAM_KEY_LEN; i++) |
||||
result[i] = ClientKey[i] ^ ClientSignature[i]; |
||||
} |
||||
|
||||
/*
|
||||
* Validate the server proof, received as part of the final exchange message |
||||
* received from the server. |
||||
*/ |
||||
static bool |
||||
verify_server_proof(fe_scram_state *state) |
||||
{ |
||||
uint8 ServerSignature[SCRAM_KEY_LEN]; |
||||
uint8 ServerKey[SCRAM_KEY_LEN]; |
||||
scram_HMAC_ctx ctx; |
||||
|
||||
scram_ClientOrServerKey(state->password, state->salt, state->saltlen, |
||||
state->iterations, SCRAM_SERVER_KEY_NAME, |
||||
ServerKey); |
||||
|
||||
/* calculate ServerSignature */ |
||||
scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN); |
||||
scram_HMAC_update(&ctx, |
||||
state->client_first_message_bare, |
||||
strlen(state->client_first_message_bare)); |
||||
scram_HMAC_update(&ctx, ",", 1); |
||||
scram_HMAC_update(&ctx, |
||||
state->server_first_message, |
||||
strlen(state->server_first_message)); |
||||
scram_HMAC_update(&ctx, ",", 1); |
||||
scram_HMAC_update(&ctx, |
||||
state->client_final_message_without_proof, |
||||
strlen(state->client_final_message_without_proof)); |
||||
scram_HMAC_final(ServerSignature, &ctx); |
||||
|
||||
if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/*
|
||||
* Random number generator. |
||||
*/ |
||||
static bool |
||||
pg_frontend_random(char *dst, int len) |
||||
{ |
||||
#ifdef HAVE_STRONG_RANDOM |
||||
return pg_strong_random(dst, len); |
||||
#else |
||||
int i; |
||||
char *end = dst + len; |
||||
|
||||
static unsigned short seed[3]; |
||||
static int mypid = 0; |
||||
|
||||
pglock_thread(); |
||||
|
||||
if (mypid != getpid()) |
||||
{ |
||||
struct timeval now; |
||||
|
||||
gettimeofday(&now, NULL); |
||||
|
||||
seed[0] = now.tv_sec ^ getpid(); |
||||
seed[1] = (unsigned short) (now.tv_usec); |
||||
seed[2] = (unsigned short) (now.tv_usec >> 16); |
||||
} |
||||
|
||||
for (i = 0; dst < end; i++) |
||||
{ |
||||
uint32 r; |
||||
int j; |
||||
|
||||
/*
|
||||
* pg_jrand48 returns a 32-bit integer. Fill the next 4 bytes from |
||||
* it. |
||||
*/ |
||||
r = (uint32) pg_jrand48(seed); |
||||
|
||||
for (j = 0; j < 4 && dst < end; j++) |
||||
{ |
||||
*(dst++) = (char) (r & 0xFF); |
||||
r >>= 8; |
||||
} |
||||
} |
||||
|
||||
pgunlock_thread(); |
||||
|
||||
return true; |
||||
#endif |
||||
} |
Loading…
Reference in new issue