@ -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,8 +168,38 @@ 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
*
@ -164,13 +207,19 @@ static char *scram_mock_salt(const char *username);
* 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 .
@ -655,6 +724,36 @@ sanitize_char(char c)
return buf ;
}
/*
* 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 .
*
@ -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_typ e )
if ( state - > channel_binding_in_us e )
{
# 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
{