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

Commit 9fd8557

Browse files
committed
Refactor SASL code with a generic interface for its mechanisms
The code of SCRAM and SASL have been tightly linked together since SCRAM exists in the core code, making hard to apprehend the addition of new SASL mechanisms, but these are by design different facilities, with SCRAM being an option for SASL. This refactors the code related to both so as the backend and the frontend use a set of callbacks for SASL mechanisms, documenting while on it what is expected by anybody adding a new SASL mechanism. The separation between both layers is neat, using two sets of callbacks for the frontend and the backend to mark the frontier between both facilities. The shape of the callbacks is now directly inspired from the routines used by SCRAM, so the code change is straight-forward, and the SASL code is moved into its own set of files. These will likely change depending on how and if new SASL mechanisms get added in the future. Author: Jacob Champion Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/3d2a6f5d50e741117d6baf83eb67ebf1a8a35a11.camel@vmware.com
1 parent 955b3e0 commit 9fd8557

File tree

14 files changed

+552
-222
lines changed

14 files changed

+552
-222
lines changed

src/backend/libpq/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
1515
# be-fsstubs is here for historical reasons, probably belongs elsewhere
1616

1717
OBJS = \
18+
auth-sasl.o \
1819
auth-scram.o \
1920
auth.o \
2021
be-fsstubs.o \

