pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feed From: Heikki Linnakangas <hlinnaka@iki.fi>
To: pgsql-hackers <pgsql-hackers@postgresql.org>
Subject: WIP: SCRAM authentication
Date: Mon, 30 Mar 2015 13:52:46 +0300
Message-ID: <55192AFE.6080106@iki.fi> (raw )
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>
There have been numerous threads on replacing our MD5 authentication
method, so I started hacking on that to see what it might look like.
Just to be clear, this is 9.6 material. Attached is a WIP patch series
that adds support for SCRAM. There's no need to look at the details yet,
but it demonstrates what the protocol changes and the code structure
would be like.
I'm not wedded to SCRAM - SRP or JPAKE or something else might be
better. But replacing the algorithm, or adding more of them, should be
straightforward with this.
There is no negotiation of the authentication mechanism. SCRAM is just
added as a new one, alongside all the existing ones. If the server
requests SCRAM authentication, but the client doesn't support it, the
attempt will fail. We might want to do something about that, to make the
transition easier, but it's an orthogonal feature and not absolutely
required.
There are four patches in the series. The first two are just
refactoring: moving the SHA-1 implementation from pgcrypto to
src/common, and some refactoring in src/backend/auth.c that IMHO would
make sense anyway.
Patches three and four are the interesting ones:
3. Allow storing multiple verifiers in pg_authid
------------------------------------------------
Replace the pg_authid.rolpassword text field with an array, and rename
it to 'rolverifiers'. This allows storing multiple password hashes: an
MD5 hash for MD5 authentication, and a SCRAM salt and stored key for
SCRAM authentication, etc. Each element in the array is a string that
begins with the method's name. For example "md5:<MD5 hash>", or
"password:<plaintext>".
For dump/reload, and for clients that wish to create the hashes in the
client-side, there is a new option to CREATE/ALTER USER commands:
PASSWORD VERIFIERS '{ ... }', that allows replacing the array.
The old "ENCRYPTED/UNENCRYPTED PASSWORD 'foo'" options are still
supported for backwards-compatibility, but it's not clear what it should
mean now.
TODO:
* Password-checking hook needs to be redesigned, to allow for more kinds
of hashes.
* With "CREATE USER PASSWORD 'foo'", which hashes/verifiers should be
generated by default? We currently have a boolean password_encryption
setting for that. Needs to be a list.
4. Implement SCRAM
------------------
The protocol and the code is structured so that it would be fairly easy
to add more built-in SASL mechanisms, or to use a SASL library to
provide more. But for now I'm focusing on adding exactly one new
built-in mechanism, to replace MD5 in the long term.
In the protocol, there is a new AuthenticationSASL message, alongside
the existing AuthenticationMD5, AuthenticationSSPI etc. The
AuthenticationSASL message contains the name of the SASL mechanism used
("SCRAM-SHA-1"). Just like in the GSSAPI/SSPI authentication, a number
of PasswordMessage and AuthenticationSASLContinue messages are exchanged
after that, carrying the data specified by the SCRAM spec, until the
authentication succeeds (or not).
TODO:
* Per the SCRAM specification, the client sends the username in the
handshake. But in the FE/BE protocol, we've already sent it in the
startup packet. In the patch, libpq always sends an empty username in
the SCRAM exchange, and the username from the startup packet is what
matters. We could also require it to be the same, but in SCRAM the
username to be UTF-8 encoded, while in PostgreSQL the username can be in
any encoding. That is a source of annoyance in itself, as it's not
well-defined in PostgreSQL which encoding to use when sending a username
to the server. But I don't want to try fixing that in this patch, so it
seems easiest to just require the username to be empty.
* Need a source of randomness in client, to generate random nonces used
in the handshake. The SCRAM specification is not explicit about it, but
I believe it doesn't need to be unpredictable, as long as a different
nonce is used for each authentication.
* The client does not authenticate the server, even though the SCRAM
protocol allows that. The client does verify the proof the server sends,
but nothing stops a malicious server that's impersonating the real
server from not requesting SCRAM authentication in the first place. It
could just send AuthenticationOK without any authentication at all. To
take advantage of the server authentication, we'll need to add something
similar to the "sslmode=verify-ca" option in the client. In that mode,
the client should refuse the connection if the server doesn't request
SCRAM authentication (or some other future authentication mechanism that
authenticates the server to the client).
* Channel binding is not implemented. Not essential, but would be nice
to have. Together with the new client option mentioned in the previous
point, it would allow the client to know that there is no
man-in-the-middle, without having to verify the server's SSL certificate.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments: download all (4 files, .tar.gz)
0001-Move-sha1.c-to-src-common.patch (application/x-patch, 26.0K) download
From 115ab59aba9c7b427834907dea85ef026fd06c13 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 5 Mar 2015 20:37:32 +0200
Subject: [PATCH 1/4] Move sha1.c to src/common
---
contrib/pgcrypto/Makefile | 4 +-
contrib/pgcrypto/internal.c | 2 +-
contrib/pgcrypto/sha1.c | 341 --------------------------------------------
contrib/pgcrypto/sha1.h | 75 ----------
src/common/Makefile | 2 +-
src/common/sha1.c | 341 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/sha1.h | 75 ++++++++++
7 files changed, 420 insertions(+), 420 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
create mode 100644 src/common/sha1.c
create mode 100644 src/include/common/sha1.h
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 18bad1a..bb5118e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,6 +1,6 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c random.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
@@ -30,7 +30,7 @@ DATA = pgcrypto--1.2.sql pgcrypto--1.1--1.2.sql pgcrypto--1.0--1.1.sql \
pgcrypto--unpackaged--1.0.sql
PGFILEDESC = "pgcrypto - cryptographic functions"
-REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \
+REGRESS = init md5 hmac-md5 hmac-sha1 blowfish rijndael \
$(CF_TESTS) \
crypt-des crypt-md5 crypt-blowfish crypt-xdes \
pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..9f42955 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -35,7 +35,7 @@
#include "px.h"
#include "md5.h"
-#include "sha1.h"
+#include "common/sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
diff --git a/contrib/pgcrypto/sha1.c b/contrib/pgcrypto/sha1.c
deleted file mode 100644
index 0e753ce..0000000
--- a/contrib/pgcrypto/sha1.c
+++ /dev/null
@@ -1,341 +0,0 @@
-/* $KAME: sha1.c,v 1.3 2000/02/22 14:01:18 itojun Exp $ */
-
-/*
- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the project nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * contrib/pgcrypto/sha1.c
- */
-/*
- * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
- * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
- * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
- */
-
-#include "postgres.h"
-
-#include <sys/param.h>
-
-#include "sha1.h"
-
-/* constant table */
-static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
-
-#define K(t) _K[(t) / 20]
-
-#define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d)))
-#define F1(b, c, d) (((b) ^ (c)) ^ (d))
-#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
-#define F3(b, c, d) (((b) ^ (c)) ^ (d))
-
-#define S(n, x) (((x) << (n)) | ((x) >> (32 - (n))))
-
-#define H(n) (ctxt->h.b32[(n)])
-#define COUNT (ctxt->count)
-#define BCOUNT (ctxt->c.b64[0] / 8)
-#define W(n) (ctxt->m.b32[(n)])
-
-#define PUTBYTE(x) \
-do { \
- ctxt->m.b8[(COUNT % 64)] = (x); \
- COUNT++; \
- COUNT %= 64; \
- ctxt->c.b64[0] += 8; \
- if (COUNT % 64 == 0) \
- sha1_step(ctxt); \
-} while (0)
-
-#define PUTPAD(x) \
-do { \
- ctxt->m.b8[(COUNT % 64)] = (x); \
- COUNT++; \
- COUNT %= 64; \
- if (COUNT % 64 == 0) \
- sha1_step(ctxt); \
-} while (0)
-
-static void sha1_step(struct sha1_ctxt *);
-
-static void
-sha1_step(struct sha1_ctxt * ctxt)
-{
- uint32 a,
- b,
- c,
- d,
- e;
- size_t t,
- s;
- uint32 tmp;
-
-#ifndef WORDS_BIGENDIAN
- struct sha1_ctxt tctxt;
-
- memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64);
- ctxt->m.b8[0] = tctxt.m.b8[3];
- ctxt->m.b8[1] = tctxt.m.b8[2];
- ctxt->m.b8[2] = tctxt.m.b8[1];
- ctxt->m.b8[3] = tctxt.m.b8[0];
- ctxt->m.b8[4] = tctxt.m.b8[7];
- ctxt->m.b8[5] = tctxt.m.b8[6];
- ctxt->m.b8[6] = tctxt.m.b8[5];
- ctxt->m.b8[7] = tctxt.m.b8[4];
- ctxt->m.b8[8] = tctxt.m.b8[11];
- ctxt->m.b8[9] = tctxt.m.b8[10];
- ctxt->m.b8[10] = tctxt.m.b8[9];
- ctxt->m.b8[11] = tctxt.m.b8[8];
- ctxt->m.b8[12] = tctxt.m.b8[15];
- ctxt->m.b8[13] = tctxt.m.b8[14];
- ctxt->m.b8[14] = tctxt.m.b8[13];
- ctxt->m.b8[15] = tctxt.m.b8[12];
- ctxt->m.b8[16] = tctxt.m.b8[19];
- ctxt->m.b8[17] = tctxt.m.b8[18];
- ctxt->m.b8[18] = tctxt.m.b8[17];
- ctxt->m.b8[19] = tctxt.m.b8[16];
- ctxt->m.b8[20] = tctxt.m.b8[23];
- ctxt->m.b8[21] = tctxt.m.b8[22];
- ctxt->m.b8[22] = tctxt.m.b8[21];
- ctxt->m.b8[23] = tctxt.m.b8[20];
- ctxt->m.b8[24] = tctxt.m.b8[27];
- ctxt->m.b8[25] = tctxt.m.b8[26];
- ctxt->m.b8[26] = tctxt.m.b8[25];
- ctxt->m.b8[27] = tctxt.m.b8[24];
- ctxt->m.b8[28] = tctxt.m.b8[31];
- ctxt->m.b8[29] = tctxt.m.b8[30];
- ctxt->m.b8[30] = tctxt.m.b8[29];
- ctxt->m.b8[31] = tctxt.m.b8[28];
- ctxt->m.b8[32] = tctxt.m.b8[35];
- ctxt->m.b8[33] = tctxt.m.b8[34];
- ctxt->m.b8[34] = tctxt.m.b8[33];
- ctxt->m.b8[35] = tctxt.m.b8[32];
- ctxt->m.b8[36] = tctxt.m.b8[39];
- ctxt->m.b8[37] = tctxt.m.b8[38];
- ctxt->m.b8[38] = tctxt.m.b8[37];
- ctxt->m.b8[39] = tctxt.m.b8[36];
- ctxt->m.b8[40] = tctxt.m.b8[43];
- ctxt->m.b8[41] = tctxt.m.b8[42];
- ctxt->m.b8[42] = tctxt.m.b8[41];
- ctxt->m.b8[43] = tctxt.m.b8[40];
- ctxt->m.b8[44] = tctxt.m.b8[47];
- ctxt->m.b8[45] = tctxt.m.b8[46];
- ctxt->m.b8[46] = tctxt.m.b8[45];
- ctxt->m.b8[47] = tctxt.m.b8[44];
- ctxt->m.b8[48] = tctxt.m.b8[51];
- ctxt->m.b8[49] = tctxt.m.b8[50];
- ctxt->m.b8[50] = tctxt.m.b8[49];
- ctxt->m.b8[51] = tctxt.m.b8[48];
- ctxt->m.b8[52] = tctxt.m.b8[55];
- ctxt->m.b8[53] = tctxt.m.b8[54];
- ctxt->m.b8[54] = tctxt.m.b8[53];
- ctxt->m.b8[55] = tctxt.m.b8[52];
- ctxt->m.b8[56] = tctxt.m.b8[59];
- ctxt->m.b8[57] = tctxt.m.b8[58];
- ctxt->m.b8[58] = tctxt.m.b8[57];
- ctxt->m.b8[59] = tctxt.m.b8[56];
- ctxt->m.b8[60] = tctxt.m.b8[63];
- ctxt->m.b8[61] = tctxt.m.b8[62];
- ctxt->m.b8[62] = tctxt.m.b8[61];
- ctxt->m.b8[63] = tctxt.m.b8[60];
-#endif
-
- a = H(0);
- b = H(1);
- c = H(2);
- d = H(3);
- e = H(4);
-
- for (t = 0; t < 20; t++)
- {
- s = t & 0x0f;
- if (t >= 16)
- W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
- tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t);
- e = d;
- d = c;
- c = S(30, b);
- b = a;
- a = tmp;
- }
- for (t = 20; t < 40; t++)
- {
- s = t & 0x0f;
- W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
- tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t);
- e = d;
- d = c;
- c = S(30, b);
- b = a;
- a = tmp;
- }
- for (t = 40; t < 60; t++)
- {
- s = t & 0x0f;
- W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
- tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t);
- e = d;
- d = c;
- c = S(30, b);
- b = a;
- a = tmp;
- }
- for (t = 60; t < 80; t++)
- {
- s = t & 0x0f;
- W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
- tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t);
- e = d;
- d = c;
- c = S(30, b);
- b = a;
- a = tmp;
- }
-
- H(0) = H(0) + a;
- H(1) = H(1) + b;
- H(2) = H(2) + c;
- H(3) = H(3) + d;
- H(4) = H(4) + e;
-
- memset(&ctxt->m.b8[0], 0, 64);
-}
-
-/*------------------------------------------------------------*/
-
-void
-sha1_init(struct sha1_ctxt * ctxt)
-{
- memset(ctxt, 0, sizeof(struct sha1_ctxt));
- H(0) = 0x67452301;
- H(1) = 0xefcdab89;
- H(2) = 0x98badcfe;
- H(3) = 0x10325476;
- H(4) = 0xc3d2e1f0;
-}
-
-void
-sha1_pad(struct sha1_ctxt * ctxt)
-{
- size_t padlen; /* pad length in bytes */
- size_t padstart;
-
- PUTPAD(0x80);
-
- padstart = COUNT % 64;
- padlen = 64 - padstart;
- if (padlen < 8)
- {
- memset(&ctxt->m.b8[padstart], 0, padlen);
- COUNT += padlen;
- COUNT %= 64;
- sha1_step(ctxt);
- padstart = COUNT % 64; /* should be 0 */
- padlen = 64 - padstart; /* should be 64 */
- }
- memset(&ctxt->m.b8[padstart], 0, padlen - 8);
- COUNT += (padlen - 8);
- COUNT %= 64;
-#ifdef WORDS_BIGENDIAN
- PUTPAD(ctxt->c.b8[0]);
- PUTPAD(ctxt->c.b8[1]);
- PUTPAD(ctxt->c.b8[2]);
- PUTPAD(ctxt->c.b8[3]);
- PUTPAD(ctxt->c.b8[4]);
- PUTPAD(ctxt->c.b8[5]);
- PUTPAD(ctxt->c.b8[6]);
- PUTPAD(ctxt->c.b8[7]);
-#else
- PUTPAD(ctxt->c.b8[7]);
- PUTPAD(ctxt->c.b8[6]);
- PUTPAD(ctxt->c.b8[5]);
- PUTPAD(ctxt->c.b8[4]);
- PUTPAD(ctxt->c.b8[3]);
- PUTPAD(ctxt->c.b8[2]);
- PUTPAD(ctxt->c.b8[1]);
- PUTPAD(ctxt->c.b8[0]);
-#endif
-}
-
-void
-sha1_loop(struct sha1_ctxt * ctxt, const uint8 *input0, size_t len)
-{
- const uint8 *input;
- size_t gaplen;
- size_t gapstart;
- size_t off;
- size_t copysiz;
-
- input = (const uint8 *) input0;
- off = 0;
-
- while (off < len)
- {
- gapstart = COUNT % 64;
- gaplen = 64 - gapstart;
-
- copysiz = (gaplen < len - off) ? gaplen : len - off;
- memmove(&ctxt->m.b8[gapstart], &input[off], copysiz);
- COUNT += copysiz;
- COUNT %= 64;
- ctxt->c.b64[0] += copysiz * 8;
- if (COUNT % 64 == 0)
- sha1_step(ctxt);
- off += copysiz;
- }
-}
-
-void
-sha1_result(struct sha1_ctxt * ctxt, uint8 *digest0)
-{
- uint8 *digest;
-
- digest = (uint8 *) digest0;
- sha1_pad(ctxt);
-#ifdef WORDS_BIGENDIAN
- memmove(digest, &ctxt->h.b8[0], 20);
-#else
- digest[0] = ctxt->h.b8[3];
- digest[1] = ctxt->h.b8[2];
- digest[2] = ctxt->h.b8[1];
- digest[3] = ctxt->h.b8[0];
- digest[4] = ctxt->h.b8[7];
- digest[5] = ctxt->h.b8[6];
- digest[6] = ctxt->h.b8[5];
- digest[7] = ctxt->h.b8[4];
- digest[8] = ctxt->h.b8[11];
- digest[9] = ctxt->h.b8[10];
- digest[10] = ctxt->h.b8[9];
- digest[11] = ctxt->h.b8[8];
- digest[12] = ctxt->h.b8[15];
- digest[13] = ctxt->h.b8[14];
- digest[14] = ctxt->h.b8[13];
- digest[15] = ctxt->h.b8[12];
- digest[16] = ctxt->h.b8[19];
- digest[17] = ctxt->h.b8[18];
- digest[18] = ctxt->h.b8[17];
- digest[19] = ctxt->h.b8[16];
-#endif
-}
diff --git a/contrib/pgcrypto/sha1.h b/contrib/pgcrypto/sha1.h
deleted file mode 100644
index 5532ca1..0000000
--- a/contrib/pgcrypto/sha1.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* contrib/pgcrypto/sha1.h */
-/* $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $ */
-
-/*
- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the project nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-/*
- * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
- * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
- * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
- */
-
-#ifndef _NETINET6_SHA1_H_
-#define _NETINET6_SHA1_H_
-
-struct sha1_ctxt
-{
- union
- {
- uint8 b8[20];
- uint32 b32[5];
- } h;
- union
- {
- uint8 b8[8];
- uint64 b64[1];
- } c;
- union
- {
- uint8 b8[64];
- uint32 b32[16];
- } m;
- uint8 count;
-};
-
-extern void sha1_init(struct sha1_ctxt *);
-extern void sha1_pad(struct sha1_ctxt *);
-extern void sha1_loop(struct sha1_ctxt *, const uint8 *, size_t);
-extern void sha1_result(struct sha1_ctxt *, uint8 *);
-
-/* compatibilty with other SHA1 source codes */
-typedef struct sha1_ctxt SHA1_CTX;
-
-#define SHA1Init(x) sha1_init((x))
-#define SHA1Update(x, y, z) sha1_loop((x), (y), (z))
-#define SHA1Final(x, y) sha1_result((y), (x))
-
-#define SHA1_RESULTLEN (160/8)
-
-#endif /* _NETINET6_SHA1_H_ */
diff --git a/src/common/Makefile b/src/common/Makefile
index c71415e..f526b42 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
OBJS_COMMON = exec.o pg_crc.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o sha1.o string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o
diff --git a/src/common/sha1.c b/src/common/sha1.c
new file mode 100644
index 0000000..4d9a325
--- /dev/null
+++ b/src/common/sha1.c
@@ -0,0 +1,341 @@
+/* $KAME: sha1.c,v 1.3 2000/02/22 14:01:18 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * src/common/sha1.c
+ */
+/*
+ * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
+ * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+ * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
+ */
+
+#include "postgres.h"
+
+#include <sys/param.h>
+
+#include "common/sha1.h"
+
+/* constant table */
+static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
+
+#define K(t) _K[(t) / 20]
+
+#define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d)))
+#define F1(b, c, d) (((b) ^ (c)) ^ (d))
+#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
+#define F3(b, c, d) (((b) ^ (c)) ^ (d))
+
+#define S(n, x) (((x) << (n)) | ((x) >> (32 - (n))))
+
+#define H(n) (ctxt->h.b32[(n)])
+#define COUNT (ctxt->count)
+#define BCOUNT (ctxt->c.b64[0] / 8)
+#define W(n) (ctxt->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctxt->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctxt->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ sha1_step(ctxt); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctxt->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ sha1_step(ctxt); \
+} while (0)
+
+static void sha1_step(struct sha1_ctxt *);
+
+static void
+sha1_step(struct sha1_ctxt * ctxt)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
+
+#ifndef WORDS_BIGENDIAN
+ struct sha1_ctxt tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctxt->m.b8[0], 64);
+ ctxt->m.b8[0] = tctxt.m.b8[3];
+ ctxt->m.b8[1] = tctxt.m.b8[2];
+ ctxt->m.b8[2] = tctxt.m.b8[1];
+ ctxt->m.b8[3] = tctxt.m.b8[0];
+ ctxt->m.b8[4] = tctxt.m.b8[7];
+ ctxt->m.b8[5] = tctxt.m.b8[6];
+ ctxt->m.b8[6] = tctxt.m.b8[5];
+ ctxt->m.b8[7] = tctxt.m.b8[4];
+ ctxt->m.b8[8] = tctxt.m.b8[11];
+ ctxt->m.b8[9] = tctxt.m.b8[10];
+ ctxt->m.b8[10] = tctxt.m.b8[9];
+ ctxt->m.b8[11] = tctxt.m.b8[8];
+ ctxt->m.b8[12] = tctxt.m.b8[15];
+ ctxt->m.b8[13] = tctxt.m.b8[14];
+ ctxt->m.b8[14] = tctxt.m.b8[13];
+ ctxt->m.b8[15] = tctxt.m.b8[12];
+ ctxt->m.b8[16] = tctxt.m.b8[19];
+ ctxt->m.b8[17] = tctxt.m.b8[18];
+ ctxt->m.b8[18] = tctxt.m.b8[17];
+ ctxt->m.b8[19] = tctxt.m.b8[16];
+ ctxt->m.b8[20] = tctxt.m.b8[23];
+ ctxt->m.b8[21] = tctxt.m.b8[22];
+ ctxt->m.b8[22] = tctxt.m.b8[21];
+ ctxt->m.b8[23] = tctxt.m.b8[20];
+ ctxt->m.b8[24] = tctxt.m.b8[27];
+ ctxt->m.b8[25] = tctxt.m.b8[26];
+ ctxt->m.b8[26] = tctxt.m.b8[25];
+ ctxt->m.b8[27] = tctxt.m.b8[24];
+ ctxt->m.b8[28] = tctxt.m.b8[31];
+ ctxt->m.b8[29] = tctxt.m.b8[30];
+ ctxt->m.b8[30] = tctxt.m.b8[29];
+ ctxt->m.b8[31] = tctxt.m.b8[28];
+ ctxt->m.b8[32] = tctxt.m.b8[35];
+ ctxt->m.b8[33] = tctxt.m.b8[34];
+ ctxt->m.b8[34] = tctxt.m.b8[33];
+ ctxt->m.b8[35] = tctxt.m.b8[32];
+ ctxt->m.b8[36] = tctxt.m.b8[39];
+ ctxt->m.b8[37] = tctxt.m.b8[38];
+ ctxt->m.b8[38] = tctxt.m.b8[37];
+ ctxt->m.b8[39] = tctxt.m.b8[36];
+ ctxt->m.b8[40] = tctxt.m.b8[43];
+ ctxt->m.b8[41] = tctxt.m.b8[42];
+ ctxt->m.b8[42] = tctxt.m.b8[41];
+ ctxt->m.b8[43] = tctxt.m.b8[40];
+ ctxt->m.b8[44] = tctxt.m.b8[47];
+ ctxt->m.b8[45] = tctxt.m.b8[46];
+ ctxt->m.b8[46] = tctxt.m.b8[45];
+ ctxt->m.b8[47] = tctxt.m.b8[44];
+ ctxt->m.b8[48] = tctxt.m.b8[51];
+ ctxt->m.b8[49] = tctxt.m.b8[50];
+ ctxt->m.b8[50] = tctxt.m.b8[49];
+ ctxt->m.b8[51] = tctxt.m.b8[48];
+ ctxt->m.b8[52] = tctxt.m.b8[55];
+ ctxt->m.b8[53] = tctxt.m.b8[54];
+ ctxt->m.b8[54] = tctxt.m.b8[53];
+ ctxt->m.b8[55] = tctxt.m.b8[52];
+ ctxt->m.b8[56] = tctxt.m.b8[59];
+ ctxt->m.b8[57] = tctxt.m.b8[58];
+ ctxt->m.b8[58] = tctxt.m.b8[57];
+ ctxt->m.b8[59] = tctxt.m.b8[56];
+ ctxt->m.b8[60] = tctxt.m.b8[63];
+ ctxt->m.b8[61] = tctxt.m.b8[62];
+ ctxt->m.b8[62] = tctxt.m.b8[61];
+ ctxt->m.b8[63] = tctxt.m.b8[60];
+#endif
+
+ a = H(0);
+ b = H(1);
+ c = H(2);
+ d = H(3);
+ e = H(4);
+
+ for (t = 0; t < 20; t++)
+ {
+ s = t & 0x0f;
+ if (t >= 16)
+ W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+ tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t);
+ e = d;
+ d = c;
+ c = S(30, b);
+ b = a;
+ a = tmp;
+ }
+ for (t = 20; t < 40; t++)
+ {
+ s = t & 0x0f;
+ W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+ tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t);
+ e = d;
+ d = c;
+ c = S(30, b);
+ b = a;
+ a = tmp;
+ }
+ for (t = 40; t < 60; t++)
+ {
+ s = t & 0x0f;
+ W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+ tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t);
+ e = d;
+ d = c;
+ c = S(30, b);
+ b = a;
+ a = tmp;
+ }
+ for (t = 60; t < 80; t++)
+ {
+ s = t & 0x0f;
+ W(s) = S(1, W((s + 13) & 0x0f) ^ W((s + 8) & 0x0f) ^ W((s + 2) & 0x0f) ^ W(s));
+ tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t);
+ e = d;
+ d = c;
+ c = S(30, b);
+ b = a;
+ a = tmp;
+ }
+
+ H(0) = H(0) + a;
+ H(1) = H(1) + b;
+ H(2) = H(2) + c;
+ H(3) = H(3) + d;
+ H(4) = H(4) + e;
+
+ memset(&ctxt->m.b8[0], 0, 64);
+}
+
+/*------------------------------------------------------------*/
+
+void
+sha1_init(struct sha1_ctxt * ctxt)
+{
+ memset(ctxt, 0, sizeof(struct sha1_ctxt));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
+}
+
+void
+sha1_pad(struct sha1_ctxt * ctxt)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctxt->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ sha1_step(ctxt);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctxt->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctxt->c.b8[0]);
+ PUTPAD(ctxt->c.b8[1]);
+ PUTPAD(ctxt->c.b8[2]);
+ PUTPAD(ctxt->c.b8[3]);
+ PUTPAD(ctxt->c.b8[4]);
+ PUTPAD(ctxt->c.b8[5]);
+ PUTPAD(ctxt->c.b8[6]);
+ PUTPAD(ctxt->c.b8[7]);
+#else
+ PUTPAD(ctxt->c.b8[7]);
+ PUTPAD(ctxt->c.b8[6]);
+ PUTPAD(ctxt->c.b8[5]);
+ PUTPAD(ctxt->c.b8[4]);
+ PUTPAD(ctxt->c.b8[3]);
+ PUTPAD(ctxt->c.b8[2]);
+ PUTPAD(ctxt->c.b8[1]);
+ PUTPAD(ctxt->c.b8[0]);
+#endif
+}
+
+void
+sha1_loop(struct sha1_ctxt * ctxt, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
+
+ input = (const uint8 *) input0;
+ off = 0;
+
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctxt->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctxt->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ sha1_step(ctxt);
+ off += copysiz;
+ }
+}
+
+void
+sha1_result(struct sha1_ctxt * ctxt, uint8 *digest0)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) digest0;
+ sha1_pad(ctxt);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctxt->h.b8[0], 20);
+#else
+ digest[0] = ctxt->h.b8[3];
+ digest[1] = ctxt->h.b8[2];
+ digest[2] = ctxt->h.b8[1];
+ digest[3] = ctxt->h.b8[0];
+ digest[4] = ctxt->h.b8[7];
+ digest[5] = ctxt->h.b8[6];
+ digest[6] = ctxt->h.b8[5];
+ digest[7] = ctxt->h.b8[4];
+ digest[8] = ctxt->h.b8[11];
+ digest[9] = ctxt->h.b8[10];
+ digest[10] = ctxt->h.b8[9];
+ digest[11] = ctxt->h.b8[8];
+ digest[12] = ctxt->h.b8[15];
+ digest[13] = ctxt->h.b8[14];
+ digest[14] = ctxt->h.b8[13];
+ digest[15] = ctxt->h.b8[12];
+ digest[16] = ctxt->h.b8[19];
+ digest[17] = ctxt->h.b8[18];
+ digest[18] = ctxt->h.b8[17];
+ digest[19] = ctxt->h.b8[16];
+#endif
+}
diff --git a/src/include/common/sha1.h b/src/include/common/sha1.h
new file mode 100644
index 0000000..d5ff296
--- /dev/null
+++ b/src/include/common/sha1.h
@@ -0,0 +1,75 @@
+/* src/include/common/sha1.h */
+/* $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
+ * based on: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+ * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org>
+ */
+
+#ifndef _NETINET6_SHA1_H_
+#define _NETINET6_SHA1_H_
+
+struct sha1_ctxt
+{
+ union
+ {
+ uint8 b8[20];
+ uint32 b32[5];
+ } h;
+ union
+ {
+ uint8 b8[8];
+ uint64 b64[1];
+ } c;
+ union
+ {
+ uint8 b8[64];
+ uint32 b32[16];
+ } m;
+ uint8 count;
+};
+
+extern void sha1_init(struct sha1_ctxt *);
+extern void sha1_pad(struct sha1_ctxt *);
+extern void sha1_loop(struct sha1_ctxt *, const uint8 *, size_t);
+extern void sha1_result(struct sha1_ctxt *, uint8 *);
+
+/* compatibilty with other SHA1 source codes */
+typedef struct sha1_ctxt SHA1_CTX;
+
+#define SHA1Init(x) sha1_init((x))
+#define SHA1Update(x, y, z) sha1_loop((x), (y), (z))
+#define SHA1Final(x, y) sha1_result((y), (x))
+
+#define SHA1_RESULTLEN (160/8)
+
+#endif /* _NETINET6_SHA1_H_ */
--
2.1.4
0002-Refactor-sendAuthRequest.patch (application/x-patch, 5.5K) download
From b21202b8fb740f27eaf09acb36282727b923e6b3 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 11 Feb 2015 23:02:30 +0200
Subject: [PATCH 2/4] Refactor sendAuthRequest
---
src/backend/libpq/auth.c | 65 ++++++++++++++++++++++++------------------------
1 file changed, 32 insertions(+), 33 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 28b050a..81226c2 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,7 +36,8 @@
* Global authentication functions
*----------------------------------------------------------------
*/
-static void sendAuthRequest(Port *port, AuthRequest areq);
+static void sendAuthRequest(Port *port, AuthRequest areq, char *extradata,
+ int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
static int recv_and_check_password_packet(Port *port, char **logdetail);
@@ -479,7 +480,7 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS);
+ sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
status = pg_GSS_recvauth(port);
#else
Assert(false);
@@ -488,7 +489,7 @@ ClientAuthentication(Port *port)
case uaSSPI:
#ifdef ENABLE_SSPI
- sendAuthRequest(port, AUTH_REQ_SSPI);
+ sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
status = pg_SSPI_recvauth(port);
#else
Assert(false);
@@ -512,12 +513,13 @@ ClientAuthentication(Port *port)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
- sendAuthRequest(port, AUTH_REQ_MD5);
+ /* Add the salt for encrypted passwords. */
+ sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
status = recv_and_check_password_packet(port, &logdetail);
break;
case uaPassword:
- sendAuthRequest(port, AUTH_REQ_PASSWORD);
+ sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
status = recv_and_check_password_packet(port, &logdetail);
break;
@@ -556,7 +558,7 @@ ClientAuthentication(Port *port)
(*ClientAuthentication_hook) (port, status);
if (status == STATUS_OK)
- sendAuthRequest(port, AUTH_REQ_OK);
+ sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
else
auth_failed(port, status, logdetail);
}
@@ -566,7 +568,7 @@ ClientAuthentication(Port *port)
* Send an authentication request packet to the frontend.
*/
static void
-sendAuthRequest(Port *port, AuthRequest areq)
+sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen)
{
StringInfoData buf;
@@ -575,27 +577,8 @@ sendAuthRequest(Port *port, AuthRequest areq)
pq_beginmessage(&buf, 'R');
pq_sendint(&buf, (int32) areq, sizeof(int32));
- /* Add the salt for encrypted passwords. */
- if (areq == AUTH_REQ_MD5)
- pq_sendbytes(&buf, port->md5Salt, 4);
-
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
-
- /*
- * Add the authentication data for the next step of the GSSAPI or SSPI
- * negotiation.
- */
- else if (areq == AUTH_REQ_GSS_CONT)
- {
- if (port->gss->outbuf.length > 0)
- {
- elog(DEBUG4, "sending GSS token of length %u",
- (unsigned int) port->gss->outbuf.length);
-
- pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
- }
- }
-#endif
+ if (extralen > 0)
+ pq_sendbytes(&buf, extradata, extralen);
pq_endmessage(&buf);
@@ -907,7 +890,15 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG4, "sending GSS response token of length %u",
(unsigned int) port->gss->outbuf.length);
- sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+ /*
+ * Add the authentication data for the next step of the GSSAPI or
+ * SSPI negotiation.
+ */
+ elog(DEBUG4, "sending GSS token of length %u",
+ (unsigned int) port->gss->outbuf.length);
+
+ sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+ port->gss->outbuf.value, port->gss->outbuf.length);
gss_release_buffer(&lmin_s, &port->gss->outbuf);
}
@@ -1150,7 +1141,15 @@ pg_SSPI_recvauth(Port *port)
port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
- sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+ /*
+ * Add the authentication data for the next step of the GSSAPI or
+ * SSPI negotiation.
+ */
+ elog(DEBUG4, "sending GSS token of length %u",
+ (unsigned int) port->gss->outbuf.length);
+
+ sendAuthRequest(port, AUTH_REQ_GSS_CONT,
+ port->gss->outbuf.value, port->gss->outbuf.length);
FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
}
@@ -1671,7 +1670,7 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
* let's go ask the client to send a password, which we
* then stuff into PAM.
*/
- sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+ sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(pam_port_cludge);
if (passwd == NULL)
{
@@ -1946,7 +1945,7 @@ CheckLDAPAuth(Port *port)
if (port->hba->ldapport == 0)
port->hba->ldapport = LDAP_PORT;
- sendAuthRequest(port, AUTH_REQ_PASSWORD);
+ sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
@@ -2302,7 +2301,7 @@ CheckRADIUSAuth(Port *port)
identifier = port->hba->radiusidentifier;
/* Send regular password request to client, and get the response */
- sendAuthRequest(port, AUTH_REQ_PASSWORD);
+ sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
--
2.1.4
0003-Add-support-for-multiple-verifiers.patch (application/x-patch, 22.6K) download
From 488b1d547cbf132bd3980971e91bbf8b8c6e442c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 3 Mar 2015 20:10:10 +0200
Subject: [PATCH 3/4] Add support for multiple verifiers.
pg_authid.rolpassword field has been replaced with an array.
Add CREATE/ALTER ROLE support for password verifiers.
---
src/backend/catalog/system_views.sql | 2 +-
src/backend/commands/user.c | 195 +++++++++++++++++++++++++----------
src/backend/libpq/crypt.c | 117 ++++++++++++++++++---
src/backend/parser/gram.y | 8 +-
src/bin/pg_dump/pg_dumpall.c | 28 ++++-
src/include/catalog/pg_authid.h | 4 +-
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/rules.out | 2 +-
8 files changed, 281 insertions(+), 76 deletions(-)
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2800f73..cd78061 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -32,7 +32,7 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ rolverifiers AS verifiers,
rolvaliduntil::abstime AS valuntil,
setconfig AS useconfig
FROM pg_authid LEFT JOIN pg_db_role_setting s
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 75f1b3c..42e473d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -24,6 +24,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
@@ -55,6 +56,9 @@ static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static Datum buildPasswordVerifiers(char *rolname, char *password,
+ bool store_encrypted,
+ char *verifiers, bool *isnull);
/* Check if current user has createrole privileges */
static bool
@@ -80,7 +84,7 @@ CreateRole(CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *passwordVerifiers = NULL;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -96,6 +100,7 @@ CreateRole(CreateRoleStmt *stmt)
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
DefElem *dpassword = NULL;
+ DefElem *dpasswordverifiers = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
@@ -141,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
}
+ else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+ {
+ if (dpassword || dpasswordverifiers)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dpasswordverifiers = defel;
+ }
else if (strcmp(defel->defname, "sysid") == 0)
{
ereport(NOTICE,
@@ -249,6 +262,8 @@ CreateRole(CreateRoleStmt *stmt)
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
+ if (dpasswordverifiers && dpasswordverifiers->arg)
+ passwordVerifiers = strVal(dpasswordverifiers->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -346,7 +361,10 @@ CreateRole(CreateRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Call the password checking hook if there is one defined. Pass the
+ * plaintext password to it if we have it, otherwise just the MD5 hash.
+ *
+ * XXX: what to do with other verifiers? Need to change the hook API...
*/
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
@@ -371,24 +389,10 @@ CreateRole(CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
- if (password)
- {
- if (!encrypt_password || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, stmt->role, strlen(stmt->role),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
- }
- else
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
+ new_record[Anum_pg_authid_rolverifiers - 1] =
+ buildPasswordVerifiers(stmt->role, password, encrypt_password,
+ passwordVerifiers,
+ &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -487,7 +491,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *passwordVerifiers = NULL;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -501,6 +505,7 @@ AlterRole(AlterRoleStmt *stmt)
bool validUntil_null;
bool bypassrls = -1;
DefElem *dpassword = NULL;
+ DefElem *dpasswordverifiers = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
@@ -519,10 +524,10 @@ AlterRole(AlterRoleStmt *stmt)
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "unencryptedPassword") == 0)
{
- if (dpassword)
+ if (dpassword || dpasswordverifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
@@ -532,6 +537,14 @@ AlterRole(AlterRoleStmt *stmt)
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
}
+ else if (strcmp(defel->defname, "passwordVerifiers") == 0)
+ {
+ if (dpassword || dpasswordverifiers)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dpasswordverifiers = defel;
+ }
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
@@ -620,6 +633,8 @@ AlterRole(AlterRoleStmt *stmt)
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
+ if (dpasswordverifiers && dpasswordverifiers->arg)
+ passwordVerifiers = strVal(dpasswordverifiers->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
@@ -718,7 +733,10 @@ AlterRole(AlterRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Call the password checking hook if there is one defined. Pass the
+ * plaintext password to it if we have it, otherwise just the MD5 hash.
+ *
+ * XXX: what to do with other verifiers? Need to change the hook API...
*/
if (check_password_hook && password)
(*check_password_hook)(rolename ,
@@ -779,28 +797,14 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
- /* password */
- if (password)
+ /* set or unset password */
+ if (dpassword)
{
- if (!encrypt_password || isMD5(password))
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(password);
- else
- {
- if (!pg_md5_encrypt(password, rolename, strlen(rolename),
- encrypted_password))
- elog(ERROR, "password encryption failed");
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(encrypted_password);
- }
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- }
-
- /* unset password */
- if (dpassword && dpassword->arg == NULL)
- {
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_record_repl[Anum_pg_authid_rolverifiers - 1] = true;
+ new_record[Anum_pg_authid_rolverifiers - 1] =
+ buildPasswordVerifiers(rolename, password, encrypt_password,
+ passwordVerifiers,
+ &new_record_nulls[Anum_pg_authid_rolverifiers - 1]);
}
/* valid until */
@@ -1192,16 +1196,43 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+ datum = heap_getattr(oldtuple, Anum_pg_authid_rolverifiers, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull)
{
- /* MD5 uses the username as salt, so just clear it on a rename */
- repl_repl[Anum_pg_authid_rolpassword - 1] = true;
- repl_null[Anum_pg_authid_rolpassword - 1] = true;
+ Datum *verifiers;
+ int nverifiers;
+ bool modified = false;
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
+ deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+ &verifiers, NULL, &nverifiers);
+ for (i = 0; i < nverifiers;)
+ {
+ char *verifier = TextDatumGetCString(verifiers[i]);
+ if (strncmp(verifier, "md5:", 4) == 0)
+ {
+ /* MD5 uses the username as salt, so clear it on a rename */
+ nverifiers--;
+ memmove(&verifiers[i], &verifiers[i + 1], nverifiers - i);
+ modified = true;
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+ }
+ else
+ i++;
+ }
+ if (modified)
+ {
+ repl_repl[Anum_pg_authid_rolverifiers - 1] = true;
+ if (nverifiers == 0)
+ repl_null[Anum_pg_authid_rolverifiers - 1] = true;
+ else
+ {
+ repl_val[Anum_pg_authid_rolverifiers - 1] =
+ PointerGetDatum(construct_array(verifiers, nverifiers, TEXTOID, -1, false, 'i'));
+ repl_null[Anum_pg_authid_rolverifiers - 1] = false;
+ }
+ }
}
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
@@ -1624,3 +1655,61 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
heap_close(pg_authmem_rel, NoLock);
}
+
+static Datum
+buildPasswordVerifiers(char *rolname, char *password, bool store_encrypted,
+ char *verifiers, bool *isnull)
+{
+ Datum datums[2];
+ int nverifiers = 0;
+ Datum result;
+ char *verifier = NULL;
+
+ /* If a verifier is given, use it as is */
+ if (verifiers)
+ {
+ result = OidInputFunctionCall(F_ARRAY_IN, verifiers, TEXTOID, -1);
+ *isnull = false;
+ return result;
+ }
+
+ /* If ENCRYPTED or UNENCRYPTED PASSWORD is given, store it */
+ if (password)
+ {
+ /*
+ * Check if the password is actually already an MD5 hash.
+ */
+ if (isMD5(password))
+ {
+ verifier = psprintf("md5:%s", password);
+ datums[nverifiers++] = CStringGetTextDatum(verifier);
+ }
+ else if (!store_encrypted)
+ {
+ verifier = psprintf("password:%s", password);
+ datums[nverifiers++] = CStringGetTextDatum(verifier);
+ }
+ else
+ {
+ char encrypted_password[MD5_PASSWD_LEN + 1];
+
+ if (!pg_md5_encrypt(password, rolname, strlen(rolname),
+ encrypted_password))
+ elog(ERROR, "password encryption failed");
+ verifier = psprintf("md5:%s", encrypted_password);
+ datums[nverifiers++] = CStringGetTextDatum(verifier);
+ }
+ }
+
+ if (nverifiers == 0)
+ {
+ result = (Datum) 0;
+ *isnull = true;
+ }
+ else
+ {
+ result = PointerGetDatum(construct_array(datums, nverifiers, TEXTOID, -1, false, 'i'));
+ *isnull = false;
+ }
+ return result;
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 97be944..4c94d63 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -21,13 +21,93 @@
#endif
#include "catalog/pg_authid.h"
+#include "catalog/pg_type.h"
#include "libpq/crypt.h"
#include "libpq/md5.h"
#include "miscadmin.h"
+#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
+/*
+ * Get verifier stored in pg_roleid tuple, for given authentication method.
+ */
+static char *
+get_role_verifier_from_tup(HeapTuple roleTup, const char *method)
+{
+ Datum datum;
+ bool isnull;
+ Datum *elems;
+ int nelems;
+ char *verifier = NULL;
+ int i;
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolverifiers, &isnull);
+ if (isnull)
+ return NULL;
+
+ /* Parse the stored value */
+ deconstruct_array(DatumGetArrayTypeP(datum), TEXTOID, -1, false, 'i',
+ &elems, NULL, &nelems);
+ for (i = 0; i < nelems; i ++)
+ {
+ char *s = TextDatumGetCString(elems[i]);
+ char *colon;
+
+ colon = strchr(s, ':');
+ if (colon == NULL)
+ continue; /* shouldn't happen, invalid verifier */
+
+ *colon = '\0';
+ verifier = colon + 1;
+
+ if (strcmp(s, method) == 0)
+ {
+ verifier = pstrdup(colon + 1);
+ pfree(s);
+ break;
+ }
+ pfree(s);
+ }
+
+ return verifier;
+}
+
+char *
+get_role_verifier(const char *role, const char *method, char **logdetail)
+{
+ char *verifier;
+ HeapTuple roleTup;
+ Datum datum;
+ bool isnull;
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
+ if (!HeapTupleIsValid(roleTup))
+ return NULL; /* no such user */
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ if (!isnull)
+ {
+ TimestampTz vuntil = DatumGetTimestampTz(datum);
+ if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ role);
+ ReleaseSysCache(roleTup);
+ return NULL;
+ }
+ }
+
+ verifier = get_role_verifier_from_tup(roleTup, method);
+
+ ReleaseSysCache(roleTup);
+
+ return verifier;
+}
/*
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,9 +119,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
char **logdetail)
{
int retval = STATUS_ERROR;
- char *shadow_pass,
+ char *verifier,
*crypt_pwd;
TimestampTz vuntil = 0;
+ bool verifier_is_md5;
char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
@@ -52,16 +133,22 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
if (!HeapTupleIsValid(roleTup))
return STATUS_ERROR; /* no such user */
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ verifier_is_md5 = true;
+ verifier = get_role_verifier_from_tup(roleTup, "md5");
+ if (verifier == NULL)
{
- ReleaseSysCache(roleTup);
- *logdetail = psprintf(_("User \"%s\" has no password assigned."),
- role);
- return STATUS_ERROR; /* user has no password */
+ /* we can also use a plaintext password, by creating the hash from it */
+ verifier_is_md5 = false;
+ verifier = get_role_verifier_from_tup(roleTup, "password");
+
+ if (verifier == NULL)
+ {
+ *logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+ role, "md5");
+ ReleaseSysCache(roleTup);
+ return STATUS_ERROR;
+ }
}
- shadow_pass = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
@@ -70,7 +157,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (*verifier == '\0')
return STATUS_ERROR; /* empty password */
CHECK_FOR_INTERRUPTS();
@@ -83,10 +170,10 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
{
case uaMD5:
crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
- if (isMD5(shadow_pass))
+ if (verifier_is_md5)
{
/* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ if (!pg_md5_encrypt(verifier + strlen("md5"),
port->md5Salt,
sizeof(port->md5Salt), crypt_pwd))
{
@@ -99,7 +186,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/* stored password is plain, double-encrypt */
char *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(shadow_pass,
+ if (!pg_md5_encrypt(verifier,
port->user_name,
strlen(port->user_name),
crypt_pwd2))
@@ -121,7 +208,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
}
break;
default:
- if (isMD5(shadow_pass))
+ if (verifier_is_md5)
{
/* Encrypt user-supplied password to match stored MD5 */
crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
@@ -134,7 +221,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
return STATUS_ERROR;
}
}
- crypt_pwd = shadow_pass;
+ crypt_pwd = verifier;
break;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3aa9e42..d68e61d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -623,7 +623,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
UNTIL UPDATE USER USING
VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
- VERBOSE VERSION_P VIEW VIEWS VOLATILE
+ VERBOSE VERIFIERS VERSION_P VIEW VIEWS VOLATILE
WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
@@ -916,6 +916,11 @@ AlterOptRoleElem:
$$ = makeDefElem("unencryptedPassword",
(Node *)makeString($3));
}
+ | PASSWORD VERIFIERS Sconst
+ {
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)makeString($3));
+ }
| INHERIT
{
$$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
@@ -13504,6 +13509,7 @@ unreserved_keyword:
| VALIDATOR
| VALUE_P
| VARYING
+ | VERIFIERS
| VERSION_P
| VIEW
| VIEWS
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 6a7a641..32d710c 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -660,7 +660,8 @@ dumpRoles(PGconn *conn)
i_rolcreatedb,
i_rolcanlogin,
i_rolconnlimit,
- i_rolpassword,
+ i_rolverifiers = -1,
+ i_rolpassword = -1,
i_rolvaliduntil,
i_rolreplication,
i_rolbypassrls,
@@ -669,10 +670,21 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
+ /* FIXME: bump this when we branch off */
if (server_version >= 90500)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, rolverifiers, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ "FROM pg_authid "
+ "ORDER BY 2");
+ else if (server_version >= 90500)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, rolbypassrls, "
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
@@ -757,7 +769,11 @@ dumpRoles(PGconn *conn)
i_rolcreatedb = PQfnumber(res, "rolcreatedb");
i_rolcanlogin = PQfnumber(res, "rolcanlogin");
i_rolconnlimit = PQfnumber(res, "rolconnlimit");
- i_rolpassword = PQfnumber(res, "rolpassword");
+ /* FIXME: version again */
+ if (server_version >= 90500)
+ i_rolverifiers = PQfnumber(res, "rolverifiers");
+ else
+ i_rolpassword = PQfnumber(res, "rolpassword");
i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
i_rolreplication = PQfnumber(res, "rolreplication");
i_rolbypassrls = PQfnumber(res, "rolbypassrls");
@@ -837,12 +853,18 @@ dumpRoles(PGconn *conn)
appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
PQgetvalue(res, i, i_rolconnlimit));
- if (!PQgetisnull(res, i, i_rolpassword))
+ if (i_rolpassword != -1 && !PQgetisnull(res, i, i_rolpassword))
{
appendPQExpBufferStr(buf, " PASSWORD ");
appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolpassword), conn);
}
+ if (i_rolverifiers != -1 && !PQgetisnull(res, i, i_rolverifiers))
+ {
+ appendPQExpBufferStr(buf, " PASSWORD VERIFIERS ");
+ appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolverifiers), conn);
+ }
+
if (!PQgetisnull(res, i, i_rolvaliduntil))
appendPQExpBuffer(buf, " VALID UNTIL '%s'",
PQgetvalue(res, i, i_rolvaliduntil));
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index d5f19d6..498c18b 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
/* remaining fields may be null; use heap_getattr to read them! */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text rolpassword; /* password, if any */
+ text rolverifiers[]; /* password hashes, if any */
timestamptz rolvaliduntil; /* password expiration time, if any */
#endif
} FormData_pg_authid;
@@ -85,7 +85,7 @@ typedef FormData_pg_authid *Form_pg_authid;
#define Anum_pg_authid_rolreplication 7
#define Anum_pg_authid_rolbypassrls 8
#define Anum_pg_authid_rolconnlimit 9
-#define Anum_pg_authid_rolpassword 10
+#define Anum_pg_authid_rolverifiers 10
#define Anum_pg_authid_rolvaliduntil 11
/* ----------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7c243ec..067112f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -407,6 +407,7 @@ PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("verifiers", VERIFIERS, UNRESERVED_KEYWORD)
PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 1788270..052c4b7 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1608,7 +1608,7 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
pg_authid.rolsuper AS usesuper,
pg_authid.rolreplication AS userepl,
pg_authid.rolbypassrls AS usebypassrls,
- pg_authid.rolpassword AS passwd,
+ pg_authid.rolverifiers AS verifiers,
(pg_authid.rolvaliduntil)::abstime AS valuntil,
s.setconfig AS useconfig
FROM (pg_authid
--
2.1.4
0004-WIP-Implement-SCRAM-authentication.patch (application/x-patch, 60.6K) download
From 3b38ec5fd7a2d4fe5f3f89c2f7cb6ea20b9fa0e5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 5 Mar 2015 09:50:52 +0200
Subject: [PATCH 4/4] WIP: Implement SCRAM authentication.
---
doc/src/sgml/protocol.sgml | 148 ++++++++-
src/backend/commands/user.c | 6 +
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 621 +++++++++++++++++++++++++++++++++++
src/backend/libpq/auth.c | 110 ++++++-
src/backend/libpq/hba.c | 14 +
src/backend/libpq/pg_hba.conf.sample | 2 +-
src/backend/utils/adt/encode.c | 8 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 161 +++++++++
src/include/common/scram-common.h | 35 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/crypt.h | 1 +
src/include/libpq/hba.h | 1 +
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 23 ++
src/include/utils/builtins.h | 4 +
src/interfaces/libpq/Makefile | 8 +-
src/interfaces/libpq/fe-auth-scram.c | 476 +++++++++++++++++++++++++++
src/interfaces/libpq/fe-auth.c | 95 ++++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 49 +++
src/interfaces/libpq/libpq-int.h | 5 +
23 files changed, 1769 insertions(+), 17 deletions(-)
create mode 100644 src/backend/libpq/auth-scram.c
create mode 100644 src/common/scram-common.c
create mode 100644 src/include/common/scram-common.h
create mode 100644 src/include/libpq/scram.h
create mode 100644 src/interfaces/libpq/fe-auth-scram.c
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3a753a0..aec0e05 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -228,11 +228,11 @@
The server then sends an appropriate authentication request message,
to which the frontend must reply with an appropriate authentication
response message (such as a password).
- For all authentication methods except GSSAPI and SSPI, there is at most
- one request and one response. In some methods, no response
+ For all authentication methods except GSSAPI, SSPI and SASL, there is at
+ most one request and one response. In some methods, no response
at all is needed from the frontend, and so no authentication request
- occurs. For GSSAPI and SSPI, multiple exchanges of packets may be needed
- to complete the authentication.
+ occurs. For GSSAPI, SSPI and SASL, multiple exchanges of packets may be
+ needed to complete the authentication.
</para>
<para>
@@ -366,6 +366,35 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>AuthenticationSASL</term>
+ <listitem>
+ <para>
+ The frontend must now initiate a SASL negotiation, using the SASL
+ mechanism specified in the message. The frontend will send a
+ PasswordMessage with the first part of the SASL data stream in
+ response to this. If further messages are needed, the server will
+ respond with AuthenticationSASLContinue.
+ </para>
+ </listitem>
+
+ </varlistentry>
+ <varlistentry>
+ <term>AuthenticationSASLContinue</term>
+ <listitem>
+ <para>
+ This message contains the response data from the previous step
+ of SASL negotiation (AuthenticationSASL, or a previous
+ AuthenticationSASLContinue). If the SASL data in this message
+ indicates more data is needed to complete the authentication,
+ the frontend must send that data as another PasswordMessage. If
+ SASL authentication is completed by this message, the server
+ will next send AuthenticationOk to indicate successful authentication
+ or ErrorResponse to indicate failure.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -2559,6 +2588,115 @@ AuthenticationGSSContinue (B)
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+AuthenticationSASL (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('R')
+</term>
+<listitem>
+<para>
+ Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32(10)
+</term>
+<listitem>
+<para>
+ Specifies that SASL authentication is started.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ String
+</term>
+<listitem>
+<para>
+ Name of a SASL authentication mechanism.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+AuthenticationSASLContinue (B)
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+ Byte1('R')
+</term>
+<listitem>
+<para>
+ Identifies the message as an authentication request.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32
+</term>
+<listitem>
+<para>
+ Length of message contents in bytes, including self.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Int32(11)
+</term>
+<listitem>
+<para>
+ Specifies that this message contains SASL-mechanism specific
+ data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+ Byte<replaceable>n</replaceable>
+</term>
+<listitem>
+<para>
+ SASL data, specific to the SASL mechanism being used.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+</listitem>
+</varlistentry>
+
<varlistentry>
<term>
@@ -4321,7 +4459,7 @@ PasswordMessage (F)
<listitem>
<para>
Identifies the message as a password response. Note that
- this is also used for GSSAPI and SSPI response messages
+ this is also used for GSSAPI, SSPI and SASL response messages
(which is really a design error, since the contained data
is not a null-terminated string in that case, but can be
arbitrary binary data).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 42e473d..4347399 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -30,6 +30,7 @@
#include "commands/seclabel.h"
#include "commands/user.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -1698,6 +1699,11 @@ buildPasswordVerifiers(char *rolname, char *password, bool store_encrypted,
elog(ERROR, "password encryption failed");
verifier = psprintf("md5:%s", encrypted_password);
datums[nverifiers++] = CStringGetTextDatum(verifier);
+
+ /* Also create a SCRAM verifier */
+ verifier = scram_build_verifier(rolname, password, 0);
+ verifier = psprintf("scram:%s", verifier);
+ datums[nverifiers++] = CStringGetTextDatum(verifier);
}
}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..3dd60e1 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
# be-fsstubs is here for historical reasons, probably belongs elsewhere
OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \
- pqformat.o pqmq.o pqsignal.o
+ pqformat.o pqmq.o pqsignal.o auth-scram.o
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
new file mode 100644
index 0000000..060193b
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,621 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See RFC 5802. Some differences:
+ *
+ * - Username from the authentication exchange is not used. The client
+ * should send an empty string as the username.
+ *
+ * - Password is not processed with the SASLprep algorithm.
+ *
+ * - Channel binding is not supported.
+ *
+ * The verifier stored in pg_authid consists of the salt, iteration count,
+ * StoredKey, and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/libpq/auth-scram.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "catalog/pg_authid.h"
+#include "common/scram-common.h"
+#include "common/sha1.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+#define SALT_LEN 10 /* length of salt when generating new
+ * verifiers */
+#define NONCE_LEN 10 /* length of random nonce generated in the
+ * authentication exchange */
+
+typedef struct
+{
+ enum
+ {
+ INIT,
+ SALT_SENT,
+ FINISHED
+ } state;
+
+ const char *username; /* username from startup packet */
+ char *salt; /* base64-encoded */
+ int iterations;
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+
+ /* These come from the client-first message */
+ char *client_first_message_bare;
+ char *client_username; /* username from client-first message */
+ char *client_authzid;
+ char *client_nonce;
+
+ /* These come from the client-final message */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ char *server_first_message;
+ char *server_nonce; /* base64-encoded */
+ char *server_signature;
+
+} scram_state;
+
+static void read_client_first_message(scram_state *state, char *input);
+static void read_client_final_message(scram_state *state, char *input);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+
+static void generate_nonce(char *out, int len);
+
+/*
+ * Initialize a new SCRAM authentication exchange, with given username and
+ * its stored verifier.
+ */
+void *
+scram_init(const char *username, const char *verifier)
+{
+ scram_state *state;
+ char *v;
+ char *p;
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ state->salt = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ state->iterations = strtol(p, &p, 10);
+ if (*p || errno != 0)
+ goto invalid_verifier;
+
+ /* storedkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->StoredKey);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ hex_decode(p, SCRAM_KEY_LEN*2, (char *) state->ServerKey);
+
+ pfree(v);
+
+ return state;
+
+invalid_verifier:
+ /* FIXME: should we keep this secret from the unauthenticated user? */
+ elog(ERROR, "invalid SCRAM verifier: %s", verifier);
+ return NULL;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ */
+int
+scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen)
+{
+ scram_state *state = (scram_state *) opaq;
+ int result;
+
+ *output = NULL;
+ *outputlen = 0;
+
+ if (inputlen > 0)
+ elog(LOG, "got SCRAM message: %s", input);
+
+ switch (state->state)
+ {
+ case INIT:
+ /* receive username and client nonce, send challenge */
+ read_client_first_message(state, input);
+ *output = build_server_first_message(state);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_CONTINUE;
+ state->state = SALT_SENT;
+ break;
+
+ case SALT_SENT:
+ /* receive response to challenge and verify it */
+ read_client_final_message(state, input);
+ if (verify_final_nonce(state) && verify_client_proof(state))
+ {
+ *output = build_server_final_message(state);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_SUCCESS;
+ }
+ else
+ {
+ result = SASL_EXCHANGE_FAILURE;
+ }
+ state->state = FINISHED;
+ break;
+
+ default:
+ elog(ERROR, "invalid SCRAM exchange state");
+ result = 0;
+ }
+
+ return result;
+}
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolverifiers.
+ *
+ * If salt is NULL, a random salt is generated. If iterations is 0, default
+ * number of iterations is used;
+ */
+char *
+scram_build_verifier(char *username, char *password, int iterations)
+{
+ uint8 keybuf[SCRAM_KEY_LEN + 1];
+ char storedkey_hex[SCRAM_KEY_LEN * 2 + 1];
+ char serverkey_hex[SCRAM_KEY_LEN * 2 + 1];
+ char salt[SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = 4096;
+
+ generate_nonce(salt, SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Client Key", keybuf);
+ scram_H(keybuf, SCRAM_KEY_LEN, keybuf); /* StoredKey */
+ (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, storedkey_hex);
+ storedkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+ /* And same for ServerKey */
+ scram_ClientOrServerKey(password, salt, SALT_LEN, iterations, "Server Key", keybuf);
+ (void) hex_encode((const char *) keybuf, SCRAM_KEY_LEN, serverkey_hex);
+ serverkey_hex[SCRAM_KEY_LEN * 2] = '\0';
+
+ return psprintf("%s:%d:%s:%s", encoded_salt, iterations, storedkey_hex, serverkey_hex);
+}
+
+
+static char *
+read_attr_value(char **input, char attr)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin != attr)
+ elog(ERROR, "malformed SCRAM message (%c expected)", attr);
+ begin++;
+
+ if (*begin != '=')
+ elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+static char *
+read_any_attr(char **input, char *attr_p)
+{
+ char *begin = *input;
+ char *end;
+ char attr = *begin;
+
+ if (!((attr >= 'A' && attr <= 'Z') ||
+ (attr >= 'a' && attr <= 'z')))
+ elog(ERROR, "malformed SCRAM message (invalid attribute char)");
+ if (attr_p)
+ *attr_p = attr;
+ begin++;
+
+ if (*begin != '=')
+ elog(ERROR, "malformed SCRAM message (expected = in attr %c)", attr);
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+ input = pstrdup(input);
+
+ /*
+ * saslname = 1*(value-safe-char / "=2C" / "=3D")
+ * ;; Conforms to <value>.
+ *
+ * authzid = "a=" saslname
+ * ;; Protocol specific.
+ *
+ * username = "n=" saslname
+ * ;; Usernames are prepared using SASLprep.
+ *
+ * gs2-cbind-flag = ("p=" cb-name) / "n" / "y"
+ * ;; "n" -> client doesn't support channel binding.
+ * ;; "y" -> client does support channel binding
+ * ;; but thinks the server does not.
+ * ;; "p" -> client requires channel binding.
+ * ;; The selected channel binding follows "p=".
+ *
+ * gs2-header = gs2-cbind-flag "," [ authzid ] ","
+ * ;; GS2 header for SCRAM
+ * ;; (the actual GS2 header includes an optional
+ * ;; flag to indicate that the GSS mechanism is not
+ * ;; "standard", but since SCRAM is "standard", we
+ * ;; don't include that flag).
+ *
+ * client-first-message-bare =
+ * [reserved-mext ","]
+ * username "," nonce ["," extensions]
+ *
+ * client-first-message =
+ * gs2-header client-first-message-bare
+ *
+ *
+ * For example:
+ * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
+ */
+
+ /* read gs2-cbind-flag */
+ switch (*input)
+ {
+ case 'n':
+ /* client does not support channel binding */
+ input++;
+ break;
+ case 'y':
+ /* client supports channel binding, but we're not doing it today */
+ input++;
+ break;
+ case 'p':
+ /* client requires channel binding. We don't support it */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("channel binding not supported")));
+ }
+
+ /* any mandatory extensions would go here. */
+ if (*input != ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("mandatory extension %c not supported", *input)));
+ input++;
+
+ /* read optional authzid (authorization identity) */
+ if (*input != ',')
+ state->client_authzid = read_attr_value(&input, 'a');
+ else
+ input++;
+
+ state->client_first_message_bare = pstrdup(input);
+
+ /* read username (FIXME: unescape) */
+ state->client_username = read_attr_value(&input, 'n');
+
+ /* read nonce */
+ state->client_nonce = read_attr_value(&input, 'r');
+
+ /*
+ * There can be any number of optional extensions after this. We don't
+ * support any extensions, so ignore them.
+ */
+ while (*input != '\0')
+ read_any_attr(&input, NULL);
+
+ /* success! */
+}
+
+static bool
+verify_final_nonce(scram_state *state)
+{
+ int client_nonce_len = strlen(state->client_nonce);
+ int server_nonce_len = strlen(state->server_nonce);
+ int final_nonce_len = strlen(state->client_final_nonce);
+
+ if (final_nonce_len != client_nonce_len + server_nonce_len)
+ return false;
+ if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+ return false;
+ if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+ return false;
+
+ return true;
+}
+
+static bool
+verify_client_proof(scram_state *state)
+{
+ uint8 ClientSignature[SCRAM_KEY_LEN];
+ uint8 ClientKey[SCRAM_KEY_LEN];
+ uint8 client_StoredKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+ int i;
+
+ /* calculate ClientSignature */
+ scram_HMAC_init(&ctx, state->StoredKey, 20);
+ 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(ClientSignature, &ctx);
+ elog(LOG, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(LOG, "AuthMessage: %s,%s,%s", state->client_first_message_bare,
+ state->server_first_message, state->client_final_message_without_proof);
+
+ /* Extract the ClientKey that the client calculated from the proof */
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+
+ /* Hash it one more time, and compare with StoredKey */
+ scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
+ elog(LOG, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(LOG, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(LOG, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ /* Also verify the nonce */
+
+ return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[NONCE_LEN];
+ int encoded_len;
+
+ /*
+ * server-first-message =
+ * [reserved-mext ","] nonce "," salt ","
+ * iteration-count ["," extensions]
+ *
+ * nonce = "r=" c-nonce [s-nonce]
+ * ;; Second part provided by server.
+ *
+ * c-nonce = printable
+ *
+ * s-nonce = printable
+ *
+ * salt = "s=" base64
+ *
+ * iteration-count = "i=" posit-number
+ * ;; A positive number.
+ *
+ * Example:
+ *
+ * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
+ */
+ generate_nonce(nonce, NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, NONCE_LEN, state->server_nonce);
+
+ state->server_nonce[encoded_len] = '\0';
+ state->server_first_message =
+ psprintf("r=%s%s,s=%s,i=%u",
+ state->client_nonce, state->server_nonce,
+ state->salt, state->iterations);
+
+ return state->server_first_message;
+}
+
+static void
+read_client_final_message(scram_state *state, char *input)
+{
+ char attr;
+ char *channel_binding;
+ char *value;
+ char *begin, *proof;
+ char *p;
+ char *client_proof;
+
+ begin = p = pstrdup(input);
+
+ /*
+ *
+ * cbind-input = gs2-header [ cbind-data ]
+ * ;; cbind-data MUST be present for
+ * ;; gs2-cbind-flag of "p" and MUST be absent
+ * ;; for "y" or "n".
+ *
+ * channel-binding = "c=" base64
+ * ;; base64 encoding of cbind-input.
+ *
+ * proof = "p=" base64
+ *
+ * client-final-message-without-proof =
+ * channel-binding "," nonce ["," extensions]
+ *
+ * client-final-message =
+ * client-final-message-without-proof "," proof
+ */
+ channel_binding = read_attr_value(&p, 'c');
+ if (strcmp(channel_binding, "biws") != 0)
+ elog(ERROR, "invalid channel binding input");
+ state->client_final_nonce = read_attr_value(&p, 'r');
+
+ /* ignore optional extensions */
+ do
+ {
+ proof = p - 1;
+ value = read_any_attr(&p, &attr);
+ } while (attr != 'p');
+
+ client_proof = palloc(b64_dec_len(value, strlen(value)));
+ if (b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+ elog(ERROR, "invalid ClientProof");
+ memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ pfree(client_proof);
+
+ if (*p != '\0')
+ elog(ERROR, "malformed SCRAM message (garbage at end of message %c)", attr);
+
+ state->client_final_message_without_proof = palloc(proof - begin + 1);
+ memcpy(state->client_final_message_without_proof, input, proof - begin);
+ state->client_final_message_without_proof[proof - begin] = '\0';
+
+ /* FIXME: check channel_binding field */
+}
+
+
+static char *
+build_server_final_message(scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ char *server_signature_base64;
+ int siglen;
+ scram_HMAC_ctx ctx;
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, state->ServerKey, 20);
+ 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);
+
+ server_signature_base64 = palloc(b64_enc_len((const char *) ServerSignature, SCRAM_KEY_LEN) + 1);
+ siglen = b64_encode((const char *) ServerSignature, SCRAM_KEY_LEN, server_signature_base64);
+ server_signature_base64[siglen] = '\0';
+
+ /*
+ *
+ * server-error = "e=" server-error-value
+ *
+ * server-error-value = "invalid-encoding" /
+ * "extensions-not-supported" / ; unrecognized 'm' value
+ * "invalid-proof" /
+ * "channel-bindings-dont-match" /
+ * "server-does-support-channel-binding" /
+ * ; server does not support channel binding
+ * "channel-binding-not-supported" /
+ * "unsupported-channel-binding-type" /
+ * "unknown-user" /
+ * "invalid-username-encoding" /
+ * ; invalid username encoding (invalid UTF-8 or
+ * ; SASLprep failed)
+ * "no-resources" /
+ * "other-error" /
+ * server-error-value-ext
+ * ; Unrecognized errors should be treated as "other-error".
+ * ; In order to prevent information disclosure, the server
+ * ; may substitute the real reason with "other-error".
+ *
+ * server-error-value-ext = value
+ * ; Additional error reasons added by extensions
+ * ; to this document.
+ *
+ * verifier = "v=" base64
+ * ;; base-64 encoded ServerSignature.
+ *
+ * server-final-message = (server-error / verifier)
+ * ["," extensions]
+ */
+ return psprintf("v=%s", server_signature_base64);
+}
+
+static void
+generate_nonce(char *result, int len)
+{
+ /*
+ * TODO: We reuse the salt generated for MD5 authentication. It's only
+ * four bytes - we'd really want to use a much longer salt.
+ */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->md5Salt, Min(sizeof(MyProcPort->md5Salt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 81226c2..54151cb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,6 +27,7 @@
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -185,6 +186,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -246,6 +253,7 @@ auth_failed(Port *port, int status, char *logdetail)
break;
case uaPassword:
case uaMD5:
+ case uaSASL:
errstr = gettext_noop("password authentication failed for user \"%s\"");
/* We use it to indicate if a .pgpass password failed. */
errcode_return = ERRCODE_INVALID_PASSWORD;
@@ -523,6 +531,10 @@ ClientAuthentication(Port *port)
status = recv_and_check_password_packet(port, &logdetail);
break;
+ case uaSASL:
+ status = CheckSASLAuth(port, &logdetail);
+ break;
+
case uaPAM:
#ifdef USE_PAM
status = CheckPAMAuth(port, port->user_name, "");
@@ -691,6 +703,96 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ char *verifier;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+
+ /*
+ * SASL auth is not supported for protocol versions before 3, because it
+ * relies on the overall message length word to determine the SASL payload
+ * size in AuthenticationSASLContinue and PasswordMessage messages. (We
+ * used to have a hard rule that protocol messages must be parsable
+ * without relying on the length word, but we hardly care about protocol
+ * version or older anymore.)
+ *
+ * FIXME: the FE/BE docs need to updated.
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* lookup verifier */
+ verifier = get_role_verifier(port->user_name, "scram", logdetail);
+ if (verifier == NULL)
+ return STATUS_ERROR;
+
+ sendAuthRequest(port, AUTH_REQ_SASL, "SCRAM-SHA-1", strlen("SCRAM-SHA-1") + 1);
+
+ scram_opaq = scram_init(port->user_name, verifier);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messags sent in both directions. First message is always from
+ * the client. All messages from client to server are password packets
+ * (type 'p').
+ */
+ do
+ {
+ pq_startmsgread();
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /* Only log error if client didn't disconnect. */
+ if (mtype != EOF)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("expected SASL response, got message type %d",
+ mtype)));
+ return STATUS_ERROR;
+ }
+
+ /* Get the actual SASL token */
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+ {
+ /* EOF - pq_getmessage already logged error */
+ pfree(buf.data);
+ return STATUS_ERROR;
+ }
+
+ elog(DEBUG4, "Processing received SASL token of length %d", buf.len);
+
+ result = scram_exchange(scram_opaq, buf.data, buf.len,
+ &output, &outputlen);
+
+ /* input buffer no longer used */
+ pfree(buf.data);
+
+ if (outputlen > 0)
+ {
+ /*
+ * Negotiation generated data to be sent to the client.
+ */
+ elog(DEBUG4, "sending SASL response token of length %u", outputlen);
+
+ sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
+ }
+ } while (result == SASL_EXCHANGE_CONTINUE);
+
+ return (result == SASL_EXCHANGE_SUCCESS) ? STATUS_OK : STATUS_ERROR;
+}
/*----------------------------------------------------------------
@@ -891,8 +993,8 @@ pg_GSS_recvauth(Port *port)
(unsigned int) port->gss->outbuf.length);
/*
- * Add the authentication data for the next step of the GSSAPI or
- * SSPI negotiation.
+ * Add the authentication data for the next step of the GSSAPI
+ * negotiation.
*/
elog(DEBUG4, "sending GSS token of length %u",
(unsigned int) port->gss->outbuf.length);
@@ -1142,8 +1244,8 @@ pg_SSPI_recvauth(Port *port)
port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
/*
- * Add the authentication data for the next step of the GSSAPI or
- * SSPI negotiation.
+ * Add the authentication data for the next step of the SSPI
+ * negotiation.
*/
elog(DEBUG4, "sending GSS token of length %u",
(unsigned int) port->gss->outbuf.length);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index a0f5396..31bd78d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,20 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ /* FIXME: could we support Db_user_namespace with SCRAM? */
+ if (Db_user_namespace)
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("SCRAM authentication is not supported when \"db_user_namespace\" is enabled"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ return NULL;
+ }
+ parsedline->auth_method = uaSASL;
+ }
else if (strcmp(token->string, "pam") == 0)
#ifdef USE_PAM
parsedline->auth_method = uaPAM;
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 86a89ed..dc3ce2f 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,7 +42,7 @@
# or "samenet" to match any address in any subnet that the server is
# directly connected to.
#
-# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi",
+# METHOD can be "trust", "reject", "md5", "password", "scram", "gss", "sspi",
# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
# "password" sends passwords in clear text; "md5" is preferred since
# it sends encrypted passwords.
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index 4b32b6c..c414e86 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -214,7 +214,7 @@ static const int8 b64lookup[128] = {
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
};
-static unsigned
+unsigned
b64_encode(const char *src, unsigned len, char *dst)
{
char *p,
@@ -261,7 +261,7 @@ b64_encode(const char *src, unsigned len, char *dst)
return p - dst;
}
-static unsigned
+unsigned
b64_decode(const char *src, unsigned len, char *dst)
{
const char *srcend = src + len,
@@ -331,14 +331,14 @@ b64_decode(const char *src, unsigned len, char *dst)
}
-static unsigned
+unsigned
b64_enc_len(const char *src, unsigned srclen)
{
/* 3 bytes will be converted to 4, linefeed after 76 chars */
return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
}
-static unsigned
+unsigned
b64_dec_len(const char *src, unsigned srclen)
{
return (srclen * 3) >> 2;
diff --git a/src/common/Makefile b/src/common/Makefile
index f526b42..5b1b8ac 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -24,7 +24,7 @@ override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LIBS += $(PTHREAD_LIBS)
OBJS_COMMON = exec.o pg_crc.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o sha1.o string.o username.o wait_error.o
+ rmtree.o scram-common.o sha1.o string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..714fd30
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,161 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechansim (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-1.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA1_HMAC_B];
+ int i;
+ uint8 keybuf[SHA1_RESULTLEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-1),
+ * pass it through SHA-1 once to shrink it down
+ */
+ if (keylen > SHA1_HMAC_B)
+ {
+ SHA1_CTX sha1_ctx;
+
+ SHA1Init(&sha1_ctx);
+ SHA1Update(&sha1_ctx, key, keylen);
+ SHA1Final(keybuf, &sha1_ctx);
+ key = keybuf;
+ keylen = SHA1_RESULTLEN;
+ }
+
+ memset(k_ipad, 0x36, SHA1_HMAC_B);
+ memset(ctx->k_opad, 0x5C, SHA1_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) */
+ SHA1Init(&ctx->sha1ctx);
+ SHA1Update(&ctx->sha1ctx, k_ipad, SHA1_HMAC_B);
+}
+
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const const char *str, int slen)
+{
+ SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
+
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SHA1_RESULTLEN];
+
+ SHA1Final(h, &ctx->sha1ctx);
+
+ /* H(K XOR opad, tmp) */
+ SHA1Init(&ctx->sha1ctx);
+ SHA1Update(&ctx->sha1ctx, ctx->k_opad, SHA1_HMAC_B);
+ SHA1Update(&ctx->sha1ctx, h, SHA1_RESULTLEN);
+ SHA1Final(result, &ctx->sha1ctx);
+}
+
+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-1 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)
+{
+ SHA1_CTX ctx;
+
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, input, len);
+ SHA1Final(result, &ctx);
+}
+
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /* TODO */
+ strlcpy(result, password, 20);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ /* FIXME: pwbuf needs to be sized properly */
+ char pwbuf[20];
+
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+}
+
+/*
+ * 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, 20);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..fce9337
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2015, 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/sha1.h"
+
+#define SCRAM_KEY_LEN SHA1_RESULTLEN
+
+#define SHA1_HMAC_B 64
+
+typedef struct
+{
+ SHA1_CTX sha1ctx;
+ uint8 k_opad[SHA1_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
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..4469565 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -22,6 +22,11 @@ extern char *pg_krb_realm;
extern void ClientAuthentication(Port *port);
+/* Return codes for SASL authentication functions */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS 1
+#define SASL_EXCHANGE_FAILURE 2
+
/* Hook for plugins to get control in ClientAuthentication() */
typedef void (*ClientAuthentication_hook_type) (Port *, int);
extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index dfab8f3..77a388e 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,7 @@
#include "libpq/libpq-be.h"
+extern char *get_role_verifier(const char *rolname, const char *method, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 68a953a..a73d2f9 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -24,6 +24,7 @@ typedef enum UserAuth
uaIdent,
uaPassword,
uaMD5,
+ uaSASL,
uaGSS,
uaSSPI,
uaPAM,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 639bf72..462a5dd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -172,6 +172,8 @@ extern bool Db_user_namespace;
#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */
#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */
#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */
+#define AUTH_REQ_SASL 10 /* SASL */
+#define AUTH_REQ_SASL_CONT 11 /* continue SASL exchange */
typedef uint32 AuthRequest;
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
new file mode 100644
index 0000000..f273392
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, 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
+
+extern void *scram_init(const char *username, const char *verifier);
+extern int scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen);
+extern char *scram_build_verifier(char *username, char *password,
+ int iterations);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 6310641..cad9852 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -157,6 +157,10 @@ extern Datum binary_encode(PG_FUNCTION_ARGS);
extern Datum binary_decode(PG_FUNCTION_ARGS);
extern unsigned hex_encode(const char *src, unsigned len, char *dst);
extern unsigned hex_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_encode(const char *src, unsigned len, char *dst);
+extern unsigned b64_decode(const char *src, unsigned len, char *dst);
+extern unsigned b64_enc_len(const char *src, unsigned srclen);
+extern unsigned b64_dec_len(const char *src, unsigned srclen);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 6973a20..4eae326 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,7 +31,7 @@ LIBS := $(LIBS:-lpgport=)
# We can't use Makefile variables here because the MSVC build system scrapes
# OBJS from this file.
-OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
+OBJS= fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
libpq-events.o
# libpgport C files we always use
@@ -43,6 +43,9 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
OBJS += ip.o md5.o
# utils/mb
OBJS += encnames.o wchar.o
+# common/
+# FIXME: any reason not to link with libpgcommon?
+OBJS += scram-common.o sha1.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -102,6 +105,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+scram-common.c sha1.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
distprep: libpq-dist.rc
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
new file mode 100644
index 0000000..9165a82
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,476 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2015, 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/scram-common.h"
+#include "fe-auth.h"
+
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+
+ /* These come from the server-first message */
+ char *server_first_message;
+ char *salt;
+ int saltlen;
+ int iterations;
+ char *server_nonce;
+
+ /* These come from the server-final message */
+ char *server_proof;
+} 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);
+static char *build_client_first_message(fe_scram_state *state);
+static char *build_client_final_message(fe_scram_state *state);
+static bool verify_server_proof(fe_scram_state *state);
+static void generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state, const char *client_final_message_without_proof, uint8 *result);
+
+static unsigned b64_decode(const char *src, unsigned len, char *dst);
+static unsigned b64_encode(const char *src, unsigned len, char *dst);
+static unsigned b64_enc_len(const char *src, unsigned srclen);
+static unsigned b64_dec_len(const char *src, unsigned srclen);
+
+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 = INIT;
+ state->username = username;
+ state->password = password;
+
+ return state;
+}
+
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+ if (state->server_proof)
+ free(state->server_proof);
+
+ free(state);
+}
+
+/*
+ * TODO: Even though SCRAM will authenticate the server, by verifying the
+ * server-proof, we don't currently store that information anywhere. Nothing
+ * stops the server from leaving the SASL exchange unfinished, and just send an
+ * AuthenticationOK message, leaving the client unsure of the server's
+ * identity.
+ */
+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;
+
+ switch (state->state)
+ {
+ case INIT:
+ /* send client nonce */
+ *output = build_client_first_message(state);
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = NONCE_SENT;
+ break;
+
+ case NONCE_SENT:
+ /* receive salt and server nonce, send response */
+ read_server_first_message(state, input, errorMessage);
+ *output = build_client_final_message(state);
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = PROOF_SENT;
+ break;
+
+ case PROOF_SENT:
+ /* receive server proof, and verify it */
+ read_server_final_message(state, input);
+ *success = verify_server_proof(state);
+ *done = true;
+ state->state = FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ *done = true;
+ *success = false;
+ printfPQExpBuffer(errorMessage, "invalid SCRAM exchange state");
+ }
+}
+
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin != attr)
+ printfPQExpBuffer(errorMessage, "malformed SCRAM message (%c expected)", attr);
+ begin++;
+
+ if (*begin != '=')
+ printfPQExpBuffer(errorMessage, "malformed SCRAM message (expected = in attr %c)", attr);
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[10 + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, 10);
+
+ msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+
+ buf = malloc(msglen + 1);
+ snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+
+ state->client_first_message_bare = strdup(buf + 3);
+ if (!state->client_first_message_bare)
+ return NULL;
+
+ return buf;
+}
+
+static bool
+read_server_first_message(fe_scram_state *state, char *input, PQExpBuffer errormessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+
+ state->server_first_message = strdup(input);
+ if (!state->server_first_message)
+ return false;
+
+ /* parse the message */
+ state->server_nonce = strdup(read_attr_value(&input, 'r', errormessage));
+ if (state->server_nonce == NULL)
+ return false;
+
+ encoded_salt = read_attr_value(&input, 's', errormessage);
+ if (encoded_salt == NULL)
+ return false;
+ state->salt = malloc(b64_dec_len(encoded_salt, strlen(encoded_salt)));
+ if (state->salt == NULL)
+ return false;
+ state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+ if (state->saltlen <= 0)
+ return false;
+
+ iterations_str = read_attr_value(&input, 'i', errormessage);
+ if (iterations_str == NULL)
+ return false;
+ state->iterations = strtol(iterations_str, &endptr, 10);
+ if (*endptr != '\0')
+ return false;
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+static bool
+read_server_final_message(fe_scram_state *state, char *input)
+{
+ /* TODO: verify ServerSignature */
+ return true;
+}
+
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+ char client_final_message_without_proof[200];
+ uint8 client_proof[SCRAM_KEY_LEN];
+ char client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+ int client_proof_len;
+ char buf[300];
+
+ snprintf(client_final_message_without_proof, sizeof(client_final_message_without_proof),
+ "c=biws,r=%s", state->server_nonce);
+
+ calculate_client_proof(state, client_final_message_without_proof, client_proof);
+ if (b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ return NULL;
+
+ client_proof_len = b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+ client_proof_base64[client_proof_len] = '\0';
+
+ snprintf(buf, sizeof(buf), "%s,p=%s", client_final_message_without_proof, client_proof_base64);
+ fprintf(stderr, "final_msg: %s", buf);
+
+ return strdup(buf);
+}
+
+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, "Client Key", ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, 20);
+ 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);
+
+ fprintf(stderr, "ClientSignature: %02X%02X\n", ClientSignature[0], ClientSignature[1]);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ /* TODO */
+ return true;
+}
+
+
+/*
+ * TODO: We need a source of randomness. libpq doesn't currently have one.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ {
+ buf[i] = 'x';
+ }
+ buf[i] = '\0';
+}
+
+
+/*
+ * 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,
+};
+
+static unsigned
+b64_encode(const char *src, unsigned len, char *dst)
+{
+ char *p,
+ *lend = dst + 76;
+ 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 (p >= lend)
+ {
+ *p++ = '\n';
+ lend = p + 76;
+ }
+ }
+ if (pos != 2)
+ {
+ *p++ = _base64[(buf >> 18) & 0x3f];
+ *p++ = _base64[(buf >> 12) & 0x3f];
+ *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+ *p++ = '=';
+ }
+
+ return p - dst;
+}
+
+static unsigned
+b64_decode(const char *src, unsigned 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++;
+
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ continue;
+
+ if (c == '=')
+ {
+ /* end sequence */
+ if (!end)
+ {
+ if (pos == 2)
+ end = 1;
+ else if (pos == 3)
+ end = 2;
+ else
+ {
+ return 0;
+ //ereport(ERROR,
+ // (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ // errmsg("unexpected \"=\" while decoding base64 sequence")));
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+ //ereport(ERROR,
+ // (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ // errmsg("invalid symbol '%c' while decoding base64 sequence", (int) c)));
+ return 0;
+ }
+ }
+ /* 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)
+ {
+ //ereport(ERROR,
+ // (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ // errmsg("invalid base64 end sequence"),
+ // errhint("Input data is missing padding, truncated, or otherwise corrupted.")));
+ return 0;
+ }
+
+ return p - dst;
+}
+
+
+static unsigned
+b64_enc_len(const char *src, unsigned srclen)
+{
+ /* 3 bytes will be converted to 4, linefeed after 76 chars */
+ return (srclen + 2) * 4 / 3 + srclen / (76 * 3 / 4);
+}
+
+static unsigned
+b64_dec_len(const char *src, unsigned srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 8927df4..24c8d4a 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -428,6 +428,74 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
}
#endif /* ENABLE_SSPI */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-1 is supported at
+ * the moment.)
+ */
+ if (strcmp(conn->auth_req_inbuf, "SCRAM-SHA-1") == 0)
+ {
+ conn->password_needed = true;
+ if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ PQnoPasswordSupplied);
+ return STATUS_ERROR;
+ }
+ conn->sasl_state = pg_fe_scram_init(conn->pguser, conn->pgpass);
+ if (!conn->sasl_state)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return STATUS_ERROR;
+ }
+ else
+ return STATUS_OK;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SASL authentication mechanism %s not supported\n"),
+ (char *) conn->auth_req_inbuf);
+ return STATUS_ERROR;
+ }
+}
+
+static int
+pg_SASL_exchange(PGconn *conn)
+{
+ char *output;
+ int outputlen;
+ bool done;
+ bool success;
+ int res;
+
+ pg_fe_scram_exchange(conn->sasl_state,
+ conn->auth_req_inbuf, conn->auth_req_inlen,
+ &output, &outputlen,
+ &done, &success, &conn->errorMessage);
+ if (outputlen != 0)
+ {
+ /*
+ * Send the SASL response to the server. We don't care if it's the
+ * first or subsequent packet, just send the same kind of password
+ * packet.
+ */
+ res = pqPacketSend(conn, 'p', output, outputlen);
+ free(output);
+
+ if (res != STATUS_OK)
+ return STATUS_ERROR;
+ }
+
+ if (done && !success)
+ return STATUS_ERROR;
+
+ return STATUS_OK;
+}
+
/*
* Respond to AUTH_REQ_SCM_CREDS challenge.
*
@@ -696,6 +764,33 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
}
break;
+ case AUTH_REQ_SASL:
+ /*
+ * The request contains the name (as assigned by IANA) of the
+ * authentication mechanism.
+ */
+ if (pg_SASL_init(conn, conn->auth_req_inbuf) != STATUS_OK)
+ {
+ /* pg_SASL_init already set the error message */
+ return STATUS_ERROR;
+ }
+ /* fall through */
+
+ case AUTH_REQ_SASL_CONT:
+ if (conn->sasl_state == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
+ return STATUS_ERROR;
+ }
+ if (pg_SASL_exchange(conn) != STATUS_OK)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ "fe_sendauth: error sending password authentication\n");
+ return STATUS_ERROR;
+ }
+ break;
+
case AUTH_REQ_SCM_CREDS:
if (pg_local_sendauth(conn) != STATUS_OK)
return STATUS_ERROR;
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..b1b0294 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -18,7 +18,15 @@
#include "libpq-int.h"
+/* Prototypes for functions in fe-auth.c */
extern int pg_fe_sendauth(AuthRequest areq, PGconn *conn);
extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
+/* Prototypes for functions in fe-auth-scram.c */
+extern void *pg_fe_scram_init(const char *username, const char *password);
+extern void pg_fe_scram_free(void *opaq);
+extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen,
+ bool *done, bool *success, PQExpBuffer errorMessage);
+
#endif /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e2a06b3..c14fe73 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2479,6 +2479,46 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload, if any */
+ if (msgLength > 4)
+ {
+ int llen = msgLength - 4;
+
+ /*
+ * We can be called repeatedly for the same buffer. Avoid
+ * re-allocating the buffer in this case - just re-use the
+ * old buffer.
+ */
+ if (llen != conn->auth_req_inlen)
+ {
+ if (conn->auth_req_inbuf)
+ {
+ free(conn->auth_req_inbuf);
+ conn->auth_req_inbuf = NULL;
+ }
+
+ conn->auth_req_inlen = llen;
+ conn->auth_req_inbuf = malloc(llen + 1);
+ if (!conn->auth_req_inbuf)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory allocating GSSAPI buffer (%d)"),
+ llen);
+ goto error_return;
+ }
+ }
+
+ if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
+ {
+ /* We'll come back when there is more data. */
+ return PGRES_POLLING_READING;
+ }
+ /*
+ * For safety and convenience, always ensure the in-buffer
+ * is NULL-terminated.
+ */
+ conn->auth_req_inbuf[llen] = '\0';
+ }
/*
* OK, we successfully read the message; mark data consumed
@@ -3035,6 +3075,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if we add support for more authentication mechanisms, this
+ * needs to call the right 'free' function.
+ */
+ pg_fe_scram_free(conn->sasl_state);
+ conn->sasl_state = NULL;
+ }
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 2175957..391192b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -421,7 +421,12 @@ struct pg_conn
PGresult *result; /* result being constructed */
PGresult *next_result; /* next result (used in single-row mode) */
+ /* Buffer to hold incoming authentication request data */
+ char *auth_req_inbuf;
+ int auth_req_inlen;
+
/* Assorted state for SSL, GSS, etc */
+ void *sasl_state;
#ifdef USE_SSL
bool allow_ssl_try; /* Allowed to try SSL negotiation */
--
2.1.4
view thread (77+ messages) latest in thread
Message-ID: <55192AFE.6080106@iki.fi>
Permalink: ../55192AFE.6080106@iki.fi/
Also on: postgresql.org/message-id/55192AFE.6080106@iki.fi
copy link · copy postgr.es · copy git am
reply Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: hlinnaka@iki.fi
Subject: Re: WIP: SCRAM authentication
In-Reply-To: <55192AFE.6080106@iki.fi>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox