Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 9288d62

Browse files
committed
Support channel binding 'tls-unique' in SCRAM
This is the basic feature set using OpenSSL to support the feature. In order to allow the frontend and the backend to fetch the sent and expected TLS Finished messages, a PG-like API is added to be able to make the interface pluggable for other SSL implementations. This commit also adds a infrastructure to facilitate the addition of future channel binding types as well as libpq parameters to control the SASL mechanism names and channel binding names. Those will be added by upcoming commits. Some tests are added to the SSL test suite to test SCRAM authentication with channel binding. Author: Michael Paquier <michael@paquier.xyz> Reviewed-by: Peter Eisentraut <peter.eisentraut@2ndquadrant.com>
1 parent 611fe7d commit 9288d62

File tree

14 files changed

+555
-112
lines changed

14 files changed

+555
-112
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,10 +1461,11 @@ SELCT 1/0;
14611461

14621462
<para>
14631463
<firstterm>SASL</firstterm> is a framework for authentication in connection-oriented
1464-
protocols. At the moment, <productname>PostgreSQL</productname> implements only one SASL
1465-
authentication mechanism, SCRAM-SHA-256, but more might be added in the
1466-
future. The below steps illustrate how SASL authentication is performed in
1467-
general, while the next subsection gives more details on SCRAM-SHA-256.
1464+
protocols. At the moment, <productname>PostgreSQL</productname> implements two SASL
1465+
authentication mechanisms, SCRAM-SHA-256 and SCRAM-SHA-256-PLUS. More
1466+
might be added in the future. The below steps illustrate how SASL
1467+
authentication is performed in general, while the next subsection gives
1468+
more details on SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
14681469
</para>
14691470

14701471
<procedure>
@@ -1518,9 +1519,10 @@ ErrorMessage.
15181519
<title>SCRAM-SHA-256 authentication</title>
15191520

15201521
<para>
1521-
<firstterm>SCRAM-SHA-256</firstterm> (called just <firstterm>SCRAM</firstterm> from now on) is
1522-
the only implemented SASL mechanism, at the moment. It is described in detail
1523-
in RFC 7677 and RFC 5802.
1522+
The implemented SASL mechanisms at the moment
1523+
are <literal>SCRAM-SHA-256</literal> and its variant with channel
1524+
binding <literal>SCRAM-SHA-256-PLUS</literal>. They are described in
1525+
detail in RFC 7677 and RFC 5802.
15241526
</para>
15251527

15261528
<para>
@@ -1547,7 +1549,10 @@ the password is in.
15471549
</para>
15481550

15491551
<para>
1550-
<firstterm>Channel binding</firstterm> has not been implemented yet.
1552+
<firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with
1553+
SSL support. The SASL mechanism name for SCRAM with channel binding
1554+
is <literal>SCRAM-SHA-256-PLUS</literal>. The only channel binding type
1555+
supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
15511556
</para>
15521557

15531558
<procedure>
@@ -1556,13 +1561,19 @@ the password is in.
15561561
<para>
15571562
The server sends an AuthenticationSASL message. It includes a list of
15581563
SASL authentication mechanisms that the server can accept.
1564+
This will be <literal>SCRAM-SHA-256-PLUS</literal>
1565+
and <literal>SCRAM-SHA-256</literal> if the server is built with SSL
1566+
support, or else just the latter.
15591567
</para>
15601568
</step>
15611569
<step id="scram-client-first">
15621570
<para>
15631571
The client responds by sending a SASLInitialResponse message, which
1564-
indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal>. In the Initial
1565-
Client response field, the message contains the SCRAM
1572+
indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal> or
1573+
<literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either
1574+
mechanism, but for better security it should choose the channel-binding
1575+
variant if it can support it.) In the Initial Client response field,
1576+
the message contains the SCRAM
15661577
<structname>client-first-message</structname>.
15671578
</para>
15681579
</step>

src/backend/libpq/auth-scram.c

Lines changed: 151 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
* by the SASLprep profile, we skip the SASLprep pre-processing and use
1818
* the raw bytes in calculating the hash.
1919
*
20-
* - Channel binding is not supported yet.
21-
*
2220
*
2321
* The password stored in pg_authid consists of the iteration count, salt,
2422
* StoredKey and ServerKey.
@@ -112,6 +110,11 @@ typedef struct
112110

