diff options
Diffstat (limited to 'src/backend/libpq/auth-scram.c')
-rw-r--r-- | src/backend/libpq/auth-scram.c | 223 |
1 files changed, 151 insertions, 72 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index 48eb531d0f0..8fbad53fa82 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -17,6 +17,19 @@ * by the SASLprep profile, we skip the SASLprep pre-processing and use * the raw bytes in calculating the hash. * + * - If channel binding is used, the channel binding type is always + * "tls-server-end-point". The spec says the default is "tls-unique" + * (RFC 5802, section 6.1. Default Channel Binding), but there are some + * problems with that. Firstly, not all SSL libraries provide an API to + * get the TLS Finished message, required to use "tls-unique". Secondly, + * "tls-unique" is not specified for TLS v1.3, and as of this writing, + * it's not clear if there will be a replacement. We could support both + * "tls-server-end-point" and "tls-unique", but for our use case, + * "tls-unique" doesn't really have any advantages. The main advantage + * of "tls-unique" would be that it works even if the server doesn't + * have a certificate, but PostgreSQL requires a server certificate + * whenever SSL is used, anyway. + * * * The password stored in pg_authid consists of the iteration count, salt, * StoredKey and ServerKey. @@ -111,8 +124,7 @@ typedef struct const char *username; /* username from startup packet */ Port *port; - char cbind_flag; - char *channel_binding_type; + bool channel_binding_in_use; int iterations; char *salt; /* base64-encoded */ @@ -120,6 +132,7 @@ typedef struct uint8 ServerKey[SCRAM_KEY_LEN]; /* Fields of the first message from client */ + char cbind_flag; char *client_first_message_bare; char *client_username; char *client_nonce; @@ -155,22 +168,58 @@ static void mock_scram_verifier(const char *username, int *iterations, char **salt, uint8 *stored_key, uint8 *server_key); static bool is_scram_printable(char *p); static char *sanitize_char(char c); +static char *sanitize_str(const char *s); static char *scram_mock_salt(const char *username); /* + * pg_be_scram_get_mechanisms + * + * Get a list of SASL mechanisms that this module supports. + * + * For the convenience of building the FE/BE packet that lists the + * mechanisms, the names are appended to the given StringInfo buffer, + * separated by '\0' bytes. + */ +void +pg_be_scram_get_mechanisms(Port *port, StringInfo buf) +{ + /* + * Advertise the mechanisms in decreasing order of importance. So the + * channel-binding variants go first, if they are supported. Channel + * binding is only supported with SSL, and only if the SSL implementation + * has a function to get the certificate's hash. + */ +#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH + if (port->ssl_in_use) + { + appendStringInfoString(buf, SCRAM_SHA_256_PLUS_NAME); + appendStringInfoChar(buf, '\0'); + } +#endif + appendStringInfoString(buf, SCRAM_SHA_256_NAME); + appendStringInfoChar(buf, '\0'); +} + +/* * pg_be_scram_init * * Initialize a new SCRAM authentication exchange status tracker. This * needs to be called before doing any exchange. It will be filled later * after the beginning of the exchange with verifier data. * - * 'username' is the username provided by the client in the startup message. + * 'selected_mech' identifies the SASL mechanism that the client selected. + * It should be one of the mechanisms that we support, as returned by + * pg_be_scram_get_mechanisms(). + * * 'shadow_pass' is the role's password verifier, from pg_authid.rolpassword. - * If 'shadow_pass' is NULL, we still perform an authentication exchange, but - * it will fail, as if an incorrect password was given. + * The username was provided by the client in the startup message, and is + * available in port->user_name. If 'shadow_pass' is NULL, we still perform + * an authentication exchange, but it will fail, as if an incorrect password + * was given. */ void * pg_be_scram_init(Port *port, + const char *selected_mech, const char *shadow_pass) { scram_state *state; @@ -179,7 +228,27 @@ pg_be_scram_init(Port *port, state = (scram_state *) palloc0(sizeof(scram_state)); state->port = port; state->state = SCRAM_AUTH_INIT; - state->channel_binding_type = NULL; + + /* + * Parse the selected mechanism. + * + * Note that if we don't support channel binding, either because the SSL + * implementation doesn't support it or we're not using SSL at all, we + * would not have advertised the PLUS variant in the first place. If the + * client nevertheless tries to select it, it's a protocol violation like + * selecting any other SASL mechanism we don't support. + */ +#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH + if (strcmp(selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 && port->ssl_in_use) + state->channel_binding_in_use = true; + else +#endif + if (strcmp(selected_mech, SCRAM_SHA_256_NAME) == 0) + state->channel_binding_in_use = false; + else + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("client selected an invalid SASL authentication mechanism"))); /* * Parse the stored password verifier. @@ -656,6 +725,36 @@ sanitize_char(char c) } /* + * Convert an arbitrary string to printable form, for error messages. + * + * Anything that's not a printable ASCII character is replaced with + * '?', and the string is truncated at 30 characters. + * + * The returned pointer points to a static buffer. + */ +static char * +sanitize_str(const char *s) +{ + static char buf[30 + 1]; + int i; + + for (i = 0; i < sizeof(buf) - 1; i++) + { + char c = s[i]; + + if (c == '\0') + break; + + if (c >= 0x21 && c <= 0x7E) + buf[i] = c; + else + buf[i] = '?'; + } + buf[i] = '\0'; + return buf; +} + +/* * Read the next attribute and value in a SCRAM exchange message. * * Returns NULL if there is attribute. @@ -715,6 +814,8 @@ read_any_attr(char **input, char *attr_p) static void read_client_first_message(scram_state *state, char *input) { + char *channel_binding_type; + input = pstrdup(input); /*------ @@ -790,6 +891,12 @@ read_client_first_message(scram_state *state, char *input) * The client does not support channel binding or has simply * decided to not use it. In that case just let it go. */ + if (state->channel_binding_in_use) + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("malformed SCRAM message"), + errdetail("The client selected SCRAM-SHA-256-PLUS, but the SCRAM message does not include channel binding data."))); + input++; if (*input != ',') ereport(ERROR, @@ -804,15 +911,22 @@ read_client_first_message(scram_state *state, char *input) /* * The client supports channel binding and thinks that the server * does not. In this case, the server must fail authentication if - * it supports channel binding, which in this implementation is - * the case if a connection is using SSL. + * it supports channel binding. */ + if (state->channel_binding_in_use) + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("malformed SCRAM message"), + errdetail("The client selected SCRAM-SHA-256-PLUS, but the SCRAM message does not include channel binding data."))); + +#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH if (state->port->ssl_in_use) ereport(ERROR, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("SCRAM channel binding negotiation error"), errdetail("The client supports SCRAM channel binding but thinks the server does not. " "However, this server does support channel binding."))); +#endif input++; if (*input != ',') ereport(ERROR, @@ -826,44 +940,25 @@ read_client_first_message(scram_state *state, char *input) /* * The client requires channel binding. Channel binding type - * follows, e.g., "p=tls-unique". + * follows, e.g., "p=tls-server-end-point". */ - { - char *channel_binding_type; - - if (!state->port->ssl_in_use) - { - /* - * Without SSL, we don't support channel binding. - * - * RFC 5802 specifies a particular error code, - * e=server-does-support-channel-binding, for this. But - * it can only be sent in the server-final message, and we - * don't want to go through the motions of the - * authentication, knowing it will fail, just to send that - * error message. - */ - ereport(ERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("client requires SCRAM channel binding, but it is not supported"))); - } + if (!state->channel_binding_in_use) + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("malformed SCRAM message"), + errdetail("The client selected SCRAM-SHA-256 without channel binding, but the SCRAM message includes channel binding data."))); - /* - * Read value provided by client. (It is not safe to print - * the name of an unsupported binding type in the error - * message. Pranksters could print arbitrary strings into the - * log that way.) - */ - channel_binding_type = read_attr_value(&input, 'p'); - if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0 && - strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_END_POINT) != 0) - ereport(ERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - (errmsg("unsupported SCRAM channel-binding type")))); - - /* Save the name for handling of subsequent messages */ - state->channel_binding_type = pstrdup(channel_binding_type); - } + channel_binding_type = read_attr_value(&input, 'p'); + + /* + * The only channel binding type we support is + * tls-server-end-point. + */ + if (strcmp(channel_binding_type, "tls-server-end-point") != 0) + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + (errmsg("unsupported SCRAM channel-binding type \"%s\"", + sanitize_str(channel_binding_type))))); break; default: ereport(ERROR, @@ -1096,8 +1191,9 @@ read_client_final_message(scram_state *state, char *input) * then followed by the actual binding data depending on the type. */ channel_binding = read_attr_value(&p, 'c'); - if (state->channel_binding_type) + if (state->channel_binding_in_use) { +#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH const char *cbind_data = NULL; size_t cbind_data_len = 0; size_t cbind_header_len; @@ -1108,39 +1204,18 @@ read_client_final_message(scram_state *state, char *input) Assert(state->cbind_flag == 'p'); - /* - * Fetch data appropriate for channel binding type - */ - if (strcmp(state->channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) == 0) - { -#ifdef USE_SSL - cbind_data = be_tls_get_peer_finished(state->port, &cbind_data_len); -#endif - } - else if (strcmp(state->channel_binding_type, - SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0) - { - /* Fetch hash data of server's SSL certificate */ -#ifdef USE_SSL - cbind_data = be_tls_get_certificate_hash(state->port, - &cbind_data_len); -#endif - } - else - { - /* should not happen */ - elog(ERROR, "invalid channel binding type"); - } + /* Fetch hash data of server's SSL certificate */ + cbind_data = be_tls_get_certificate_hash(state->port, + &cbind_data_len); /* should not happen */ if (cbind_data == NULL || cbind_data_len == 0) - elog(ERROR, "empty channel binding data for channel binding type \"%s\"", - state->channel_binding_type); + elog(ERROR, "could not get server certificate hash"); - cbind_header_len = 4 + strlen(state->channel_binding_type); /* p=type,, */ + cbind_header_len = strlen("p=tls-server-end-point,,"); /* p=type,, */ cbind_input_len = cbind_header_len + cbind_data_len; cbind_input = palloc(cbind_input_len); - snprintf(cbind_input, cbind_input_len, "p=%s,,", state->channel_binding_type); + snprintf(cbind_input, cbind_input_len, "p=tls-server-end-point,,"); memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len); b64_message = palloc(pg_b64_enc_len(cbind_input_len) + 1); @@ -1156,6 +1231,10 @@ read_client_final_message(scram_state *state, char *input) ereport(ERROR, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), (errmsg("SCRAM channel binding check failed")))); +#else + /* shouldn't happen, because we checked this earlier already */ + elog(ERROR, "channel binding not supported by this build"); +#endif } else { |