src/backend/libpq/auth-sasl.c

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* auth-sasl.c
4+
* Routines to handle authentication via SASL
5+
*
6+
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7+
* Portions Copyright (c) 1994, Regents of the University of California
8+
*
9+
*
10+
* IDENTIFICATION
11+
* src/backend/libpq/auth-sasl.c
12+
*
13+
*-------------------------------------------------------------------------
14+
*/
15+
16+
#include "postgres.h"
17+
18+
#include "libpq/auth.h"
19+
#include "libpq/libpq.h"
20+
#include "libpq/pqformat.h"
21+
#include "libpq/sasl.h"
22+
23+
/*
24+
* Maximum accepted size of SASL messages.
25+
*
26+
* The messages that the server or libpq generate are much smaller than this,
27+
* but have some headroom.
28+
*/
29+
#define PG_MAX_SASL_MESSAGE_LENGTH 1024
30+
31+
/*
32+
* Perform a SASL exchange with a libpq client, using a specific mechanism
33+
* implementation.
34+
*
35+
* shadow_pass is an optional pointer to the stored secret of the role
36+
* authenticated, from pg_authid.rolpassword. For mechanisms that use
37+
* shadowed passwords, a NULL pointer here means that an entry could not
38+
* be found for the role (or the user does not exist), and the mechanism
39+
* should fail the authentication exchange.
40+
*
41+
* Mechanisms must take care not to reveal to the client that a user entry
42+
* does not exist; ideally, the external failure mode is identical to that
43+
* of an incorrect password. Mechanisms may instead use the logdetail
44+
* output parameter to internally differentiate between failure cases and
45+
* assist debugging by the server admin.
46+
*
47+
* A mechanism is not required to utilize a shadow entry, or even a password
48+
* system at all; for these cases, shadow_pass may be ignored and the caller
49+
* should just pass NULL.
50+
*/
51+
int
52+
CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
53+
char **logdetail)
54+
{
55+
StringInfoData sasl_mechs;
56+
int mtype;
57+
StringInfoData buf;
58+
void *opaq = NULL;
59+
char *output = NULL;
60+
int outputlen = 0;
61+
const char *input;
62+
int inputlen;
63+
int result;
64+
bool initial;
65+
66+
/*
67+
* Send the SASL authentication request to user. It includes the list of
68+
* authentication mechanisms that are supported.
69+
*/
70+
initStringInfo(&sasl_mechs);
71+
72+
mech->get_mechanisms(port, &sasl_mechs);
73+
/* Put another '\0' to mark that list is finished. */
74+
appendStringInfoChar(&sasl_mechs, '\0');
75+
76+
sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
77+
pfree(sasl_mechs.data);
78+
79+
/*
80+
* Loop through SASL message exchange. This exchange can consist of
81+
* multiple messages sent in both directions. First message is always
82+
* from the client. All messages from client to server are password
83+
* packets (type 'p').
84+
*/
85+
initial = true;
86+
do
87+
{
88+
pq_startmsgread();
89+
mtype = pq_getbyte();
90+
if (mtype != 'p')
91+
{
92+
/* Only log error if client didn't disconnect. */
93+
if (mtype != EOF)
94+
{
95+
ereport(ERROR,
96+
(errcode(ERRCODE_PROTOCOL_VIOLATION),
97+
errmsg("expected SASL response, got message type %d",
98+
mtype)));
99+
}
100+
else
101+
return STATUS_EOF;
102+
}
103+
104+
/* Get the actual SASL message */
105+
initStringInfo(&buf);
106+
if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
107+
{
108+
/* EOF - pq_getmessage already logged error */
109+
pfree(buf.data);
110+
return STATUS_ERROR;
111+
}
112+
113+
elog(DEBUG4, "processing received SASL response of length %d", buf.len);
114+
115+
/*
116+
* The first SASLInitialResponse message is different from the others.
117+
* It indicates which SASL mechanism the client selected, and contains
118+
* an optional Initial Client Response payload. The subsequent
119+
* SASLResponse messages contain just the SASL payload.
120+
*/
121+
if (initial)
122+
{
123+
const char *selected_mech;
124+
125+
selected_mech = pq_getmsgrawstring(&buf);
126+
127+
/*
128+
* Initialize the status tracker for message exchanges.
129+
*
130+
* If the user doesn't exist, or doesn't have a valid password, or
131+
* it's expired, we still go through the motions of SASL
132+
* authentication, but tell the authentication method that the
133+
* authentication is "doomed". That is, it's going to fail, no
134+
* matter what.
135+
*
136+
* This is because we don't want to reveal to an attacker what
137+
* usernames are valid, nor which users have a valid password.
138+
*/
139+
opaq = mech->init(port, selected_mech, shadow_pass);
140+
141+
inputlen = pq_getmsgint(&buf, 4);
142+
if (inputlen == -1)
143+
input = NULL;
144+
else
145+
input = pq_getmsgbytes(&buf, inputlen);
146+
147+
initial = false;
148+
}
149+
else
150+
{
151+
inputlen = buf.len;
152+
input = pq_getmsgbytes(&buf, buf.len);
153+
}
154+
pq_getmsgend(&buf);
155+
156+
/*
157+
* The StringInfo guarantees that there's a \0 byte after the
158+
* response.
159+
*/
160+
Assert(input == NULL || input[inputlen] == '\0');
161+
162+
/*
163+
* Hand the incoming message to the mechanism implementation.
164+
*/
165+
result = mech->exchange(opaq, input, inputlen,
166+
&output, &outputlen,
167+
logdetail);
168+
169+
/* input buffer no longer used */
170+
pfree(buf.data);
171+
172+
if (output)
173+
{
174+
/*
175+
* Negotiation generated data to be sent to the client.
176+
*/
177+
elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
178+
179+
if (result == PG_SASL_EXCHANGE_SUCCESS)
180+
sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
181+
else
182+
sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
183+
184+
pfree(output);
185+
}
186+
} while (result == PG_SASL_EXCHANGE_CONTINUE);
187+
188+
/* Oops, Something bad happened */
189+
if (result != PG_SASL_EXCHANGE_SUCCESS)
190+
{
191+
return STATUS_ERROR;
192+
}
193+
194+
return STATUS_OK;
195+
}

src/backend/libpq/auth-scram.c

+30-21
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,25 @@
101101
#include "common/sha2.h"
102102
#include "libpq/auth.h"
103103
#include "libpq/crypt.h"
104+
#include "libpq/sasl.h"
104105
#include "libpq/scram.h"
105106
#include "miscadmin.h"
106107
#include "utils/builtins.h"
107108
#include "utils/timestamp.h"
108109