113111
const char *username; /* username from startup packet */
114112

113+
bool ssl_in_use;
114+
const char *tls_finished_message;
115+
size_t tls_finished_len;
116+
char *channel_binding_type;
117+
115118
int iterations;
116119
char *salt; /* base64-encoded */
117120
uint8 StoredKey[SCRAM_KEY_LEN];
@@ -168,14 +171,22 @@ static char *scram_mock_salt(const char *username);
168171
* it will fail, as if an incorrect password was given.
169172
*/
170173
void *
171-
pg_be_scram_init(const char *username, const char *shadow_pass)
174+
pg_be_scram_init(const char *username,
175+
const char *shadow_pass,
176+
bool ssl_in_use,
177+
const char *tls_finished_message,
178+
size_t tls_finished_len)
172179
{
173180
scram_state *state;
174181
bool got_verifier;
175182

176183
state = (scram_state *) palloc0(sizeof(scram_state));
177184
state->state = SCRAM_AUTH_INIT;
178185
state->username = username;
186+
state->ssl_in_use = ssl_in_use;
187+
state->tls_finished_message = tls_finished_message;
188+
state->tls_finished_len = tls_finished_len;
189+
state->channel_binding_type = NULL;
179190

180191
/*
181192
* Parse the stored password verifier.
@@ -773,45 +784,96 @@ read_client_first_message(scram_state *state, char *input)
773784
*------
774785
*/
775786

776-
/* read gs2-cbind-flag */
787+
/*
788+
* Read gs2-cbind-flag. (For details see also RFC 5802 Section 6 "Channel
789+
* Binding".)
790+
*/
777791
switch (*input)
778792
{
779793
case 'n':
780-
/* Client does not support channel binding */
794+
/*
795+
* The client does not support channel binding or has simply
796+
* decided to not use it. In that case just let it go.
797+
*/
798+
input++;
799+
if (*input != ',')
800+
ereport(ERROR,
801+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
802+
errmsg("malformed SCRAM message"),
803+
errdetail("Comma expected, but found character \"%s\".",
804+
sanitize_char(*input))));
781805
input++;
782806
break;
783807
case 'y':
784-
/* Client supports channel binding, but we're not doing it today */
808+
/*
809+
* The client supports channel binding and thinks that the server
810+
* does not. In this case, the server must fail authentication if
811+
* it supports channel binding, which in this implementation is
812+
* the case if a connection is using SSL.
813+
*/
814+
if (state->ssl_in_use)
815+
ereport(ERROR,
816+
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
817+
errmsg("SCRAM channel binding negotiation error"),
818+
errdetail("The client supports SCRAM channel binding but thinks the server does not. "
819+
"However, this server does support channel binding.")));
820+
input++;
821+
if (*input != ',')
822+
ereport(ERROR,
823+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
824+
errmsg("malformed SCRAM message"),
825+
errdetail("Comma expected, but found character \"%s\".",
826+
sanitize_char(*input))));
785827
input++;
786828
break;
787829
case 'p':
788-
789830
/*
790-
* Client requires channel binding. We don't support it.
791-
*
792-
* RFC 5802 specifies a particular error code,
793-
* e=server-does-support-channel-binding, for this. But it can
794-
* only be sent in the server-final message, and we don't want to
795-
* go through the motions of the authentication, knowing it will
796-
* fail, just to send that error message.
831+
* The client requires channel binding. Channel binding type
832+
* follows, e.g., "p=tls-unique".
797833
*/
798-
ereport(ERROR,
799-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
800-
errmsg("client requires SCRAM channel binding, but it is not supported")));
834+
{
835+
char *channel_binding_type;
836+
837+
if (!state->ssl_in_use)
838+
{
839+
/*
840+
* Without SSL, we don't support channel binding.
841+
*
842+
* RFC 5802 specifies a particular error code,
843+
* e=server-does-support-channel-binding, for this. But
844+
* it can only be sent in the server-final message, and we
845+
* don't want to go through the motions of the
846+
* authentication, knowing it will fail, just to send that
847+
* error message.
848+
*/
849+
ereport(ERROR,
850+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
851+
errmsg("client requires SCRAM channel binding, but it is not supported")));
852+
}
853+
854+
/*
855+
* Read value provided by client; only tls-unique is supported
856+
* for now. (It is not safe to print the name of an
857+
* unsupported binding type in the error message. Pranksters
858+
* could print arbitrary strings into the log that way.)
859+
*/
860+
channel_binding_type = read_attr_value(&input, 'p');
861+
if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0)
862+
ereport(ERROR,
863+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
864+
(errmsg("unsupported SCRAM channel-binding type"))));
865+
866+
/* Save the name for handling of subsequent messages */
867+
state->channel_binding_type = pstrdup(channel_binding_type);
868+
}
869+
break;
801870
default:
802871
ereport(ERROR,
803872
(errcode(ERRCODE_PROTOCOL_VIOLATION),
804873
errmsg("malformed SCRAM message"),
805874
errdetail("Unexpected channel-binding flag \"%s\".",
806875
sanitize_char(*input))));
807876
}
808-
if (*input != ',')
809-
ereport(ERROR,
810-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
811-
errmsg("malformed SCRAM message"),
812-
errdetail("Comma expected, but found character \"%s\".",
813-
sanitize_char(*input))));
814-
input++;
815877

816878
/*
817879
* Forbid optional authzid (authorization identity). We don't support it.
@@ -1032,14 +1094,73 @@ read_client_final_message(scram_state *state, char *input)
10321094
*/
10331095

10341096
/*
1035-
* Read channel-binding. We don't support channel binding, so it's
1036-
* expected to always be "biws", which is "n,,", base64-encoded.
1097+
* Read channel binding. This repeats the channel-binding flags and is
1098+
* then followed by the actual binding data depending on the type.
10371099
*/
10381100
channel_binding = read_attr_value(&p, 'c');
1039-
if (strcmp(channel_binding, "biws") != 0)
1040-
ereport(ERROR,
1041-
(errcode(ERRCODE_PROTOCOL_VIOLATION),
1042-
(errmsg("unexpected SCRAM channel-binding attribute in client-final-message"))));
1101+
if (state->channel_binding_type)
1102+
{
1103+
const char *cbind_data = NULL;
1104+
size_t cbind_data_len = 0;
1105+
size_t cbind_header_len;
1106+
char *cbind_input;
1107+
size_t cbind_input_len;
1108+
char *b64_message;
1109+
int b64_message_len;
1110+
1111+
/*
1112+
* Fetch data appropriate for channel binding type
1113+
*/
1114+
if (strcmp(state->channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) == 0)
1115+
{
1116+
cbind_data = state->tls_finished_message;
1117+
cbind_data_len = state->tls_finished_len;
1118+
}
1119+
else
1120+
{
1121+
/* should not happen */
1122+
elog(ERROR, "invalid channel binding type");
1123+
}
1124+
1125+
/* should not happen */
1126+
if (cbind_data == NULL || cbind_data_len == 0)
1127+
elog(ERROR, "empty channel binding data for channel binding type \"%s\"",
1128+
state->channel_binding_type);
1129+
1130+
cbind_header_len = 4 + strlen(state->channel_binding_type); /* p=type,, */
1131+
cbind_input_len = cbind_header_len + cbind_data_len;
1132+
cbind_input = palloc(cbind_input_len);
1133+
snprintf(cbind_input, cbind_input_len, "p=%s,,", state->channel_binding_type);
1134+
memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len);
1135+
1136+
b64_message = palloc(pg_b64_enc_len(cbind_input_len) + 1);
1137+
b64_message_len = pg_b64_encode(cbind_input, cbind_input_len,
1138+
b64_message);
1139+
b64_message[b64_message_len] = '\0';
1140+
1141+
/*
1142+
* Compare the value sent by the client with the value expected by
1143+
* the server.
1144+
*/
1145+
if (strcmp(channel_binding, b64_message) != 0)
1146+
ereport(ERROR,
1147+
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
1148+
(errmsg("SCRAM channel binding check failed"))));
1149+
}
1150+
else
1151+
{
1152+
/*
1153+
* If we are not using channel binding, the binding data is expected
1154+
* to always be "biws", which is "n,," base64-encoded, or "eSws",
1155+
* which is "y,,".
1156+
*/
1157+
if (strcmp(channel_binding, "biws") != 0 &&
1158+
strcmp(channel_binding, "eSws") != 0)
1159+
ereport(ERROR,
1160+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
1161+
(errmsg("unexpected SCRAM channel-binding attribute in client-final-message"))));
1162+
}
1163+
10431164
state->client_final_nonce = read_attr_value(&p, 'r');
10441165