110+
static void scram_get_mechanisms(Port *port, StringInfo buf);
111+
static void *scram_init(Port *port, const char *selected_mech,
112+
const char *shadow_pass);
113+
static int scram_exchange(void *opaq, const char *input, int inputlen,
114+
char **output, int *outputlen, char **logdetail);
115+
116+
/* Mechanism declaration */
117+
const pg_be_sasl_mech pg_be_scram_mech = {
118+
scram_get_mechanisms,
119+
scram_init,
120+
scram_exchange
121+
};
122+
109123
/*
110124
* Status data for a SCRAM authentication exchange. This should be kept
111125
* internal to this file.
@@ -170,16 +184,14 @@ static char *sanitize_str(const char *s);
170184
static char *scram_mock_salt(const char *username);
171185

172186
/*
173-
* pg_be_scram_get_mechanisms
174-
*
175187
* Get a list of SASL mechanisms that this module supports.
176188
*
177189
* For the convenience of building the FE/BE packet that lists the
178190
* mechanisms, the names are appended to the given StringInfo buffer,
179191
* separated by '\0' bytes.
180192
*/
181-
void
182-
pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
193+
static void
194+
scram_get_mechanisms(Port *port, StringInfo buf)
183195
{
184196
/*
185197
* Advertise the mechanisms in decreasing order of importance. So the
@@ -199,26 +211,22 @@ pg_be_scram_get_mechanisms(Port *port, StringInfo buf)
199211
}
200212

201213
/*
202-
* pg_be_scram_init
203-
*
204214
* Initialize a new SCRAM authentication exchange status tracker. This
205215
* needs to be called before doing any exchange. It will be filled later
206216
* after the beginning of the exchange with authentication information.
207217
*
208218
* 'selected_mech' identifies the SASL mechanism that the client selected.
209219
* It should be one of the mechanisms that we support, as returned by
210-
* pg_be_scram_get_mechanisms().
220+
* scram_get_mechanisms().
211221
*
212222
* 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
213223
* The username was provided by the client in the startup message, and is
214224
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
215225
* an authentication exchange, but it will fail, as if an incorrect password
216226
* was given.
217227
*/
218-
void *
219-
pg_be_scram_init(Port *port,
220-
const char *selected_mech,
221-
const char *shadow_pass)
228+
static void *
229+
scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
222230
{
223231
scram_state *state;
224232
bool got_secret;
@@ -325,9 +333,9 @@ pg_be_scram_init(Port *port,
325333
* string at *logdetail that will be sent to the postmaster log (but not
326334
* the client).
327335
*/
328-
int
329-
pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
330-
char **output, int *outputlen, char **logdetail)
336+
static int
337+
scram_exchange(void *opaq, const char *input, int inputlen,
338+
char **output, int *outputlen, char **logdetail)
331339
{
332340
scram_state *state = (scram_state *) opaq;
333341
int result;
@@ -346,7 +354,7 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
346354

347355
*output = pstrdup("");
348356
*outputlen = 0;
349-
return SASL_EXCHANGE_CONTINUE;
357+
return PG_SASL_EXCHANGE_CONTINUE;
350358
}
351359

352360
/*
@@ -379,7 +387,7 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
379387
*output = build_server_first_message(state);
380388

381389
state->state = SCRAM_AUTH_SALT_SENT;
382-
result = SASL_EXCHANGE_CONTINUE;
390+
result = PG_SASL_EXCHANGE_CONTINUE;
383391
break;
384392

385393
case SCRAM_AUTH_SALT_SENT:
@@ -408,7 +416,8 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
408416
* erroring out in an application-specific way. We choose to do
409417
* the latter, so that the error message for invalid password is
410418
* the same for all authentication methods. The caller will call
411-
* ereport(), when we return SASL_EXCHANGE_FAILURE with no output.
419+
* ereport(), when we return PG_SASL_EXCHANGE_FAILURE with no
420+
* output.
412421
*
413422
* NB: the order of these checks is intentional. We calculate the
414423
* client proof even in a mock authentication, even though it's
@@ -417,24 +426,24 @@ pg_be_scram_exchange(void *opaq, const char *input, int inputlen,
417426
*/
418427
if (!verify_client_proof(state) || state->doomed)
419428
{
420-
result = SASL_EXCHANGE_FAILURE;
429+
result = PG_SASL_EXCHANGE_FAILURE;
421430
break;
422431
}
423432

424433
/* Build final message for client */
425434
*output = build_server_final_message(state);
426435

427436
/* Success! */
428-
result = SASL_EXCHANGE_SUCCESS;
437+
result = PG_SASL_EXCHANGE_SUCCESS;
429438
state->state = SCRAM_AUTH_FINISHED;
430439
break;
431440

432441
default:
433442
elog(ERROR, "invalid SCRAM exchange state");
434-
result = SASL_EXCHANGE_FAILURE;
443+
result = PG_SASL_EXCHANGE_FAILURE;
435444
}
436445

437-
if (result == SASL_EXCHANGE_FAILURE && state->logdetail && logdetail)
446+
if (result == PG_SASL_EXCHANGE_FAILURE && state->logdetail && logdetail)
438447
*logdetail = state->logdetail;
439448

440449
if (*output)

0 commit comments

Comments
 (0)