10451166
/* ignore optional extensions */

src/backend/libpq/auth.c

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,8 @@ CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
860860
static int
861861
CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
862862
{
863+
char *sasl_mechs;
864+
char *p;
863865
int mtype;
864866
StringInfoData buf;
865867
void *scram_opaq;
@@ -869,6 +871,8 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
869871
int inputlen;
870872
int result;
871873
bool initial;
874+
char *tls_finished = NULL;
875+
size_t tls_finished_len = 0;
872876

873877
/*
874878
* SASL auth is not supported for protocol versions before 3, because it
@@ -885,12 +889,39 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
885889

886890
/*
887891
* Send the SASL authentication request to user. It includes the list of
888-
* authentication mechanisms (which is trivial, because we only support
889-
* SCRAM-SHA-256 at the moment). The extra "\0" is for an empty string to
890-
* terminate the list.
892+
* authentication mechanisms that are supported. The order of mechanisms
893+
* is advertised in decreasing order of importance. So the
894+
* channel-binding variants go first, if they are supported. Channel
895+
* binding is only supported in SSL builds.
891896
*/
892-
sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME "\0",
893-
strlen(SCRAM_SHA256_NAME) + 2);
897+
sasl_mechs = palloc(strlen(SCRAM_SHA256_PLUS_NAME) +
898+
strlen(SCRAM_SHA256_NAME) + 3);
899+
p = sasl_mechs;
900+
901+
if (port->ssl_in_use)
902+
{
903+
strcpy(p, SCRAM_SHA256_PLUS_NAME);
904+
p += strlen(SCRAM_SHA256_PLUS_NAME) + 1;
905+
}
906+
907+
strcpy(p, SCRAM_SHA256_NAME);
908+
p += strlen(SCRAM_SHA256_NAME) + 1;
909+
910+
/* Put another '\0' to mark that list is finished. */
911+
p[0] = '\0';
912+
913+
sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs, p - sasl_mechs + 1);
914+
pfree(sasl_mechs);
915+
916+
#ifdef USE_SSL
917+
/*
918+
* Get data for channel binding.
919+
*/
920+
if (port->ssl_in_use)
921+
{
922+
tls_finished = be_tls_get_peer_finished(port, &tls_finished_len);
923+
}
924+
#endif
894925

895926
/*
896927
* Initialize the status tracker for message exchanges.
@@ -903,7 +934,11 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
903934
* This is because we don't want to reveal to an attacker what usernames
904935
* are valid, nor which users have a valid password.
905936
*/
906-
scram_opaq = pg_be_scram_init(port->user_name, shadow_pass);
937+
scram_opaq = pg_be_scram_init(port->user_name,
938+
shadow_pass,
939+
port->ssl_in_use,
940+
tls_finished,
941+
tls_finished_len);
907942

908943
/*
909944
* Loop through SASL message exchange. This exchange can consist of
@@ -951,12 +986,9 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
951986
{
952987
const char *selected_mech;
953988

954-
/*
955-
* We only support SCRAM-SHA-256 at the moment, so anything else
956-
* is an error.
957-
*/
958989
selected_mech = pq_getmsgrawstring(&buf);
959-
if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0)
990+
if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0 &&
991+
strcmp(selected_mech, SCRAM_SHA256_PLUS_NAME) != 0)
960992
{
961993
ereport(ERROR,
962994
(errcode(ERRCODE_PROTOCOL_VIOLATION),

0 commit comments

Comments
 (0)