Password identifiers, protocol aging and SCRAM protocol
Hi all
As a continuation of the thread firstly dedicated to SCRAM:
/messages/by-id/55192AFE.6080106@iki.fi
Here is a new thread aimed at gathering all the ideas of this previous
thread and aimed at clarifying a bit what has been discussed until now
regarding password protocols, verifiers, and SCRAM itself.
Attached is a set of patches implementing a couple of things that have
been discussed, so let's roll in. There are a couple of concepts that
are introduced in this set of patches, and those patches are aimed at
resolving the following things:
- Introduce in Postgres an extensible password aging facility, by
having a new concept of 1 user/multiple password verifier, one
password verifier per protocol.
- Give to system administrators tools to decide unsupported protocols,
and have pg_upgrade use that
- Introduce new password protocols for Postgres, aimed at replacing
existing, say limited ones.
Note that here is not discussed the point of password verifier
rolling, which is the possibility to have multiple verifiers of the
same protocol for the same user (this maps with the fact that
valid_until is still part of pg_authid here, but in order to support
authentication rolling it would be necessary to move it to
pg_auth_verifiers).
Here is a short description of each patch and what they do:
1) 0001, removing the password column from pg_authid and putting it
into a new catalog called pg_auth_verifiers that has the following
format:
- Role OID
- Password protocol
- Password verifier
The protocols proposed in this patch are "plain" and "md5", which map
to the current things that Postgres has, so there is nothing new. What
is new is the new clause PASSWORD VERIFIERS usable by CREATE/ALTER
USER, like that:
ALTER ROLE foo PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo');
This is easily extensible as new protocols can be added on top of
that. This has been discussed in the previous thread.
As discussed as well previously, password_encryption is switched from
a boolean switch to a list of protocols, which is md5 by default in
this patch.
Also, as discussed in 6174.1455501497@sss.pgh.pa.us, pg_shadow has
been changed so as the password value is replaced by '*****'.
This patch adds docs, regression tests, pg_dump support, etc.
2) 0002, introduction of a new GUC parameter password_protocols
(superuser-only) aimed at controlling the password verifiers of
protocols that can be created. This is quite simple: all the protocols
specified in this list define what are the protocols allowed when
creating password verifiers using CREATE/ALTER ROLE. By default, and
in this patch, this is set to 'plain,md5', which is the current
default in Postgres, though a system admin could set it to 'md5', to
forbid the creation of unencrypted passwords for example. Docs and
regressions are added on the stack, the regression tests taking
advantage of the fact that this is a superuser parameters.
This patch is an answer to remarks done in the last thread regarding
the fact that there is no way to handle how a system controls what are
the password verifier types created, and protocol aging gets its sense
with with patch and 0003...
3) 0003, Introduction of a system function, that I called
pg_auth_verifiers_sanitize, which is superuser-only, aimed at cleaning
up password verifiers in pg_auth_verifiers depending on what the user
has defined in password_protocols. This basically does a heap scan of
pg_auth_verifiers, and deletes the tuple entries that are of protocols
not listed in password_protocols. I have hesitated to put that in
pg_upgrade_support.c, perhaps it would make more sense to have it
there, but feedback is welcome. I have in mind that it is actually
useful for users to have this function at hand to do post-upgrade
cleanup operations. Regression tests cannot be added for this one, I
guess the reason to not have them is obvious when considering
installcheck...
4) 0004, Have pg_upgrade make use of the system function introduced by
0003. This is quite simple, and this allows pg_upgrade to remove
entries of outdated protocols.
Those 4 patches are aimed at putting in-core basics for the concept I
call password protocol aging, which is a way to allow multiple
password protocols to be defined in Postgres, and aimed at easing
administration as well as retirement of outdated protocols, which is
something that is not doable now in Postgres.
The second set of patch 0005~0008 introduces a new protocol, SCRAM.
This is a brushed up, rebased version of the previous patches, and is
divided as follows:
5) 0005, Move of SHA1 routines of pgcrypto to src/common to allow
frontend authentication code path to use SHA1.
6) 0006 is a refactoring of sendAuthRequest that taken independently
makes sense.
7) 0007 is a small refactoring of RandomSalt(), to allow this function
to handle salt values of different lengths
8) 0008 is another refactoring, moving a set of encoding routines from
the backend's encode.c to src/common, escape, base64 and hex are moved
as such, though SCRAM uses only base64. For consistency moving all the
set made more sense to me.
9) 0009 is the SCRAM authentication itself....
The first 4 patches obviously are the core portion that I would like
to discuss about in this CF, as they put in the base for the rest, and
will surely help Postgres long-term. 0005~0008 are just refactoring
patches, so they are quite simple. 0009 though is quite difficult, and
needs careful review because it manipulates areas of the code where it
is not necessary to be an authenticated user, so if there are bugs in
it it would be possible for example to crash down Postgres just by
sending authentication requests.
Regards,
--
Michael
Attachments:
0001-Add-facility-to-store-multiple-password-verifiers.patchtext/x-patch; charset=US-ASCII; name=0001-Add-facility-to-store-multiple-password-verifiers.patchDownload
From a3bff38400697a3038ec9fe00e96d7972899f28b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:39:41 +0900
Subject: [PATCH 1/9] Add facility to store multiple password verifiers
This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
able to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.
CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.
password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.
pg_shadown, which had up to now the concept of storing the password plain
or md5 value is now clobbered. Users and client applications are advised to
switch to pg_auth_verifiers instead.
The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.
Extra facility for protocol aging and upgrades will be added in a different
commit.
Regression tests and documentation are added accordingly.
---
contrib/passwordcheck/passwordcheck.c | 138 +++++------
doc/src/sgml/catalogs.sgml | 103 ++++++---
doc/src/sgml/config.sgml | 17 +-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/catalog.c | 4 +
src/backend/catalog/system_views.sql | 2 +-
src/backend/commands/user.c | 318 +++++++++++++++++---------
src/backend/libpq/crypt.c | 72 ++++--
src/backend/nodes/copyfuncs.c | 14 ++
src/backend/nodes/equalfuncs.c | 12 +
src/backend/parser/gram.y | 98 +++++++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 23 +-
src/backend/utils/cache/syscache.c | 23 ++
src/backend/utils/misc/guc.c | 65 ++++--
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/bin/initdb/initdb.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 77 ++++++-
src/include/catalog/indexing.h | 5 +
src/include/catalog/pg_auth_verifiers.h | 62 +++++
src/include/catalog/pg_authid.h | 8 +-
src/include/commands/user.h | 11 +-
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 11 +
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/password.out | 105 +++++++++
src/test/regress/expected/roleattributes.out | 192 ++++++++--------
src/test/regress/expected/rules.out | 2 +-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 72 ++++++
34 files changed, 1110 insertions(+), 367 deletions(-)
create mode 100644 src/include/catalog/pg_auth_verifiers.h
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index b4c1ce0..13ad053 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
#include <crack.h>
#endif
+#include "catalog/pg_auth_verifiers.h"
#include "commands/user.h"
#include "fmgr.h"
#include "libpq/md5.h"
+#include "nodes/parsenodes.h"
PG_MODULE_MAGIC;
@@ -50,87 +52,93 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ List *passwordVerifiers,
Datum validuntil_time,
bool validuntil_null)
{
int namelen = strlen(username);
- int pwdlen = strlen(password);
+ int pwdlen;
char encrypted[MD5_PASSWD_LEN + 1];
int i;
bool pwd_has_letter,
pwd_has_nonletter;
+ ListCell *l;
- switch (password_type)
+ foreach(l, passwordVerifiers)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
- /*
- * For unencrypted passwords we can perform better checks
- */
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ switch (spec->veriftype)
+ {
+ case AUTH_VERIFIER_MD5:
+
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ if (!pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ if (strcmp(spec->value, encrypted) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ break;
+
+ case AUTH_VERIFIER_PLAIN:
+
/*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
+ * For unencrypted passwords we can perform better checks
*/
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must contain both letters and nonletters")));
+ pwdlen = strlen(spec->value);
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(spec->value, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
+ /*
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
+ */
+ if (isalpha((unsigned char) spec->value[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
+ break;
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
+ default:
+ elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+ break;
+ }
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d77e999..0faad9d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1161,13 +1161,6 @@
</para>
<para>
- Since this catalog contains passwords, it must not be publicly readable.
- <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
- is a publicly readable view on
- <structname>pg_authid</structname> that blanks out the password field.
- </para>
-
- <para>
<xref linkend="user-manag"> contains detailed information about user and
privilege management.
</para>
@@ -1270,21 +1263,6 @@
</row>
<row>
- <entry><structfield>rolpassword</structfield></entry>
- <entry><type>text</type></entry>
- <entry>
- Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
- </entry>
- </row>
-
- <row>
<entry><structfield>rolvaliduntil</structfield></entry>
<entry><type>timestamptz</type></entry>
<entry>Password expiry time (only used for password authentication);
@@ -1296,6 +1274,77 @@
</sect1>
+ <sect1 id="catalog-pg-auth-verifiers">
+ <title><structname>pg_auth_verifiers</structname></title>
+
+ <indexterm zone="catalog-pg-auth-verifiers">
+ <primary>pg_auth_verifiers</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_auth_verifiers</structname> contains password
+ information for database authorization identifiers (roles).
+ </para>
+
+ <para>
+ Since this catalog contains passwords, it must not be publicly readable.
+ </para>
+
+ <para>
+ Because user identities are cluster-wide,
+ <structname>pg_auth_verifiers</structname>
+ is shared across all databases of a cluster: there is only one
+ copy of <structname>pg_auth_verifiers</structname> per cluster, not
+ one per database.
+ </para>
+
+ <table>
+ <title><structname>pg_auth_verifiers</> Columns</title>
+
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>oid</structfield></entry>
+ <entry><type>roleid</type></entry>
+ <entry>Role identifier OID</entry>
+ </row>
+
+ <row>
+ <entry><structfield>verimet</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry>
+ <literal>p</> = plain format,
+ <literal>m</> = MD5-encrypted
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>text</structfield></entry>
+ <entry><type>verival</type></entry>
+ <entry>
+ Password (possibly encrypted with format defined in
+ <structfield>verimet</>). If the password
+ is MD5-encrypted, this column will begin with the string <literal>md5</>
+ followed by a 32-character hexadecimal MD5 hash. The MD5 hash
+ will be of the user's password concatenated to their user name.
+ For example, if user <literal>joe</> has password <literal>xyzzy</>,
+ <productname>PostgreSQL</> will store the md5 hash of
+ <literal>xyzzyjoe</>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
<sect1 id="catalog-pg-auth-members">
<title><structname>pg_auth_members</structname></title>
@@ -9285,9 +9334,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry><structfield>passwd</structfield></entry>
<entry><type>text</type></entry>
<entry></entry>
- <entry>Password (possibly encrypted); null if none. See
- <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>
- for details of how encrypted passwords are stored.</entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
@@ -9757,9 +9804,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</row>
<row>
- <entry><structfield>passwd</structfield></entry>
- <entry><type>text</type></entry>
- <entry>Not the password (always reads as <literal>********</>)</entry>
+ <entry><structfield>passwd</structfield></entry>
+ <entry><type>text</type></entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a09ceb2..226d51d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>string</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
+ Specifies a comma-separated list of password encryption formats.
+ Supported formats are <literal>plain</> and <literal>md5</>.
+ </para>
+
+ <para>
When a password is specified in <xref
linkend="sql-createuser"> or
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
- <literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ <literal>UNENCRYPTED</>, this parameter determines the list of
+ encryption formats this password is to be stored as.
+ </para>
+
+ <para>
+ The default is <literal>md5</> (encrypt the password with MD5
+ encryption).
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..513e5b7 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -211,9 +212,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
roles having the <literal>LOGIN</literal> attribute, but you
can nonetheless define one for roles without it.) If you do
not plan to use password authentication you can omit this
- option. If no password is specified, the password will be set
- to null and password authentication will always fail for that
- user. A null password can optionally be written explicitly as
+ option. If no password is specified, no password will be set
+ and password authentication will always fail for that user.
+ A null password can optionally be written explicitly as
<literal>PASSWORD NULL</literal>.
</para>
</listitem>
@@ -245,6 +246,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term>PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>'</term>
+ <listitem>
+ <para>
+ Sets the list of password verifiers for the role. Currently only
+ <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+ for unencrypted format, can be specified as verifier format type
+ for <literal>verifier_type</>. If the password defined with
+ <literal>plain</> type is already in MD5-encrypted format
+ the password will be stored as MD5-encrypted. If the password defined
+ is in plain-format and that <literal>verifier_type</> is set
+ to <literal>md5</>, the password will be stored as MD5-encrypted.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
- pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+ pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+ pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index bead2c1..7406d2f 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthVerifRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes (see indexing.h) */
if (relationId == AuthIdRolnameIndexId ||
relationId == AuthIdOidIndexId ||
+ relationId == AuthVerifRoleMethodIndexId ||
+ relationId == AuthVerifMethodRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index abf9a70..7344c7b 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,7 +40,7 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ '********'::text as passwd,
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 4baeaa2..83fc210 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#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"
@@ -42,9 +44,6 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
-extern bool Password_encryption;
-
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+ char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
/* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
Oid roleid;
ListCell *item;
ListCell *option;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
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;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
+ */
+ FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+ /*
+ * Call the password checking hook if there is one defined.
*/
- if (check_password_hook && password)
+ if (check_password_hook && passwordVerifiers != NIL)
(*check_password_hook) (stmt->role,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -365,24 +364,6 @@ 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_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
roleid = simple_heap_insert(pg_authid_rel, tuple);
CatalogUpdateIndexes(pg_authid_rel, tuple);
+ /* store password verifiers */
+ if (passwordVerifiers)
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
Form_pg_authid authform;
ListCell *option;
char *rolename = NULL;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
int bypassrls = -1;
- DefElem *dpassword = NULL;
+ DefElem *dpasswordVerifiers = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
!dconnlimit &&
!rolemembers &&
!validUntil &&
- dpassword &&
+ dpasswordVerifiers &&
roleid == GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
*/
- if (check_password_hook && password)
+ FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+ /*
+ * Call the password checking hook if there is one defined.
+ */
+ if (check_password_hook && passwordVerifiers)
(*check_password_hook) (rolename,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
- /* password */
- if (password)
- {
- 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;
- }
-
/* valid until */
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
heap_freetuple(new_tuple);
/*
+ * Update password verifiers. The old entries are completely cleaned up
+ * and are updated by what is wanted through this command.
+ */
+ if (passwordVerifiers)
+ {
+ DeletePasswordVerifiers(roleid);
+ CommandCounterIncrement();
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+ }
+
+ /* remove password verifiers */
+ if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+ DeletePasswordVerifiers(roleid);
+
+ /*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
/*
- * Remove any comments or security labels on this role.
+ * Remove any comments, password verifiers or security labels on this
+ * role.
*/
+ DeletePasswordVerifiers(roleid);
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
@@ -1106,11 +1080,11 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ authtuple;
TupleDesc dsc;
- Relation rel;
- Datum datum;
- bool isnull;
+ Relation pg_authid_rel,
+ pg_auth_verifiers_rel;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
- rel = heap_open(AuthIdRelationId, RowExclusiveLock);
- dsc = RelationGetDescr(rel);
+ pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+ dsc = RelationGetDescr(pg_authid_rel);
oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ 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);
-
- if (!isnull && isMD5(TextDatumGetCString(datum)))
- {
- /* 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;
-
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
- }
-
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &oldtuple->t_self, newtuple);
+ simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
- CatalogUpdateIndexes(rel, newtuple);
+ CatalogUpdateIndexes(pg_authid_rel, newtuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
ReleaseSysCache(oldtuple);
+ /* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+ authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(AUTH_VERIFIER_MD5));
+ if (HeapTupleIsValid(authtuple))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+ ReleaseSysCache(authtuple);
+ }
+
/*
- * Close pg_authid, but keep lock till commit.
+ * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
*/
- heap_close(rel, NoLock);
+ heap_close(pg_auth_verifiers_rel, NoLock);
+ heap_close(pg_authid_rel, NoLock);
return address;
}
@@ -1611,3 +1588,130 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
heap_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+ ListCell *l;
+
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ if (spec->value == NULL)
+ continue;
+
+ /*
+ * Check if given value for an MD5 verifier is adapted and do
+ * conversion as needed. If an MD5 password is provided but that
+ * the verifier has a plain format switch type of verifier
+ * accordingly.
+ * This machinery is here for backward-compatibility with pre-9.6
+ * instances of Postgres, an md5 hash passed as a plain verifier
+ * should still be treated as an MD5 entry.
+ */
+ if (spec->veriftype == AUTH_VERIFIER_MD5 &&
+ !isMD5(spec->value))
+ {
+ char encrypted_passwd[MD5_PASSWD_LEN + 1];
+ if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
+ encrypted_passwd))
+ elog(ERROR, "password encryption failed");
+ spec->value = pstrdup(encrypted_passwd);
+ }
+ else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
+ isMD5(spec->value))
+ spec->veriftype = AUTH_VERIFIER_MD5;
+ }
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+ ListCell *l;
+ Relation pg_auth_verifiers_rel;
+ TupleDesc pg_auth_verifiers_dsc;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+ foreach(l, verifiers)
+ {
+ Datum new_record[Natts_pg_auth_verifiers];
+ bool new_record_nulls[Natts_pg_auth_verifiers];
+ HeapTuple tuple;
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ /* Move on if no verifier value define */
+ if (spec->value == NULL)
+ continue;
+
+ /* Build tuple and insert it */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+ new_record[Anum_pg_auth_verifiers_roleid - 1] = ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_verifiers_method - 1] =
+ CharGetDatum(spec->veriftype);
+
+ new_record[Anum_pg_auth_verifiers_value - 1] =
+ CStringGetTextDatum(spec->value);
+
+ tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+ new_record, new_record_nulls);
+
+ simple_heap_insert(pg_auth_verifiers_rel, tuple);
+ CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+ /* CCI after each change */
+ CommandCounterIncrement();
+ }
+
+ /* Keep locks until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+ Relation pg_auth_verifiers_rel;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+ HeapTuple tmp_tuple;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ /*
+ * Remove role entries from pg_auth_verifiers table. All the tuples that
+ * are similar to the role dropped need to be removed.
+ */
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_verifiers_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_verifiers_rel,
+ AuthVerifRoleMethodIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+ }
+
+ systable_endscan(sscan);
+
+ /* keep lock until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d79f5a2..9211ec2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
#include <crypt.h>
#endif
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#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_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+ HeapTuple tuple;
+ Datum verifier_datum;
+ char *verifier;
+ bool isnull;
+
+ /* Now attempt to grab the verifier value */
+ tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(method));
+ if (!HeapTupleIsValid(tuple))
+ return NULL; /* no verifier available */
+
+ verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+ Anum_pg_auth_verifiers_value,
+ &isnull);
+ verifier = TextDatumGetCString(verifier_datum);
+
+ ReleaseSysCache(tuple);
+
+ return verifier;
+}
/*
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,13 +71,15 @@ 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;
bool isnull;
+ Oid roleid;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -56,16 +90,24 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
return STATUS_ERROR; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ verifier_is_md5 = true;
+
+ roleid = HeapTupleGetOid(roleTup);
+ verifier = get_role_verifier(roleid, AUTH_VERIFIER_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(roleid, AUTH_VERIFIER_PLAIN);
+
+ 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);
@@ -74,7 +116,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (*verifier == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
@@ -92,10 +134,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))
{
@@ -108,7 +150,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))
@@ -130,7 +172,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);
@@ -143,7 +185,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9e9cc3..a1c043c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2696,6 +2696,17 @@ _copyRoleSpec(const RoleSpec *from)
return newnode;
}
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+ AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+ COPY_SCALAR_FIELD(veriftype);
+ COPY_STRING_FIELD(value);
+
+ return newnode;
+}
+
static Query *
_copyQuery(const Query *from)
{
@@ -5011,6 +5022,9 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_AuthVerifierSpec:
+ retval = _copyAuthVerifierSpec(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9c3959..8087c39 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2594,6 +2594,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+ COMPARE_SCALAR_FIELD(veriftype);
+ COMPARE_STRING_FIELD(value);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3338,6 +3347,9 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_AuthVerifierSpec:
+ retval = _equalAuthVerifierSpec(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b307b48..3f9506a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "commands/user.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
#include "parser/parser.h"
#include "parser/parse_expr.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
+ auth_verifier_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name
%type <node> var_value zone_value
%type <node> auth_ident RoleSpec opt_granted_by
+%type <node> AuthVerifierSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,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
@@ -918,25 +924,86 @@ AlterOptRoleList:
| /* EMPTY */ { $$ = NIL; }
;
+auth_verifier_list:
+ AuthVerifierSpec
+ { $$ = list_make1((Node*)$1); }
+ | auth_verifier_list ',' AuthVerifierSpec
+ { $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+ NonReservedWord '=' Sconst
+ {
+ char type;
+
+ if (strcmp($1, "md5") == 0)
+ type = AUTH_VERIFIER_MD5;
+ else if (strcmp($1, "plain") == 0)
+ type = AUTH_VERIFIER_PLAIN;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized authorization verifier option \"%s\"", $1),
+ parser_errposition(@1)));
+ $$ = (Node *) makeAuthVerifierSpec(type, $3);
+ }
+
AlterOptRoleElem:
PASSWORD Sconst
{
- $$ = makeDefElem("password",
- (Node *)makeString($2));
+ char *rawstring = pstrdup(Password_encryption);
+ List *elemlist;
+ ListCell *l;
+ List *result = NIL;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l);
+ char veriftype;
+ AuthVerifierSpec *n;
+
+ if (strcmp(meth_name, "md5") == 0)
+ veriftype = AUTH_VERIFIER_MD5;
+ else if (strcmp(meth_name, "plain") == 0)
+ veriftype = AUTH_VERIFIER_PLAIN;
+ else
+ Assert(false); /* should not happen */
+ n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(veriftype, $2);
+ result = lappend(result, (Node *)n);
+ }
+ pfree(rawstring);
+ list_free(elemlist);
+
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *) result);
}
| PASSWORD NULL_P
{
- $$ = makeDefElem("password", NULL);
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| ENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("encryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| UNENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("unencryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
+ }
+ | PASSWORD VERIFIERS '(' auth_verifier_list ')'
+ {
+ $$ = makeDefElem("passwordVerifiers", (Node *)$4);
}
| INHERIT
{
@@ -13902,6 +13969,7 @@ unreserved_keyword:
| VALIDATOR
| VALUE_P
| VARYING
+ | VERIFIERS
| VERSION_P
| VIEW
| VIEWS
@@ -14296,6 +14364,20 @@ makeRoleSpec(RoleSpecType type, int location)
return (Node *) spec;
}
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+ AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+ spec->veriftype = type;
+ spec->value = password;
+
+ return (Node *) spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index e929616..bb83df2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
case AUTHMEMMEMROLE:
+ case AUTHVERIFROLEMETH:
/*
* Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 130c06d..c1b6330 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -98,6 +99,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -1567,8 +1569,8 @@ LookupOpclassInfo(Oid operatorClassOid,
* catalogs.
*
* formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_shseclabel, pg_class, pg_attribute, pg_proc, and pg_type
- * (see RelationCacheInitializePhase2/3).
+ * pg_auth_verifiers pg_shseclabel, pg_class, pg_attribute, pg_proc, and
+ * pg_type (see RelationCacheInitializePhase2/3).
*
* Note that these catalogs can't have constraints (except attnotnull),
* default values, rules, or triggers, since we don't cope with any of that.
@@ -2871,6 +2873,7 @@ RelationBuildLocalRelation(const char *relname,
case DatabaseRelationId:
case AuthIdRelationId:
case AuthMemRelationId:
+ case AuthVerifRelationId:
case RelationRelationId:
case AttributeRelationId:
case ProcedureRelationId:
@@ -3257,10 +3260,12 @@ RelationCacheInitializePhase2(void)
true, Natts_pg_authid, Desc_pg_authid);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
false, Natts_pg_auth_members, Desc_pg_auth_members);
+ formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+ false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
false, Natts_pg_shseclabel, Desc_pg_shseclabel);
-#define NUM_CRITICAL_SHARED_RELS 4 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -3380,10 +3385,10 @@ RelationCacheInitializePhase3(void)
* initial lookup of MyDatabaseId, without which we'll never find any
* non-shared catalogs at all. Autovacuum calls InitPostgres with a
* database OID, so it instead depends on DatabaseOidIndexId. We also
- * need to nail up some indexes on pg_authid and pg_auth_members for use
- * during client authentication. SharedSecLabelObjectIndexId isn't
- * critical for the core system, but authentication hooks might be
- * interested in it.
+ * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+ * pg_auth_members for use during client authentication.
+ * SharedSecLabelObjectIndexId isn't critical for the core system, but
+ * authentication hooks might be interested in it.
*/
if (!criticalSharedRelcachesBuilt)
{
@@ -3397,10 +3402,12 @@ RelationCacheInitializePhase3(void)
AuthIdRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
+ load_critical_index(AuthVerifRoleMethodIndexId,
+ AuthVerifRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..ede8fc6 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -28,6 +28,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -248,6 +249,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthVerifRelationId, /* AUTHVERIFMETHROLE */
+ AuthVerifMethodRoleIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_method,
+ Anum_pg_auth_verifiers_roleid,
+ 0,
+ 0
+ },
+ 4
+ },
+ {AuthVerifRelationId, /* AUTHVERIFROLEMETH */
+ AuthVerifRoleMethodIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_roleid,
+ Anum_pg_auth_verifiers_method,
+ 0,
+ 0
+ },
+ 4
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ea5a09a..ca962bf 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,6 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -422,8 +424,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -435,6 +435,8 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
+char *Password_encryption;
+
char *cluster_name = "";
char *ConfigFileName;
char *HbaFileName;
@@ -1309,17 +1311,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3328,6 +3319,19 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password encryption methods."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines how the password is to be encrypted."),
+ GUC_LIST_INPUT
+ },
+ &Password_encryption,
+ "md5",
+ check_password_encryption, NULL, NULL
+ },
+
+ {
{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
gettext_noop("Location of the SSL server certificate file."),
NULL
@@ -10166,6 +10170,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
return true;
}
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+ char *rawstring = pstrdup(*newval); /* get copy of list string */
+ List *elemlist;
+ ListCell *l;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ pfree(rawstring);
+ list_free(elemlist);
+ Assert(false);
+ return false; /* GUC machinery should have already complained */
+ }
+
+ /* Check that only supported formats are listed */
+ foreach(l, elemlist)
+ {
+ char *encryption_name = (char *) lfirst(l);
+
+ if (strcmp(encryption_name, "md5") != 0 &&
+ strcmp(encryption_name, "plain") != 0)
+ {
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ return true;
+}
+
static const char *
show_unix_socket_permissions(void)
{
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee3d378..d6da960 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
#db_user_namespace = off
#row_security = on
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ed3ba7b..5315db7 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1530,10 +1530,11 @@ setup_auth(FILE *cmdfd)
const char *const * line;
static const char *const pg_authid_setup[] = {
/*
- * The authid table shouldn't be readable except through views, to
- * ensure passwords are not publicly visible.
+ * The authorization tables shouldn't be readable except through
+ * views, to ensure password data are not publicly visible.
*/
"REVOKE ALL on pg_authid FROM public;\n\n",
+ "REVOKE ALL on pg_auth_verifiers FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index be6b4a8..fc7a5ae 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
i_is_current_user;
int i;
- /* note: rolconfig is dumped later */
- if (server_version >= 90500)
+ /*
+ * Note: rolconfig is dumped later. In 9.6 and above, password
+ * information is dumped later on.
+ */
+ if (server_version >= 90600)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, "
+ "null::text as rolpassword, "
+ "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, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
PQclear(res);
+ /*
+ * Dump password configuration for all roles.
+ */
+ if (server_version >= 90600)
+ {
+ char *current_user = NULL;
+ bool first_elt = true;
+ res = executeQuery(conn,
+ "SELECT a.rolname, v.verimet, v.verival "
+ "FROM pg_auth_verifiers AS v "
+ "LEFT JOIN pg_authid AS a ON (v.roleid = a.oid) "
+ "ORDER BY rolname;");
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *user_name = PQgetvalue(res, i, 0);
+ char verifier_meth = *PQgetvalue(res, i, 1);
+ char *verifier_value = PQgetvalue(res, i, 2);
+
+ /* Switch to new ALTER ROLE query when a different user is found */
+ if (current_user == NULL ||
+ strcmp(user_name, current_user) != 0)
+ {
+ /* Finish last query */
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+
+ resetPQExpBuffer(buf);
+
+ if (current_user)
+ pg_free(current_user);
+ current_user = pg_strdup(user_name);
+ first_elt = true;
+ appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+ current_user);
+ }
+
+ if (first_elt)
+ first_elt = false;
+ else
+ appendPQExpBufferStr(buf, ", ");
+
+ if (verifier_meth == 'm')
+ appendPQExpBufferStr(buf, "md5 = ");
+ else if (verifier_meth == 'p')
+ appendPQExpBufferStr(buf, "plain = ");
+ appendStringLiteralConn(buf, verifier_value, conn);
+ }
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+ }
+
+
fprintf(OPF, "\n\n");
destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ab2c1a8..f8c445c 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
#define AuthMemMemRoleIndexId 2695
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3320, on pg_auth_verifiers using btree(roleid oid_ops, verimet char_ops));
+#define AuthVerifRoleMethodIndexId 3320
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3321, on pg_auth_verifiers using btree(verimet char_ops, roleid oid_ops));
+#define AuthVerifMethodRoleIndexId 3321
+
DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
#define CastOidIndexId 2660
DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..c6de9cf
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ * definition of the system "authorization password hashes" relation
+ * (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_auth_verifiers definition. cpp turns this into
+ * typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId 3318
+#define AuthVerifRelation_Rowtype_Id 3319
+
+CATALOG(pg_auth_verifiers,3318) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3319) BKI_SCHEMA_MACRO
+{
+ Oid roleid; /* ID of the role using this hash */
+ char verimet; /* Method used to generate the hash *
+ * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text verival BKI_FORCE_NOT_NULL; /* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ * Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ * the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ * compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers 3
+#define Anum_pg_auth_verifiers_roleid 1
+#define Anum_pg_auth_verifiers_method 2
+#define Anum_pg_auth_verifiers_value 3
+
+#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
+#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+
+#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index c163083..35f74d0 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ 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 */
timestamptz rolvaliduntil; /* password expiration time, if any */
#endif
} FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* compiler constants for pg_authid
* ----------------
*/
-#define Natts_pg_authid 11
+#define Natts_pg_authid 10
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
@@ -85,8 +84,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_rolvaliduntil 11
+#define Anum_pg_authid_rolvaliduntil 10
/* ----------------
* initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* user choices.
* ----------------
*/
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
#define BOOTSTRAP_SUPERUSERID 10
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
+/* GUC parameter */
+extern char *Password_encryption;
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+ List *passwordVerifiers,
+ Datum validuntil_time,
+ bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c407fa2..66104a0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -433,6 +433,7 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_AuthVerifierSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2fd0629..a14969c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -312,6 +312,17 @@ typedef struct RoleSpec
} RoleSpec;
/*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+ NodeTag type;
+ char veriftype; /* type of this verifier, as listed in *
+ * pg_auth_verifiers.h */
+ char *value; /* value specified by user */
+} AuthVerifierSpec;
+
+/*
* FuncCall - a function or aggregate invocation
*
* agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6e1e820..56635d5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -416,6 +416,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/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..300ebaa 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHVERIFMETHROLE,
+ AUTHVERIFROLEMETH,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..73ca2e5
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,105 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR: invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | p | rol
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd3 | p | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+------------------+---------+--------
+ role_passwd3_new | p | rol
+(1 row)
+
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | p | foo
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR: unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+ ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | m | md5
+ role_passwd2 | p | foo
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
+ rolname | verimet
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index aa5f42a..66ea550 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE test_def_superuser;
SELECT * FROM pg_authid WHERE rolname = 'test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH NOSUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE test_def_inherit;
SELECT * FROM pg_authid WHERE rolname = 'test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH INHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE test_def_createrole;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH NOCREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE test_def_createdb;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH NOCREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE test_def_role_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER test_def_user_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE test_def_replication;
SELECT * FROM pg_authid WHERE rolname = 'test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE test_replication WITH NOREPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE test_def_bypassrls;
SELECT * FROM pg_authid WHERE rolname = 'test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH NOBYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81bc5c9..3d4b50f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1631,7 +1631,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,
+ '********'::text AS passwd,
(pg_authid.rolvaliduntil)::abstime AS valuntil,
s.setconfig AS useconfig
FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
pg_attrdef|t
pg_attribute|t
pg_auth_members|t
+pg_auth_verifiers|t
pg_authid|t
pg_cast|t
pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index bec0316..bbe969b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7e9b319..cd724b6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -111,6 +111,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..0376e1b
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,72 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.verimet;
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
--
2.7.1
0002-Introduce-password_protocols.patchtext/x-patch; charset=US-ASCII; name=0002-Introduce-password_protocols.patchDownload
From 818028fcd648abe87a1c5d5dc582c34a907044b4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:32:50 +0900
Subject: [PATCH 2/9] Introduce password_protocols
This new superuser GUC parameters specifies a list of supported password
protocols in Postgres backend. This is useful for system maintainers to
prevent the creation of password using a protocol thought as unsafe in
certain deployments.
The current default is 'plain,md5', authorizing the creation of both plain
passwords and MD5-encrypted passwords in the system.
---
doc/src/sgml/config.sgml | 27 ++++++++++++++++
src/backend/commands/user.c | 46 +++++++++++++++++++++++++++
src/backend/utils/misc/guc.c | 29 +++++++++++++----
src/backend/utils/misc/postgresql.conf.sample | 2 ++
src/include/commands/user.h | 3 +-
src/test/regress/expected/password.out | 28 ++++++++++++++++
src/test/regress/sql/password.sql | 20 ++++++++++++
7 files changed, 147 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 226d51d..e62c729 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1190,6 +1190,33 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-password-protocols" xreflabel="password_protocols">
+ <term><varname>password_protocols</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>password_protocols</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies a comma-separated list of supported password formats by
+ the server. Supported formats are currently <literal>plain</> and
+ <literal>md5</>.
+ </para>
+
+ <para>
+ When a password is specified in <xref linkend="sql-createuser"> or
+ <xref linkend="sql-alterrole">, this parameter determines if the
+ password specified is authorized to be stored or not, returning
+ an error message to caller if it is not.
+ </para>
+
+ <para>
+ The default is <literal>plain,md5</>, meaning that MD5-encrypted
+ passwords and plain passwords are both accepted.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
<term><varname>krb_server_keyfile</varname> (<type>string</type>)
<indexterm>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 83fc210..2b3a33c 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1597,6 +1597,8 @@ static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
{
ListCell *l;
+ char *rawstring;
+ List *elemlist;
foreach(l, verifiers)
{
@@ -1627,6 +1629,50 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
isMD5(spec->value))
spec->veriftype = AUTH_VERIFIER_MD5;
}
+
+ /*
+ * Now that the list of verifiers is built and consistent with the input
+ * values, check that the list of verifiers specified is actually
+ * supported by server or not.
+ */
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ /*
+ * This is O(N ^ 2), but the small number of elements in the list of
+ * protocols supported is not worth complicating this code.
+ */
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+ ListCell *l2;
+ bool found_match = false;
+
+ foreach(l2, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l2);
+
+ if ((strcmp(meth_name, "md5") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_MD5) ||
+ (strcmp(meth_name, "plain") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_PLAIN))
+ {
+ found_match = true;
+ break;
+ }
+ }
+
+ if (!found_match)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("specified password protocol not allowed"),
+ errdetail("List of authorized protocols is specified by password_protocols.")));
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ca962bf..e5610aa 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,8 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
-static bool check_password_encryption(char **newval, void **extra,
- GucSource source);
+static bool check_password_methods(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -436,6 +436,7 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
char *Password_encryption;
+char *password_protocols;
char *cluster_name = "";
char *ConfigFileName;
@@ -3328,7 +3329,21 @@ static struct config_string ConfigureNamesString[] =
},
&Password_encryption,
"md5",
- check_password_encryption, NULL, NULL
+ check_password_methods, NULL, NULL
+ },
+
+ {
+ {"password_protocols", PGC_SUSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password protocols supported."),
+ gettext_noop("The list of password protocols specified by this "
+ "parameter determines what are the authorized methods "
+ "on the server when running CREATE USER or ALTER "
+ "USER."),
+ GUC_LIST_INPUT
+ },
+ &password_protocols,
+ "plain,md5",
+ check_password_methods, NULL, NULL
},
{
@@ -10171,7 +10186,7 @@ check_cluster_name(char **newval, void **extra, GucSource source)
}
static bool
-check_password_encryption(char **newval, void **extra, GucSource source)
+check_password_methods(char **newval, void **extra, GucSource source)
{
char *rawstring = pstrdup(*newval); /* get copy of list string */
List *elemlist;
@@ -10189,10 +10204,10 @@ check_password_encryption(char **newval, void **extra, GucSource source)
/* Check that only supported formats are listed */
foreach(l, elemlist)
{
- char *encryption_name = (char *) lfirst(l);
+ char *method_name = (char *) lfirst(l);
- if (strcmp(encryption_name, "md5") != 0 &&
- strcmp(encryption_name, "plain") != 0)
+ if (strcmp(method_name, "md5") != 0 &&
+ strcmp(method_name, "plain") != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d6da960..065b4ab 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -88,6 +88,8 @@
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
#password_encryption = 'md5'
+#password_protocols = 'plain,md5' # comma-separated list of supported
+ # password protocols.
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 636e8ac..7a73bc5 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,8 +14,9 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
-/* GUC parameter */
+/* GUC parameters */
extern char *Password_encryption;
+extern char *password_protocols;
typedef void (*check_password_hook_type) (const char *username,
List *passwordVerifiers,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 73ca2e5..f5fdc3f 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -9,6 +9,14 @@ ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+ERROR: invalid value for parameter "password_protocols": "novalue"
+SET password_protocols = true; -- error
+ERROR: invalid value for parameter "password_protocols": "true"
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -89,6 +97,26 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
role_passwd4 | m | md5
(4 rows)
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 0376e1b..5cba2d8 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -9,6 +9,13 @@ SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+SET password_protocols = true; -- error
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
+
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -59,6 +66,19 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
WHERE a.rolname LIKE 'role_passwd%'
ORDER BY a.rolname, v.verimet;
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
--
2.7.1
0003-Add-pg_auth_verifiers_sanitize.patchtext/x-patch; charset=US-ASCII; name=0003-Add-pg_auth_verifiers_sanitize.patchDownload
From 8bc1385ad90814128dd21a6dac9eb6e2ba379418 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 14:31:03 +0900
Subject: [PATCH 3/9] Add pg_auth_verifiers_sanitize
This function is aimed at being used by pg_upgrade and system administers
to filter out password verifiers that are based on protocols not defined
on the system per the list given by password_protocols.
---
doc/src/sgml/func.sgml | 34 +++++++++++++++++++++
src/backend/commands/user.c | 71 +++++++++++++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +++
src/include/commands/user.h | 2 ++
4 files changed, 111 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b001ce5..b57f4e4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18578,6 +18578,40 @@ SELECT (pg_stat_file('filename')).modification;
</sect2>
+ <sect2 id="functions-password">
+ <title>Password Functions</title>
+
+ <para>
+ The functions shown in <xref linkend="functions-password-table"> manage
+ system passwords.
+ </para>
+
+ <table id="functions-password-table">
+ <title>Password Functions</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <literal><function>pg_auth_verifiers_sanitize()</function></literal>
+ </entry>
+ <entry><type>integer</type></entry>
+ <entry>remove password verifier entries in
+ <link linkend="catalog-pg-auth-verifiers"></> for password protocols
+ not listed in <xref linkend="guc-password-protocols">. This function
+ is limited to superusers. The return result is the number of entries
+ removed from the table.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="functions-trigger">
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2b3a33c..a4aeb6a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1761,3 +1761,74 @@ DeletePasswordVerifiers(Oid roleid)
/* keep lock until the end of transaction */
heap_close(pg_auth_verifiers_rel, NoLock);
}
+
+/*
+ * pg_auth_sanitize
+ *
+ * Scan through pg_auth_verifiers and remove all the password verifiers
+ * that are not part of the list of supported protocols as defined by
+ * password_protocols. Returns to caller the number of entries removed.
+ */
+Datum
+pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
+{
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tup;
+ char *rawstring;
+ List *elemlist;
+ int res = 0;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to sanitize \"pg_auth_verifiers\""))));
+
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ rel = heap_open(AuthVerifRelationId, AccessShareLock);
+ scan = heap_beginscan_catalog(rel, 0, NULL);
+ while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_auth_verifiers authform =
+ (Form_pg_auth_verifiers) GETSTRUCT(tup);
+ ListCell *l;
+ bool remove_entry = true;
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = lfirst(l);
+
+ /* Check for protocol matches */
+ if (authform->verimet == AUTH_VERIFIER_MD5 &&
+ strcmp(meth_name, "md5") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ else if (authform->verimet == AUTH_VERIFIER_PLAIN &&
+ strcmp(meth_name, "plain") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ }
+
+ if (remove_entry)
+ {
+ simple_heap_delete(rel, &tup->t_self);
+ res++;
+ }
+ }
+
+ heap_endscan(scan);
+ heap_close(rel, NoLock);
+
+ pfree(rawstring);
+ list_free(elemlist);
+
+ PG_RETURN_INT32(res);
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 59c50d9..4ad707d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5167,6 +5167,10 @@ DESCR("for use by pg_upgrade");
DATA(insert OID = 3591 ( binary_upgrade_create_empty_extension PGNSP PGUID 12 1 0 0 0 f f f f f f v r 7 0 2278 "25 25 16 25 1028 1009 1009" _null_ _null_ _null_ _null_ _null_ binary_upgrade_create_empty_extension _null_ _null_ _null_ ));
DESCR("for use by pg_upgrade");
+/* commands/user.h */
+DATA(insert OID = 3322 ( pg_auth_verifiers_sanitize PGNSP PGUID 12 1 0 0 0 f f f f t f v u 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_auth_verifiers_sanitize _null_ _null_ _null_ ));
+DESCR("sanitize entries of pg_auth_verifiers using password_protocols");
+
/* replication/origin.h */
DATA(insert OID = 6003 ( pg_replication_origin_create PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 26 "25" _null_ _null_ _null_ _null_ _null_ pg_replication_origin_create _null_ _null_ _null_ ));
DESCR("create a replication origin");
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a73bc5..4277d5f 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -35,4 +35,6 @@ extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
extern List *roleSpecsToIds(List *memberNames);
+extern Datum pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS);
+
#endif /* USER_H */
--
2.7.1
0004-Remove-password-verifiers-for-unsupported-protocols-.patchtext/x-patch; charset=US-ASCII; name=0004-Remove-password-verifiers-for-unsupported-protocols-.patchDownload
From 43cb17f5be2e30f94c7a8165e03c44c31403c43a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 14:53:37 +0900
Subject: [PATCH 4/9] Remove password verifiers for unsupported protocols in
pg_upgrade
This uses pg_auth_verifiers_sanitize to perform the cleanup in
pg_auth_verifiers that has been introduced previously.
---
src/bin/pg_upgrade/pg_upgrade.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 984c395..4459990 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -262,6 +262,18 @@ prepare_new_cluster(void)
check_ok();
get_pg_database_relfilenode(&new_cluster);
+
+ /*
+ * In order to ensure that the freshly-deployed cluster has no outdated
+ * password verifier entries, sanitize pg_auth_verifiers using the
+ * in-core function aimed at this purpose.
+ */
+ prep_status("Removing password verifiers for unsupported protocols");
+ exec_prog(UTILITY_LOG_FILE, NULL, true,
+ "\"%s/psql\" " EXEC_PSQL_ARGS
+ " %s -c \"SELECT pg_auth_verifiers_sanitize()\"",
+ new_cluster.bindir, cluster_conn_opts(&new_cluster));
+ check_ok();
}
--
2.7.1
0005-Move-sha1.c-to-src-common.patchtext/x-patch; charset=US-ASCII; name=0005-Move-sha1.c-to-src-common.patchDownload
From 1fc053ca4b0f67b17512b242190db856e818fdd4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:33:27 +0900
Subject: [PATCH 5/9] Move sha1.c to src/common
This set of routines taken from pgcrypto will be used on both backend
and frontend for authentication purposes.
---
contrib/pgcrypto/Makefile | 4 ++--
contrib/pgcrypto/internal.c | 2 +-
src/common/Makefile | 2 +-
{contrib/pgcrypto => src/common}/sha1.c | 4 ++--
{contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
6 files changed, 8 insertions(+), 8 deletions(-)
rename {contrib/pgcrypto => src/common}/sha1.c (99%)
rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)
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/src/common/Makefile b/src/common/Makefile
index bde4fc2..611ad11 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,7 +37,7 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o exec.o pg_lzcompress.o pgfnames.o psprintf.o \
- relpath.o rmtree.o string.o username.o wait_error.o
+ relpath.o rmtree.o sha1.o string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
*/
/*
* FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
#include <sys/param.h>
-#include "sha1.h"
+#include "common/sha1.h"
/* constant table */
static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 5532ca1..d5ff296 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/* contrib/pgcrypto/sha1.h */
+/* src/include/common/sha1.h */
/* $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $ */
/*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index e4fb44e..cc48d30 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -107,7 +107,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c exec.c pg_lzcompress.c pgfnames.c psprintf.c
- relpath.c rmtree.c string.c username.c wait_error.c);
+ relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
--
2.7.1
0006-Refactor-sendAuthRequest.patchtext/x-patch; charset=US-ASCII; name=0006-Refactor-sendAuthRequest.patchDownload
From 7d2cac66d21cb60894ac3016d2bb44b4948cd857 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 6/9] 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 57c2f48..2b75b91 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);
}
@@ -1673,7 +1672,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)
{
@@ -1948,7 +1947,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)
@@ -2308,7 +2307,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.7.1
0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchtext/x-patch; charset=US-ASCII; name=0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From e43b93a12ffc19dc21229cd44f85ca0f8c944694 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 11 Aug 2015 20:43:26 +0900
Subject: [PATCH 7/9] Refactor RandomSalt to handle salts of different lengths
---
src/backend/postmaster/postmaster.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b16fc28..525155b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -404,7 +404,7 @@ static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -2339,7 +2339,7 @@ ConnCreate(int serverFd)
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
- RandomSalt(port->md5Salt);
+ RandomSalt(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
@@ -5079,23 +5079,21 @@ StartupPacketTimeoutHandler(void)
* RandomSalt
*/
static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
{
long rand;
+ int i;
/*
* We use % 255, sacrificing one possible byte value, so as to ensure that
* all bits of the random() value participate in the result. While at it,
* add one to avoid generating any null bytes.
*/
- rand = PostmasterRandom();
- md5Salt[0] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[1] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[2] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[3] = (rand % 255) + 1;
+ for (i = 0; i < len; i++)
+ {
+ rand = PostmasterRandom();
+ md5Salt[i] = (rand % 255) + 1;
+ }
}
/*
--
2.7.1
0008-Move-encoding-routines-to-src-common.patchtext/x-patch; charset=US-ASCII; name=0008-Move-encoding-routines-to-src-common.patchDownload
From e6189dec4252c4efe6921c5aa5af734eaf3ca990 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:42:35 +0900
Subject: [PATCH 8/9] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA1, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +----------------------------
src/common/Makefile | 5 +-
src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
src/include/common/encode.h | 30 +++
src/tools/msvc/Mkvcbuild.pm | 2 +-
5 files changed, 167 insertions(+), 634 deletions(-)
copy src/{backend/utils/adt => common}/encode.c (72%)
create mode 100644 src/include/common/encode.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..76747bf 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/common/Makefile b/src/common/Makefile
index 611ad11..a6a1454 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -36,8 +36,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o exec.o pg_lzcompress.o pgfnames.o psprintf.o \
- relpath.o rmtree.o sha1.o string.o username.o wait_error.o
+OBJS_COMMON = config_info.o encode.o exec.o pg_lzcompress.o pgfnames.o \
+ psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
+ wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index d833efc..4a07089 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
* encode.c
- * Various data encoding/decoding things.
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the \
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..8166376
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ * encode.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* COMMON_ENCODE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index cc48d30..df46f3b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,7 +106,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c exec.c pg_lzcompress.c pgfnames.c psprintf.c
+ config_info.c encode.c exec.c pg_lzcompress.c pgfnames.c psprintf.c
relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
our @pgcommonfrontendfiles = (
--
2.7.1
0009-SCRAM-authentication.patchtext/x-patch; charset=US-ASCII; name=0009-SCRAM-authentication.patchDownload
From 900263a46db58b76c246e28f844327c93f709ff1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 18 Aug 2015 13:29:50 +0900
Subject: [PATCH 9/9] SCRAM authentication
---
contrib/passwordcheck/passwordcheck.c | 4 +
doc/src/sgml/catalogs.sgml | 3 +-
doc/src/sgml/config.sgml | 7 +-
doc/src/sgml/protocol.sgml | 148 ++++++-
src/backend/commands/user.c | 51 ++-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 682 ++++++++++++++++++++++++++++++++
src/backend/libpq/auth.c | 117 ++++++
src/backend/libpq/crypt.c | 4 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 2 +-
src/backend/parser/gram.y | 4 +
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/adt/varlena.c | 1 +
src/backend/utils/misc/guc.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 2 +
src/common/Makefile | 4 +-
src/common/scram-common.c | 170 ++++++++
src/include/catalog/pg_auth_verifiers.h | 1 +
src/include/common/scram-common.h | 45 +++
src/include/libpq/auth.h | 5 +
src/include/libpq/crypt.h | 1 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 3 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 ++
src/include/utils/builtins.h | 2 -
src/interfaces/libpq/.gitignore | 3 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-scram.c | 386 ++++++++++++++++++
src/interfaces/libpq/fe-auth.c | 96 +++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 51 +++
src/interfaces/libpq/libpq-int.h | 5 +
src/test/regress/expected/password.out | 13 +-
src/test/regress/sql/password.sql | 9 +-
36 files changed, 1847 insertions(+), 38 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 13ad053..57f7f49 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,10 @@ check_password(const char *username,
#endif
break;
+ case AUTH_VERIFIER_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
+
default:
elog(ERROR, "unrecognized password type: %d", spec->veriftype);
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0faad9d..5874a8d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1323,7 +1323,8 @@
<entry><type>char</type></entry>
<entry>
<literal>p</> = plain format,
- <literal>m</> = MD5-encrypted
+ <literal>m</> = MD5-encrypted,
+ <literal>s</> = SCRAM-SHA1-encrypted
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e62c729..418f065 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
<listitem>
<para>
Specifies a comma-separated list of password encryption formats.
- Supported formats are <literal>plain</> and <literal>md5</>.
+ Supported formats are <literal>plain</>,<literal>md5</> and
+ <literal>scram</>.
</para>
<para>
@@ -1211,8 +1212,8 @@ include_dir 'conf.d'
</para>
<para>
- The default is <literal>plain,md5</>, meaning that MD5-encrypted
- passwords and plain passwords are both accepted.
+ The default is <literal>plain,md5,scram</>, meaning that MD5-encrypted
+ passwords, plain passwords, and SCRAM-encrypted passwords are accepted.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 522128e..e1238d7 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>
@@ -2578,6 +2607,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>
@@ -4340,7 +4478,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 a4aeb6a..bd517aa 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,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"
@@ -1591,7 +1592,9 @@ DelRoleMems(const char *rolename, Oid roleid,
/*
* FlattenPasswordIdentifiers
- * Make list of password verifier types and values consistent with input.
+ * Make list of password verifier types and values consistent with the output
+ * wanted, and adapt the specifier value if possible, informing user in case of
+ * incorrect verifier used.
*/
static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
@@ -1616,18 +1619,34 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
* instances of Postgres, an md5 hash passed as a plain verifier
* should still be treated as an MD5 entry.
*/
- if (spec->veriftype == AUTH_VERIFIER_MD5 &&
- !isMD5(spec->value))
+ switch (spec->veriftype)
{
- char encrypted_passwd[MD5_PASSWD_LEN + 1];
- if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
- encrypted_passwd))
- elog(ERROR, "password encryption failed");
- spec->value = pstrdup(encrypted_passwd);
+ case AUTH_VERIFIER_MD5:
+ if (is_scram_verifier(spec->value))
+ elog(ERROR, "Cannot use SCRAM verifier as MD5 verifier");
+ if (!isMD5(spec->value))
+ {
+ char encrypted_passwd[MD5_PASSWD_LEN + 1];
+ if (!pg_md5_encrypt(spec->value, rolname,
+ strlen(rolname),
+ encrypted_passwd))
+ elog(ERROR, "password encryption failed");
+ spec->value = pstrdup(encrypted_passwd);
+ }
+ break;
+ case AUTH_VERIFIER_PLAIN:
+ if (is_scram_verifier(spec->value))
+ spec->veriftype = AUTH_VERIFIER_SCRAM;
+ else if (isMD5(spec->value))
+ spec->veriftype = AUTH_VERIFIER_MD5;
+ break;
+ case AUTH_VERIFIER_SCRAM:
+ if (isMD5(spec->value))
+ elog(ERROR, "Cannot use MD5 verifier as SCRAM verifier");
+ if (!is_scram_verifier(spec->value))
+ spec->value = scram_build_verifier(rolname, spec->value, 0);
+ break;
}
- else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
- isMD5(spec->value))
- spec->veriftype = AUTH_VERIFIER_MD5;
}
/*
@@ -1657,7 +1676,9 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
if ((strcmp(meth_name, "md5") == 0 &&
spec->veriftype == AUTH_VERIFIER_MD5) ||
(strcmp(meth_name, "plain") == 0 &&
- spec->veriftype == AUTH_VERIFIER_PLAIN))
+ spec->veriftype == AUTH_VERIFIER_PLAIN) ||
+ (strcmp(meth_name, "scram") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_SCRAM))
{
found_match = true;
break;
@@ -1815,6 +1836,12 @@ pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
remove_entry = false;
break;
}
+ else if (authform->verimet == AUTH_VERIFIER_SCRAM &&
+ strcmp(meth_name, "scram") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
}
if (remove_entry)
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..0d53348
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,682 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_auth_verifiers 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/encode.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"
+
+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;
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+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 */
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+
+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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 2b75b91..a77431a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,15 +21,19 @@
#include <arpa/inet.h>
#include <unistd.h>
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/ip.h"
#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"
+#include "utils/syscache.h"
/*----------------------------------------------------------------
@@ -185,6 +189,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -246,6 +256,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 +534,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, "");
@@ -690,6 +705,108 @@ 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;
+ HeapTuple roleTup;
+
+ /*
+ * 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")));
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+ if (!HeapTupleIsValid(roleTup))
+ return STATUS_ERROR;
+
+ /* lookup verifier */
+ verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+ if (verifier == NULL)
+ {
+ ReleaseSysCache(roleTup);
+ return STATUS_ERROR;
+ }
+
+ ReleaseSysCache(roleTup);
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA1_NAME,
+ strlen(SCRAM_SHA1_NAME) + 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;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 9211ec2..6df2bf2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
#include "utils/timestamp.h"
/*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
* method.
*/
-static char *
+char *
get_role_verifier(Oid roleid, const char method)
{
HeapTuple tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 28f9fb5..df0cc1d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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/parser/gram.y b/src/backend/parser/gram.y
index 3f9506a..5e7ca87 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -939,6 +939,8 @@ AuthVerifierSpec:
type = AUTH_VERIFIER_MD5;
else if (strcmp($1, "plain") == 0)
type = AUTH_VERIFIER_PLAIN;
+ else if (strcmp($1, "scram") == 0)
+ type = AUTH_VERIFIER_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -968,6 +970,8 @@ AlterOptRoleElem:
veriftype = AUTH_VERIFIER_MD5;
else if (strcmp(meth_name, "plain") == 0)
veriftype = AUTH_VERIFIER_PLAIN;
+ else if (strcmp(meth_name, "scram") == 0)
+ veriftype = AUTH_VERIFIER_SCRAM;
else
Assert(false); /* should not happen */
n = (AuthVerifierSpec *)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 525155b..00e95bb 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2340,6 +2340,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 94599cc..862d315 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode.h"
#include "lib/hyperloglog.h"
#include "libpq/md5.h"
#include "libpq/pqformat.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e5610aa..bb429af 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3342,7 +3342,7 @@ static struct config_string ConfigureNamesString[] =
GUC_LIST_INPUT
},
&password_protocols,
- "plain,md5",
+ "plain,md5,scram",
check_password_methods, NULL, NULL
},
@@ -10207,7 +10207,8 @@ check_password_methods(char **newval, void **extra, GucSource source)
char *method_name = (char *) lfirst(l);
if (strcmp(method_name, "md5") != 0 &&
- strcmp(method_name, "plain") != 0)
+ strcmp(method_name, "plain") != 0 &&
+ strcmp(method_name, "scram") != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index fc7a5ae..6008b93 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
appendPQExpBufferStr(buf, "md5 = ");
else if (verifier_meth == 'p')
appendPQExpBufferStr(buf, "plain = ");
+ else if (verifier_meth == 's')
+ appendPQExpBufferStr(buf, "scram = ");
appendStringLiteralConn(buf, verifier_value, conn);
}
if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index a6a1454..a5a3154 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,8 +37,8 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o encode.o exec.o pg_lzcompress.o pgfnames.o \
- psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
- wait_error.o
+ psprintf.o relpath.o rmtree.o scram-common.o sha1.o string.o \
+ username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..a17387e
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,170 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (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"
+#include "utils/memutils.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 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)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index c6de9cf..9cebf65 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -58,5 +58,6 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+#define AUTH_VERIFIER_SCRAM 's' /* SCRAM verifier */
#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..3d99bc8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+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 3cd06b7..5a02534 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 5725bb4..93eec02 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(Oid roleid, char method);
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/libpq-be.h b/src/include/libpq/libpq-be.h
index 5d07b78..c5663f4 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..b9af4c4
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* Name of SCRAM-SHA1 per IANA */
+#define SCRAM_SHA1_NAME "SCRAM-SHA-1"
+
+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);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 94c1881..3509c69 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -156,8 +156,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..225cfe4 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,8 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha1.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 1b292d2..cf5c813 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,8 @@ 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/
+OBJS += encode.o scram-common.o sha1.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode.c 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..ebbd1db
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,386 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/encode.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;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+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;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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 != SCRAM_SALT_LEN)
+ 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,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ 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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ 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, SCRAM_CLIENT_KEY_NAME, 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index cd863a5..91e952b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -428,6 +429,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(auth_mechanism, SCRAM_SHA1_NAME) == 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 +765,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 9d11654..f779fb2 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 5ad4755..6cd38bb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,48 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if (msgLength > 4 &&
+ (areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT))
+ {
+ 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 SASL 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
@@ -3042,6 +3084,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 6c9bbf7..087c731 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 */
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index f5fdc3f..35862f1 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -8,7 +8,8 @@ SET password_encryption = true; -- error
ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
ERROR: invalid value for parameter "password_protocols": "novalue"
@@ -16,7 +17,8 @@ SET password_protocols = true; -- error
ERROR: invalid value for parameter "password_protocols": "true"
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -84,6 +86,11 @@ LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ERROR: Cannot use SCRAM verifier as MD5 verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ERROR: Cannot use MD5 verifier as SCRAM verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.roleid = a.oid)
@@ -94,7 +101,7 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
role_passwd1 | m | md5
role_passwd2 | p | foo
role_passwd3 | m | md5
- role_passwd4 | m | md5
+ role_passwd4 | s | XxC
(4 rows)
-- entries for password_protocols
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 5cba2d8..e9fb4e0 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -7,14 +7,16 @@ SET password_encryption = 'novalue'; -- error
SET password_encryption = true; -- error
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
SET password_protocols = true; -- error
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
@@ -60,6 +62,9 @@ ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.roleid = a.oid)
--
2.7.1
Hi, Michael
23.02.2016 10:17, Michael Paquier пишет:
Attached is a set of patches implementing a couple of things that have
been discussed, so let's roll in.Those 4 patches are aimed at putting in-core basics for the concept I
call password protocol aging, which is a way to allow multiple
password protocols to be defined in Postgres, and aimed at easing
administration as well as retirement of outdated protocols, which is
something that is not doable now in Postgres.The second set of patch 0005~0008 introduces a new protocol, SCRAM.
9) 0009 is the SCRAM authentication itself....
The theme with password checking is interesting for me, and I can give
review for CF for some features.
I think that review of all suggested features will require a lot of time.
Is it possible to make subset of patches concerning only password
strength and its aging?
The patches you have applied are non-independent. They should be apply
consequentially one by one.
Thus the patch 0009 can't be applied without git error before 0001.
In this conditions all patches were successfully applied and compiled.
All tests successfully passed.
The first 4 patches obviously are the core portion that I would like
to discuss about in this CF, as they put in the base for the rest, and
will surely help Postgres long-term. 0005~0008 are just refactoring
patches, so they are quite simple. 0009 though is quite difficult, and
needs careful review because it manipulates areas of the code where it
is not necessary to be an authenticated user, so if there are bugs in
it it would be possible for example to crash down Postgres just by
sending authentication requests.
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 26, 2016 at 1:38 AM, Valery Popov <v.popov@postgrespro.ru> wrote:
Hi, Michael
23.02.2016 10:17, Michael Paquier пишет:
Attached is a set of patches implementing a couple of things that have
been discussed, so let's roll in.Those 4 patches are aimed at putting in-core basics for the concept I
call password protocol aging, which is a way to allow multiple
password protocols to be defined in Postgres, and aimed at easing
administration as well as retirement of outdated protocols, which is
something that is not doable now in Postgres.The second set of patch 0005~0008 introduces a new protocol, SCRAM.
9) 0009 is the SCRAM authentication itself....The theme with password checking is interesting for me, and I can give
review for CF for some features.
I think that review of all suggested features will require a lot of time.
Is it possible to make subset of patches concerning only password strength
and its aging?
The patches you have applied are non-independent. They should be apply
consequentially one by one.
Thus the patch 0009 can't be applied without git error before 0001.
In this conditions all patches were successfully applied and compiled.
All tests successfully passed.
If you want to focus on the password protocol aging, you could just
have a look at 0001~0004.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
26.02.2016 01:10, Michael Paquier пишет:
On Fri, Feb 26, 2016 at 1:38 AM, Valery Popov <v.popov@postgrespro.ru> wrote:
Hi, Michael
23.02.2016 10:17, Michael Paquier пишет:
Attached is a set of patches implementing a couple of things that have
been discussed, so let's roll in.Those 4 patches are aimed at putting in-core basics for the concept I
call password protocol aging, which is a way to allow multiple
password protocols to be defined in Postgres, and aimed at easing
administration as well as retirement of outdated protocols, which is
something that is not doable now in Postgres.The second set of patch 0005~0008 introduces a new protocol, SCRAM.
9) 0009 is the SCRAM authentication itself....The theme with password checking is interesting for me, and I can give
review for CF for some features.
I think that review of all suggested features will require a lot of time.
Is it possible to make subset of patches concerning only password strength
and its aging?
The patches you have applied are non-independent. They should be apply
consequentially one by one.
Thus the patch 0009 can't be applied without git error before 0001.
In this conditions all patches were successfully applied and compiled.
All tests successfully passed.If you want to focus on the password protocol aging, you could just
have a look at 0001~0004.
OK, I will review patches 0001-0004, for starting.
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi, Michael
23.02.2016 10:17, Michael Paquier пишет:
Attached is a set of patches implementing a couple of things that have
been discussed, so let's roll in.Those 4 patches are aimed at putting in-core basics for the concept I
call password protocol aging, which is a way to allow multiple
password protocols to be defined in Postgres, and aimed at easing
administration as well as retirement of outdated protocols, which is
something that is not doable now in Postgres.The second set of patch 0005~0008 introduces a new protocol, SCRAM.
9) 0009 is the SCRAM authentication itself....The theme with password checking is interesting for me, and I can give
review for CF for some features.
I think that review of all suggested features will require a lot of
time.
Is it possible to make subset of patches concerning only password
strength
and its aging?
The patches you have applied are non-independent. They should be apply
consequentially one by one.
Thus the patch 0009 can't be applied without git error before 0001.
In this conditions all patches were successfully applied and compiled.
All tests successfully passed.If you want to focus on the password protocol aging, you could just
have a look at 0001~0004.OK, I will review patches 0001-0004, for starting.
Below are the results of compiling and testing.
============================
I've got the last version of sources from
git://git.postgresql.org/git/postgresql.git.
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git branch
* master
Then I've applied patches 0001-0004 with two warnings:
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git apply
0001-Add-facility-to-store-multiple-password-verifiers.patch
0001-Add-facility-to-store-multiple-password-verifiers.patch:2547:
trailing whitespace.
warning: 1 line adds whitespace errors.
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git apply
0002-Introduce-password_protocols.patch
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git apply
0003-Add-pg_auth_verifiers_sanitize.patch
0003-Add-pg_auth_verifiers_sanitize.patch:87: indent with spaces.
if (!superuser())
warning: 1 line adds whitespace errors.
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git apply
0004-Remove-password-verifiers-for-unsupported-protocols-.patch
The compilation with option ./configure --enable-debug --enable-nls
--enable-cassert --enable-tap-tests --with-perl
was successful.
Regression tests and all TAP-tests also passed successfully.
Also I've applied patches 0005-0008 into clean sources directory with no
warnings.
vpopov@vpopov-Ubuntu:~/Projects/pwdtest2/postgresql$ git apply
0005-Move-sha1.c-to-src-common.patch
vpopov@vpopov-Ubuntu:~/Projects/pwdtest2/postgresql$ git apply
0006-Refactor-sendAuthRequest.patch
vpopov@vpopov-Ubuntu:~/Projects/pwdtest2/postgresql$ git apply
0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patch
vpopov@vpopov-Ubuntu:~/Projects/pwdtest2/postgresql$ git apply
0008-Move-encoding-routines-to-src-common.patch
The compilation with option ./configure --enable-debug --enable-nls
--enable-cassert --enable-tap-tests --with-perl
was successful.
Regression and the TAP-tests also passed successfully.
The patch 0009 depends on all previous patches 0001-0008: first we need
to apply patches 0001-0008, then 0009.
Then, all patches were successfully compiled.
All test passed.
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Feb 29, 2016 at 8:43 PM, Valery Popov <v.popov@postgrespro.ru> wrote:
vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git branch
Thanks for the input!
0001-Add-facility-to-store-multiple-password-verifiers.patch:2547: trailing
whitespace.
warning: 1 line adds whitespace errors.
0003-Add-pg_auth_verifiers_sanitize.patch:87: indent with spaces.
if (!superuser())
warning: 1 line adds whitespace errors.
Argh, yes. Those two ones have slipped though my successive rebases I
think. Will fix in my tree, I don't think that it is worth sending
again the whole series just for that though.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 1 March 2016 at 06:34, Michael Paquier <michael.paquier@gmail.com> wrote:
On Mon, Feb 29, 2016 at 8:43 PM, Valery Popov <v.popov@postgrespro.ru>
wrote:vpopov@vpopov-Ubuntu:~/Projects/pwdtest/postgresql$ git branch
Thanks for the input!
0001-Add-facility-to-store-multiple-password-verifiers.patch:2547:
trailing
whitespace.
warning: 1 line adds whitespace errors.
0003-Add-pg_auth_verifiers_sanitize.patch:87: indent with spaces.
if (!superuser())
warning: 1 line adds whitespace errors.Argh, yes. Those two ones have slipped though my successive rebases I
think. Will fix in my tree, I don't think that it is worth sending
again the whole series just for that though.
--
Michael--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi, Michael
Few questions about the documentation.
config.sgml:1200
<listitem>
<para>
Specifies a comma-separated list of supported password formats by
the server. Supported formats are currently <literal>plain</> and
<literal>md5</>.
</para><para>
When a password is specified in <xref linkend="sql-createuser"> or
<xref linkend="sql-alterrole">, this parameter determines if the
password specified is authorized to be stored or not, returning
an error message to caller if it is not.
</para><para>
The default is <literal>plain,md5,scram</>, meaning that
MD5-encrypted
passwords, plain passwords, and SCRAM-encrypted passwords are
accepted.
</para>
</listitem>
The default value contains "scram". Shouldn't be here also:
Specifies a comma-separated list of supported password formats by
the server. Supported formats are currently <literal>plain</>,
<literal>md5</> and <literal>scram</>.
Or I missed something?
And one more:
config.sgml:1284
<para>
<varname>db_user_namespace</> causes the client's and
server's user name representation to differ.
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
<literal>md5</> uses the user name as salt on both the
client and server, <literal>md5</> cannot be used with
<varname>db_user_namespace</>.
</para>
Looks like the same (pls, correct me if I'm wrong) is applicable for
"scram" as I see from the code below. Shouldn't be "scram" mentioned here
also? Here's the code:
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 28f9fb5..df0cc1d 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char
*raw_line)
} parsedline->auth_method = uaMD5; } + else if (strcmp(token->string, "scram") == 0) + { + if (Db_user_namespace) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("SCRAM authentication is not supported when \"db_user_namespace\"
is enabled"),
Show quoted text
+ 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;
On Wed, Mar 2, 2016 at 4:05 AM, Dmitry Dolgov <9erthalion6@gmail.com> wrote:
[...]
Thanks for the review.
The default value contains "scram". Shouldn't be here also:
Specifies a comma-separated list of supported password formats by
the server. Supported formats are currently <literal>plain</>,
<literal>md5</> and <literal>scram</>.Or I missed something?
Ah, I see. That's in the documentation of password_protocols. Yes
scram should be listed there as well. That should be fixed in 0009.
<para>
<varname>db_user_namespace</> causes the client's and
server's user name representation to differ.
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
<literal>md5</> uses the user name as salt on both the
client and server, <literal>md5</> cannot be used with
<varname>db_user_namespace</>.
</para>Looks like the same (pls, correct me if I'm wrong) is applicable for "scram"
as I see from the code below. Shouldn't be "scram" mentioned here also?
Oops. Good catch. Yes it should be mentioned as part of the SCRAM patch (0009).
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
<para>
<varname>db_user_namespace</> causes the client's and
server's user name representation to differ.
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
<literal>md5</> uses the user name as salt on both the
client and server, <literal>md5</> cannot be used with
<varname>db_user_namespace</>.
</para>
Also in doc/src/sgml/ref/create_role.sgml is should be instead of
<term>PASSWORD VERIFIERS ( <replaceable
class="PARAMETER">verifier_type</replaceable> = '<replaceable
class="PARAMETER">password</replaceable>'</term>
like this
<term><literal>PASSWORD VERIFIERS</> ( <replaceable
class="PARAMETER">verifier_type</replaceable> = '<replaceable
class="PARAMETER">password</replaceable>'</term>-- Regards, Valery Popov
Postgres Professional http://www.postgrespro.com The Russian Postgres
Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 2, 2016 at 5:43 PM, Valery Popov <v.popov@postgrespro.ru> wrote:
<para>
<varname>db_user_namespace</> causes the client's and
server's user name representation to differ.
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
<literal>md5</> uses the user name as salt on both the
client and server, <literal>md5</> cannot be used with
<varname>db_user_namespace</>.
</para>Also in doc/src/sgml/ref/create_role.sgml is should be instead of
<term>PASSWORD VERIFIERS ( <replaceable
class="PARAMETER">verifier_type</replaceable> = '<replaceable
class="PARAMETER">password</replaceable>'</term>
like this
<term><literal>PASSWORD VERIFIERS</> ( <replaceable
class="PARAMETER">verifier_type</replaceable> = '<replaceable
class="PARAMETER">password</replaceable>'</term>
So the <literal> markup is missing. Thanks. I am taking note of it.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
This is a review of "Password identifiers, protocol aging and SCRAM
protocol" patches
/messages/by-id/CAB7nPqSMXU35g=W9X74HVeQp0uvgJxvYOuA4A-A3M+0wfEBv-w@mail.gmail.com
Contents & Purpose
--------------------------
There was a discussion dedicated to SCRAM:
/messages/by-id/55192AFE.6080106@iki.fi
This set of patches implements the following:
- Introduce in Postgres an extensible password aging facility, by having
a new concept of 1 user/multiple password verifier, one password
verifier per protocol.
- Give to system administrators tools to decide unsupported protocols,
and have pg_upgrade use that
- Introduce new password protocols for Postgres, aimed at replacing
existing, say limited ones.
This set of patches consists of 9 separate patches.
Description of each patch is well described in initial thread email and
in comments.
The first set of patches 0001-0008 adds facility to store multiple
password verifiers,
CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS, new
superuser GUC parameters which specifies a list of supported password
protocols in Postgres backend, added pg_auth_verifiers_sanitize
function, removed password verifiers for unsupported protocols in
pg_upgrade, and more features.
The second set of patch 0005~0008 introduces a new protocol, SCRAM, and
0009 is SCRAM itself.
Initial Run
-------------
Included in the patches are:
- source code
- regression tests
- documentation
The source code is well commented.
The patches are in context diff format and were applied correctly to
HEAD (there were 2 warnings, and it was fixed by author).
There were several markup warnings, should be fixed by author.
Regression tests pass successfully, without errors. It seems that the
patches work as expected.
The patch 0009 depends on all previous patches 0001-0008: first we need
to apply patches 0001-0008, then 0009.
Performance
-----------
I have not tested possible performance issues yet.
Conclusion
--------------
I think introduced features are useful and I vote for commit +1.
On 03/02/2016 02:55 PM, Michael Paquier wrote:
On Wed, Mar 2, 2016 at 5:43 PM, Valery Popov <v.popov@postgrespro.ru> wrote:
So the <literal> markup is missing. Thanks. I am taking note of it.
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2/23/16 2:17 AM, Michael Paquier wrote:
As a continuation of the thread firstly dedicated to SCRAM:
/messages/by-id/55192AFE.6080106@iki.fi
Here is a new thread aimed at gathering all the ideas of this previous
thread and aimed at clarifying a bit what has been discussed until now
regarding password protocols, verifiers, and SCRAM itself.
It looks like this patch set is a bit out of date.
When applying 0004:
$ git apply
../other/0004-Remove-password-verifiers-for-unsupported-protocols-.patch
error: patch failed: src/bin/pg_upgrade/pg_upgrade.c:262
error: src/bin/pg_upgrade/pg_upgrade.c: patch does not apply
Then I tried to build with just 0001-0003:
cd /postgres/src/include/catalog && '/usr/bin/perl' ./duplicate_oids
3318
3319
3320
3321
3322
make[3]: *** [postgres.bki] Error 1
Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Mar 14, 2016 at 4:32 PM, David Steele <david@pgmasters.net> wrote:
On 2/23/16 2:17 AM, Michael Paquier wrote:
As a continuation of the thread firstly dedicated to SCRAM:
/messages/by-id/55192AFE.6080106@iki.fi
Here is a new thread aimed at gathering all the ideas of this previous
thread and aimed at clarifying a bit what has been discussed until now
regarding password protocols, verifiers, and SCRAM itself.It looks like this patch set is a bit out of date.
When applying 0004:
$ git apply
../other/0004-Remove-password-verifiers-for-unsupported-protocols-.patch
error: patch failed: src/bin/pg_upgrade/pg_upgrade.c:262
error: src/bin/pg_upgrade/pg_upgrade.c: patch does not applyThen I tried to build with just 0001-0003:
cd /postgres/src/include/catalog && '/usr/bin/perl' ./duplicate_oids
3318
3319
3320
3321
3322
make[3]: *** [postgres.bki] Error 1Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".
Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Mar 14, 2016 at 5:06 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Mar 14, 2016 at 4:32 PM, David Steele <david@pgmasters.net> wrote:
Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.
And here they are. I have addressed the documentation and the
whitespaces reported up to now at the same time.
--
Michael
Attachments:
0001-Add-facility-to-store-multiple-password-verifiers.patchtext/x-patch; charset=US-ASCII; name=0001-Add-facility-to-store-multiple-password-verifiers.patchDownload
From cdab0fa69a20b3a65dcf106b1cee24a5701b3e3b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 14 Mar 2016 23:37:28 +0100
Subject: [PATCH 1/9] Add facility to store multiple password verifiers
This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
able to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.
CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.
password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.
pg_shadown, which had up to now the concept of storing the password plain
or md5 value is now clobbered. Users and client applications are advised to
switch to pg_auth_verifiers instead.
The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.
Extra facility for protocol aging and upgrades will be added in a different
commit.
Regression tests and documentation are added accordingly.
---
contrib/passwordcheck/passwordcheck.c | 138 +++++------
doc/src/sgml/catalogs.sgml | 103 ++++++---
doc/src/sgml/config.sgml | 17 +-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/catalog.c | 4 +
src/backend/catalog/system_views.sql | 2 +-
src/backend/commands/user.c | 318 +++++++++++++++++---------
src/backend/libpq/crypt.c | 72 ++++--
src/backend/nodes/copyfuncs.c | 14 ++
src/backend/nodes/equalfuncs.c | 12 +
src/backend/parser/gram.y | 98 +++++++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 23 +-
src/backend/utils/cache/syscache.c | 23 ++
src/backend/utils/misc/guc.c | 65 ++++--
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/bin/initdb/initdb.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 77 ++++++-
src/include/catalog/indexing.h | 5 +
src/include/catalog/pg_auth_verifiers.h | 62 +++++
src/include/catalog/pg_authid.h | 8 +-
src/include/commands/user.h | 11 +-
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 11 +
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/password.out | 104 +++++++++
src/test/regress/expected/roleattributes.out | 192 ++++++++--------
src/test/regress/expected/rules.out | 2 +-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 72 ++++++
34 files changed, 1109 insertions(+), 367 deletions(-)
create mode 100644 src/include/catalog/pg_auth_verifiers.h
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index b4c1ce0..13ad053 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
#include <crack.h>
#endif
+#include "catalog/pg_auth_verifiers.h"
#include "commands/user.h"
#include "fmgr.h"
#include "libpq/md5.h"
+#include "nodes/parsenodes.h"
PG_MODULE_MAGIC;
@@ -50,87 +52,93 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ List *passwordVerifiers,
Datum validuntil_time,
bool validuntil_null)
{
int namelen = strlen(username);
- int pwdlen = strlen(password);
+ int pwdlen;
char encrypted[MD5_PASSWD_LEN + 1];
int i;
bool pwd_has_letter,
pwd_has_nonletter;
+ ListCell *l;
- switch (password_type)
+ foreach(l, passwordVerifiers)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
- /*
- * For unencrypted passwords we can perform better checks
- */
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ switch (spec->veriftype)
+ {
+ case AUTH_VERIFIER_MD5:
+
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ if (!pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ if (strcmp(spec->value, encrypted) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ break;
+
+ case AUTH_VERIFIER_PLAIN:
+
/*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
+ * For unencrypted passwords we can perform better checks
*/
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must contain both letters and nonletters")));
+ pwdlen = strlen(spec->value);
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(spec->value, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
+ /*
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
+ */
+ if (isalpha((unsigned char) spec->value[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
+ break;
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
+ default:
+ elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+ break;
+ }
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 951f59b..9a880be 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1161,13 +1161,6 @@
</para>
<para>
- Since this catalog contains passwords, it must not be publicly readable.
- <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
- is a publicly readable view on
- <structname>pg_authid</structname> that blanks out the password field.
- </para>
-
- <para>
<xref linkend="user-manag"> contains detailed information about user and
privilege management.
</para>
@@ -1270,21 +1263,6 @@
</row>
<row>
- <entry><structfield>rolpassword</structfield></entry>
- <entry><type>text</type></entry>
- <entry>
- Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
- </entry>
- </row>
-
- <row>
<entry><structfield>rolvaliduntil</structfield></entry>
<entry><type>timestamptz</type></entry>
<entry>Password expiry time (only used for password authentication);
@@ -1296,6 +1274,77 @@
</sect1>
+ <sect1 id="catalog-pg-auth-verifiers">
+ <title><structname>pg_auth_verifiers</structname></title>
+
+ <indexterm zone="catalog-pg-auth-verifiers">
+ <primary>pg_auth_verifiers</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_auth_verifiers</structname> contains password
+ information for database authorization identifiers (roles).
+ </para>
+
+ <para>
+ Since this catalog contains passwords, it must not be publicly readable.
+ </para>
+
+ <para>
+ Because user identities are cluster-wide,
+ <structname>pg_auth_verifiers</structname>
+ is shared across all databases of a cluster: there is only one
+ copy of <structname>pg_auth_verifiers</structname> per cluster, not
+ one per database.
+ </para>
+
+ <table>
+ <title><structname>pg_auth_verifiers</> Columns</title>
+
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>oid</structfield></entry>
+ <entry><type>roleid</type></entry>
+ <entry>Role identifier OID</entry>
+ </row>
+
+ <row>
+ <entry><structfield>verimet</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry>
+ <literal>p</> = plain format,
+ <literal>m</> = MD5-encrypted
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>text</structfield></entry>
+ <entry><type>verival</type></entry>
+ <entry>
+ Password (possibly encrypted with format defined in
+ <structfield>verimet</>). If the password
+ is MD5-encrypted, this column will begin with the string <literal>md5</>
+ followed by a 32-character hexadecimal MD5 hash. The MD5 hash
+ will be of the user's password concatenated to their user name.
+ For example, if user <literal>joe</> has password <literal>xyzzy</>,
+ <productname>PostgreSQL</> will store the md5 hash of
+ <literal>xyzzyjoe</>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
<sect1 id="catalog-pg-auth-members">
<title><structname>pg_auth_members</structname></title>
@@ -9299,9 +9348,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry><structfield>passwd</structfield></entry>
<entry><type>text</type></entry>
<entry></entry>
- <entry>Password (possibly encrypted); null if none. See
- <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>
- for details of how encrypted passwords are stored.</entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
@@ -9771,9 +9818,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</row>
<row>
- <entry><structfield>passwd</structfield></entry>
- <entry><type>text</type></entry>
- <entry>Not the password (always reads as <literal>********</>)</entry>
+ <entry><structfield>passwd</structfield></entry>
+ <entry><type>text</type></entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6c73fb4..31605b2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>string</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
+ Specifies a comma-separated list of password encryption formats.
+ Supported formats are <literal>plain</> and <literal>md5</>.
+ </para>
+
+ <para>
When a password is specified in <xref
linkend="sql-createuser"> or
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
- <literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ <literal>UNENCRYPTED</>, this parameter determines the list of
+ encryption formats this password is to be stored as.
+ </para>
+
+ <para>
+ The default is <literal>md5</> (encrypt the password with MD5
+ encryption).
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..d58431f 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -211,9 +212,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
roles having the <literal>LOGIN</literal> attribute, but you
can nonetheless define one for roles without it.) If you do
not plan to use password authentication you can omit this
- option. If no password is specified, the password will be set
- to null and password authentication will always fail for that
- user. A null password can optionally be written explicitly as
+ option. If no password is specified, no password will be set
+ and password authentication will always fail for that user.
+ A null password can optionally be written explicitly as
<literal>PASSWORD NULL</literal>.
</para>
</listitem>
@@ -245,6 +246,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD VERIFIERS</literal> ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' )</term>
+ <listitem>
+ <para>
+ Sets the list of password verifiers for the role. Currently only
+ <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+ for unencrypted format, can be specified as verifier format type
+ for <literal>verifier_type</>. If the password defined with
+ <literal>plain</> type is already in MD5-encrypted format
+ the password will be stored as MD5-encrypted. If the password defined
+ is in plain-format and that <literal>verifier_type</> is set
+ to <literal>md5</>, the password will be stored as MD5-encrypted.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
- pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+ pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+ pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index bead2c1..7406d2f 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthVerifRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes (see indexing.h) */
if (relationId == AuthIdRolnameIndexId ||
relationId == AuthIdOidIndexId ||
+ relationId == AuthVerifRoleMethodIndexId ||
+ relationId == AuthVerifMethodRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 84aa061..3cf8c98 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,7 +40,7 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ '********'::text as passwd,
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 4baeaa2..83fc210 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#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"
@@ -42,9 +44,6 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
-extern bool Password_encryption;
-
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+ char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
/* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
Oid roleid;
ListCell *item;
ListCell *option;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
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;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
+ */
+ FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+ /*
+ * Call the password checking hook if there is one defined.
*/
- if (check_password_hook && password)
+ if (check_password_hook && passwordVerifiers != NIL)
(*check_password_hook) (stmt->role,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -365,24 +364,6 @@ 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_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
roleid = simple_heap_insert(pg_authid_rel, tuple);
CatalogUpdateIndexes(pg_authid_rel, tuple);
+ /* store password verifiers */
+ if (passwordVerifiers)
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
Form_pg_authid authform;
ListCell *option;
char *rolename = NULL;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
int bypassrls = -1;
- DefElem *dpassword = NULL;
+ DefElem *dpasswordVerifiers = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
!dconnlimit &&
!rolemembers &&
!validUntil &&
- dpassword &&
+ dpasswordVerifiers &&
roleid == GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
*/
- if (check_password_hook && password)
+ FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+ /*
+ * Call the password checking hook if there is one defined.
+ */
+ if (check_password_hook && passwordVerifiers)
(*check_password_hook) (rolename,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
- /* password */
- if (password)
- {
- 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;
- }
-
/* valid until */
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
heap_freetuple(new_tuple);
/*
+ * Update password verifiers. The old entries are completely cleaned up
+ * and are updated by what is wanted through this command.
+ */
+ if (passwordVerifiers)
+ {
+ DeletePasswordVerifiers(roleid);
+ CommandCounterIncrement();
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+ }
+
+ /* remove password verifiers */
+ if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+ DeletePasswordVerifiers(roleid);
+
+ /*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
/*
- * Remove any comments or security labels on this role.
+ * Remove any comments, password verifiers or security labels on this
+ * role.
*/
+ DeletePasswordVerifiers(roleid);
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
@@ -1106,11 +1080,11 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ authtuple;
TupleDesc dsc;
- Relation rel;
- Datum datum;
- bool isnull;
+ Relation pg_authid_rel,
+ pg_auth_verifiers_rel;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
- rel = heap_open(AuthIdRelationId, RowExclusiveLock);
- dsc = RelationGetDescr(rel);
+ pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+ dsc = RelationGetDescr(pg_authid_rel);
oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ 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);
-
- if (!isnull && isMD5(TextDatumGetCString(datum)))
- {
- /* 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;
-
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
- }
-
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &oldtuple->t_self, newtuple);
+ simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
- CatalogUpdateIndexes(rel, newtuple);
+ CatalogUpdateIndexes(pg_authid_rel, newtuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
ReleaseSysCache(oldtuple);
+ /* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+ authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(AUTH_VERIFIER_MD5));
+ if (HeapTupleIsValid(authtuple))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+ ReleaseSysCache(authtuple);
+ }
+
/*
- * Close pg_authid, but keep lock till commit.
+ * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
*/
- heap_close(rel, NoLock);
+ heap_close(pg_auth_verifiers_rel, NoLock);
+ heap_close(pg_authid_rel, NoLock);
return address;
}
@@ -1611,3 +1588,130 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
heap_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+ ListCell *l;
+
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ if (spec->value == NULL)
+ continue;
+
+ /*
+ * Check if given value for an MD5 verifier is adapted and do
+ * conversion as needed. If an MD5 password is provided but that
+ * the verifier has a plain format switch type of verifier
+ * accordingly.
+ * This machinery is here for backward-compatibility with pre-9.6
+ * instances of Postgres, an md5 hash passed as a plain verifier
+ * should still be treated as an MD5 entry.
+ */
+ if (spec->veriftype == AUTH_VERIFIER_MD5 &&
+ !isMD5(spec->value))
+ {
+ char encrypted_passwd[MD5_PASSWD_LEN + 1];
+ if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
+ encrypted_passwd))
+ elog(ERROR, "password encryption failed");
+ spec->value = pstrdup(encrypted_passwd);
+ }
+ else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
+ isMD5(spec->value))
+ spec->veriftype = AUTH_VERIFIER_MD5;
+ }
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+ ListCell *l;
+ Relation pg_auth_verifiers_rel;
+ TupleDesc pg_auth_verifiers_dsc;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+ foreach(l, verifiers)
+ {
+ Datum new_record[Natts_pg_auth_verifiers];
+ bool new_record_nulls[Natts_pg_auth_verifiers];
+ HeapTuple tuple;
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ /* Move on if no verifier value define */
+ if (spec->value == NULL)
+ continue;
+
+ /* Build tuple and insert it */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+ new_record[Anum_pg_auth_verifiers_roleid - 1] = ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_verifiers_method - 1] =
+ CharGetDatum(spec->veriftype);
+
+ new_record[Anum_pg_auth_verifiers_value - 1] =
+ CStringGetTextDatum(spec->value);
+
+ tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+ new_record, new_record_nulls);
+
+ simple_heap_insert(pg_auth_verifiers_rel, tuple);
+ CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+ /* CCI after each change */
+ CommandCounterIncrement();
+ }
+
+ /* Keep locks until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+ Relation pg_auth_verifiers_rel;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+ HeapTuple tmp_tuple;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ /*
+ * Remove role entries from pg_auth_verifiers table. All the tuples that
+ * are similar to the role dropped need to be removed.
+ */
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_verifiers_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_verifiers_rel,
+ AuthVerifRoleMethodIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+ }
+
+ systable_endscan(sscan);
+
+ /* keep lock until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d79f5a2..9211ec2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
#include <crypt.h>
#endif
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#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_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+ HeapTuple tuple;
+ Datum verifier_datum;
+ char *verifier;
+ bool isnull;
+
+ /* Now attempt to grab the verifier value */
+ tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(method));
+ if (!HeapTupleIsValid(tuple))
+ return NULL; /* no verifier available */
+
+ verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+ Anum_pg_auth_verifiers_value,
+ &isnull);
+ verifier = TextDatumGetCString(verifier_datum);
+
+ ReleaseSysCache(tuple);
+
+ return verifier;
+}
/*
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,13 +71,15 @@ 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;
bool isnull;
+ Oid roleid;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -56,16 +90,24 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
return STATUS_ERROR; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ verifier_is_md5 = true;
+
+ roleid = HeapTupleGetOid(roleTup);
+ verifier = get_role_verifier(roleid, AUTH_VERIFIER_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(roleid, AUTH_VERIFIER_PLAIN);
+
+ 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);
@@ -74,7 +116,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (*verifier == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
@@ -92,10 +134,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))
{
@@ -108,7 +150,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))
@@ -130,7 +172,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);
@@ -143,7 +185,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index df7c2fa..a2cb4b5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2696,6 +2696,17 @@ _copyRoleSpec(const RoleSpec *from)
return newnode;
}
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+ AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+ COPY_SCALAR_FIELD(veriftype);
+ COPY_STRING_FIELD(value);
+
+ return newnode;
+}
+
static Query *
_copyQuery(const Query *from)
{
@@ -5011,6 +5022,9 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_AuthVerifierSpec:
+ retval = _copyAuthVerifierSpec(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9c3959..8087c39 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2594,6 +2594,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+ COMPARE_SCALAR_FIELD(veriftype);
+ COMPARE_STRING_FIELD(value);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3338,6 +3347,9 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_AuthVerifierSpec:
+ retval = _equalAuthVerifierSpec(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b9aeb31..ccac887 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "commands/user.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
#include "parser/parser.h"
#include "parser/parse_expr.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
+ auth_verifier_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name
%type <node> var_value zone_value
%type <node> auth_ident RoleSpec opt_granted_by
+%type <node> AuthVerifierSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,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
@@ -918,25 +924,86 @@ AlterOptRoleList:
| /* EMPTY */ { $$ = NIL; }
;
+auth_verifier_list:
+ AuthVerifierSpec
+ { $$ = list_make1((Node*)$1); }
+ | auth_verifier_list ',' AuthVerifierSpec
+ { $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+ NonReservedWord '=' Sconst
+ {
+ char type;
+
+ if (strcmp($1, "md5") == 0)
+ type = AUTH_VERIFIER_MD5;
+ else if (strcmp($1, "plain") == 0)
+ type = AUTH_VERIFIER_PLAIN;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized authorization verifier option \"%s\"", $1),
+ parser_errposition(@1)));
+ $$ = (Node *) makeAuthVerifierSpec(type, $3);
+ }
+
AlterOptRoleElem:
PASSWORD Sconst
{
- $$ = makeDefElem("password",
- (Node *)makeString($2));
+ char *rawstring = pstrdup(Password_encryption);
+ List *elemlist;
+ ListCell *l;
+ List *result = NIL;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l);
+ char veriftype;
+ AuthVerifierSpec *n;
+
+ if (strcmp(meth_name, "md5") == 0)
+ veriftype = AUTH_VERIFIER_MD5;
+ else if (strcmp(meth_name, "plain") == 0)
+ veriftype = AUTH_VERIFIER_PLAIN;
+ else
+ Assert(false); /* should not happen */
+ n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(veriftype, $2);
+ result = lappend(result, (Node *)n);
+ }
+ pfree(rawstring);
+ list_free(elemlist);
+
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *) result);
}
| PASSWORD NULL_P
{
- $$ = makeDefElem("password", NULL);
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| ENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("encryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| UNENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("unencryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
+ }
+ | PASSWORD VERIFIERS '(' auth_verifier_list ')'
+ {
+ $$ = makeDefElem("passwordVerifiers", (Node *)$4);
}
| INHERIT
{
@@ -13902,6 +13969,7 @@ unreserved_keyword:
| VALIDATOR
| VALUE_P
| VARYING
+ | VERIFIERS
| VERSION_P
| VIEW
| VIEWS
@@ -14296,6 +14364,20 @@ makeRoleSpec(RoleSpecType type, int location)
return (Node *) spec;
}
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+ AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+ spec->veriftype = type;
+ spec->value = password;
+
+ return (Node *) spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index e929616..bb83df2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
case AUTHMEMMEMROLE:
+ case AUTHVERIFROLEMETH:
/*
* Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 130c06d..c1b6330 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -98,6 +99,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -1567,8 +1569,8 @@ LookupOpclassInfo(Oid operatorClassOid,
* catalogs.
*
* formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_shseclabel, pg_class, pg_attribute, pg_proc, and pg_type
- * (see RelationCacheInitializePhase2/3).
+ * pg_auth_verifiers pg_shseclabel, pg_class, pg_attribute, pg_proc, and
+ * pg_type (see RelationCacheInitializePhase2/3).
*
* Note that these catalogs can't have constraints (except attnotnull),
* default values, rules, or triggers, since we don't cope with any of that.
@@ -2871,6 +2873,7 @@ RelationBuildLocalRelation(const char *relname,
case DatabaseRelationId:
case AuthIdRelationId:
case AuthMemRelationId:
+ case AuthVerifRelationId:
case RelationRelationId:
case AttributeRelationId:
case ProcedureRelationId:
@@ -3257,10 +3260,12 @@ RelationCacheInitializePhase2(void)
true, Natts_pg_authid, Desc_pg_authid);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
false, Natts_pg_auth_members, Desc_pg_auth_members);
+ formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+ false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
false, Natts_pg_shseclabel, Desc_pg_shseclabel);
-#define NUM_CRITICAL_SHARED_RELS 4 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -3380,10 +3385,10 @@ RelationCacheInitializePhase3(void)
* initial lookup of MyDatabaseId, without which we'll never find any
* non-shared catalogs at all. Autovacuum calls InitPostgres with a
* database OID, so it instead depends on DatabaseOidIndexId. We also
- * need to nail up some indexes on pg_authid and pg_auth_members for use
- * during client authentication. SharedSecLabelObjectIndexId isn't
- * critical for the core system, but authentication hooks might be
- * interested in it.
+ * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+ * pg_auth_members for use during client authentication.
+ * SharedSecLabelObjectIndexId isn't critical for the core system, but
+ * authentication hooks might be interested in it.
*/
if (!criticalSharedRelcachesBuilt)
{
@@ -3397,10 +3402,12 @@ RelationCacheInitializePhase3(void)
AuthIdRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
+ load_critical_index(AuthVerifRoleMethodIndexId,
+ AuthVerifRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..ede8fc6 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -28,6 +28,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -248,6 +249,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthVerifRelationId, /* AUTHVERIFMETHROLE */
+ AuthVerifMethodRoleIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_method,
+ Anum_pg_auth_verifiers_roleid,
+ 0,
+ 0
+ },
+ 4
+ },
+ {AuthVerifRelationId, /* AUTHVERIFROLEMETH */
+ AuthVerifRoleMethodIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_roleid,
+ Anum_pg_auth_verifiers_method,
+ 0,
+ 0
+ },
+ 4
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index edcafce..ea7e4f7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,6 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -422,8 +424,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -435,6 +435,8 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
+char *Password_encryption;
+
char *cluster_name = "";
char *ConfigFileName;
char *HbaFileName;
@@ -1309,17 +1311,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3364,6 +3355,19 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password encryption methods."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines how the password is to be encrypted."),
+ GUC_LIST_INPUT
+ },
+ &Password_encryption,
+ "md5",
+ check_password_encryption, NULL, NULL
+ },
+
+ {
{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
gettext_noop("Location of the SSL server certificate file."),
NULL
@@ -10198,6 +10202,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
return true;
}
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+ char *rawstring = pstrdup(*newval); /* get copy of list string */
+ List *elemlist;
+ ListCell *l;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ pfree(rawstring);
+ list_free(elemlist);
+ Assert(false);
+ return false; /* GUC machinery should have already complained */
+ }
+
+ /* Check that only supported formats are listed */
+ foreach(l, elemlist)
+ {
+ char *encryption_name = (char *) lfirst(l);
+
+ if (strcmp(encryption_name, "md5") != 0 &&
+ strcmp(encryption_name, "plain") != 0)
+ {
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ return true;
+}
+
static const char *
show_unix_socket_permissions(void)
{
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee3d378..d6da960 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
#db_user_namespace = off
#row_security = on
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ed3ba7b..5315db7 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1530,10 +1530,11 @@ setup_auth(FILE *cmdfd)
const char *const * line;
static const char *const pg_authid_setup[] = {
/*
- * The authid table shouldn't be readable except through views, to
- * ensure passwords are not publicly visible.
+ * The authorization tables shouldn't be readable except through
+ * views, to ensure password data are not publicly visible.
*/
"REVOKE ALL on pg_authid FROM public;\n\n",
+ "REVOKE ALL on pg_auth_verifiers FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index be6b4a8..fc7a5ae 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
i_is_current_user;
int i;
- /* note: rolconfig is dumped later */
- if (server_version >= 90500)
+ /*
+ * Note: rolconfig is dumped later. In 9.6 and above, password
+ * information is dumped later on.
+ */
+ if (server_version >= 90600)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, "
+ "null::text as rolpassword, "
+ "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, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
PQclear(res);
+ /*
+ * Dump password configuration for all roles.
+ */
+ if (server_version >= 90600)
+ {
+ char *current_user = NULL;
+ bool first_elt = true;
+ res = executeQuery(conn,
+ "SELECT a.rolname, v.verimet, v.verival "
+ "FROM pg_auth_verifiers AS v "
+ "LEFT JOIN pg_authid AS a ON (v.roleid = a.oid) "
+ "ORDER BY rolname;");
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *user_name = PQgetvalue(res, i, 0);
+ char verifier_meth = *PQgetvalue(res, i, 1);
+ char *verifier_value = PQgetvalue(res, i, 2);
+
+ /* Switch to new ALTER ROLE query when a different user is found */
+ if (current_user == NULL ||
+ strcmp(user_name, current_user) != 0)
+ {
+ /* Finish last query */
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+
+ resetPQExpBuffer(buf);
+
+ if (current_user)
+ pg_free(current_user);
+ current_user = pg_strdup(user_name);
+ first_elt = true;
+ appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+ current_user);
+ }
+
+ if (first_elt)
+ first_elt = false;
+ else
+ appendPQExpBufferStr(buf, ", ");
+
+ if (verifier_meth == 'm')
+ appendPQExpBufferStr(buf, "md5 = ");
+ else if (verifier_meth == 'p')
+ appendPQExpBufferStr(buf, "plain = ");
+ appendStringLiteralConn(buf, verifier_value, conn);
+ }
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+ }
+
+
fprintf(OPF, "\n\n");
destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ab2c1a8..28e5e56 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
#define AuthMemMemRoleIndexId 2695
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3337, on pg_auth_verifiers using btree(roleid oid_ops, verimet char_ops));
+#define AuthVerifRoleMethodIndexId 3337
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3338, on pg_auth_verifiers using btree(verimet char_ops, roleid oid_ops));
+#define AuthVerifMethodRoleIndexId 3338
+
DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
#define CastOidIndexId 2660
DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..d1281b7
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ * definition of the system "authorization password hashes" relation
+ * (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_auth_verifiers definition. cpp turns this into
+ * typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId 3335
+#define AuthVerifRelation_Rowtype_Id 3336
+
+CATALOG(pg_auth_verifiers,3335) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3336) BKI_SCHEMA_MACRO
+{
+ Oid roleid; /* ID of the role using this hash */
+ char verimet; /* Method used to generate the hash *
+ * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text verival BKI_FORCE_NOT_NULL; /* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ * Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ * the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ * compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers 3
+#define Anum_pg_auth_verifiers_roleid 1
+#define Anum_pg_auth_verifiers_method 2
+#define Anum_pg_auth_verifiers_value 3
+
+#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
+#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+
+#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index c163083..35f74d0 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ 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 */
timestamptz rolvaliduntil; /* password expiration time, if any */
#endif
} FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* compiler constants for pg_authid
* ----------------
*/
-#define Natts_pg_authid 11
+#define Natts_pg_authid 10
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
@@ -85,8 +84,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_rolvaliduntil 11
+#define Anum_pg_authid_rolvaliduntil 10
/* ----------------
* initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* user choices.
* ----------------
*/
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
#define BOOTSTRAP_SUPERUSERID 10
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
+/* GUC parameter */
+extern char *Password_encryption;
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+ List *passwordVerifiers,
+ Datum validuntil_time,
+ bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fad9988..2c37850 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -448,6 +448,7 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_AuthVerifierSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2fd0629..a14969c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -312,6 +312,17 @@ typedef struct RoleSpec
} RoleSpec;
/*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+ NodeTag type;
+ char veriftype; /* type of this verifier, as listed in *
+ * pg_auth_verifiers.h */
+ char *value; /* value specified by user */
+} AuthVerifierSpec;
+
+/*
* FuncCall - a function or aggregate invocation
*
* agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6e1e820..56635d5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -416,6 +416,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/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..300ebaa 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHVERIFMETHROLE,
+ AUTHVERIFROLEMETH,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..ffc66a3
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,104 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR: invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | p | rol
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd3 | p | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+------------------+---------+--------
+ role_passwd3_new | p | rol
+(1 row)
+
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | p | foo
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR: unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+ ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+ rolname | verimet | substr
+--------------+---------+--------
+ role_passwd1 | m | md5
+ role_passwd2 | p | foo
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
+ rolname | verimet
+---------+---------
+(0 rows)
+
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index aa5f42a..66ea550 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE test_def_superuser;
SELECT * FROM pg_authid WHERE rolname = 'test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH NOSUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE test_def_inherit;
SELECT * FROM pg_authid WHERE rolname = 'test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH INHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE test_def_createrole;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH NOCREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE test_def_createdb;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH NOCREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE test_def_role_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER test_def_user_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE test_def_replication;
SELECT * FROM pg_authid WHERE rolname = 'test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE test_replication WITH NOREPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE test_def_bypassrls;
SELECT * FROM pg_authid WHERE rolname = 'test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH NOBYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 22ea06c..82b5394 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1631,7 +1631,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,
+ '********'::text AS passwd,
(pg_authid.rolvaliduntil)::abstime AS valuntil,
s.setconfig AS useconfig
FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
pg_attrdef|t
pg_attribute|t
pg_auth_members|t
+pg_auth_verifiers|t
pg_authid|t
pg_cast|t
pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index bec0316..bbe969b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7e9b319..cd724b6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -111,6 +111,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4f4f577
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,72 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.verimet;
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.verimet;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.verimet
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.roleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.verimet;
--
2.7.3
0002-Introduce-password_protocols.patchtext/x-patch; charset=US-ASCII; name=0002-Introduce-password_protocols.patchDownload
From 98c28efa80e4b55747b2eeb03ea42c91f2d98f31 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:32:50 +0900
Subject: [PATCH 2/9] Introduce password_protocols
This new superuser GUC parameters specifies a list of supported password
protocols in Postgres backend. This is useful for system maintainers to
prevent the creation of password using a protocol thought as unsafe in
certain deployments.
The current default is 'plain,md5', authorizing the creation of both plain
passwords and MD5-encrypted passwords in the system.
---
doc/src/sgml/config.sgml | 27 ++++++++++++++++
src/backend/commands/user.c | 46 +++++++++++++++++++++++++++
src/backend/utils/misc/guc.c | 29 +++++++++++++----
src/backend/utils/misc/postgresql.conf.sample | 2 ++
src/include/commands/user.h | 3 +-
src/test/regress/expected/password.out | 28 ++++++++++++++++
src/test/regress/sql/password.sql | 20 ++++++++++++
7 files changed, 147 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 31605b2..154c1a7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1190,6 +1190,33 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-password-protocols" xreflabel="password_protocols">
+ <term><varname>password_protocols</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>password_protocols</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies a comma-separated list of supported password formats by
+ the server. Supported formats are currently <literal>plain</> and
+ <literal>md5</>.
+ </para>
+
+ <para>
+ When a password is specified in <xref linkend="sql-createuser"> or
+ <xref linkend="sql-alterrole">, this parameter determines if the
+ password specified is authorized to be stored or not, returning
+ an error message to caller if it is not.
+ </para>
+
+ <para>
+ The default is <literal>plain,md5</>, meaning that MD5-encrypted
+ passwords and plain passwords are both accepted.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
<term><varname>krb_server_keyfile</varname> (<type>string</type>)
<indexterm>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 83fc210..2b3a33c 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1597,6 +1597,8 @@ static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
{
ListCell *l;
+ char *rawstring;
+ List *elemlist;
foreach(l, verifiers)
{
@@ -1627,6 +1629,50 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
isMD5(spec->value))
spec->veriftype = AUTH_VERIFIER_MD5;
}
+
+ /*
+ * Now that the list of verifiers is built and consistent with the input
+ * values, check that the list of verifiers specified is actually
+ * supported by server or not.
+ */
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ /*
+ * This is O(N ^ 2), but the small number of elements in the list of
+ * protocols supported is not worth complicating this code.
+ */
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+ ListCell *l2;
+ bool found_match = false;
+
+ foreach(l2, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l2);
+
+ if ((strcmp(meth_name, "md5") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_MD5) ||
+ (strcmp(meth_name, "plain") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_PLAIN))
+ {
+ found_match = true;
+ break;
+ }
+ }
+
+ if (!found_match)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("specified password protocol not allowed"),
+ errdetail("List of authorized protocols is specified by password_protocols.")));
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ea7e4f7..17a5038 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -179,8 +179,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
-static bool check_password_encryption(char **newval, void **extra,
- GucSource source);
+static bool check_password_methods(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -436,6 +436,7 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
char *Password_encryption;
+char *password_protocols;
char *cluster_name = "";
char *ConfigFileName;
@@ -3364,7 +3365,21 @@ static struct config_string ConfigureNamesString[] =
},
&Password_encryption,
"md5",
- check_password_encryption, NULL, NULL
+ check_password_methods, NULL, NULL
+ },
+
+ {
+ {"password_protocols", PGC_SUSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password protocols supported."),
+ gettext_noop("The list of password protocols specified by this "
+ "parameter determines what are the authorized methods "
+ "on the server when running CREATE USER or ALTER "
+ "USER."),
+ GUC_LIST_INPUT
+ },
+ &password_protocols,
+ "plain,md5",
+ check_password_methods, NULL, NULL
},
{
@@ -10203,7 +10218,7 @@ check_cluster_name(char **newval, void **extra, GucSource source)
}
static bool
-check_password_encryption(char **newval, void **extra, GucSource source)
+check_password_methods(char **newval, void **extra, GucSource source)
{
char *rawstring = pstrdup(*newval); /* get copy of list string */
List *elemlist;
@@ -10221,10 +10236,10 @@ check_password_encryption(char **newval, void **extra, GucSource source)
/* Check that only supported formats are listed */
foreach(l, elemlist)
{
- char *encryption_name = (char *) lfirst(l);
+ char *method_name = (char *) lfirst(l);
- if (strcmp(encryption_name, "md5") != 0 &&
- strcmp(encryption_name, "plain") != 0)
+ if (strcmp(method_name, "md5") != 0 &&
+ strcmp(method_name, "plain") != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d6da960..065b4ab 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -88,6 +88,8 @@
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
#password_encryption = 'md5'
+#password_protocols = 'plain,md5' # comma-separated list of supported
+ # password protocols.
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 636e8ac..7a73bc5 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,8 +14,9 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
-/* GUC parameter */
+/* GUC parameters */
extern char *Password_encryption;
+extern char *password_protocols;
typedef void (*check_password_hook_type) (const char *username,
List *passwordVerifiers,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index ffc66a3..b82cad6 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -9,6 +9,14 @@ ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+ERROR: invalid value for parameter "password_protocols": "novalue"
+SET password_protocols = true; -- error
+ERROR: invalid value for parameter "password_protocols": "true"
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -88,6 +96,26 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
role_passwd4 | m | md5
(4 rows)
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 4f4f577..79df8da 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -9,6 +9,13 @@ SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+SET password_protocols = true; -- error
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
+
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -59,6 +66,19 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
WHERE a.rolname LIKE 'role_passwd%'
ORDER BY a.rolname, v.verimet;
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
--
2.7.3
0003-Add-pg_auth_verifiers_sanitize.patchtext/x-patch; charset=US-ASCII; name=0003-Add-pg_auth_verifiers_sanitize.patchDownload
From 6934d0f185ea7e30afc447547e21d876cae8e58e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 14 Mar 2016 23:49:23 +0100
Subject: [PATCH 3/9] Add pg_auth_verifiers_sanitize
This function is aimed at being used by pg_upgrade and system administers
to filter out password verifiers that are based on protocols not defined
on the system per the list given by password_protocols.
---
doc/src/sgml/func.sgml | 34 +++++++++++++++++++++
src/backend/commands/user.c | 71 +++++++++++++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +++
src/include/commands/user.h | 2 ++
4 files changed, 111 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 000489d..3129da6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -19050,6 +19050,40 @@ SELECT (pg_stat_file('filename')).modification;
</sect2>
+ <sect2 id="functions-password">
+ <title>Password Functions</title>
+
+ <para>
+ The functions shown in <xref linkend="functions-password-table"> manage
+ system passwords.
+ </para>
+
+ <table id="functions-password-table">
+ <title>Password Functions</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <literal><function>pg_auth_verifiers_sanitize()</function></literal>
+ </entry>
+ <entry><type>integer</type></entry>
+ <entry>remove password verifier entries in
+ <link linkend="catalog-pg-auth-verifiers"></> for password protocols
+ not listed in <xref linkend="guc-password-protocols">. This function
+ is limited to superusers. The return result is the number of entries
+ removed from the table.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="functions-trigger">
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2b3a33c..d77e379 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1761,3 +1761,74 @@ DeletePasswordVerifiers(Oid roleid)
/* keep lock until the end of transaction */
heap_close(pg_auth_verifiers_rel, NoLock);
}
+
+/*
+ * pg_auth_sanitize
+ *
+ * Scan through pg_auth_verifiers and remove all the password verifiers
+ * that are not part of the list of supported protocols as defined by
+ * password_protocols. Returns to caller the number of entries removed.
+ */
+Datum
+pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
+{
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tup;
+ char *rawstring;
+ List *elemlist;
+ int res = 0;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to sanitize \"pg_auth_verifiers\""))));
+
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ rel = heap_open(AuthVerifRelationId, AccessShareLock);
+ scan = heap_beginscan_catalog(rel, 0, NULL);
+ while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_auth_verifiers authform =
+ (Form_pg_auth_verifiers) GETSTRUCT(tup);
+ ListCell *l;
+ bool remove_entry = true;
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = lfirst(l);
+
+ /* Check for protocol matches */
+ if (authform->verimet == AUTH_VERIFIER_MD5 &&
+ strcmp(meth_name, "md5") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ else if (authform->verimet == AUTH_VERIFIER_PLAIN &&
+ strcmp(meth_name, "plain") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ }
+
+ if (remove_entry)
+ {
+ simple_heap_delete(rel, &tup->t_self);
+ res++;
+ }
+ }
+
+ heap_endscan(scan);
+ heap_close(rel, NoLock);
+
+ pfree(rawstring);
+ list_free(elemlist);
+
+ PG_RETURN_INT32(res);
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 5c71bce..dd99480 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5196,6 +5196,10 @@ DESCR("for use by pg_upgrade");
DATA(insert OID = 3591 ( binary_upgrade_create_empty_extension PGNSP PGUID 12 1 0 0 0 f f f f f f v r 7 0 2278 "25 25 16 25 1028 1009 1009" _null_ _null_ _null_ _null_ _null_ binary_upgrade_create_empty_extension _null_ _null_ _null_ ));
DESCR("for use by pg_upgrade");
+/* commands/user.h */
+DATA(insert OID = 3339 ( pg_auth_verifiers_sanitize PGNSP PGUID 12 1 0 0 0 f f f f t f v u 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_auth_verifiers_sanitize _null_ _null_ _null_ ));
+DESCR("sanitize entries of pg_auth_verifiers using password_protocols");
+
/* replication/origin.h */
DATA(insert OID = 6003 ( pg_replication_origin_create PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 26 "25" _null_ _null_ _null_ _null_ _null_ pg_replication_origin_create _null_ _null_ _null_ ));
DESCR("create a replication origin");
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a73bc5..4277d5f 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -35,4 +35,6 @@ extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
extern List *roleSpecsToIds(List *memberNames);
+extern Datum pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS);
+
#endif /* USER_H */
--
2.7.3
0004-Remove-password-verifiers-for-unsupported-protocols-.patchtext/x-patch; charset=US-ASCII; name=0004-Remove-password-verifiers-for-unsupported-protocols-.patchDownload
From c99d85a3a5bca67af4938dbd6e58363733744257 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 14:53:37 +0900
Subject: [PATCH 4/9] Remove password verifiers for unsupported protocols in
pg_upgrade
This uses pg_auth_verifiers_sanitize to perform the cleanup in
pg_auth_verifiers that has been introduced previously.
---
src/bin/pg_upgrade/pg_upgrade.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4f5361a..390d8ee 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -260,6 +260,18 @@ prepare_new_cluster(void)
new_cluster.bindir, cluster_conn_opts(&new_cluster),
log_opts.verbose ? "--verbose" : "");
check_ok();
+
+ /*
+ * In order to ensure that the freshly-deployed cluster has no outdated
+ * password verifier entries, sanitize pg_auth_verifiers using the
+ * in-core function aimed at this purpose.
+ */
+ prep_status("Removing password verifiers for unsupported protocols");
+ exec_prog(UTILITY_LOG_FILE, NULL, true,
+ "\"%s/psql\" " EXEC_PSQL_ARGS
+ " %s -c \"SELECT pg_auth_verifiers_sanitize()\"",
+ new_cluster.bindir, cluster_conn_opts(&new_cluster));
+ check_ok();
}
--
2.7.3
0005-Move-sha1.c-to-src-common.patchtext/x-patch; charset=US-ASCII; name=0005-Move-sha1.c-to-src-common.patchDownload
From 5475da280fcb53e611fb84a50e6d5a43c05be79a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:33:27 +0900
Subject: [PATCH 5/9] Move sha1.c to src/common
This set of routines taken from pgcrypto will be used on both backend
and frontend for authentication purposes.
---
contrib/pgcrypto/Makefile | 4 ++--
contrib/pgcrypto/internal.c | 2 +-
src/common/Makefile | 3 ++-
{contrib/pgcrypto => src/common}/sha1.c | 4 ++--
{contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
6 files changed, 9 insertions(+), 8 deletions(-)
rename {contrib/pgcrypto => src/common}/sha1.c (99%)
rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)
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/src/common/Makefile b/src/common/Makefile
index f7a4a4d..16052de 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,7 +37,8 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o pg_lzcompress.o \
- pgfnames.o psprintf.o relpath.o rmtree.o string.o username.o wait_error.o
+ pgfnames.o psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
+ wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
*/
/*
* FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
#include <sys/param.h>
-#include "sha1.h"
+#include "common/sha1.h"
/* constant table */
static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 5532ca1..d5ff296 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/* contrib/pgcrypto/sha1.h */
+/* src/include/common/sha1.h */
/* $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $ */
/*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 949077a..f7e803b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -107,7 +107,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c exec.c pg_lzcompress.c pgfnames.c
- psprintf.c relpath.c rmtree.c string.c username.c wait_error.c);
+ psprintf.c relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
--
2.7.3
0006-Refactor-sendAuthRequest.patchtext/x-patch; charset=US-ASCII; name=0006-Refactor-sendAuthRequest.patchDownload
From 26acaa2d05accb37c87abee66d7f89f76d56e18a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 6/9] 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 57c2f48..2b75b91 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);
}
@@ -1673,7 +1672,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)
{
@@ -1948,7 +1947,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)
@@ -2308,7 +2307,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.7.3
0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchtext/x-patch; charset=US-ASCII; name=0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From 6cb5f956457d4aa45c291025088f9511647430cb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 11 Aug 2015 20:43:26 +0900
Subject: [PATCH 7/9] Refactor RandomSalt to handle salts of different lengths
---
src/backend/postmaster/postmaster.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b16fc28..525155b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -404,7 +404,7 @@ static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -2339,7 +2339,7 @@ ConnCreate(int serverFd)
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
- RandomSalt(port->md5Salt);
+ RandomSalt(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
@@ -5079,23 +5079,21 @@ StartupPacketTimeoutHandler(void)
* RandomSalt
*/
static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
{
long rand;
+ int i;
/*
* We use % 255, sacrificing one possible byte value, so as to ensure that
* all bits of the random() value participate in the result. While at it,
* add one to avoid generating any null bytes.
*/
- rand = PostmasterRandom();
- md5Salt[0] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[1] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[2] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[3] = (rand % 255) + 1;
+ for (i = 0; i < len; i++)
+ {
+ rand = PostmasterRandom();
+ md5Salt[i] = (rand % 255) + 1;
+ }
}
/*
--
2.7.3
0008-Move-encoding-routines-to-src-common.patchtext/x-patch; charset=US-ASCII; name=0008-Move-encoding-routines-to-src-common.patchDownload
From f9daa160ec0b68c9dacd5281f2822021c99d2c30 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:42:35 +0900
Subject: [PATCH 8/9] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA1, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +----------------------------
src/common/Makefile | 6 +-
src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
src/include/common/encode.h | 30 +++
src/tools/msvc/Mkvcbuild.pm | 5 +-
5 files changed, 169 insertions(+), 636 deletions(-)
copy src/{backend/utils/adt => common}/encode.c (72%)
create mode 100644 src/include/common/encode.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..76747bf 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/common/Makefile b/src/common/Makefile
index 16052de..2fb88ff 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -36,9 +36,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o pg_lzcompress.o \
- pgfnames.o psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
- wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
+ pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o sha1.o \
+ string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index d833efc..4a07089 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
* encode.c
- * Various data encoding/decoding things.
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the \
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..8166376
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ * encode.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* COMMON_ENCODE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f7e803b..60d379a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,8 +106,9 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c pg_lzcompress.c pgfnames.c
- psprintf.c relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
+ config_info.c controldata_utils.c encode.c exec.c pg_lzcompress.c
+ pgfnames.c psprintf.c relpath.c rmtree.c sha1.c string.c username.c
+ wait_error.c);
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
--
2.7.3
0009-SCRAM-authentication.patchtext/x-patch; charset=US-ASCII; name=0009-SCRAM-authentication.patchDownload
From f1864a7e54af5e84c7878899d44702ddacc35981 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 18 Aug 2015 13:29:50 +0900
Subject: [PATCH 9/9] SCRAM authentication
---
contrib/passwordcheck/passwordcheck.c | 4 +
doc/src/sgml/catalogs.sgml | 3 +-
doc/src/sgml/config.sgml | 17 +-
doc/src/sgml/protocol.sgml | 148 ++++++-
src/backend/commands/user.c | 51 ++-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 682 ++++++++++++++++++++++++++++++++
src/backend/libpq/auth.c | 117 ++++++
src/backend/libpq/crypt.c | 4 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 2 +-
src/backend/parser/gram.y | 4 +
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/adt/varlena.c | 1 +
src/backend/utils/misc/guc.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 2 +
src/common/Makefile | 4 +-
src/common/scram-common.c | 170 ++++++++
src/include/catalog/pg_auth_verifiers.h | 1 +
src/include/common/scram-common.h | 45 +++
src/include/libpq/auth.h | 5 +
src/include/libpq/crypt.h | 1 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 3 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 ++
src/include/utils/builtins.h | 2 -
src/interfaces/libpq/.gitignore | 3 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-scram.c | 386 ++++++++++++++++++
src/interfaces/libpq/fe-auth.c | 96 +++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 51 +++
src/interfaces/libpq/libpq-int.h | 5 +
src/test/regress/expected/password.out | 13 +-
src/test/regress/sql/password.sql | 9 +-
36 files changed, 1853 insertions(+), 42 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 13ad053..57f7f49 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,10 @@ check_password(const char *username,
#endif
break;
+ case AUTH_VERIFIER_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
+
default:
elog(ERROR, "unrecognized password type: %d", spec->veriftype);
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9a880be..195e81d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1323,7 +1323,8 @@
<entry><type>char</type></entry>
<entry>
<literal>p</> = plain format,
- <literal>m</> = MD5-encrypted
+ <literal>m</> = MD5-encrypted,
+ <literal>s</> = SCRAM-SHA1-encrypted
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 154c1a7..c6abef0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
<listitem>
<para>
Specifies a comma-separated list of password encryption formats.
- Supported formats are <literal>plain</> and <literal>md5</>.
+ Supported formats are <literal>plain</>,<literal>md5</> and
+ <literal>scram</>.
</para>
<para>
@@ -1199,8 +1200,8 @@ include_dir 'conf.d'
<listitem>
<para>
Specifies a comma-separated list of supported password formats by
- the server. Supported formats are currently <literal>plain</> and
- <literal>md5</>.
+ the server. Supported formats are currently <literal>plain</>,
+ <literal>md5</> and <literal>scram</>.
</para>
<para>
@@ -1211,8 +1212,8 @@ include_dir 'conf.d'
</para>
<para>
- The default is <literal>plain,md5</>, meaning that MD5-encrypted
- passwords and plain passwords are both accepted.
+ The default is <literal>plain,md5,scram</>, meaning that MD5-encrypted
+ passwords, plain passwords, and SCRAM-encrypted passwords are accepted.
</para>
</listitem>
</varlistentry>
@@ -1286,8 +1287,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 522128e..e1238d7 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>
@@ -2578,6 +2607,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>
@@ -4340,7 +4478,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 d77e379..f48415b 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,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"
@@ -1591,7 +1592,9 @@ DelRoleMems(const char *rolename, Oid roleid,
/*
* FlattenPasswordIdentifiers
- * Make list of password verifier types and values consistent with input.
+ * Make list of password verifier types and values consistent with the output
+ * wanted, and adapt the specifier value if possible, informing user in case of
+ * incorrect verifier used.
*/
static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
@@ -1616,18 +1619,34 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
* instances of Postgres, an md5 hash passed as a plain verifier
* should still be treated as an MD5 entry.
*/
- if (spec->veriftype == AUTH_VERIFIER_MD5 &&
- !isMD5(spec->value))
+ switch (spec->veriftype)
{
- char encrypted_passwd[MD5_PASSWD_LEN + 1];
- if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
- encrypted_passwd))
- elog(ERROR, "password encryption failed");
- spec->value = pstrdup(encrypted_passwd);
+ case AUTH_VERIFIER_MD5:
+ if (is_scram_verifier(spec->value))
+ elog(ERROR, "Cannot use SCRAM verifier as MD5 verifier");
+ if (!isMD5(spec->value))
+ {
+ char encrypted_passwd[MD5_PASSWD_LEN + 1];
+ if (!pg_md5_encrypt(spec->value, rolname,
+ strlen(rolname),
+ encrypted_passwd))
+ elog(ERROR, "password encryption failed");
+ spec->value = pstrdup(encrypted_passwd);
+ }
+ break;
+ case AUTH_VERIFIER_PLAIN:
+ if (is_scram_verifier(spec->value))
+ spec->veriftype = AUTH_VERIFIER_SCRAM;
+ else if (isMD5(spec->value))
+ spec->veriftype = AUTH_VERIFIER_MD5;
+ break;
+ case AUTH_VERIFIER_SCRAM:
+ if (isMD5(spec->value))
+ elog(ERROR, "Cannot use MD5 verifier as SCRAM verifier");
+ if (!is_scram_verifier(spec->value))
+ spec->value = scram_build_verifier(rolname, spec->value, 0);
+ break;
}
- else if (spec->veriftype == AUTH_VERIFIER_PLAIN &&
- isMD5(spec->value))
- spec->veriftype = AUTH_VERIFIER_MD5;
}
/*
@@ -1657,7 +1676,9 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
if ((strcmp(meth_name, "md5") == 0 &&
spec->veriftype == AUTH_VERIFIER_MD5) ||
(strcmp(meth_name, "plain") == 0 &&
- spec->veriftype == AUTH_VERIFIER_PLAIN))
+ spec->veriftype == AUTH_VERIFIER_PLAIN) ||
+ (strcmp(meth_name, "scram") == 0 &&
+ spec->veriftype == AUTH_VERIFIER_SCRAM))
{
found_match = true;
break;
@@ -1815,6 +1836,12 @@ pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
remove_entry = false;
break;
}
+ else if (authform->verimet == AUTH_VERIFIER_SCRAM &&
+ strcmp(meth_name, "scram") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
}
if (remove_entry)
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..0d53348
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,682 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_auth_verifiers 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/encode.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"
+
+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;
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+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 */
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+
+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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 2b75b91..a77431a 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,15 +21,19 @@
#include <arpa/inet.h>
#include <unistd.h>
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/ip.h"
#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"
+#include "utils/syscache.h"
/*----------------------------------------------------------------
@@ -185,6 +189,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -246,6 +256,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 +534,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, "");
@@ -690,6 +705,108 @@ 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;
+ HeapTuple roleTup;
+
+ /*
+ * 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")));
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+ if (!HeapTupleIsValid(roleTup))
+ return STATUS_ERROR;
+
+ /* lookup verifier */
+ verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+ if (verifier == NULL)
+ {
+ ReleaseSysCache(roleTup);
+ return STATUS_ERROR;
+ }
+
+ ReleaseSysCache(roleTup);
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA1_NAME,
+ strlen(SCRAM_SHA1_NAME) + 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;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 9211ec2..6df2bf2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
#include "utils/timestamp.h"
/*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
* method.
*/
-static char *
+char *
get_role_verifier(Oid roleid, const char method)
{
HeapTuple tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 28f9fb5..df0cc1d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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/parser/gram.y b/src/backend/parser/gram.y
index ccac887..f454cc8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -939,6 +939,8 @@ AuthVerifierSpec:
type = AUTH_VERIFIER_MD5;
else if (strcmp($1, "plain") == 0)
type = AUTH_VERIFIER_PLAIN;
+ else if (strcmp($1, "scram") == 0)
+ type = AUTH_VERIFIER_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -968,6 +970,8 @@ AlterOptRoleElem:
veriftype = AUTH_VERIFIER_MD5;
else if (strcmp(meth_name, "plain") == 0)
veriftype = AUTH_VERIFIER_PLAIN;
+ else if (strcmp(meth_name, "scram") == 0)
+ veriftype = AUTH_VERIFIER_SCRAM;
else
Assert(false); /* should not happen */
n = (AuthVerifierSpec *)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 525155b..00e95bb 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2340,6 +2340,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 94599cc..862d315 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode.h"
#include "lib/hyperloglog.h"
#include "libpq/md5.h"
#include "libpq/pqformat.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17a5038..69ebb82 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3378,7 +3378,7 @@ static struct config_string ConfigureNamesString[] =
GUC_LIST_INPUT
},
&password_protocols,
- "plain,md5",
+ "plain,md5,scram",
check_password_methods, NULL, NULL
},
@@ -10239,7 +10239,8 @@ check_password_methods(char **newval, void **extra, GucSource source)
char *method_name = (char *) lfirst(l);
if (strcmp(method_name, "md5") != 0 &&
- strcmp(method_name, "plain") != 0)
+ strcmp(method_name, "plain") != 0 &&
+ strcmp(method_name, "scram") != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index fc7a5ae..6008b93 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
appendPQExpBufferStr(buf, "md5 = ");
else if (verifier_meth == 'p')
appendPQExpBufferStr(buf, "plain = ");
+ else if (verifier_meth == 's')
+ appendPQExpBufferStr(buf, "scram = ");
appendStringLiteralConn(buf, verifier_value, conn);
}
if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index 2fb88ff..7b891c6 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,8 +37,8 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
- pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o sha1.o \
- string.o username.o wait_error.o
+ pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
+ scram-common.o sha1.o string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..a17387e
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,170 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (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"
+#include "utils/memutils.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 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)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index d1281b7..874e740 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -58,5 +58,6 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+#define AUTH_VERIFIER_SCRAM 's' /* SCRAM verifier */
#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..3d99bc8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+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 3cd06b7..5a02534 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 5725bb4..93eec02 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(Oid roleid, char method);
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/libpq-be.h b/src/include/libpq/libpq-be.h
index 5d07b78..c5663f4 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..b9af4c4
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* Name of SCRAM-SHA1 per IANA */
+#define SCRAM_SHA1_NAME "SCRAM-SHA-1"
+
+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);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 59a00bb..7f09eef 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -156,8 +156,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..225cfe4 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,8 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha1.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 1b292d2..cf5c813 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,8 @@ 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/
+OBJS += encode.o scram-common.o sha1.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode.c 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..ebbd1db
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,386 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/encode.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;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+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;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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 != SCRAM_SALT_LEN)
+ 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,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ 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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ 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, SCRAM_CLIENT_KEY_NAME, 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index cd863a5..91e952b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -428,6 +429,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(auth_mechanism, SCRAM_SHA1_NAME) == 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 +765,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 9d11654..f779fb2 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 5ad4755..6cd38bb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,48 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if (msgLength > 4 &&
+ (areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT))
+ {
+ 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 SASL 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
@@ -3042,6 +3084,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 6c9bbf7..087c731 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 */
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index b82cad6..d0fe179 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -8,7 +8,8 @@ SET password_encryption = true; -- error
ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
ERROR: invalid value for parameter "password_protocols": "novalue"
@@ -16,7 +17,8 @@ SET password_protocols = true; -- error
ERROR: invalid value for parameter "password_protocols": "true"
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -83,6 +85,11 @@ LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ERROR: Cannot use SCRAM verifier as MD5 verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ERROR: Cannot use MD5 verifier as SCRAM verifier
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.roleid = a.oid)
@@ -93,7 +100,7 @@ SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
role_passwd1 | m | md5
role_passwd2 | p | foo
role_passwd3 | m | md5
- role_passwd4 | m | md5
+ role_passwd4 | s | XxC
(4 rows)
-- entries for password_protocols
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 79df8da..61cb90b 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -7,14 +7,16 @@ SET password_encryption = 'novalue'; -- error
SET password_encryption = true; -- error
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
SET password_protocols = true; -- error
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
@@ -60,6 +62,9 @@ ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.verimet, substr(v.verival, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.roleid = a.oid)
--
2.7.3
Hi, All
On 03/15/2016 02:07 AM, Michael Paquier wrote:
Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.
And here they are. I have addressed the documentation and the
whitespaces reported up to now at the same time.
I've applied all of 0001-0009 patches from the new set with no any
warnings to today's master branch.
Then compiled with configure options:
./configure --enable-debug --enable-nls --enable-cassert
--enable-tap-tests --with-perl
All regression tests passed successfully.
make check-world passed successfully.
make installcheck-world failed on several contrib modules:
dblink, file_fdw, hstore, pgcrypto, pgstattuple, postgres_fdw,
tablefunc. The tests results are attached.
Documentation looks good.
Where may be a problem with make check-world and make installcheck-world
results?
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
Attachments:
pgstattuple.diffstext/plain; charset=UTF-8; name=pgstattuple.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgstattuple/expected/pgstattuple.out 2016-03-15 14:32:12.937978027 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgstattuple/results/pgstattuple.out 2016-03-15 16:39:52.009673824 +0300
***************
*** 1,4 ****
--- 1,5 ----
CREATE EXTENSION pgstattuple;
+ ERROR: could not load library "/usr/local/pgsql/lib/pgstattuple.so": /usr/local/pgsql/lib/pgstattuple.so: undefined symbol: visibilitymap_test
--
-- It's difficult to come up with platform-independent test cases for
-- the pgstattuple functions, but the results for empty tables and
***************
*** 6,132 ****
--
create table test (a int primary key, b int[]);
select * from pgstattuple('test');
! table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
! -----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
! 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
! (1 row)
!
select * from pgstattuple('test'::text);
! table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
! -----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
! 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
! (1 row)
!
select * from pgstattuple('test'::name);
! table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
! -----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
! 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
! (1 row)
!
select * from pgstattuple('test'::regclass);
! table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
! -----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
! 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0
! (1 row)
!
select pgstattuple(oid) from pg_class where relname = 'test';
! pgstattuple
! ---------------------
! (0,0,0,0,0,0,0,0,0)
! (1 row)
!
select pgstattuple(relname) from pg_class where relname = 'test';
! pgstattuple
! ---------------------
! (0,0,0,0,0,0,0,0,0)
! (1 row)
!
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey');
! version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
! ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
! 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
! (1 row)
!
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::text);
! version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
! ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
! 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
! (1 row)
!
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::name);
! version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
! ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
! 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
! (1 row)
!
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::regclass);
! version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation
! ---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+--------------------
! 2 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN
! (1 row)
!
select pg_relpages('test');
! pg_relpages
! -------------
! 0
! (1 row)
!
select pg_relpages('test_pkey');
! pg_relpages
! -------------
! 1
! (1 row)
!
select pg_relpages('test_pkey'::text);
! pg_relpages
! -------------
! 1
! (1 row)
!
select pg_relpages('test_pkey'::name);
! pg_relpages
! -------------
! 1
! (1 row)
!
select pg_relpages('test_pkey'::regclass);
! pg_relpages
! -------------
! 1
! (1 row)
!
select pg_relpages(oid) from pg_class where relname = 'test_pkey';
! pg_relpages
! -------------
! 1
! (1 row)
!
select pg_relpages(relname) from pg_class where relname = 'test_pkey';
! pg_relpages
! -------------
! 1
! (1 row)
!
create index test_ginidx on test using gin (b);
select * from pgstatginindex('test_ginidx');
! version | pending_pages | pending_tuples
! ---------+---------------+----------------
! 2 | 0 | 0
! (1 row)
!
--- 7,115 ----
--
create table test (a int primary key, b int[]);
select * from pgstattuple('test');
! ERROR: function pgstattuple(unknown) does not exist
! LINE 1: select * from pgstattuple('test');
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select * from pgstattuple('test'::text);
! ERROR: function pgstattuple(text) does not exist
! LINE 1: select * from pgstattuple('test'::text);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select * from pgstattuple('test'::name);
! ERROR: function pgstattuple(name) does not exist
! LINE 1: select * from pgstattuple('test'::name);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select * from pgstattuple('test'::regclass);
! ERROR: function pgstattuple(regclass) does not exist
! LINE 1: select * from pgstattuple('test'::regclass);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pgstattuple(oid) from pg_class where relname = 'test';
! ERROR: function pgstattuple(oid) does not exist
! LINE 1: select pgstattuple(oid) from pg_class where relname = 'test'...
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pgstattuple(relname) from pg_class where relname = 'test';
! ERROR: function pgstattuple(name) does not exist
! LINE 1: select pgstattuple(relname) from pg_class where relname = 't...
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey');
! ERROR: function pgstatindex(unknown) does not exist
! LINE 5: from pgstatindex('test_pkey');
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::text);
! ERROR: function pgstatindex(text) does not exist
! LINE 5: from pgstatindex('test_pkey'::text);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::name);
! ERROR: function pgstatindex(name) does not exist
! LINE 5: from pgstatindex('test_pkey'::name);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select version, tree_level,
index_size / current_setting('block_size')::int as index_size,
root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages,
avg_leaf_density, leaf_fragmentation
from pgstatindex('test_pkey'::regclass);
! ERROR: function pgstatindex(regclass) does not exist
! LINE 5: from pgstatindex('test_pkey'::regclass);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages('test');
! ERROR: function pg_relpages(unknown) does not exist
! LINE 1: select pg_relpages('test');
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages('test_pkey');
! ERROR: function pg_relpages(unknown) does not exist
! LINE 1: select pg_relpages('test_pkey');
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages('test_pkey'::text);
! ERROR: function pg_relpages(text) does not exist
! LINE 1: select pg_relpages('test_pkey'::text);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages('test_pkey'::name);
! ERROR: function pg_relpages(name) does not exist
! LINE 1: select pg_relpages('test_pkey'::name);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages('test_pkey'::regclass);
! ERROR: function pg_relpages(regclass) does not exist
! LINE 1: select pg_relpages('test_pkey'::regclass);
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages(oid) from pg_class where relname = 'test_pkey';
! ERROR: function pg_relpages(oid) does not exist
! LINE 1: select pg_relpages(oid) from pg_class where relname = 'test_...
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select pg_relpages(relname) from pg_class where relname = 'test_pkey';
! ERROR: function pg_relpages(name) does not exist
! LINE 1: select pg_relpages(relname) from pg_class where relname = 't...
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
create index test_ginidx on test using gin (b);
select * from pgstatginindex('test_ginidx');
! ERROR: function pgstatginindex(unknown) does not exist
! LINE 1: select * from pgstatginindex('test_ginidx');
! ^
! HINT: No function matches the given name and argument types. You might need to add explicit type casts.
======================================================================
dblink.diffstext/plain; charset=UTF-8; name=dblink.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/dblink/expected/dblink.out 2016-03-15 14:32:12.917977851 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/dblink/results/dblink.out 2016-03-15 15:35:27.756996876 +0300
***************
*** 16,1128 ****
-- list the primary key fields
SELECT *
FROM dblink_get_pkey('foo');
! position | colname
! ----------+---------
! 1 | f1
! 2 | f2
! (2 rows)
!
! -- build an insert statement based on a local tuple,
! -- replacing the primary key values with new ones
! SELECT dblink_build_sql_insert('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}');
! dblink_build_sql_insert
! -----------------------------------------------------------
! INSERT INTO foo(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}')
! (1 row)
!
! -- too many pk fields, should fail
! SELECT dblink_build_sql_insert('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
! ERROR: invalid attribute number 4
! -- build an update statement based on a local tuple,
! -- replacing the primary key values with new ones
! SELECT dblink_build_sql_update('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}');
! dblink_build_sql_update
! ----------------------------------------------------------------------------------------
! UPDATE foo SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz'
! (1 row)
!
! -- too many pk fields, should fail
! SELECT dblink_build_sql_update('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}');
! ERROR: invalid attribute number 4
! -- build a delete statement based on a local tuple,
! SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}');
! dblink_build_sql_delete
! ---------------------------------------------
! DELETE FROM foo WHERE f1 = '0' AND f2 = 'a'
! (1 row)
!
! -- too many pk fields, should fail
! SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}');
! ERROR: invalid attribute number 4
! -- retest using a quoted and schema qualified table
! CREATE SCHEMA "MySchema";
! CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2));
! INSERT INTO "MySchema"."Foo" VALUES (0,'a','{"a0","b0","c0"}');
! -- list the primary key fields
! SELECT *
! FROM dblink_get_pkey('"MySchema"."Foo"');
! position | colname
! ----------+---------
! 1 | f1
! 2 | f2
! (2 rows)
!
! -- build an insert statement based on a local tuple,
! -- replacing the primary key values with new ones
! SELECT dblink_build_sql_insert('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}');
! dblink_build_sql_insert
! ------------------------------------------------------------------------
! INSERT INTO "MySchema"."Foo"(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}')
! (1 row)
!
! -- build an update statement based on a local tuple,
! -- replacing the primary key values with new ones
! SELECT dblink_build_sql_update('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}');
! dblink_build_sql_update
! -----------------------------------------------------------------------------------------------------
! UPDATE "MySchema"."Foo" SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz'
! (1 row)
!
! -- build a delete statement based on a local tuple,
! SELECT dblink_build_sql_delete('"MySchema"."Foo"','1 2',2,'{"0", "a"}');
! dblink_build_sql_delete
! ----------------------------------------------------------
! DELETE FROM "MySchema"."Foo" WHERE f1 = '0' AND f2 = 'a'
! (1 row)
!
! CREATE FUNCTION connection_parameters() RETURNS text LANGUAGE SQL AS $f$
! SELECT $$dbname='$$||current_database()||$$' port=$$||current_setting('port');
! $f$;
! -- regular old dblink
! SELECT *
! FROM dblink(connection_parameters(),'SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! a | b | c
! ---+---+------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! (2 rows)
!
! -- should generate "connection not available" error
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! ERROR: connection not available
! -- The first-level connection's backend will crash on exit given OpenLDAP
! -- [2.4.24, 2.4.31]. We won't see evidence of any crash until the victim
! -- process terminates and the postmaster responds. If process termination
! -- entails writing a core dump, that can take awhile. Wait for the process to
! -- vanish. At that point, the postmaster has called waitpid() on the crashed
! -- process, and it will accept no new connections until it has reinitialized
! -- the cluster. (We can't exploit pg_stat_activity, because the crash happens
! -- after the backend updates shared memory to reflect its impending exit.)
! DO $pl$
! DECLARE
! detail text;
! BEGIN
! PERFORM wait_pid(crash_pid)
! FROM dblink(connection_parameters(), $$
! SELECT pg_backend_pid() FROM dblink(
! 'service=test_ldap '||connection_parameters(),
! -- This string concatenation is a hack to shoehorn a
! -- set_pgservicefile call into the SQL statement.
! 'SELECT 1' || set_pgservicefile('pg_service.conf')
! ) t(c int)
! $$) AS t(crash_pid int);
! EXCEPTION WHEN OTHERS THEN
! GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
! -- Expected error in a non-LDAP build.
! IF NOT detail LIKE 'syntax error in service file%' THEN RAISE; END IF;
! END
! $pl$;
! -- create a persistent connection
! SELECT dblink_connect(connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! -- use the persistent connection
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! a | b | c
! ---+---+------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! (2 rows)
!
! -- open a cursor with bad SQL and fail_on_error set to false
! SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foobar',false);
! NOTICE: relation "foobar" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not open cursor.
! dblink_open
! -------------
! ERROR
! (1 row)
!
! -- reset remote transaction state
! SELECT dblink_exec('ABORT');
! dblink_exec
! -------------
! ROLLBACK
! (1 row)
!
! -- open a cursor
! SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- close the cursor
! SELECT dblink_close('rmt_foo_cursor',false);
! dblink_close
! --------------
! OK
! (1 row)
!
! -- open the cursor again
! SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- fetch some data
! SELECT *
! FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ---+---+------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! (4 rows)
!
! SELECT *
! FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ---+---+------------
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! (4 rows)
!
! -- this one only finds two rows left
! SELECT *
! FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ---+---+------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! (2 rows)
!
! -- intentionally botch a fetch
! SELECT *
! FROM dblink_fetch('rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]);
! NOTICE: cursor "rmt_foobar_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not fetch from cursor.
! a | b | c
! ---+---+---
! (0 rows)
!
! -- reset remote transaction state
! SELECT dblink_exec('ABORT');
! dblink_exec
! -------------
! ROLLBACK
! (1 row)
!
! -- close the wrong cursor
! SELECT dblink_close('rmt_foobar_cursor',false);
! NOTICE: cursor "rmt_foobar_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not close cursor.
! dblink_close
! --------------
! ERROR
! (1 row)
!
! -- should generate 'cursor "rmt_foo_cursor" not found' error
! SELECT *
! FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! ERROR: cursor "rmt_foo_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not fetch from cursor.
! -- this time, 'cursor "rmt_foo_cursor" not found' as a notice
! SELECT *
! FROM dblink_fetch('rmt_foo_cursor',4,false) AS t(a int, b text, c text[]);
! NOTICE: cursor "rmt_foo_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not fetch from cursor.
! a | b | c
! ---+---+---
! (0 rows)
!
! -- close the persistent connection
! SELECT dblink_disconnect();
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- should generate "connection not available" error
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! ERROR: connection not available
! -- put more data into our slave table, first using arbitrary connection syntax
! -- but truncate the actual return value so we can use diff to check for success
! SELECT substr(dblink_exec(connection_parameters(),'INSERT INTO foo VALUES(10,''k'',''{"a10","b10","c10"}'')'),1,6);
! substr
! --------
! INSERT
! (1 row)
!
! -- create a persistent connection
! SELECT dblink_connect(connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! -- put more data into our slave table, using persistent connection syntax
! -- but truncate the actual return value so we can use diff to check for success
! SELECT substr(dblink_exec('INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6);
! substr
! --------
! INSERT
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]);
! a | b | c
! ----+---+---------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! 11 | l | {a11,b11,c11}
! (12 rows)
!
! -- bad remote select
! SELECT *
! FROM dblink('SELECT * FROM foobar',false) AS t(a int, b text, c text[]);
! NOTICE: relation "foobar" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not execute query.
! a | b | c
! ---+---+---
! (0 rows)
!
! -- change some data
! SELECT dblink_exec('UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11');
! dblink_exec
! -------------
! UPDATE 1
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE a = 11;
! a | b | c
! ----+---+---------------
! 11 | l | {a11,b99,c11}
! (1 row)
!
! -- botch a change to some other data
! SELECT dblink_exec('UPDATE foobar SET f3[2] = ''b99'' WHERE f1 = 11',false);
! NOTICE: relation "foobar" does not exist
! CONTEXT: Error occurred on dblink connection named "unnamed": could not execute command.
! dblink_exec
! -------------
! ERROR
! (1 row)
!
! -- delete some data
! SELECT dblink_exec('DELETE FROM foo WHERE f1 = 11');
! dblink_exec
! -------------
! DELETE 1
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE a = 11;
! a | b | c
! ---+---+---
! (0 rows)
!
! -- close the persistent connection
! SELECT dblink_disconnect();
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! --
! -- tests for the new named persistent connection syntax
! --
! -- should generate "missing "=" after "myconn" in connection info string" error
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! ERROR: could not establish connection
! DETAIL: missing "=" after "myconn" in connection info string
!
! -- create a named persistent connection
! SELECT dblink_connect('myconn',connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! -- use the named persistent connection
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! a | b | c
! ----+---+---------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! (3 rows)
!
! -- use the named persistent connection, but get it wrong
! SELECT *
! FROM dblink('myconn','SELECT * FROM foobar',false) AS t(a int, b text, c text[])
! WHERE t.a > 7;
! NOTICE: relation "foobar" does not exist
! CONTEXT: Error occurred on dblink connection named "myconn": could not execute query.
! a | b | c
! ---+---+---
! (0 rows)
!
! -- create a second named persistent connection
! -- should error with "duplicate connection name"
! SELECT dblink_connect('myconn',connection_parameters());
! ERROR: duplicate connection name
! -- create a second named persistent connection with a new name
! SELECT dblink_connect('myconn2',connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! -- use the second named persistent connection
! SELECT *
! FROM dblink('myconn2','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! a | b | c
! ----+---+---------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! (3 rows)
!
! -- close the second named persistent connection
! SELECT dblink_disconnect('myconn2');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- open a cursor incorrectly
! SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foobar',false);
! NOTICE: relation "foobar" does not exist
! CONTEXT: Error occurred on dblink connection named "myconn": could not open cursor.
! dblink_open
! -------------
! ERROR
! (1 row)
!
! -- reset remote transaction state
! SELECT dblink_exec('myconn','ABORT');
! dblink_exec
! -------------
! ROLLBACK
! (1 row)
!
! -- test opening cursor in a transaction
! SELECT dblink_exec('myconn','BEGIN');
! dblink_exec
! -------------
! BEGIN
! (1 row)
!
! -- an open transaction will prevent dblink_open() from opening its own
! SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- this should not commit the transaction because the client opened it
! SELECT dblink_close('myconn','rmt_foo_cursor');
! dblink_close
! --------------
! OK
! (1 row)
!
! -- this should succeed because we have an open transaction
! SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo');
! dblink_exec
! ----------------
! DECLARE CURSOR
! (1 row)
!
! -- commit remote transaction
! SELECT dblink_exec('myconn','COMMIT');
! dblink_exec
! -------------
! COMMIT
! (1 row)
!
! -- test automatic transactions for multiple cursor opens
! SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- the second cursor
! SELECT dblink_open('myconn','rmt_foo_cursor2','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- this should not commit the transaction
! SELECT dblink_close('myconn','rmt_foo_cursor2');
! dblink_close
! --------------
! OK
! (1 row)
!
! -- this should succeed because we have an open transaction
! SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo');
! dblink_exec
! ----------------
! DECLARE CURSOR
! (1 row)
!
! -- this should commit the transaction
! SELECT dblink_close('myconn','rmt_foo_cursor');
! dblink_close
! --------------
! OK
! (1 row)
!
! -- this should fail because there is no open transaction
! SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo');
! ERROR: DECLARE CURSOR can only be used in transaction blocks
! CONTEXT: Error occurred on dblink connection named "myconn": could not execute command.
! -- reset remote transaction state
! SELECT dblink_exec('myconn','ABORT');
! dblink_exec
! -------------
! ROLLBACK
! (1 row)
!
! -- open a cursor
! SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo');
! dblink_open
! -------------
! OK
! (1 row)
!
! -- fetch some data
! SELECT *
! FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ---+---+------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! (4 rows)
!
! SELECT *
! FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ---+---+------------
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! (4 rows)
!
! -- this one only finds three rows left
! SELECT *
! FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! a | b | c
! ----+---+---------------
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! (3 rows)
!
! -- fetch some data incorrectly
! SELECT *
! FROM dblink_fetch('myconn','rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]);
! NOTICE: cursor "rmt_foobar_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "myconn": could not fetch from cursor.
! a | b | c
! ---+---+---
! (0 rows)
!
! -- reset remote transaction state
! SELECT dblink_exec('myconn','ABORT');
! dblink_exec
! -------------
! ROLLBACK
! (1 row)
!
! -- should generate 'cursor "rmt_foo_cursor" not found' error
! SELECT *
! FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]);
! ERROR: cursor "rmt_foo_cursor" does not exist
! CONTEXT: Error occurred on dblink connection named "myconn": could not fetch from cursor.
! -- close the named persistent connection
! SELECT dblink_disconnect('myconn');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- should generate "missing "=" after "myconn" in connection info string" error
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE t.a > 7;
! ERROR: could not establish connection
! DETAIL: missing "=" after "myconn" in connection info string
!
! -- create a named persistent connection
! SELECT dblink_connect('myconn',connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! -- put more data into our slave table, using named persistent connection syntax
! -- but truncate the actual return value so we can use diff to check for success
! SELECT substr(dblink_exec('myconn','INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6);
! substr
! --------
! INSERT
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]);
! a | b | c
! ----+---+---------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! 11 | l | {a11,b11,c11}
! (12 rows)
!
! -- change some data
! SELECT dblink_exec('myconn','UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11');
! dblink_exec
! -------------
! UPDATE 1
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE a = 11;
! a | b | c
! ----+---+---------------
! 11 | l | {a11,b99,c11}
! (1 row)
!
! -- delete some data
! SELECT dblink_exec('myconn','DELETE FROM foo WHERE f1 = 11');
! dblink_exec
! -------------
! DELETE 1
! (1 row)
!
! -- let's see it
! SELECT *
! FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[])
! WHERE a = 11;
! a | b | c
! ---+---+---
! (0 rows)
!
! -- close the named persistent connection
! SELECT dblink_disconnect('myconn');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- close the named persistent connection again
! -- should get 'connection "myconn" not available' error
! SELECT dblink_disconnect('myconn');
! ERROR: connection "myconn" not available
! -- test asynchronous queries
! SELECT dblink_connect('dtest1', connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! SELECT * from
! dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1;
! t1
! ----
! 1
! (1 row)
!
! SELECT dblink_connect('dtest2', connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! SELECT * from
! dblink_send_query('dtest2', 'select * from foo where f1 > 2 and f1 < 7') as t1;
! t1
! ----
! 1
! (1 row)
!
! SELECT dblink_connect('dtest3', connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! SELECT * from
! dblink_send_query('dtest3', 'select * from foo where f1 > 6') as t1;
! t1
! ----
! 1
! (1 row)
!
! CREATE TEMPORARY TABLE result AS
! (SELECT * from dblink_get_result('dtest1') as t1(f1 int, f2 text, f3 text[]))
! UNION
! (SELECT * from dblink_get_result('dtest2') as t2(f1 int, f2 text, f3 text[]))
! UNION
! (SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[]))
! ORDER by f1;
! -- dblink_get_connections returns an array with elements in a machine-dependent
! -- ordering, so we must resort to unnesting and sorting for a stable result
! create function unnest(anyarray) returns setof anyelement
! language sql strict immutable as $$
! select $1[i] from generate_series(array_lower($1,1), array_upper($1,1)) as i
! $$;
! SELECT * FROM unnest(dblink_get_connections()) ORDER BY 1;
! unnest
! --------
! dtest1
! dtest2
! dtest3
! (3 rows)
!
! SELECT dblink_is_busy('dtest1');
! dblink_is_busy
! ----------------
! 0
! (1 row)
!
! SELECT dblink_disconnect('dtest1');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! SELECT dblink_disconnect('dtest2');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! SELECT dblink_disconnect('dtest3');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! SELECT * from result;
! f1 | f2 | f3
! ----+----+---------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! (11 rows)
!
! SELECT dblink_connect('dtest1', connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! SELECT * from
! dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1;
! t1
! ----
! 1
! (1 row)
!
! SELECT dblink_cancel_query('dtest1');
! dblink_cancel_query
! ---------------------
! OK
! (1 row)
!
! SELECT dblink_error_message('dtest1');
! dblink_error_message
! ----------------------
! OK
! (1 row)
!
! SELECT dblink_disconnect('dtest1');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- test foreign data wrapper functionality
! CREATE ROLE dblink_regression_test;
! DO $d$
! BEGIN
! EXECUTE $$CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw
! OPTIONS (dbname '$$||current_database()||$$',
! port '$$||current_setting('port')||$$'
! )$$;
! END;
! $d$;
! CREATE USER MAPPING FOR public SERVER fdtest
! OPTIONS (server 'localhost'); -- fail, can't specify server here
! ERROR: invalid option "server"
! HINT: Valid options in this context are: user, password
! CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER');
! GRANT USAGE ON FOREIGN SERVER fdtest TO dblink_regression_test;
! GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO dblink_regression_test;
! SET SESSION AUTHORIZATION dblink_regression_test;
! -- should fail
! SELECT dblink_connect('myconn', 'fdtest');
! ERROR: password is required
! DETAIL: Non-superusers must provide a password in the connection string.
! -- should succeed
! SELECT dblink_connect_u('myconn', 'fdtest');
! dblink_connect_u
! ------------------
! OK
! (1 row)
!
! SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]);
! a | b | c
! ----+---+---------------
! 0 | a | {a0,b0,c0}
! 1 | b | {a1,b1,c1}
! 2 | c | {a2,b2,c2}
! 3 | d | {a3,b3,c3}
! 4 | e | {a4,b4,c4}
! 5 | f | {a5,b5,c5}
! 6 | g | {a6,b6,c6}
! 7 | h | {a7,b7,c7}
! 8 | i | {a8,b8,c8}
! 9 | j | {a9,b9,c9}
! 10 | k | {a10,b10,c10}
! (11 rows)
!
! \c - -
! REVOKE USAGE ON FOREIGN SERVER fdtest FROM dblink_regression_test;
! REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM dblink_regression_test;
! DROP USER dblink_regression_test;
! DROP USER MAPPING FOR public SERVER fdtest;
! DROP SERVER fdtest;
! -- test asynchronous notifications
! SELECT dblink_connect(connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! --should return listen
! SELECT dblink_exec('LISTEN regression');
! dblink_exec
! -------------
! LISTEN
! (1 row)
!
! --should return listen
! SELECT dblink_exec('LISTEN foobar');
! dblink_exec
! -------------
! LISTEN
! (1 row)
!
! SELECT dblink_exec('NOTIFY regression');
! dblink_exec
! -------------
! NOTIFY
! (1 row)
!
! SELECT dblink_exec('NOTIFY foobar');
! dblink_exec
! -------------
! NOTIFY
! (1 row)
!
! SELECT notify_name, be_pid = (select t.be_pid from dblink('select pg_backend_pid()') as t(be_pid int)) AS is_self_notify, extra from dblink_get_notify();
! notify_name | is_self_notify | extra
! -------------+----------------+-------
! regression | t |
! foobar | t |
! (2 rows)
!
! SELECT * from dblink_get_notify();
! notify_name | be_pid | extra
! -------------+--------+-------
! (0 rows)
!
! SELECT dblink_disconnect();
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! -- test dropped columns in dblink_build_sql_insert, dblink_build_sql_update
! CREATE TEMP TABLE test_dropped
! (
! col1 INT NOT NULL DEFAULT 111,
! id SERIAL PRIMARY KEY,
! col2 INT NOT NULL DEFAULT 112,
! col2b INT NOT NULL DEFAULT 113
! );
! INSERT INTO test_dropped VALUES(default);
! ALTER TABLE test_dropped
! DROP COLUMN col1,
! DROP COLUMN col2,
! ADD COLUMN col3 VARCHAR(10) NOT NULL DEFAULT 'foo',
! ADD COLUMN col4 INT NOT NULL DEFAULT 42;
! SELECT dblink_build_sql_insert('test_dropped', '1', 1,
! ARRAY['1'::TEXT], ARRAY['2'::TEXT]);
! dblink_build_sql_insert
! ---------------------------------------------------------------------------
! INSERT INTO test_dropped(id,col2b,col3,col4) VALUES('2','113','foo','42')
! (1 row)
!
! SELECT dblink_build_sql_update('test_dropped', '1', 1,
! ARRAY['1'::TEXT], ARRAY['2'::TEXT]);
! dblink_build_sql_update
! -------------------------------------------------------------------------------------------
! UPDATE test_dropped SET id = '2', col2b = '113', col3 = 'foo', col4 = '42' WHERE id = '2'
! (1 row)
!
! SELECT dblink_build_sql_delete('test_dropped', '1', 1,
! ARRAY['2'::TEXT]);
! dblink_build_sql_delete
! -----------------------------------------
! DELETE FROM test_dropped WHERE id = '2'
! (1 row)
!
! -- test local mimicry of remote GUC values that affect datatype I/O
! SET datestyle = ISO, MDY;
! SET intervalstyle = postgres;
! SET timezone = UTC;
! SELECT dblink_connect('myconn',connection_parameters());
! dblink_connect
! ----------------
! OK
! (1 row)
!
! SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
! dblink_exec
! -------------
! SET
! (1 row)
!
! -- single row synchronous case
! SELECT *
! FROM dblink('myconn',
! 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t')
! AS t(a timestamptz);
! a
! ------------------------
! 2013-03-12 00:00:00+00
! (1 row)
!
! -- multi-row synchronous case
! SELECT *
! FROM dblink('myconn',
! 'SELECT * FROM
! (VALUES (''12.03.2013 00:00:00+00''),
! (''12.03.2013 00:00:00+00'')) t')
! AS t(a timestamptz);
! a
! ------------------------
! 2013-03-12 00:00:00+00
! 2013-03-12 00:00:00+00
! (2 rows)
!
! -- single-row asynchronous case
! SELECT *
! FROM dblink_send_query('myconn',
! 'SELECT * FROM
! (VALUES (''12.03.2013 00:00:00+00'')) t');
! dblink_send_query
! -------------------
! 1
! (1 row)
!
! CREATE TEMPORARY TABLE result AS
! (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
! UNION ALL
! (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
! SELECT * FROM result;
! t
! ------------------------
! 2013-03-12 00:00:00+00
! (1 row)
!
! DROP TABLE result;
! -- multi-row asynchronous case
! SELECT *
! FROM dblink_send_query('myconn',
! 'SELECT * FROM
! (VALUES (''12.03.2013 00:00:00+00''),
! (''12.03.2013 00:00:00+00'')) t');
! dblink_send_query
! -------------------
! 1
! (1 row)
!
! CREATE TEMPORARY TABLE result AS
! (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
! UNION ALL
! (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
! UNION ALL
! (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
! SELECT * FROM result;
! t
! ------------------------
! 2013-03-12 00:00:00+00
! 2013-03-12 00:00:00+00
! (2 rows)
!
! DROP TABLE result;
! -- Try an ambiguous interval
! SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
! dblink_exec
! -------------
! SET
! (1 row)
!
! SELECT *
! FROM dblink('myconn',
! 'SELECT * FROM (VALUES (''-1 2:03:04'')) i')
! AS i(i interval);
! i
! -------------------
! -1 days -02:03:04
! (1 row)
!
! -- Try swapping to another format to ensure the GUCs are tracked
! -- properly through a change.
! CREATE TEMPORARY TABLE result (t timestamptz);
! SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
! dblink_exec
! -------------
! SET
! (1 row)
!
! INSERT INTO result
! SELECT *
! FROM dblink('myconn',
! 'SELECT * FROM (VALUES (''03.12.2013 00:00:00+00'')) t')
! AS t(a timestamptz);
! SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
! dblink_exec
! -------------
! SET
! (1 row)
!
! INSERT INTO result
! SELECT *
! FROM dblink('myconn',
! 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t')
! AS t(a timestamptz);
! SELECT * FROM result;
! t
! ------------------------
! 2013-03-12 00:00:00+00
! 2013-03-12 00:00:00+00
! (2 rows)
!
! DROP TABLE result;
! -- Check error throwing in dblink_fetch
! SELECT dblink_open('myconn','error_cursor',
! 'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
! dblink_open
! -------------
! OK
! (1 row)
!
! SELECT *
! FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
! i
! ---
! 1
! (1 row)
!
! SELECT *
! FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
! ERROR: invalid input syntax for integer: "not an int"
! -- Make sure that the local settings have retained their values in spite
! -- of shenanigans on the connection.
! SHOW datestyle;
! DateStyle
! -----------
! ISO, MDY
! (1 row)
!
! SHOW intervalstyle;
! IntervalStyle
! ---------------
! postgres
! (1 row)
!
! -- Clean up GUC-setting tests
! SELECT dblink_disconnect('myconn');
! dblink_disconnect
! -------------------
! OK
! (1 row)
!
! RESET datestyle;
! RESET intervalstyle;
! RESET timezone;
--- 16,22 ----
-- list the primary key fields
SELECT *
FROM dblink_get_pkey('foo');
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
file_fdw.diffstext/plain; charset=UTF-8; name=file_fdw.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/expected/file_fdw.out 2016-03-15 16:11:08.209906170 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/results/file_fdw.out 2016-03-15 16:11:09.497917931 +0300
***************
*** 104,122 ****
) SERVER file_server
OPTIONS (format 'text', filename '/home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/data/text.csv', null 'NULL');
SELECT * FROM text_csv; -- ERROR
! ERROR: COPY force not null available only in CSV mode
ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv');
\pset null _null_
SELECT * FROM text_csv;
! word1 | word2 | word3 | word4
! -------+--------+--------+--------
! AAA | aaa | 123 |
! XYZ | xyz | | 321
! NULL | _null_ | _null_ | _null_
! NULL | _null_ | _null_ | _null_
! ABC | abc | |
! (5 rows)
!
-- force_not_null and force_null can be used together on the same column
ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true');
ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true');
--- 104,114 ----
) SERVER file_server
OPTIONS (format 'text', filename '/home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/data/text.csv', null 'NULL');
SELECT * FROM text_csv; -- ERROR
! ERROR: unrecognized node type: 539
ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv');
\pset null _null_
SELECT * FROM text_csv;
! ERROR: unrecognized node type: 539
-- force_not_null and force_null can be used together on the same column
ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true');
ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true');
***************
*** 148,345 ****
HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding
-- basic query tests
SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a;
! a | b
! -----+--------
! 42 | 324.78
! 100 | 99.097
! (2 rows)
!
SELECT * FROM agg_csv ORDER BY a;
! a | b
! -----+---------
! 0 | 0.09561
! 42 | 324.78
! 100 | 99.097
! (3 rows)
!
SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a;
! a | b | a | b
! -----+---------+-----+---------
! 0 | 0.09561 | 0 | 0.09561
! 42 | 324.78 | 42 | 324.78
! 100 | 99.097 | 100 | 99.097
! (3 rows)
!
! -- error context report tests
! SELECT * FROM agg_bad; -- ERROR
! ERROR: invalid input syntax for type real: "aaa"
! CONTEXT: COPY agg_bad, line 3, column b: "aaa"
! -- misc query tests
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv;
! Foreign Scan on public.agg_csv
! Output: a, b
! Foreign File: /home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/data/agg.csv
!
! \t off
! PREPARE st(int) AS SELECT * FROM agg_csv WHERE a = $1;
! EXECUTE st(100);
! a | b
! -----+--------
! 100 | 99.097
! (1 row)
!
! EXECUTE st(100);
! a | b
! -----+--------
! 100 | 99.097
! (1 row)
!
! DEALLOCATE st;
! -- tableoid
! SELECT tableoid::regclass, b FROM agg_csv;
! tableoid | b
! ----------+---------
! agg_csv | 99.097
! agg_csv | 0.09561
! agg_csv | 324.78
! (3 rows)
!
! -- updates aren't supported
! INSERT INTO agg_csv VALUES(1,2.0);
! ERROR: cannot insert into foreign table "agg_csv"
! UPDATE agg_csv SET a = 1;
! ERROR: cannot update foreign table "agg_csv"
! DELETE FROM agg_csv WHERE a = 100;
! ERROR: cannot delete from foreign table "agg_csv"
! -- but this should be allowed
! SELECT * FROM agg_csv FOR UPDATE;
! a | b
! -----+---------
! 100 | 99.097
! 0 | 0.09561
! 42 | 324.78
! (3 rows)
!
! -- constraint exclusion tests
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
! Foreign Scan on public.agg_csv
! Output: a, b
! Filter: (agg_csv.a < 0)
! Foreign File: /home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/data/agg.csv
!
! \t off
! SELECT * FROM agg_csv WHERE a < 0;
! a | b
! ---+---
! (0 rows)
!
! SET constraint_exclusion = 'on';
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0;
! Result
! Output: a, b
! One-Time Filter: false
!
! \t off
! SELECT * FROM agg_csv WHERE a < 0;
! a | b
! ---+---
! (0 rows)
!
! RESET constraint_exclusion;
! -- table inheritance tests
! CREATE TABLE agg (a int2, b float4);
! ALTER FOREIGN TABLE agg_csv INHERIT agg;
! SELECT tableoid::regclass, * FROM agg;
! tableoid | a | b
! ----------+-----+---------
! agg_csv | 100 | 99.097
! agg_csv | 0 | 0.09561
! agg_csv | 42 | 324.78
! (3 rows)
!
! SELECT tableoid::regclass, * FROM agg_csv;
! tableoid | a | b
! ----------+-----+---------
! agg_csv | 100 | 99.097
! agg_csv | 0 | 0.09561
! agg_csv | 42 | 324.78
! (3 rows)
!
! SELECT tableoid::regclass, * FROM ONLY agg;
! tableoid | a | b
! ----------+---+---
! (0 rows)
!
! -- updates aren't supported
! UPDATE agg SET a = 1;
! ERROR: cannot update foreign table "agg_csv"
! DELETE FROM agg WHERE a = 100;
! ERROR: cannot delete from foreign table "agg_csv"
! -- but this should be allowed
! SELECT tableoid::regclass, * FROM agg FOR UPDATE;
! tableoid | a | b
! ----------+-----+---------
! agg_csv | 100 | 99.097
! agg_csv | 0 | 0.09561
! agg_csv | 42 | 324.78
! (3 rows)
!
! ALTER FOREIGN TABLE agg_csv NO INHERIT agg;
! DROP TABLE agg;
! -- privilege tests
! SET ROLE file_fdw_superuser;
! SELECT * FROM agg_text ORDER BY a;
! a | b
! -----+---------
! 0 | 0.09561
! 42 | 324.78
! 56 | 7.8
! 100 | 99.097
! (4 rows)
!
! SET ROLE file_fdw_user;
! SELECT * FROM agg_text ORDER BY a;
! a | b
! -----+---------
! 0 | 0.09561
! 42 | 324.78
! 56 | 7.8
! 100 | 99.097
! (4 rows)
!
! SET ROLE no_priv_user;
! SELECT * FROM agg_text ORDER BY a; -- ERROR
! ERROR: permission denied for relation agg_text
! SET ROLE file_fdw_user;
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_text WHERE a > 0;
! Foreign Scan on public.agg_text
! Output: a, b
! Filter: (agg_text.a > 0)
! Foreign File: /home/vpopov/Projects/pwdtest.new/postgresql/contrib/file_fdw/data/agg.data
!
! \t off
! -- privilege tests for object
! SET ROLE file_fdw_superuser;
! ALTER FOREIGN TABLE agg_text OWNER TO file_fdw_user;
! ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
! SET ROLE file_fdw_user;
! ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
! ERROR: only superuser can change options of a file_fdw foreign table
! SET ROLE file_fdw_superuser;
! -- cleanup
! RESET ROLE;
! DROP EXTENSION file_fdw CASCADE;
! NOTICE: drop cascades to 8 other objects
! DETAIL: drop cascades to server file_server
! drop cascades to user mapping for file_fdw_user on server file_server
! drop cascades to user mapping for file_fdw_superuser on server file_server
! drop cascades to user mapping for no_priv_user on server file_server
! drop cascades to foreign table agg_text
! drop cascades to foreign table agg_csv
! drop cascades to foreign table agg_bad
! drop cascades to foreign table text_csv
! DROP ROLE file_fdw_superuser, file_fdw_user, no_priv_user;
--- 140,150 ----
HINT: Valid options in this context are: filename, format, header, delimiter, quote, escape, null, encoding
-- basic query tests
SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a;
! ERROR: unrecognized node type: 539
SELECT * FROM agg_csv ORDER BY a;
! ERROR: unrecognized node type: 539
SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a;
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
hstore.diffstext/plain; charset=UTF-8; name=hstore.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/hstore/expected/hstore.out 2016-03-15 14:32:12.921977887 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/hstore/results/hstore.out 2016-03-15 16:19:53.174671801 +0300
***************
*** 1161,1509 ****
(1 row)
select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
! skeys
! -------
! b
! aa
! cq
! fg
! (4 rows)
!
! select * from skeys('""=>1');
! skeys
! -------
!
! (1 row)
!
! select * from skeys('');
! skeys
! -------
! (0 rows)
!
! select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
! svals
! -------
! g
! 1
! l
! f
! (4 rows)
!
! select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL');
! svals | ?column?
! -------+----------
! g | f
! 1 | f
! l | f
! | t
! (4 rows)
!
! select * from svals('""=>1');
! svals
! -------
! 1
! (1 row)
!
! select * from svals('');
! svals
! -------
! (0 rows)
!
! select * from each('aaa=>bq, b=>NULL, ""=>1 ');
! key | value
! -----+-------
! | 1
! b |
! aaa | bq
! (3 rows)
!
! -- @>
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b';
! ?column?
! ----------
! t
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL';
! ?column?
! ----------
! t
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL';
! ?column?
! ----------
! f
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL';
! ?column?
! ----------
! f
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c';
! ?column?
! ----------
! f
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b';
! ?column?
! ----------
! t
! (1 row)
!
! select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q';
! ?column?
! ----------
! f
! (1 row)
!
! CREATE TABLE testhstore (h hstore);
! \copy testhstore from 'data/hstore.data'
! select count(*) from testhstore where h @> 'wait=>NULL';
! count
! -------
! 1
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC';
! count
! -------
! 15
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC, public=>t';
! count
! -------
! 2
! (1 row)
!
! select count(*) from testhstore where h ? 'public';
! count
! -------
! 194
! (1 row)
!
! select count(*) from testhstore where h ?| ARRAY['public','disabled'];
! count
! -------
! 337
! (1 row)
!
! select count(*) from testhstore where h ?& ARRAY['public','disabled'];
! count
! -------
! 42
! (1 row)
!
! create index hidx on testhstore using gist(h);
! set enable_seqscan=off;
! select count(*) from testhstore where h @> 'wait=>NULL';
! count
! -------
! 1
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC';
! count
! -------
! 15
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC, public=>t';
! count
! -------
! 2
! (1 row)
!
! select count(*) from testhstore where h ? 'public';
! count
! -------
! 194
! (1 row)
!
! select count(*) from testhstore where h ?| ARRAY['public','disabled'];
! count
! -------
! 337
! (1 row)
!
! select count(*) from testhstore where h ?& ARRAY['public','disabled'];
! count
! -------
! 42
! (1 row)
!
! drop index hidx;
! create index hidx on testhstore using gin (h);
! set enable_seqscan=off;
! select count(*) from testhstore where h @> 'wait=>NULL';
! count
! -------
! 1
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC';
! count
! -------
! 15
! (1 row)
!
! select count(*) from testhstore where h @> 'wait=>CC, public=>t';
! count
! -------
! 2
! (1 row)
!
! select count(*) from testhstore where h ? 'public';
! count
! -------
! 194
! (1 row)
!
! select count(*) from testhstore where h ?| ARRAY['public','disabled'];
! count
! -------
! 337
! (1 row)
!
! select count(*) from testhstore where h ?& ARRAY['public','disabled'];
! count
! -------
! 42
! (1 row)
!
! select count(*) from (select (each(h)).key from testhstore) as wow ;
! count
! -------
! 4781
! (1 row)
!
! select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key;
! key | count
! -----------+-------
! line | 884
! query | 207
! pos | 203
! node | 202
! space | 197
! status | 195
! public | 194
! title | 190
! wait | 190
! org | 189
! user | 189
! coauthors | 188
! disabled | 185
! indexed | 184
! cleaned | 180
! bad | 179
! date | 179
! world | 176
! state | 172
! subtitle | 169
! auth | 168
! abstract | 161
! (22 rows)
!
! -- sort/hash
! select count(distinct h) from testhstore;
! count
! -------
! 885
! (1 row)
!
! set enable_hashagg = false;
! select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
! count
! -------
! 885
! (1 row)
!
! set enable_hashagg = true;
! set enable_sort = false;
! select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2;
! count
! -------
! 885
! (1 row)
!
! select distinct * from (values (hstore '' || ''),('')) v(h);
! h
! ---
!
! (1 row)
!
! set enable_sort = true;
! -- btree
! drop index hidx;
! create index hidx on testhstore using btree (h);
! set enable_seqscan=off;
! select count(*) from testhstore where h #># 'p=>1';
! count
! -------
! 125
! (1 row)
!
! select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t';
! count
! -------
! 1
! (1 row)
!
! -- json and jsonb
! select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
! hstore_to_json
! -------------------------------------------------------------------------------------------------
! {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
! (1 row)
!
! select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json);
! json
! -------------------------------------------------------------------------------------------------
! {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
! (1 row)
!
! select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
! hstore_to_json_loose
! -------------------------------------------------------------------------------------------------------------
! {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1}
! (1 row)
!
! select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4');
! hstore_to_jsonb
! -------------------------------------------------------------------------------------------------
! {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
! (1 row)
!
! select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb);
! jsonb
! -------------------------------------------------------------------------------------------------
! {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}
! (1 row)
!
! select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"');
! hstore_to_jsonb_loose
! ----------------------------------------------------------------------------------------------------------
! {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1}
! (1 row)
!
! create table test_json_agg (f1 text, f2 hstore);
! insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'),
! ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4');
! select json_agg(q) from test_json_agg q;
! json_agg
! ----------------------------------------------------------------------------------------------------------------------------
! [{"f1":"rec1","f2":{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}}, +
! {"f1":"rec2","f2":{"b": "f", "c": "null", "d": "-12345", "e": "012345.6", "f": "-1.234", "g": "0.345e-4", "a key": "2"}}]
! (1 row)
!
! select json_agg(q) from (select f1, hstore_to_json_loose(f2) as f2 from test_json_agg) q;
! json_agg
! ----------------------------------------------------------------------------------------------------------------------
! [{"f1":"rec1","f2":{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}}, +
! {"f1":"rec2","f2":{"b": false, "c": "null", "d": -12345, "e": "012345.6", "f": -1.234, "g": 0.345e-4, "a key": 2}}]
! (1 row)
!
--- 1161,1167 ----
(1 row)
select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f');
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
pgcrypto.diffstext/plain; charset=UTF-8; name=pgcrypto.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-armor.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-armor.out 2016-03-15 16:28:48.539641414 +0300
***************
*** 111,372 ****
=ZZZZ
-----END PGP MESSAGE-----
');
! ERROR: Corrupt ascii-armor
! -- corrupt (no empty line)
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! ERROR: Corrupt ascii-armor
! -- no headers
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! -----+-------
! (0 rows)
!
! -- header with empty value
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! foo:
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! -----+-------
! foo |
! (1 row)
!
! -- simple
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! fookey: foovalue
! barkey: barvalue
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! --------+----------
! fookey | foovalue
! barkey | barvalue
! (2 rows)
!
! -- insane keys, part 1
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! insane:key :
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! -------------+-------
! insane:key |
! (1 row)
!
! -- insane keys, part 2
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! insane:key : text value here
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! -------------+-----------------
! insane:key | text value here
! (1 row)
!
! -- long value
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! ------+-----------------------------------------------------------------------------------------------------------------
! long | this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880
! (1 row)
!
! -- long value, split up
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! long: this value is more than 76 characters long, but it should still
! long: parse correctly as that''s permitted by RFC 4880
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! ------+------------------------------------------------------------------
! long | this value is more than 76 characters long, but it should still
! long | parse correctly as that's permitted by RFC 4880
! (2 rows)
!
! -- long value, split up, part 2
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! long: this value is more than
! long: 76 characters long, but it should still
! long: parse correctly as that''s permitted by RFC 4880
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! ------+-------------------------------------------------
! long | this value is more than
! long | 76 characters long, but it should still
! long | parse correctly as that's permitted by RFC 4880
! (3 rows)
!
! -- long value, split up, part 3
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! emptykey:
! long: this value is more than
! emptykey:
! long: 76 characters long, but it should still
! emptykey:
! long: parse correctly as that''s permitted by RFC 4880
! emptykey:
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ');
! key | value
! ----------+-------------------------------------------------
! emptykey |
! long | this value is more than
! emptykey |
! long | 76 characters long, but it should still
! emptykey |
! long | parse correctly as that's permitted by RFC 4880
! emptykey |
! (7 rows)
!
! select * from pgp_armor_headers('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.blowfish.sha1.mdc.s2k3.z0
!
! jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
! yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
! =JcP+
! -----END PGP MESSAGE-----
! ');
! key | value
! ---------+--------------------------------
! Comment | dat1.blowfish.sha1.mdc.s2k3.z0
! (1 row)
!
! -- test CR+LF line endings
! select * from pgp_armor_headers(replace('
! -----BEGIN PGP MESSAGE-----
! fookey: foovalue
! barkey: barvalue
!
! em9va2E=
! =ZZZZ
! -----END PGP MESSAGE-----
! ', E'\n', E'\r\n'));
! key | value
! --------+----------
! fookey | foovalue
! barkey | barvalue
! (2 rows)
!
! -- test header generation
! select armor('zooka', array['foo'], array['bar']);
! armor
! -----------------------------
! -----BEGIN PGP MESSAGE-----+
! foo: bar +
! +
! em9va2E= +
! =D5cR +
! -----END PGP MESSAGE----- +
!
! (1 row)
!
! select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']);
! armor
! ---------------------------------------------------------------------
! -----BEGIN PGP MESSAGE----- +
! Version: Created by pgcrypto +
! Comment: PostgreSQL, the world's most advanced open source database+
! +
! em9va2E= +
! =D5cR +
! -----END PGP MESSAGE----- +
!
! (1 row)
!
! select * from pgp_armor_headers(
! armor('zooka', array['Version', 'Comment'],
! array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']));
! key | value
! ---------+------------------------------------------------------------
! Version | Created by pgcrypto
! Comment | PostgreSQL, the world's most advanced open source database
! (2 rows)
!
! -- error/corner cases
! select armor('', array['foo'], array['too', 'many']);
! ERROR: mismatched array dimensions
! select armor('', array['too', 'many'], array['foo']);
! ERROR: mismatched array dimensions
! select armor('', array[['']], array['foo']);
! ERROR: wrong number of array subscripts
! select armor('', array['foo'], array[['']]);
! ERROR: wrong number of array subscripts
! select armor('', array[null], array['foo']);
! ERROR: null value not allowed for header key
! select armor('', array['foo'], array[null]);
! ERROR: null value not allowed for header value
! select armor('', '[0:0]={"foo"}', array['foo']);
! armor
! -----------------------------
! -----BEGIN PGP MESSAGE-----+
! foo: foo +
! +
! =twTO +
! -----END PGP MESSAGE----- +
!
! (1 row)
!
! select armor('', array['foo'], '[0:0]={"foo"}');
! armor
! -----------------------------
! -----BEGIN PGP MESSAGE-----+
! foo: foo +
! +
! =twTO +
! -----END PGP MESSAGE----- +
!
! (1 row)
!
! select armor('', array[E'embedded\nnewline'], array['foo']);
! ERROR: header key must not contain newlines
! select armor('', array['foo'], array[E'embedded\nnewline']);
! ERROR: header value must not contain newlines
! select armor('', array['embedded: colon+space'], array['foo']);
! ERROR: header key must not contain ": "
--- 111,117 ----
=ZZZZ
-----END PGP MESSAGE-----
');
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-decrypt.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-decrypt.out 2016-03-15 16:28:48.603642001 +0300
***************
*** 1,425 ****
! --
! -- pgp_descrypt tests
! --
! -- Checking ciphers
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.blowfish.sha1.mdc.s2k3.z0
!
! jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS
! yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE=
! =JcP+
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest
! UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA==
! =XtrP
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes192.sha1.mdc.s2k3.z0
!
! jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI
! 5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g==
! =rCZt
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes256.sha1.mdc.s2k3.z0
!
! jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/
! lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog==
! =fB6S
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- Checking MDC modes
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.nomdc.s2k3.z0
!
! jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+
! u9YkgfJfsuRJmgQ9tmo=
! =60ui
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE
! 8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q==
! =moGf
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- Checking hashes
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.md5.mdc.s2k3.z0
!
! jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO
! KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA==
! =NyLk
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m
! /PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw==
! =FxbQ
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- Checking S2K modes
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k0.z0
!
! jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw
! Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw==
! =YvkV
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k1.z0
!
! jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK
! 4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz
! =dbXm
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl
! z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg==
! =VJKg
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes192.sha1.mdc.s2k0.z0
!
! jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E
! Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw==
! =cg+i
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes192.sha1.mdc.s2k1.z0
!
! jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9
! 7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt
! =aHmC
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes192.sha1.mdc.s2k3.z0
!
! jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv
! q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw==
! =K0LS
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes256.sha1.mdc.s2k0.z0
!
! jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu
! rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w==
! =RGts
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes256.sha1.mdc.s2k1.z0
!
! jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO
! +KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR
! =SUrU
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes256.sha1.mdc.s2k3.z0
!
! jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+
! 4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA==
! =XZrG
! -----END PGP MESSAGE-----
! '), 'foobar');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- Checking longer passwords
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi
! tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw==
! =XKKG
! -----END PGP MESSAGE-----
! '), '0123456789abcdefghij');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6
! CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg==
! =gWDh
! -----END PGP MESSAGE-----
! '), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt
! FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q==
! =OxOF
! -----END PGP MESSAGE-----
! '), 'x');
! pgp_sym_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- Checking various data
! select encode(digest(pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat1.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8
! Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg==
! =W/ik
! -----END PGP MESSAGE-----
! '), '0123456789abcdefghij'), 'sha1'), 'hex');
! encode
! ------------------------------------------
! 0225e3ede6f2587b076d021a189ff60aad67e066
! (1 row)
!
! -- expected: 0225e3ede6f2587b076d021a189ff60aad67e066
! select encode(digest(pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat2.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB
! SaV9L04ky1qECNDx3XjnoKLC+H7IOQ==
! =Fxen
! -----END PGP MESSAGE-----
! '), '0123456789abcdefghij'), 'sha1'), 'hex');
! encode
! ------------------------------------------
! da39a3ee5e6b4b0d3255bfef95601890afd80709
! (1 row)
!
! -- expected: da39a3ee5e6b4b0d3255bfef95601890afd80709
! select encode(digest(pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: dat3.aes.sha1.mdc.s2k3.z0
!
! jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8
! gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk
! 73Hb8m1yRhQK
! =ivrD
! -----END PGP MESSAGE-----
! '), '0123456789abcdefghij'), 'sha1'), 'hex');
! encode
! ------------------------------------------
! 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
! (1 row)
!
! -- expected: 5e5c135efc0dd00633efc6dfd6e731ea408a5b4c
! -- Checking CRLF
! select encode(digest(pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: crlf mess
!
! ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
! a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
! =mBP9
! -----END PGP MESSAGE-----
! '), 'key', 'convert-crlf=0'), 'sha1'), 'hex');
! encode
! ------------------------------------------
! 9353062be7720f1446d30b9e75573a4833886784
! (1 row)
!
! -- expected: 9353062be7720f1446d30b9e75573a4833886784
! select encode(digest(pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
! Comment: crlf mess
!
! ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms
! a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs
! =mBP9
! -----END PGP MESSAGE-----
! '), 'key', 'convert-crlf=1'), 'sha1'), 'hex');
! encode
! ------------------------------------------
! 7efefcab38467f7484d6fa43dc86cf5281bd78e2
! (1 row)
!
! -- expected: 7efefcab38467f7484d6fa43dc86cf5281bd78e2
! -- check BUG #11905, problem with messages 6 less than a power of 2.
! select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530);
! ?column?
! ----------
! t
! (1 row)
!
! -- expected: true
! -- Negative tests
! -- Decryption with a certain incorrect key yields an apparent Literal Data
! -- packet reporting its content to be binary data. Ciphertext source:
! -- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave
! -- rise to that property.
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
!
! ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV
! VsxxqLSPzNLAeIspJk5G
! =mSd/
! -----END PGP MESSAGE-----
! '), 'wrong-key', 'debug=1');
! NOTICE: dbg: prefix_init: corrupt prefix
! NOTICE: dbg: parse_literal_data: data type=b
! NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
! ERROR: Wrong key or corrupt data
! -- Routine text/binary mismatch.
! select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1');
! NOTICE: dbg: parse_literal_data: data type=b
! ERROR: Not text data
! -- Decryption with a certain incorrect key yields an apparent BZip2-compressed
! -- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key')
! -- until the random prefix gave rise to that property.
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
!
! ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP
! GXsd65oYJZp3Khz0qfyn
! =Nmpq
! -----END PGP MESSAGE-----
! '), 'wrong-key', 'debug=1');
! NOTICE: dbg: prefix_init: corrupt prefix
! NOTICE: dbg: parse_compressed_data: bzip2 unsupported
! NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr
! ERROR: Wrong key or corrupt data
! -- Routine use of BZip2 compression. Ciphertext source:
! -- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \
! -- --personal-cipher-preferences aes --no-emit-version --batch \
! -- --symmetric --passphrase key --armor
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
!
! jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe
! QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R
! UCAAw2JRIISttRHMfDpDuZJpvYo=
! =AZ9M
! -----END PGP MESSAGE-----
! '), 'key', 'debug=1');
! NOTICE: dbg: parse_compressed_data: bzip2 unsupported
! ERROR: Unsupported compression algorithm
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-encrypt.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-encrypt.out 2016-03-15 16:28:48.611642073 +0300
***************
*** 1,210 ****
! --
! -- PGP encrypt
! --
! -- ensure consistent test output regardless of the default bytea format
! SET bytea_output TO escape;
! select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- check whether the defaults are ok
! select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
! 'key', 'expect-cipher-algo=aes128,
! expect-disable-mdc=0,
! expect-sess-key=0,
! expect-s2k-mode=3,
! expect-s2k-digest-algo=sha1,
! expect-compress-algo=0
! ');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- maybe the expect- stuff simply does not work
! select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'),
! 'key', 'expect-cipher-algo=bf,
! expect-disable-mdc=1,
! expect-sess-key=1,
! expect-s2k-mode=0,
! expect-s2k-digest-algo=md5,
! expect-compress-algo=1
! ');
! NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7
! NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3
! NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2
! NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0
! NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0
! NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- bytea as text
! select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz');
! ERROR: Not text data
! -- text as bytea
! select pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz');
! pgp_sym_decrypt_bytea
! -----------------------
! Text
! (1 row)
!
! -- algorithm change
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'),
! 'key', 'expect-cipher-algo=bf');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'),
! 'key', 'expect-cipher-algo=aes128');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'),
! 'key', 'expect-cipher-algo=aes192');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- s2k change
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'),
! 'key', 'expect-s2k-mode=0');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'),
! 'key', 'expect-s2k-mode=1');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'),
! 'key', 'expect-s2k-mode=3');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- s2k count change
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'),
! 'key', 'expect-s2k-count=1024');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- s2k_count rounds up
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'),
! 'key', 'expect-s2k-count=65000000');
! NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- s2k digest change
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'),
! 'key', 'expect-s2k-digest-algo=md5');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'),
! 'key', 'expect-s2k-digest-algo=sha1');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- sess key
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'),
! 'key', 'expect-sess-key=0');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'),
! 'key', 'expect-sess-key=1');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'),
! 'key', 'expect-sess-key=1, expect-cipher-algo=bf');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'),
! 'key', 'expect-sess-key=1, expect-cipher-algo=aes192');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'),
! 'key', 'expect-sess-key=1, expect-cipher-algo=aes256');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- no mdc
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'),
! 'key', 'expect-disable-mdc=1');
! pgp_sym_decrypt
! -----------------
! Secret.
! (1 row)
!
! -- crlf
! select encode(pgp_sym_decrypt_bytea(
! pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'),
! 'key'), 'hex');
! encode
! ----------------------
! 310d0a320d0a330d0d0a
! (1 row)
!
! -- conversion should be lossless
! select encode(digest(pgp_sym_decrypt(
! pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'),
! 'key', 'convert-crlf=1'), 'sha1'), 'hex') as result,
! encode(digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1'), 'hex') as expect;
! result | expect
! ------------------------------------------+------------------------------------------
! 47bde5d88d6ef8770572b9cbb4278b402aa69966 | 47bde5d88d6ef8770572b9cbb4278b402aa69966
! (1 row)
!
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-compression.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-compression.out 2016-03-15 16:28:48.615642110 +0300
***************
*** 1,50 ****
! --
! -- PGP compression support
! --
! select pgp_sym_decrypt(dearmor('
! -----BEGIN PGP MESSAGE-----
!
! ww0ECQMCsci6AdHnELlh0kQB4jFcVwHMJg0Bulop7m3Mi36s15TAhBo0AnzIrRFrdLVCkKohsS6+
! DMcmR53SXfLoDJOv/M8uKj3QSq7oWNIp95pxfA==
! =tbSn
! -----END PGP MESSAGE-----
! '), 'key', 'expect-compress-algo=1');
! pgp_sym_decrypt
! -----------------
! Secret message
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret message', 'key', 'compress-algo=0'),
! 'key', 'expect-compress-algo=0');
! pgp_sym_decrypt
! -----------------
! Secret message
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret message', 'key', 'compress-algo=1'),
! 'key', 'expect-compress-algo=1');
! pgp_sym_decrypt
! -----------------
! Secret message
! (1 row)
!
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret message', 'key', 'compress-algo=2'),
! 'key', 'expect-compress-algo=2');
! pgp_sym_decrypt
! -----------------
! Secret message
! (1 row)
!
! -- level=0 should turn compression off
! select pgp_sym_decrypt(
! pgp_sym_encrypt('Secret message', 'key',
! 'compress-algo=2, compress-level=0'),
! 'key', 'expect-compress-algo=0');
! pgp_sym_decrypt
! -----------------
! Secret message
! (1 row)
!
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-pubkey-decrypt.out 2016-03-15 16:28:48.619642147 +0300
***************
*** 1,656 ****
! --
! -- PGP Public Key Encryption
! --
! -- As most of the low-level stuff is tested in symmetric key
! -- tests, here's only public-key specific tests
! create table keytbl (
! id int4,
! name text,
! pubkey text,
! seckey text
! );
! create table encdata (
! id int4,
! data text
! );
! insert into keytbl (id, name, pubkey, seckey)
! values (1, 'elg1024', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
! tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
! xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
! klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
! YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
! PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
! jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
! saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
! IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
! MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
! AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
! AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
! ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
! sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
! +h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
! 6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
! k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
! iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
! ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
! =RWci
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
! tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
! xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
! klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
! YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
! PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
! jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
! saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
! IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6
! WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I
! XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz
! ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd
! ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp
! m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h
! iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE
! LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk
! a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9
! VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM
! m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk
! 7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w==
! =nvqq
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (2, 'elg2048', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
! vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
! 5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
! RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
! 8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
! Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
! J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
! T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
! 0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy
! MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH
! AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq
! or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9
! AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX
! MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/
! QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN
! gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ
! LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11
! k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC
! bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR
! Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R
! QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF
! Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A
! Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI
! 6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5
! 1kNTmEU=
! =8QM5
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n
! vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke
! 5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO
! RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij
! 8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl
! Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt
! J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/
! T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5
! 0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS
! 8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v
! cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN
! Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55
! n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D
! 29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN
! PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO
! kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr
! VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp
! gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh
! frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu
! VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy
! NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo
! 7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun
! fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R
! dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S
! EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM
! 3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8=
! =3Dgk
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (3, 'elg4096', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQGiBELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY
! 05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz
! 2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98
! cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN
! SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4
! 18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG
! 7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt
! q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh
! uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk27QjRWxnYW1hbCA0
! MDk2IDx0ZXN0NDA5NkBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgjvAIbAwYLCQgH
! AwIDFQIDAxYCAQIeAQIXgAAKCRBj+HX2P2d0oAEDAJ9lI+CNmb42z3+a6TnVusM6
! FI7oLwCfUwA1zEcRdsT3nIkoYh0iKxFSDFW5BA0EQsgkdhAQAJQbLXlgcJ/jq+Xh
! Eujb77/eeftFJObNIRYD9fmJ7HFIXbUcknEpbs+cRH/nrj5dGSY3OT3jCXOUtvec
! sCoX/CpZWL0oqDjAiZtNSFiulw5Gav4gHYkWKgKdSo+2rkavEPqKIVHvMeXaJtGT
! d7v/AmL/P8T7gls93o5WFBOLtPbDvWqaKRy2U5TAhl1laiM0vGALRVjvSCgnGw9g
! FpSnXbO3AfenUSjDzZujfGLHtU44ixHSS/D4DepiF3YaYLsN4CBqZRv6FbMZD5W3
! DnJY4kS1kH0MzdcF19TlcZ3itTCcGIt1tMKf84mccPoqdMzH7vumBGTeFEly5Afp
! 9berJcirqh2fzlunN0GS02z6SGWnjTbDlkNDxuxPSBbpcpNyD3jpYAUqSwRsZ/+5
! zkzcbGtDmvy9sJ5lAXkxGoIoQ1tEVX/LOHnh2NQHK8ourVOnr7MS0nozssITZJ5E
! XqtHiREjiYEuPyZiVZKJHLWuYYaF+n40znnz3sJuXFRreHhHbbvRdlYUU5mJV+XZ
! BLgKuS33NdpGeMIngnCc/9IQ6OZb6ixc94kbkd3w2PVr8CbKlu/IHTjWOO2mAo+D
! +OydlYl23FiM3KOyMP1HcEOJMB/nwkMtrvd+522Lu9n77ktKfot9IPrQDIQTyXjR
! 3pCOFtCOBnk2tJHMPoG9jn9ah/LHAAMHEACDZ5I/MHGfmiKg2hrmqBu2J2j/deC8
! CpwcyDH1ovQ0gHvb9ESa+CVRU2Wdy2CD7Q9SmtMverB5eneL418iPVRcQdwRmQ2y
! IH4udlBa6ce9HTUCaecAZ4/tYBnaC0Av/9l9tz14eYcwRMDpB+bnkhgF+PZ1KAfD
! 9wcY2aHbtsf3lZBc5h4owPJkxpe/BNzuJxW3q4VpSbLsZhwnCZ2wg7DRwP44wFIk
! 00ptmoBY59gsU6I40XtzrF8JDr0cA57xND5RY21Z8lnnYRE1Tc8h5REps9ZIxW3/
! yl91404bPLqxczpUHQAMSTAmBaStPYX1nS51uofOhLs5SKPCUmxfGKIOhsD0oLUn
! 78DnkONVGeXzBibSwwtbgfMzee4G8wSUfJ7w8WXz1TyanaGLnJ+DuKASSOrFoBCD
! HEDuWZWgSL74NOQupFRk0gxOPmqU94Y8HziQWma/cETbmD83q8rxN+GM2oBxQkQG
! xcbqMTHE7aVhV3tymbSWVaYhww3oIwsZS9oUIi1DnPEowS6CpVRrwdvLjLJnJzzV
! O3AFPn9eZ1Q7R1tNx+zZ4OOfhvI/OlRJ3HBx2L53embkbdY9gFYCCdTjPyjKoDIx
! kALgCajjCYMNUsAKNSd6mMCQ8TtvukSzkZS1RGKP27ohsdnzIVsiEAbxDMMcI4k1
! ul0LExUTCXSjeIhJBBgRAgAJBQJCyCR2AhsMAAoJEGP4dfY/Z3Sg19sAn0NDS8pb
! qrMpQAxSb7zRTmcXEFd9AJ435H0ttP/NhLHXC9ezgbCMmpXMOQ==
! =kRxT
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQG7BELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY
! 05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz
! 2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98
! cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN
! SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4
! 18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG
! 7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt
! q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh
! uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk2wAAoJCUNy6awTkw
! XfbLbpqh0fvDst7jDLa0I0VsZ2FtYWwgNDA5NiA8dGVzdDQwOTZAZXhhbXBsZS5v
! cmc+iF4EExECAB4FAkLII7wCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQY/h1
! 9j9ndKABAwCeNEOVK87EzXYbtxYBsnjrUI948NIAn2+f3BXiBFDV5NvqPwIZ0m77
! Fwy4nQRMBELIJHYQEACUGy15YHCf46vl4RLo2++/3nn7RSTmzSEWA/X5iexxSF21
! HJJxKW7PnER/564+XRkmNzk94wlzlLb3nLAqF/wqWVi9KKg4wImbTUhYrpcORmr+
! IB2JFioCnUqPtq5GrxD6iiFR7zHl2ibRk3e7/wJi/z/E+4JbPd6OVhQTi7T2w71q
! mikctlOUwIZdZWojNLxgC0VY70goJxsPYBaUp12ztwH3p1Eow82bo3xix7VOOIsR
! 0kvw+A3qYhd2GmC7DeAgamUb+hWzGQ+Vtw5yWOJEtZB9DM3XBdfU5XGd4rUwnBiL
! dbTCn/OJnHD6KnTMx+77pgRk3hRJcuQH6fW3qyXIq6odn85bpzdBktNs+khlp402
! w5ZDQ8bsT0gW6XKTcg946WAFKksEbGf/uc5M3GxrQ5r8vbCeZQF5MRqCKENbRFV/
! yzh54djUByvKLq1Tp6+zEtJ6M7LCE2SeRF6rR4kRI4mBLj8mYlWSiRy1rmGGhfp+
! NM55897CblxUa3h4R2270XZWFFOZiVfl2QS4Crkt9zXaRnjCJ4JwnP/SEOjmW+os
! XPeJG5Hd8Nj1a/AmypbvyB041jjtpgKPg/jsnZWJdtxYjNyjsjD9R3BDiTAf58JD
! La73fudti7vZ++5LSn6LfSD60AyEE8l40d6QjhbQjgZ5NrSRzD6BvY5/WofyxwAD
! BxAAg2eSPzBxn5oioNoa5qgbtido/3XgvAqcHMgx9aL0NIB72/REmvglUVNlnctg
! g+0PUprTL3qweXp3i+NfIj1UXEHcEZkNsiB+LnZQWunHvR01AmnnAGeP7WAZ2gtA
! L//Zfbc9eHmHMETA6Qfm55IYBfj2dSgHw/cHGNmh27bH95WQXOYeKMDyZMaXvwTc
! 7icVt6uFaUmy7GYcJwmdsIOw0cD+OMBSJNNKbZqAWOfYLFOiONF7c6xfCQ69HAOe
! 8TQ+UWNtWfJZ52ERNU3PIeURKbPWSMVt/8pfdeNOGzy6sXM6VB0ADEkwJgWkrT2F
! 9Z0udbqHzoS7OUijwlJsXxiiDobA9KC1J+/A55DjVRnl8wYm0sMLW4HzM3nuBvME
! lHye8PFl89U8mp2hi5yfg7igEkjqxaAQgxxA7lmVoEi++DTkLqRUZNIMTj5qlPeG
! PB84kFpmv3BE25g/N6vK8TfhjNqAcUJEBsXG6jExxO2lYVd7cpm0llWmIcMN6CML
! GUvaFCItQ5zxKMEugqVUa8Hby4yyZyc81TtwBT5/XmdUO0dbTcfs2eDjn4byPzpU
! Sdxwcdi+d3pm5G3WPYBWAgnU4z8oyqAyMZAC4Amo4wmDDVLACjUnepjAkPE7b7pE
! s5GUtURij9u6IbHZ8yFbIhAG8QzDHCOJNbpdCxMVEwl0o3gAAckBdfKuasiNUn5G
! L5XRnSvaOFzftr8zteOlZChCSNvzH5k+i1j7RJbWq06OeKRywPzjfjgM2MvRzI43
! ICeISQQYEQIACQUCQsgkdgIbDAAKCRBj+HX2P2d0oNfbAJ9+G3SeXrk+dWwo9EGi
! hqMi2GVTsgCfeoQJPsc8FLYUgfymc/3xqAVLUtg=
! =Gjq6
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (4, 'rsa2048', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
! ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
! 55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
! 5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
! MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
! R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0
! OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC
! HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR
! nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw
! bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP
! D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv
! 9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv
! S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA==
! =lR4n
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP
! ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2
! 55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx
! 5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K
! MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz
! R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6
! fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe
! D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w
! 5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF
! 5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu
! 7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn//
! GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP
! 2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj
! TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ
! St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH
! WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2
! V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30
! 89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB
! 9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC
! Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC
! AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8
! DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54
! GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59
! uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9
! NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp
! rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx
! fDs/qQ9dZhtEmDbKPLTVEA==
! =WKAv
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (5, 'psw-elg1024', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
! tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
! xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
! klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
! YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
! PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
! jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
! saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
! IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx
! MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV
! AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc
! AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd
! ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P
! sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI
! +h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9
! 6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF
! k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v
! iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F
! ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80
! =RWci
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9
! tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE
! xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth
! klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5
! YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic
! PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL
! jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv
! saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v
! IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT
! D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H
! ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E
! ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6
! 2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf
! BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq
! BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr
! B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY
! d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk
! qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY
! mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa
! 9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk
! f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe
! IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/
! =SNvr
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (6, 'rsaenc2048', '
! -----BEGIN PGP PUBLIC KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
! UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
! czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
! 4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
! dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
! NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz
! YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV
! AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8
! JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw
! +IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku
! UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ
! RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8
! 0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE
! QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX
! z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK
! lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE
! FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U
! rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF
! JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ
! yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0
! WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg
! w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X
! dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro
! PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh
! CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
! =pwU2
! -----END PGP PUBLIC KEY BLOCK-----
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! lQOWBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
! UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
! czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
! 4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
! dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
! NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYpAAf9GuKpxrXp267eSPw9ZeSw
! Ik6ob1I0MHbhhHeaXQnF0SuOViJ1+Bs74hUB3/F5fqrnjVLIS/ysYzegYpbpXOIa
! MZwYcp2e+dpmVb7tkGQgzXH0igGtBQBqoSUVq9mG2XKPVh2JmiYgOH6GrHSGmnCq
! GCgEK4ezSomB/3OtPFSjAxOlSw6dXSkapSxW3pEGvCdaWd9p8yl4rSpGsZEErPPL
! uSbZZrHtWfgq5UXdPeE1UnMlBcvSruvpN4qgWMgSMs4d2lXvzXJLcht/nryP+atT
! H1gwnRmlDCVv5BeJepKo3ORJDvcPlXkJPhqS9If3BhTqt6QgQEFI4aIYYZOZpZoi
! 2QQA2Zckzktmsc1MS04zS9gm1CbxM9d2KK8EOlh7fycRQhYYqqavhTBH2MgEp+Dd
! ZtuEN5saNDe9x/fwi2ok1Bq6luGMWPZU/nZe7fxadzwfliy/qPzStWFW3vY9mMLu
! 6uEqgjin/lf4YrAswXDZaEc5e4GuNgGfwr27hpjxE1jg3PsEAPMqXEOMT2yh+yRu
! DlLRbFhYOI4aUHY2CGoQQONnwv2O5gFvmOcPlg3J5lvnwlOYCx0c3bDxAtHyjPJq
! FAZqcJBaB9RDhKHwlWDrbx/6FPH2SuKE+u4msIhPFin4V3FAP+yTem/TKrdnaWy6
! EUrhCWTXVRTijBaCudfjFd/ipHZbA/0dv7UAcoWK6kiVLzyE+jOvtN+ZxTzxq7CW
! mlFPgAC966hgJmz9IXqadtMgPAoL3PK9q1DbPM3JhsQcJrNzTJqZrdN1/kPU0HHa
! +aof1BVy3wSvp2mXgaRUULStyhUIyBRM6hAYp3/MoWEYn/bwr+zQkIU8Zsk6OsZ6
! q1xE3cowrUWFtCVSU0EgMjA0OCBFbmMgPHJzYTIwNDhlbmNAZXhhbXBsZS5vcmc+
! iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEMiZ6pNE
! GVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8JCaxHa31wH3PKqGtq2M+cpb2rXf7
! gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw+IkhihEb5bWxQBNj+3zAFs1YX6v2
! HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzkuUOhcgfpBgIt3Q+MpT6M2+OIF7lVf
! Sb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQRJ6DWH61F8fFIDJg1z+A/Obx4fqX
! 6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO80g/oVYBbuvOYedffDBeQarhERZ5W
! 2TnIE+nqY61YOLBqosliygdZTXULzNidA5YEQuvaugEIAOuCJZdkzORA6e1lr81L
! nr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPXz1YIrMTu+1rMIiy10IWbA6zgMTpz
! PhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfKlAXIxJurCHXTbEa+YvPdn76vJ3Hs
! XOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zEFZcAoKbFqAAjDLEai64SoOFh0W3C
! sD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9UrHlolqYNhYze/uRLyfnUx9PN4r/G
! hEzauyDMV0smo91uB3aewPft+eCpmeWnu0PFJVK4xyRmhIq2rVCw16a1pBJirvGM
! +y0ABikAB/oC3z7lv6sVg+ngjbpWy9lZu2/ECZ9FqViVz7bUkjfvSuowgpncryLW
! 4EpVV4U6mMSgU6kAi5VGT/BvYGSAtnqDWGiPs7Kk+h4Adz74bEAXzU280pNBtSfX
! tGvzlS4a376KzYFSCJDRBdMebEhJMbY0wQmR8lTZu5JSUI4YYEuN0c7ckdsw8w42
! QWTLonG8HC6h8UPKS0EAcaCo7tFubMIesU6cWuTYucsHE+wjbADjuSNX968qczNe
! NoL2BUznXOQoPu6HQO4/8cr7ib+VQkB2bHQcMoZazPUStIID1e4CL4XcxfuAmT8o
! 3XDvMLgVqNp5W2f8Mzmk3/DbtsLXLOv5BADsCzQpseC8ikSYJC72hcon1wlUmGeH
! 3qgGiiHhYXFa18xgI5juoO8DaWno0rPPlgr36Y8mSB5qjYHMXwjKnKyUmt11H+hU
! +6uk4hq3Rjd8l+vfuOSr1xoTrtBUg9Rwfw6JVo0DC+8CWg4oBWsLXVM6KQXPFdJs
! 8kyFQplR/iP1XQQA/2tbDANjAYGNNDjJO9/0kEnSAUyYMasFJDrA2q17J5CroVQw
! QpMmWwdDkRANUVPKnWHS5sS65BRc7UytKe2f3A3ZInGXJIK2Hl+TzapWYcYxql+4
! ol5mEDDMDbhEE8Wmj9KyB6iifdLI0K+yxNb9T4Jpj3J18+St+G8+9AcFcBEEAM1b
! M9C+/05cnV8gjcByqH9M9ypo8fzPvMKVXWwCLQXpaL50QIkzLURkiMoEWrCdELaA
! sVPotRzePTIQ1ooLeDxd1gRnDqjZiIR0kwmv6vq8tfzY96O2ZbGWFI5eth89aWEJ
! WB8AR3zYcXpwJLwPuhXW2/NlZF0bclJ3jNzAfTIeQmeJAR8EGAECAAkFAkLr2roC
! GwwACgkQyJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+
! 1iqYE0B0WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwar
! ANhHxkWgw/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx
! 2eVM1k+XdmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOy
! la+wJdroPYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CC
! hbt6LhKhCLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug==
! =UKh3
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! insert into keytbl (id, name, pubkey, seckey)
! values (7, 'rsaenc2048-psw', '
! same key with password
! ', '
! -----BEGIN PGP PRIVATE KEY BLOCK-----
! Version: GnuPG v1.4.11 (GNU/Linux)
!
! lQPEBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj
! UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW
! czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT
! 4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ
! dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4
! NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYp/gcDCNnoEKwFo86JYCE1J92R
! HRQ7DoyAZpW1O0dTXL8Epk0sKsKDrCJOrIkDymsjfyBexADIeqOkioy/50wD2Mku
! CVHKWO2duAiJN5t/FoRgpR1/Q11K6QdfqOG0HxwfIXLcPv7eSIso8kWorj+I01BP
! Fn/atGEbIjdWaz/q2XHbu0Q3x6Et2gIsbLRVMhiYz1UG9uzGJ0TYCdBa2SFhs184
! 52akMpD+XVdM0Sq9/Cx40Seo8hzERB96+GXnQ48q2OhlvcEXiFyD6M6wYCWbEV+6
! XQVMymbl22FPP/bD9ReQX2kjrkQlFAtmhr+0y8reMCbcxwLuQfA3173lSPo7jrbH
! oLrGhkRpqd2bYCelqdy/XMmRFso0+7uytHfTFrUNfDWfmHVrygoVrNnarCbxMMI0
! I8Q+tKHMThWgf0rIOSh0+w38kOXFCEqEWF8YkAqCrMZIlJIed78rOCFgG4aHajZR
! D8rpXdUOIr/WeUddK25Tu8IuNJb0kFf12IMgNh0nS+mzlqWiofS5kA0TeB8wBV6t
! RotaeyDNSsMoowfN8cf1yHMTxli+K1Tasg003WVUoWgUc+EsJ5+KTNwaX5uGv0Cs
! j6dg6/FVeVRL9UsyF+2kt7euX3mABuUtcVGx/ZKTq/MNGEh6/r3B5U37qt+FDRbw
! ppKPc2AP+yBUWsQskyrxFgv4eSpcLEg+lgdz/zLyG4qW4lrFUoO790Cm/J6C7/WQ
! Z+E8kcS8aINJkg1skahH31d59ZkbW9PVeJMFGzNb0Z2LowngNP/BMrJ0LT2CQyLs
! UxbT16S/gwAyUpJnbhWYr3nDdlwtC0rVopVTPD7khPRppcsq1f8D70rdIxI4Ouuw
! vbjNZ1EWRJ9f2Ywb++k/xgSXwJkGodUlrUr+3i8cv8mPx+fWvif9q7Y5Ex1wCRa8
! 8FAj/o+hEbQlUlNBIDIwNDggRW5jIDxyc2EyMDQ4ZW5jQGV4YW1wbGUub3JnPokB
! NAQTAQIAHgUCQuvabQIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDImeqTRBlV
! WRzJCACbRhx2fYjPGKta69M5dS+kr5UD/CQmsR2t9cB9zyqhratjPnKW9q13+4AG
! P3aByT14IH1c5Mha8rJkNYD2wxmC8jrrcPiJIYoRG+W1sUATY/t8wBbNWF+r9h11
! m0lEpsmNVff/jU7SpNN6JQ3P7MHd5V85LlDoXIH6QYCLd0PjKU+jNvjiBe5VX0m9
! a1nacE3xoWc1vbM0DnqEuID78Qgkcrmm0ESeg1h+tRfHxSAyYNc/gPzm8eH6l+hj
! gOvUc4Gd6LpBQSF8TcFfT2TZwJh7WVWDvNIP6FWAW7rzmHnX3wwXkGq4REWeVtk5
! yBPp6mOtWDiwaqLJYsoHWU11C8zYnQPEBELr2roBCADrgiWXZMzkQOntZa/NS56+
! CczLFQRQPl/8iJAW1eql/wOJ1UiwGSjT189WCKzE7vtazCIstdCFmwOs4DE6cz4S
! UX4HjzjYHZwmMiuSrIefwuZ7cysMBsMXypQFyMSbqwh102xGvmLz3Z++rydx7Fzl
! 1RC/ny2+FN5dzYPO2DNtNi4dR2tjHktsxBWXAKCmxagAIwyxGouuEqDhYdFtwrA9
! Qy+M5n6fmGa1Dx07WWnbIud4uCilv8LPVKx5aJamDYWM3v7kS8n51MfTzeK/xoRM
! 2rsgzFdLJqPdbgd2nsD37fngqZnlp7tDxSVSuMckZoSKtq1QsNemtaQSYq7xjPst
! AAYp/gcDCNnoEKwFo86JYAsxoD+wQ0zBi5RBM5EphXTpM1qKxmigsKOvBSaMmr0y
! VjHtGY3poyV3t6VboOGCsFcaKm0tIdDL7vrxxwyYESETpF29b7QrYcoaLKMG7fsy
! t9SUI3UV2H9uUquHgqHtsqz0jYOgm9tYnpesgQ/kOAWI/tej1ZJXUIWEmZMH/W6d
! ATNvZ3ivwApfC0qF5G3oPgBSoIuQ/8I+pN/kmuyNAnJWNgagFhA/2VFBvh5XgztV
! NW7G//KpR1scsn140SO/wpGBM3Kr4m8ztl9w9U6a7NlQZ2ub3/pIUTpSzyLBxJZ/
! RfuZI7ROdgDMKmEgCYrN2kfp0LIxnYL6ZJu3FDcS4V098lyf5rHvB3PAEdL6Zyhd
! qYp3Sx68r0F4vzk5iAIWf6pG2YdfoP2Z48Pmq9xW8qD9iwFcoz9oAzDEMENn6dfq
! 6MzfoaXEoYp8cR/o+aeEaGUtYBHiaxQcJYx35B9IhsXXA49yRORK8qdwhSHxB3NQ
! H3pUWkfw368f/A207hQVs9yYXlEvMZikxl58gldCd3BAPqHm/XzgknRRNQZBPPKJ
! BMZebZ22Dm0qDuIqW4GXLB4sLf0+UXydVINIUOlzg+S4jrwx7eZqb6UkRXTIWVo5
! psTsD14wzWBRdUQHZOZD33+M8ugmewvLY/0Uix+2RorkmB7/jqoZvx/MehDwmCZd
! VH8sb2wpZ55sj7gCXxvrfieQD/VeH54OwjjbtK56iYq56RVD0h1az8xDY2GZXeT7
! J0c3BGpuoca5xOFWr1SylAr/miEPxOBfnfk8oZQJvZrjSBGjsTbALep2vDJk8ROD
! sdQCJuU1RHDrwKHlbUL0NbGRO2juJGsatdWnuVKsFbaFW2pHHkezKuwOcaAJv7Xt
! 8LRF17czAJ1uaLKwV8Paqx6UIv+089GbWZi7HIkBHwQYAQIACQUCQuvaugIbDAAK
! CRDImeqTRBlVWS7XCACDVstKM+SHD6V0bkfO6ampHzj4krKjN0lonN5+7b7WKpgT
! QHRYvPY8lUiIrjXGISQqEG9M5Bi5ea1aoBZem0P3U/lKheg0lYtA7dM3BqsA2EfG
! RaDD9M5TFCqhy2VFR6Pk0MP7h5bkb2VxLUUQa4oNa1fT3q7zS875NvImO/HZ5UzW
! T5d2Z5iwY6I2AOKYKt4kZhzXgbt5j2O3biDDXSfWwwAojWqbqVygepn047KVr7Al
! 2ug9hkY7tHz7U71HbZasroFgNPmP/UnAxmps4RKM28MRVPTI4cKUIdE3gIKFu3ou
! EqEItQ13P+50i3QkALpz8d08tJbceeYzf6I2P4q6
! =QFm5
! -----END PGP PRIVATE KEY BLOCK-----
! ');
! -- elg1024 / aes128
! insert into encdata (id, data) values (1, '
! -----BEGIN PGP MESSAGE-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy
! faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr
! e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD
! /2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY
! PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X
! uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw
! wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA==
! =PABx
! -----END PGP MESSAGE-----
! ');
! -- elg2048 / blowfish
! insert into encdata (id, data) values (2, '
! -----BEGIN PGP MESSAGE-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8
! Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC
! vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP
! OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD
! e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY
! m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq
! QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu
! iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70
! YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd
! YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9
! p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1
! +NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc
! G2pAE+3k
! =TBHV
! -----END PGP MESSAGE-----
! ');
! -- elg4096 / aes256
! insert into encdata (id, data) values (3, '
! -----BEGIN PGP MESSAGE-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS
! SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT
! Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx
! z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ
! 1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU
! ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon
! nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH
! 2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2
! +t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku
! C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo
! D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P
! /j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j
! txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU
! WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr
! 6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1
! KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO
! ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT
! 0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0
! 3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy
! huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i
! exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK
! 2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG
! DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ==
! =L/M/
! -----END PGP MESSAGE-----
! ');
! -- rsaenc2048 / aes128
! insert into encdata (id, data) values (4, '
! -----BEGIN PGP MESSAGE-----
! Version: GnuPG v1.4.1 (GNU/Linux)
!
! hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r
! pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg
! DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR
! yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb
! VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4
! HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK
! eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL
! GQ==
! =XHkF
! -----END PGP MESSAGE-----
! ');
! -- rsaenc2048 / aes128 (not from gnupg)
! insert into encdata (id, data) values (5, '
! -----BEGIN PGP MESSAGE-----
!
! wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T
! p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A
! 7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr
! C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9
! pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx
! 3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj
! 5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW
! vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es
! /eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI
! 7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP
! tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1
! g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY
! Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8
! blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA=
! =PHJ1
! -----END PGP MESSAGE-----
! ');
! -- successful decrypt
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=1 and encdata.id=1;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=2 and encdata.id=2;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=3 and encdata.id=3;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=6 and encdata.id=4;
! pgp_pub_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- wrong key
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=2 and encdata.id=1;
! ERROR: Wrong key
! -- sign-only key
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=4 and encdata.id=1;
! ERROR: No encryption key found
! -- rsa: password-protected secret key, wrong password
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123')
! from keytbl, encdata where keytbl.id=7 and encdata.id=4;
! ERROR: Wrong key or corrupt data
! -- rsa: password-protected secret key, right password
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
! from keytbl, encdata where keytbl.id=7 and encdata.id=4;
! pgp_pub_decrypt
! -----------------
! Secret message.
! (1 row)
!
! -- password-protected secret key, no password
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=5 and encdata.id=1;
! ERROR: Need password for secret key
! -- password-protected secret key, wrong password
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo')
! from keytbl, encdata where keytbl.id=5 and encdata.id=1;
! ERROR: Wrong key or corrupt data
! -- password-protected secret key, right password
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool')
! from keytbl, encdata where keytbl.id=5 and encdata.id=1;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! -- test for a short read from prefix_init
! select pgp_pub_decrypt(dearmor(data), dearmor(seckey))
! from keytbl, encdata where keytbl.id=6 and encdata.id=5;
! ERROR: Wrong key or corrupt data
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-pubkey-encrypt.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-pubkey-encrypt.out 2016-03-15 16:28:48.623642183 +0300
***************
*** 1,70 ****
! --
! -- PGP Public Key Encryption
! --
! -- ensure consistent test output regardless of the default bytea format
! SET bytea_output TO escape;
! -- successful encrypt/decrypt
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=1;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=2;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=3;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=6;
! pgp_pub_decrypt
! -----------------
! Secret msg
! (1 row)
!
! -- try with rsa-sign only
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=4;
! ERROR: No encryption key found
! -- try with secret key
! select pgp_pub_decrypt(
! pgp_pub_encrypt('Secret msg', dearmor(seckey)),
! dearmor(seckey))
! from keytbl where keytbl.id=1;
! ERROR: Refusing to encrypt with secret key
! -- does text-to-bytea works
! select pgp_pub_decrypt_bytea(
! pgp_pub_encrypt('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=1;
! pgp_pub_decrypt_bytea
! -----------------------
! Secret msg
! (1 row)
!
! -- and bytea-to-text?
! select pgp_pub_decrypt(
! pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)),
! dearmor(seckey))
! from keytbl where keytbl.id=1;
! ERROR: Not text data
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/expected/pgp-info.out 2016-03-15 14:32:12.933977992 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/pgcrypto/results/pgp-info.out 2016-03-15 16:28:48.631642256 +0300
***************
*** 1,79 ****
! --
! -- PGP info functions
! --
! -- pgp_key_id
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=1;
! pgp_key_id
! ------------------
! D936CF64BB73F466
! (1 row)
!
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=2;
! pgp_key_id
! ------------------
! 2C226E1FFE5CC7D4
! (1 row)
!
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=3;
! pgp_key_id
! ------------------
! B68504FD128E1FF9
! (1 row)
!
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=4; -- should fail
! ERROR: No encryption key found
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=5;
! pgp_key_id
! ------------------
! D936CF64BB73F466
! (1 row)
!
! select pgp_key_id(dearmor(pubkey)) from keytbl where id=6;
! pgp_key_id
! ------------------
! FD0206C409B74875
! (1 row)
!
! select pgp_key_id(dearmor(seckey)) from keytbl where id=1;
! pgp_key_id
! ------------------
! D936CF64BB73F466
! (1 row)
!
! select pgp_key_id(dearmor(seckey)) from keytbl where id=2;
! pgp_key_id
! ------------------
! 2C226E1FFE5CC7D4
! (1 row)
!
! select pgp_key_id(dearmor(seckey)) from keytbl where id=3;
! pgp_key_id
! ------------------
! B68504FD128E1FF9
! (1 row)
!
! select pgp_key_id(dearmor(seckey)) from keytbl where id=4; -- should fail
! ERROR: No encryption key found
! select pgp_key_id(dearmor(seckey)) from keytbl where id=5;
! pgp_key_id
! ------------------
! D936CF64BB73F466
! (1 row)
!
! select pgp_key_id(dearmor(seckey)) from keytbl where id=6;
! pgp_key_id
! ------------------
! FD0206C409B74875
! (1 row)
!
! select pgp_key_id(dearmor(data)) as data_key_id
! from encdata order by id;
! data_key_id
! ------------------
! D936CF64BB73F466
! 2C226E1FFE5CC7D4
! B68504FD128E1FF9
! FD0206C409B74875
! FD0206C409B74875
! (5 rows)
!
--- 1 ----
! psql: FATAL: the database system is in recovery mode
======================================================================
postgres_fdw.diffstext/plain; charset=UTF-8; name=postgres_fdw.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/postgres_fdw/expected/postgres_fdw.out 2016-03-15 14:32:12.937978027 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/postgres_fdw/results/postgres_fdw.out 2016-03-15 16:47:34.565851690 +0300
***************
*** 187,5102 ****
-- To exercise multiple code paths, we use local stats on ft1
-- and remote-estimate mode on ft2.
ANALYZE ft1;
ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
-- ===================================================================
-- simple queries
-- ===================================================================
-- single table without alias
EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------
! Limit
! -> Foreign Scan on ft1
! (2 rows)
!
! SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo
! 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
! 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo
! 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo
! (10 rows)
!
! -- single table with alias - also test that tableoid sort is not pushed to remote side
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------
! Limit
! Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
! -> Sort
! Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
! Sort Key: t1.c3, t1.c1, t1.tableoid
! -> Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (8 rows)
!
! SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo
! 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
! 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo
! 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo
! (10 rows)
!
! -- whole-row reference
! EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.*, c3, c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.*, c3, c1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST
! (5 rows)
!
! SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! t1
! --------------------------------------------------------------------------------------------
! (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo)
! (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo)
! (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo)
! (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo)
! (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo)
! (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo)
! (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo)
! (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo)
! (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo)
! (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo)
! (10 rows)
!
! -- empty result
! SELECT * FROM ft1 WHERE false;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+----+----+----+----+----+----
! (0 rows)
!
! -- with WHERE clause
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text))
! (3 rows)
!
! SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! -- with FOR UPDATE/SHARE
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------
! LockRows
! Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
! -> Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE
! (5 rows)
!
! SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------
! LockRows
! Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
! -> Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE
! (5 rows)
!
! SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! (1 row)
!
! -- aggregate
! SELECT COUNT(*) FROM ft1 t1;
! count
! -------
! 1000
! (1 row)
!
! -- subquery
! SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo
! 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
! 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo
! 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo
! (10 rows)
!
! -- subquery+MAX
! SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+----+-------+------------------------------+--------------------------+----+------------+-----
! 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo
! (1 row)
!
! -- used in CTE
! WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
! c1 | c2 | c3 | c4
! ----+----+-------+------------------------------
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST
! 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST
! 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST
! 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST
! 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST
! 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST
! 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST
! 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST
! 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST
! (10 rows)
!
! -- fixed values
! SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
! ?column? | ?column?
! ----------+----------
! fixed |
! (1 row)
!
! -- Test forcing the remote server to produce sorted data for a merge join.
! SET enable_hashjoin TO false;
! SET enable_nestloop TO false;
! -- inner join; expressions in the clauses appear in the equivalence class list
! EXPLAIN (VERBOSE, COSTS false)
! SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2."C 1"
! -> Merge Join
! Output: t1.c1, t2."C 1"
! Merge Cond: (t1.c1 = t2."C 1")
! -> Foreign Scan on public.ft2 t1
! Output: t1.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
! -> Index Only Scan using t1_pkey on "S 1"."T 1" t2
! Output: t2."C 1"
! (10 rows)
!
! SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
! c1 | C 1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- outer join; expressions in the clauses do not appear in equivalence class
! -- list but no output change as compared to the previous query
! EXPLAIN (VERBOSE, COSTS false)
! SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2."C 1"
! -> Merge Left Join
! Output: t1.c1, t2."C 1"
! Merge Cond: (t1.c1 = t2."C 1")
! -> Foreign Scan on public.ft2 t1
! Output: t1.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
! -> Index Only Scan using t1_pkey on "S 1"."T 1" t2
! Output: t2."C 1"
! (10 rows)
!
! SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
! c1 | C 1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- A join between local table and foreign join. ORDER BY clause is added to the
! -- foreign join so that the local table can be joined using merge join strategy.
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1."C 1"
! -> Merge Right Join
! Output: t1."C 1"
! Merge Cond: (t3.c1 = t1."C 1")
! -> Foreign Scan
! Output: t3.c1
! Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
! Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (TRUE)) WHERE ((r2."C 1" = r3."C 1")) ORDER BY r2."C 1" ASC NULLS LAST
! -> Index Only Scan using t1_pkey on "S 1"."T 1" t1
! Output: t1."C 1"
! (11 rows)
!
! SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
! C 1
! -----
! 101
! 102
! 103
! 104
! 105
! 106
! 107
! 108
! 109
! 110
! (10 rows)
!
! RESET enable_hashjoin;
! RESET enable_nestloop;
! -- ===================================================================
! -- WHERE with remotely-executable conditions
! -- ===================================================================
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
! QUERY PLAN
! -------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1")))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((1::numeric = ("C 1" !)))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL)))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)])))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1])))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
! QUERY PLAN
! -------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (4 rows)
!
! -- parameterized remote path for foreign table
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------
! Nested Loop
! Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
! -> Index Scan using t1_pkey on "S 1"."T 1" a
! Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8
! Index Cond: (a."C 1" = 47)
! -> Foreign Scan on public.ft2 b
! Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1"))
! (8 rows)
!
! SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----
! 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! (1 row)
!
! -- check both safe and unsafe join conditions
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM ft2 a, ft2 b
! WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------
! Nested Loop
! Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
! -> Foreign Scan on public.ft2 a
! Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8
! Filter: (a.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6))
! -> Foreign Scan on public.ft2 b
! Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
! Filter: (upper((a.c7)::text) = (b.c7)::text)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1"))
! (10 rows)
!
! SELECT * FROM ft2 a, ft2 b
! WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+-----
! 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo
! 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo
! 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo
! 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo
! 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo
! 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo
! 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo
! 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo
! 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo
! (100 rows)
!
! -- bug before 9.3.5 due to sloppy handling of remote-estimate parameters
! SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5));
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo
! (4 rows)
!
! SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo
! (4 rows)
!
! -- we should not push order by clause with volatile expressions or unsafe
! -- collations
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM ft2 ORDER BY ft2.c1, random();
! QUERY PLAN
! -------------------------------------------------------------------------------
! Sort
! Output: c1, c2, c3, c4, c5, c6, c7, c8, (random())
! Sort Key: ft2.c1, (random())
! -> Foreign Scan on public.ft2
! Output: c1, c2, c3, c4, c5, c6, c7, c8, random()
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (6 rows)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C";
! QUERY PLAN
! -------------------------------------------------------------------------------
! Sort
! Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text)
! Sort Key: ft2.c1, ft2.c3 COLLATE "C"
! -> Foreign Scan on public.ft2
! Output: c1, c2, c3, c4, c5, c6, c7, c8, c3
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (6 rows)
!
! -- user-defined operator/function
! CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
! BEGIN
! RETURN abs($1);
! END
! $$ LANGUAGE plpgsql IMMUTABLE;
! CREATE OPERATOR === (
! LEFTARG = int,
! RIGHTARG = int,
! PROCEDURE = int4eq,
! COMMUTATOR = ===
! );
! -- built-in operators and functions can be shipped for remote execution
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! QUERY PLAN
! --------------------------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = abs(c2)))
! (5 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! count
! -------
! 9
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2;
! QUERY PLAN
! ---------------------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = c2))
! (5 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2;
! count
! -------
! 9
! (1 row)
!
! -- by default, user-defined ones cannot
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
! QUERY PLAN
! -----------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Filter: (t1.c1 = postgres_fdw_abs(t1.c2))
! Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
! (6 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
! count
! -------
! 9
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
! QUERY PLAN
! -----------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Filter: (t1.c1 === t1.c2)
! Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
! (6 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
! count
! -------
! 9
! (1 row)
!
! -- but let's put them in an extension ...
! ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int);
! ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int);
! ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
! -- ... now they can be shipped
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
! QUERY PLAN
! ----------------------------------------------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2)))
! (5 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
! count
! -------
! 9
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
! QUERY PLAN
! ----------------------------------------------------------------------------------------
! Aggregate
! Output: count(c3)
! -> Foreign Scan on public.ft1 t1
! Output: c3
! Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2))
! (5 rows)
!
! SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
! count
! -------
! 9
! (1 row)
!
! -- ===================================================================
! -- JOIN queries
! -- ===================================================================
! -- Analyze ft4 and ft5 so that we have better statistics. These tables do not
! -- have use_remote_estimate set.
! ANALYZE ft4;
! ANALYZE ft5;
! -- join two tables
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
! (6 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- join three tables
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c2, t3.c3, t1.c3
! -> Sort
! Output: t1.c1, t2.c2, t3.c3, t1.c3
! Sort Key: t1.c3, t1.c1
! -> Foreign Scan
! Output: t1.c1, t2.c2, t3.c3, t1.c3
! Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
! Remote SQL: SELECT r1."C 1", r1.c3, r2.c2, r4.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) INNER JOIN "S 1"."T 3" r4 ON (TRUE)) WHERE ((r1."C 1" = r4.c1)) AND ((r1."C 1" = r2."C 1"))
! (9 rows)
!
! SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
! c1 | c2 | c3
! ----+----+--------
! 22 | 2 | AAA022
! 24 | 4 | AAA024
! 26 | 6 | AAA026
! 28 | 8 | AAA028
! 30 | 0 | AAA030
! 32 | 2 | AAA032
! 34 | 4 | AAA034
! 36 | 6 | AAA036
! 38 | 8 | AAA038
! 40 | 0 | AAA040
! (10 rows)
!
! -- left outer join
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2)
! Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST
! (6 rows)
!
! SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
! c1 | c1
! ----+----
! 22 |
! 24 | 24
! 26 |
! 28 |
! 30 | 30
! 32 |
! 34 |
! 36 | 36
! 38 |
! 40 |
! (10 rows)
!
! -- left outer join + placement of clauses.
! -- clauses within the nullable side are not pulled up, but top level clause on
! -- non-nullable side is pushed into non-nullable side
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan
! Output: t1.c1, t1.c2, ft5.c1, ft5.c2
! Relations: (public.ft4 t1) LEFT JOIN (public.ft5)
! Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10))
! (4 rows)
!
! SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10;
! c1 | c2 | c1 | c2
! ----+----+----+----
! 2 | 3 | |
! 4 | 5 | |
! 6 | 7 | 6 | 7
! 8 | 9 | |
! (4 rows)
!
! -- clauses within the nullable side are not pulled up, but the top level clause
! -- on nullable side is not pushed down into nullable side
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1)
! WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan
! Output: t1.c1, t1.c2, ft5.c1, ft5.c2
! Relations: (public.ft4 t1) LEFT JOIN (public.ft5)
! Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10))
! (4 rows)
!
! SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1)
! WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10;
! c1 | c2 | c1 | c2
! ----+----+----+----
! 2 | 3 | |
! 4 | 5 | |
! 6 | 7 | 6 | 7
! 8 | 9 | |
! (4 rows)
!
! -- right outer join
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1)
! Remote SQL: SELECT r2.c1, r1.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST
! (6 rows)
!
! SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10;
! c1 | c1
! ----+----
! | 22
! 24 | 24
! | 26
! | 28
! 30 | 30
! | 32
! | 34
! 36 | 36
! | 38
! | 40
! (10 rows)
!
! -- full outer join
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2)
! Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST
! (6 rows)
!
! SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10;
! c1 | c1
! -----+----
! 92 |
! 94 |
! 96 | 96
! 98 |
! 100 |
! | 3
! | 9
! | 15
! | 21
! | 27
! (10 rows)
!
! -- full outer join + WHERE clause, only matched rows
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Sort
! Output: t1.c1, t2.c1
! Sort Key: t1.c1, t2.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2)
! Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL)))
! (9 rows)
!
! SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
! c1 | c1
! ----+----
! 66 | 66
! 72 | 72
! 78 | 78
! 84 | 84
! 90 | 90
! 96 | 96
! | 3
! | 9
! | 15
! | 21
! (10 rows)
!
! -- join two tables with FOR UPDATE clause
! -- tests whole-row reference for row marks
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> LockRows
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1
! -> Merge Join
! Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
! Merge Cond: (t1.c1 = t2.c1)
! -> Sort
! Output: t1.c1, t1.c3, t1.*
! Sort Key: t1.c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c3, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
! -> Sort
! Output: t2.c1, t2.*
! Sort Key: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1, t2.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (23 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> LockRows
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2
! -> Merge Join
! Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
! Merge Cond: (t1.c1 = t2.c1)
! -> Sort
! Output: t1.c1, t1.c3, t1.*
! Sort Key: t1.c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c3, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
! -> Sort
! Output: t2.c1, t2.*
! Sort Key: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1, t2.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
! (23 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- join two tables with FOR SHARE clause
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> LockRows
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1
! -> Merge Join
! Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
! Merge Cond: (t1.c1 = t2.c1)
! -> Sort
! Output: t1.c1, t1.c3, t1.*
! Sort Key: t1.c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c3, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
! -> Sort
! Output: t2.c1, t2.*
! Sort Key: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1, t2.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (23 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> LockRows
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2
! -> Merge Join
! Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
! Merge Cond: (t1.c1 = t2.c1)
! -> Sort
! Output: t1.c1, t1.c3, t1.*
! Sort Key: t1.c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c3, t1.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
! -> Sort
! Output: t2.c1, t2.*
! Sort Key: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1, t2.*
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
! (23 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- join in CTE
! EXPLAIN (COSTS false, VERBOSE)
! WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t.c1_1, t.c2_1, t.c1_3
! CTE t
! -> Foreign Scan
! Output: t1.c1, t1.c3, t2.c1
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
! -> Sort
! Output: t.c1_1, t.c2_1, t.c1_3
! Sort Key: t.c1_3, t.c1_1
! -> CTE Scan on t
! Output: t.c1_1, t.c2_1, t.c1_3
! (12 rows)
!
! WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10;
! c1_1 | c2_1
! ------+------
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- ctid with whole-row reference
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
! -> Foreign Scan
! Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1.ctid, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r1."C 1", r1.c3, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
! (6 rows)
!
! -- SEMI JOIN, not pushed down
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1
! -> Merge Semi Join
! Output: t1.c1
! Merge Cond: (t1.c1 = t2.c1)
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
! -> Materialize
! Output: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
! (13 rows)
!
! SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
! c1
! -----
! 101
! 102
! 103
! 104
! 105
! 106
! 107
! 108
! 109
! 110
! (10 rows)
!
! -- ANTI JOIN, not pushed down
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
! Limit
! Output: t1.c1
! -> Merge Anti Join
! Output: t1.c1
! Merge Cond: (t1.c1 = t2.c2)
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
! -> Materialize
! Output: t2.c2
! -> Foreign Scan on public.ft2 t2
! Output: t2.c2
! Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST
! (13 rows)
!
! SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
! c1
! -----
! 110
! 111
! 112
! 113
! 114
! 115
! 116
! 117
! 118
! 119
! (10 rows)
!
! -- CROSS JOIN, not pushed down
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Sort
! Output: t1.c1, t2.c1
! Sort Key: t1.c1, t2.c1
! -> Nested Loop
! Output: t1.c1, t2.c1
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
! -> Materialize
! Output: t2.c1
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
! (15 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! ----+-----
! 1 | 101
! 1 | 102
! 1 | 103
! 1 | 104
! 1 | 105
! 1 | 106
! 1 | 107
! 1 | 108
! 1 | 109
! 1 | 110
! (10 rows)
!
! -- different server, not pushed down. No result expected.
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Merge Join
! Output: t1.c1, t2.c1
! Merge Cond: (t2.c1 = t1.c1)
! -> Foreign Scan on public.ft6 t2
! Output: t2.c1, t2.c2, t2.c3
! Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
! -> Materialize
! Output: t1.c1, t1.c2, t1.c3
! -> Foreign Scan on public.ft5 t1
! Output: t1.c1, t1.c2, t1.c3
! Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
! (13 rows)
!
! SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! ----+----
! (0 rows)
!
! -- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS
! -- JOIN since c8 in both tables has same value.
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Sort
! Output: t1.c1, t2.c1
! Sort Key: t1.c1, t2.c1
! -> Merge Left Join
! Output: t1.c1, t2.c1
! Merge Cond: (t1.c8 = t2.c8)
! -> Sort
! Output: t1.c1, t1.c8
! Sort Key: t1.c8
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c8
! Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1"
! -> Sort
! Output: t2.c1, t2.c8
! Sort Key: t2.c8
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1, t2.c8
! Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1"
! (20 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! ----+-----
! 1 | 101
! 1 | 102
! 1 | 103
! 1 | 104
! 1 | 105
! 1 | 106
! 1 | 107
! 1 | 108
! 1 | 109
! 1 | 110
! (10 rows)
!
! -- unsafe conditions on one side (c8 has a UDT), not pushed down.
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! -----------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3
! -> Sort
! Output: t1.c1, t2.c1, t1.c3
! Sort Key: t1.c3, t1.c1
! -> Hash Right Join
! Output: t1.c1, t2.c1, t1.c3
! Hash Cond: (t2.c1 = t1.c1)
! -> Foreign Scan on public.ft2 t2
! Output: t2.c1
! Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
! -> Hash
! Output: t1.c1, t1.c3
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c3
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1"
! (17 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- join where unsafe to pushdown condition in WHERE clause has a column not
! -- in the SELECT clause. In this test unsafe clause needs to have column
! -- references from both joining sides so that the clause is not pushed down
! -- into one of the joining sides.
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1, t1.c3
! -> Sort
! Output: t1.c1, t2.c1, t1.c3
! Sort Key: t1.c3, t1.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1, t1.c3
! Filter: (t1.c8 = t2.c8)
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
! (10 rows)
!
! SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
! c1 | c1
! -----+-----
! 101 | 101
! 102 | 102
! 103 | 103
! 104 | 104
! 105 | 105
! 106 | 106
! 107 | 107
! 108 | 108
! 109 | 109
! 110 | 110
! (10 rows)
!
! -- Aggregate after UNION, for testing setrefs
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, (avg((t1.c1 + t2.c1)))
! -> Sort
! Output: t1.c1, (avg((t1.c1 + t2.c1)))
! Sort Key: t1.c1
! -> HashAggregate
! Output: t1.c1, avg((t1.c1 + t2.c1))
! Group Key: t1.c1
! -> HashAggregate
! Output: t1.c1, t2.c1
! Group Key: t1.c1, t2.c1
! -> Append
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
! -> Foreign Scan
! Output: t1_1.c1, t2_1.c1
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
! (20 rows)
!
! SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
! t1c1 | avg
! ------+----------------------
! 101 | 202.0000000000000000
! 102 | 204.0000000000000000
! 103 | 206.0000000000000000
! 104 | 208.0000000000000000
! 105 | 210.0000000000000000
! 106 | 212.0000000000000000
! 107 | 214.0000000000000000
! 108 | 216.0000000000000000
! 109 | 218.0000000000000000
! 110 | 220.0000000000000000
! (10 rows)
!
! -- join with lateral reference
! EXPLAIN (COSTS false, VERBOSE)
! SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1."C 1"
! -> Nested Loop
! Output: t1."C 1"
! -> Index Scan using t1_pkey on "S 1"."T 1" t1
! Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! -> HashAggregate
! Output: t2.c1, t3.c1
! Group Key: t2.c1, t3.c1
! -> Foreign Scan
! Output: t2.c1, t3.c1
! Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer))
! (13 rows)
!
! SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
! C 1
! -----
! 1
! 1
! 1
! 1
! 1
! 1
! 1
! 1
! 1
! 1
! (10 rows)
!
! -- create another user for permission, user mapping, effective user tests
! CREATE USER view_owner;
! -- grant privileges on ft4 and ft5 to view_owner
! GRANT ALL ON ft4 TO view_owner;
! GRANT ALL ON ft5 TO view_owner;
! -- prepare statement with current session user
! PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
! EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, t2.c1
! -> Foreign Scan
! Output: t1.c1, t2.c1
! Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2)
! Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST
! (6 rows)
!
! EXECUTE join_stmt;
! c1 | c1
! ----+----
! 22 |
! 24 | 24
! 26 |
! 28 |
! 30 | 30
! 32 |
! 34 |
! 36 | 36
! 38 |
! 40 |
! (10 rows)
!
! -- change the session user to view_owner and execute the statement. Because of
! -- change in session user, the plan should get invalidated and created again.
! -- While creating the plan, it should throw error since there is no user mapping
! -- available for view_owner.
! SET SESSION ROLE view_owner;
! EXPLAIN (COSTS OFF, VERBOSE) EXECUTE join_stmt;
! ERROR: user mapping not found for "view_owner"
! EXECUTE join_stmt;
! ERROR: user mapping not found for "view_owner"
! RESET ROLE;
! DEALLOCATE join_stmt;
! CREATE VIEW v_ft5 AS SELECT * FROM ft5;
! -- change owner of v_ft5 to view_owner so that the effective user for scan on
! -- ft5 is view_owner and not the current user.
! ALTER VIEW v_ft5 OWNER TO view_owner;
! -- create a public user mapping for loopback server
! -- drop user mapping for current_user.
! DROP USER MAPPING FOR CURRENT_USER SERVER loopback;
! CREATE USER MAPPING FOR PUBLIC SERVER loopback;
! -- different effective user for permission check, but same user mapping for the
! -- joining sides, join pushed down, no result expected.
! PREPARE join_stmt AS SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN v_ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
! EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, ft5.c1
! -> Foreign Scan
! Output: t1.c1, ft5.c1
! Relations: (public.ft5 t1) INNER JOIN (public.ft5)
! Remote SQL: SELECT r1.c1, r6.c1 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 4" r6 ON (TRUE)) WHERE ((r1.c1 = r6.c1)) ORDER BY r1.c1 ASC NULLS LAST
! (6 rows)
!
! EXECUTE join_stmt;
! c1 | c1
! ----+----
! (0 rows)
!
! -- create user mapping for view_owner and execute the prepared statement
! -- the join should not be pushed down since joining relations now use two
! -- different user mappings
! CREATE USER MAPPING FOR view_owner SERVER loopback;
! EXPLAIN (COSTS false, VERBOSE) EXECUTE join_stmt;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
! Limit
! Output: t1.c1, ft5.c1
! -> Merge Join
! Output: t1.c1, ft5.c1
! Merge Cond: (t1.c1 = ft5.c1)
! -> Foreign Scan on public.ft5 t1
! Output: t1.c1, t1.c2, t1.c3
! Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
! -> Materialize
! Output: ft5.c1, ft5.c2, ft5.c3
! -> Foreign Scan on public.ft5
! Output: ft5.c1, ft5.c2, ft5.c3
! Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
! (13 rows)
!
! EXECUTE join_stmt;
! c1 | c1
! ----+----
! (0 rows)
!
! -- recreate the dropped user mapping for further tests
! CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
! DROP USER MAPPING FOR PUBLIC SERVER loopback;
! -- ===================================================================
! -- parameterized queries
! -- ===================================================================
! -- simple join
! PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan
! Output: t1.c3, t2.c3
! Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
! Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r2."C 1" = 2)) AND ((r1."C 1" = 1))
! (4 rows)
!
! EXECUTE st1(1, 1);
! c3 | c3
! -------+-------
! 00001 | 00001
! (1 row)
!
! EXECUTE st1(101, 101);
! c3 | c3
! -------+-------
! 00101 | 00101
! (1 row)
!
! -- subquery using stable function (can't be sent to remote)
! PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------
! Sort
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Sort Key: t1.c1
! -> Nested Loop Semi Join
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Join Filter: (t1.c3 = t2.c3)
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20))
! -> Materialize
! Output: t2.c3
! -> Foreign Scan on public.ft2 t2
! Output: t2.c3
! Filter: (date(t2.c4) = '01-17-1970'::date)
! Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10))
! (15 rows)
!
! EXECUTE st2(10, 20);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! (1 row)
!
! EXECUTE st2(101, 121);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -----+----+-------+------------------------------+--------------------------+----+------------+-----
! 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! (1 row)
!
! -- subquery using immutable function (can be sent to remote)
! PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------
! Sort
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Sort Key: t1.c1
! -> Nested Loop Semi Join
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Join Filter: (t1.c3 = t2.c3)
! -> Foreign Scan on public.ft1 t1
! Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20))
! -> Materialize
! Output: t2.c3
! -> Foreign Scan on public.ft2 t2
! Output: t2.c3
! Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
! (14 rows)
!
! EXECUTE st3(10, 20);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo
! (1 row)
!
! EXECUTE st3(20, 30);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+----+----+----+----+----+----
! (0 rows)
!
! -- custom plan should be chosen initially
! PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (3 rows)
!
! -- once we try it enough times, should switch to generic plan
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
! (3 rows)
!
! -- value of $1 should not be sent to remote
! PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (4 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (4 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (4 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (4 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = 'foo'::user_enum)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
! (4 rows)
!
! EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.c8 = $1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
! (4 rows)
!
! EXECUTE st5('foo', 1);
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! -- cleanup
! DEALLOCATE st1;
! DEALLOCATE st2;
! DEALLOCATE st3;
! DEALLOCATE st4;
! DEALLOCATE st5;
! -- System columns, except ctid, should not be sent to remote
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
! QUERY PLAN
! -------------------------------------------------------------------------------
! Limit
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Filter: (t1.tableoid = '1259'::oid)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (6 rows)
!
! SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
! QUERY PLAN
! -------------------------------------------------------------------------------
! Limit
! Output: ((tableoid)::regclass), c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1 t1
! Output: (tableoid)::regclass, c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (5 rows)
!
! SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
! tableoid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----------+----+----+-------+------------------------------+--------------------------+----+------------+-----
! ft1 | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------
! Foreign Scan on public.ft1 t1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((ctid = '(0,2)'::tid))
! (3 rows)
!
! SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! (1 row)
!
! EXPLAIN (VERBOSE, COSTS false)
! SELECT ctid, * FROM ft1 t1 LIMIT 1;
! QUERY PLAN
! -------------------------------------------------------------------------------------
! Limit
! Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1 t1
! Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1"
! (5 rows)
!
! SELECT ctid, * FROM ft1 t1 LIMIT 1;
! ctid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! -------+----+----+-------+------------------------------+--------------------------+----+------------+-----
! (0,1) | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! -- ===================================================================
! -- used in pl/pgsql function
! -- ===================================================================
! CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
! DECLARE
! v_c1 int;
! BEGIN
! SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
! PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
! RETURN v_c1;
! END;
! $$ LANGUAGE plpgsql;
! SELECT f_test(100);
! f_test
! --------
! 100
! (1 row)
!
! DROP FUNCTION f_test(int);
! -- ===================================================================
! -- conversion error
! -- ===================================================================
! ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
! SELECT * FROM ft1 WHERE c1 = 1; -- ERROR
! ERROR: invalid input syntax for integer: "foo"
! CONTEXT: column "c8" of foreign table "ft1"
! SELECT ft1.c1, ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
! ERROR: invalid input syntax for integer: "foo"
! CONTEXT: column "c8" of foreign table "ft1"
! ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
! -- ===================================================================
! -- subtransaction
! -- + local/remote error doesn't break cursor
! -- ===================================================================
! BEGIN;
! DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
! FETCH c;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! SAVEPOINT s;
! ERROR OUT; -- ERROR
! ERROR: syntax error at or near "ERROR"
! LINE 1: ERROR OUT;
! ^
! ROLLBACK TO s;
! FETCH c;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo
! (1 row)
!
! SAVEPOINT s;
! SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR
! ERROR: division by zero
! CONTEXT: Remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0))
! ROLLBACK TO s;
! FETCH c;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo
! (1 row)
!
! SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ----+----+-------+------------------------------+--------------------------+----+------------+-----
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo
! (1 row)
!
! COMMIT;
! -- ===================================================================
! -- test handling of collations
! -- ===================================================================
! create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique);
! create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10))
! server loopback options (table_name 'loct3', use_remote_estimate 'true');
! -- can be sent to remote
! explain (verbose, costs off) select * from ft3 where f1 = 'foo';
! QUERY PLAN
! ------------------------------------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
! (3 rows)
!
! explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
! QUERY PLAN
! ------------------------------------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
! (3 rows)
!
! explain (verbose, costs off) select * from ft3 where f2 = 'foo';
! QUERY PLAN
! ------------------------------------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text))
! (3 rows)
!
! explain (verbose, costs off) select * from ft3 where f3 = 'foo';
! QUERY PLAN
! ------------------------------------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text))
! (3 rows)
!
! explain (verbose, costs off) select * from ft3 f, loct3 l
! where f.f3 = l.f3 and l.f1 = 'foo';
! QUERY PLAN
! --------------------------------------------------------------------------------------------------
! Nested Loop
! Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3
! -> Index Scan using loct3_f1_key on public.loct3 l
! Output: l.f1, l.f2, l.f3
! Index Cond: (l.f1 = 'foo'::text)
! -> Foreign Scan on public.ft3 f
! Output: f.f1, f.f2, f.f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE (($1::character varying(10) = f3))
! (8 rows)
!
! -- can't be sent to remote
! explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
! QUERY PLAN
! ---------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Filter: ((ft3.f1)::text = 'foo'::text)
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3
! (4 rows)
!
! explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
! QUERY PLAN
! ---------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Filter: (ft3.f1 = 'foo'::text COLLATE "C")
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3
! (4 rows)
!
! explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
! QUERY PLAN
! ---------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Filter: ((ft3.f2)::text = 'foo'::text)
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3
! (4 rows)
!
! explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
! QUERY PLAN
! ---------------------------------------------------
! Foreign Scan on public.ft3
! Output: f1, f2, f3
! Filter: (ft3.f2 = 'foo'::text COLLATE "C")
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3
! (4 rows)
!
! explain (verbose, costs off) select * from ft3 f, loct3 l
! where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo';
! QUERY PLAN
! -------------------------------------------------------------
! Hash Join
! Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3
! Hash Cond: ((f.f3)::text = (l.f3)::text)
! -> Foreign Scan on public.ft3 f
! Output: f.f1, f.f2, f.f3
! Remote SQL: SELECT f1, f2, f3 FROM public.loct3
! -> Hash
! Output: l.f1, l.f2, l.f3
! -> Index Scan using loct3_f1_key on public.loct3 l
! Output: l.f1, l.f2, l.f3
! Index Cond: (l.f1 = 'foo'::text)
! (11 rows)
!
! -- ===================================================================
! -- test writable foreign table stuff
! -- ===================================================================
! EXPLAIN (verbose, costs off)
! INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Insert on public.ft2
! Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
! -> Subquery Scan on "*SELECT*"
! Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum
! -> Limit
! Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
! -> Foreign Scan on public.ft2 ft2_1
! Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
! Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
! (9 rows)
!
! INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
! INSERT INTO ft2 (c1,c2,c3)
! VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+-----+----+----+----+------------+----
! 1101 | 201 | aaa | | | | ft2 |
! 1102 | 202 | bbb | | | | ft2 |
! 1103 | 203 | ccc | | | | ft2 |
! (3 rows)
!
! INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
! UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
! UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
! 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
! 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
! 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
! 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
! 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
! 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
! 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
! 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
! 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
! 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
! 1007 | 507 | 0000700007_update7 | | | | ft2 |
! 1017 | 507 | 0001700017_update7 | | | | ft2 |
! (102 rows)
!
! EXPLAIN (verbose, costs off)
! UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
! FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Update on public.ft2
! Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3, c7 = $4 WHERE ctid = $1
! -> Foreign Scan
! Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft1.*
! Relations: (public.ft2) INNER JOIN (public.ft1)
! Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c8, r1.ctid, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9)) FOR UPDATE OF r1
! -> Hash Join
! Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft1.*
! Hash Cond: (ft2.c2 = ft1.c1)
! -> Foreign Scan on public.ft2
! Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE
! -> Hash
! Output: ft1.*, ft1.c1
! -> Foreign Scan on public.ft1
! Output: ft1.*, ft1.c1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))
! (17 rows)
!
! UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
! FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
! EXPLAIN (verbose, costs off)
! DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
! QUERY PLAN
! ----------------------------------------------------------------------------------------
! Delete on public.ft2
! Output: c1, c4
! Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4
! -> Foreign Scan on public.ft2
! Output: ctid
! Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
! (6 rows)
!
! DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
! c1 | c4
! ------+------------------------------
! 5 | Tue Jan 06 00:00:00 1970 PST
! 15 | Fri Jan 16 00:00:00 1970 PST
! 25 | Mon Jan 26 00:00:00 1970 PST
! 35 | Thu Feb 05 00:00:00 1970 PST
! 45 | Sun Feb 15 00:00:00 1970 PST
! 55 | Wed Feb 25 00:00:00 1970 PST
! 65 | Sat Mar 07 00:00:00 1970 PST
! 75 | Tue Mar 17 00:00:00 1970 PST
! 85 | Fri Mar 27 00:00:00 1970 PST
! 95 | Mon Apr 06 00:00:00 1970 PST
! 105 | Tue Jan 06 00:00:00 1970 PST
! 115 | Fri Jan 16 00:00:00 1970 PST
! 125 | Mon Jan 26 00:00:00 1970 PST
! 135 | Thu Feb 05 00:00:00 1970 PST
! 145 | Sun Feb 15 00:00:00 1970 PST
! 155 | Wed Feb 25 00:00:00 1970 PST
! 165 | Sat Mar 07 00:00:00 1970 PST
! 175 | Tue Mar 17 00:00:00 1970 PST
! 185 | Fri Mar 27 00:00:00 1970 PST
! 195 | Mon Apr 06 00:00:00 1970 PST
! 205 | Tue Jan 06 00:00:00 1970 PST
! 215 | Fri Jan 16 00:00:00 1970 PST
! 225 | Mon Jan 26 00:00:00 1970 PST
! 235 | Thu Feb 05 00:00:00 1970 PST
! 245 | Sun Feb 15 00:00:00 1970 PST
! 255 | Wed Feb 25 00:00:00 1970 PST
! 265 | Sat Mar 07 00:00:00 1970 PST
! 275 | Tue Mar 17 00:00:00 1970 PST
! 285 | Fri Mar 27 00:00:00 1970 PST
! 295 | Mon Apr 06 00:00:00 1970 PST
! 305 | Tue Jan 06 00:00:00 1970 PST
! 315 | Fri Jan 16 00:00:00 1970 PST
! 325 | Mon Jan 26 00:00:00 1970 PST
! 335 | Thu Feb 05 00:00:00 1970 PST
! 345 | Sun Feb 15 00:00:00 1970 PST
! 355 | Wed Feb 25 00:00:00 1970 PST
! 365 | Sat Mar 07 00:00:00 1970 PST
! 375 | Tue Mar 17 00:00:00 1970 PST
! 385 | Fri Mar 27 00:00:00 1970 PST
! 395 | Mon Apr 06 00:00:00 1970 PST
! 405 | Tue Jan 06 00:00:00 1970 PST
! 415 | Fri Jan 16 00:00:00 1970 PST
! 425 | Mon Jan 26 00:00:00 1970 PST
! 435 | Thu Feb 05 00:00:00 1970 PST
! 445 | Sun Feb 15 00:00:00 1970 PST
! 455 | Wed Feb 25 00:00:00 1970 PST
! 465 | Sat Mar 07 00:00:00 1970 PST
! 475 | Tue Mar 17 00:00:00 1970 PST
! 485 | Fri Mar 27 00:00:00 1970 PST
! 495 | Mon Apr 06 00:00:00 1970 PST
! 505 | Tue Jan 06 00:00:00 1970 PST
! 515 | Fri Jan 16 00:00:00 1970 PST
! 525 | Mon Jan 26 00:00:00 1970 PST
! 535 | Thu Feb 05 00:00:00 1970 PST
! 545 | Sun Feb 15 00:00:00 1970 PST
! 555 | Wed Feb 25 00:00:00 1970 PST
! 565 | Sat Mar 07 00:00:00 1970 PST
! 575 | Tue Mar 17 00:00:00 1970 PST
! 585 | Fri Mar 27 00:00:00 1970 PST
! 595 | Mon Apr 06 00:00:00 1970 PST
! 605 | Tue Jan 06 00:00:00 1970 PST
! 615 | Fri Jan 16 00:00:00 1970 PST
! 625 | Mon Jan 26 00:00:00 1970 PST
! 635 | Thu Feb 05 00:00:00 1970 PST
! 645 | Sun Feb 15 00:00:00 1970 PST
! 655 | Wed Feb 25 00:00:00 1970 PST
! 665 | Sat Mar 07 00:00:00 1970 PST
! 675 | Tue Mar 17 00:00:00 1970 PST
! 685 | Fri Mar 27 00:00:00 1970 PST
! 695 | Mon Apr 06 00:00:00 1970 PST
! 705 | Tue Jan 06 00:00:00 1970 PST
! 715 | Fri Jan 16 00:00:00 1970 PST
! 725 | Mon Jan 26 00:00:00 1970 PST
! 735 | Thu Feb 05 00:00:00 1970 PST
! 745 | Sun Feb 15 00:00:00 1970 PST
! 755 | Wed Feb 25 00:00:00 1970 PST
! 765 | Sat Mar 07 00:00:00 1970 PST
! 775 | Tue Mar 17 00:00:00 1970 PST
! 785 | Fri Mar 27 00:00:00 1970 PST
! 795 | Mon Apr 06 00:00:00 1970 PST
! 805 | Tue Jan 06 00:00:00 1970 PST
! 815 | Fri Jan 16 00:00:00 1970 PST
! 825 | Mon Jan 26 00:00:00 1970 PST
! 835 | Thu Feb 05 00:00:00 1970 PST
! 845 | Sun Feb 15 00:00:00 1970 PST
! 855 | Wed Feb 25 00:00:00 1970 PST
! 865 | Sat Mar 07 00:00:00 1970 PST
! 875 | Tue Mar 17 00:00:00 1970 PST
! 885 | Fri Mar 27 00:00:00 1970 PST
! 895 | Mon Apr 06 00:00:00 1970 PST
! 905 | Tue Jan 06 00:00:00 1970 PST
! 915 | Fri Jan 16 00:00:00 1970 PST
! 925 | Mon Jan 26 00:00:00 1970 PST
! 935 | Thu Feb 05 00:00:00 1970 PST
! 945 | Sun Feb 15 00:00:00 1970 PST
! 955 | Wed Feb 25 00:00:00 1970 PST
! 965 | Sat Mar 07 00:00:00 1970 PST
! 975 | Tue Mar 17 00:00:00 1970 PST
! 985 | Fri Mar 27 00:00:00 1970 PST
! 995 | Mon Apr 06 00:00:00 1970 PST
! 1005 |
! 1015 |
! 1105 |
! (103 rows)
!
! EXPLAIN (verbose, costs off)
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Delete on public.ft2
! Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
! -> Foreign Scan
! Output: ft2.ctid, ft1.*
! Relations: (public.ft2) INNER JOIN (public.ft1)
! Remote SQL: SELECT r1.ctid, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2)) FOR UPDATE OF r1
! -> Hash Join
! Output: ft2.ctid, ft1.*
! Hash Cond: (ft2.c2 = ft1.c1)
! -> Foreign Scan on public.ft2
! Output: ft2.ctid, ft2.c2
! Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE
! -> Hash
! Output: ft1.*, ft1.c1
! -> Foreign Scan on public.ft1
! Output: ft1.*, ft1.c1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))
! (17 rows)
!
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
! SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
! c1 | c2 | c3 | c4
! ------+-----+--------------------+------------------------------
! 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST
! 3 | 303 | 00003_update3 | Sun Jan 04 00:00:00 1970 PST
! 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST
! 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST
! 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST
! 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST
! 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST
! 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST
! 11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST
! 13 | 303 | 00013_update3 | Wed Jan 14 00:00:00 1970 PST
! 14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST
! 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST
! 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST
! 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST
! 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST
! 20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST
! 21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST
! 23 | 303 | 00023_update3 | Sat Jan 24 00:00:00 1970 PST
! 24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST
! 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST
! 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST
! 28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST
! 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST
! 30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST
! 31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST
! 33 | 303 | 00033_update3 | Tue Feb 03 00:00:00 1970 PST
! 34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST
! 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST
! 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST
! 38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST
! 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST
! 40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST
! 41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST
! 43 | 303 | 00043_update3 | Fri Feb 13 00:00:00 1970 PST
! 44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST
! 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST
! 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST
! 48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST
! 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST
! 50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST
! 51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST
! 53 | 303 | 00053_update3 | Mon Feb 23 00:00:00 1970 PST
! 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST
! 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST
! 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST
! 58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST
! 59 | 509 | 00059_update9 | Sun Mar 01 00:00:00 1970 PST
! 60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST
! 61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST
! 63 | 303 | 00063_update3 | Thu Mar 05 00:00:00 1970 PST
! 64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST
! 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST
! 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST
! 68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST
! 69 | 509 | 00069_update9 | Wed Mar 11 00:00:00 1970 PST
! 70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST
! 71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST
! 73 | 303 | 00073_update3 | Sun Mar 15 00:00:00 1970 PST
! 74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST
! 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST
! 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST
! 78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST
! 79 | 509 | 00079_update9 | Sat Mar 21 00:00:00 1970 PST
! 80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST
! 81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST
! 83 | 303 | 00083_update3 | Wed Mar 25 00:00:00 1970 PST
! 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST
! 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST
! 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST
! 88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST
! 89 | 509 | 00089_update9 | Tue Mar 31 00:00:00 1970 PST
! 90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST
! 91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST
! 93 | 303 | 00093_update3 | Sat Apr 04 00:00:00 1970 PST
! 94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST
! 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST
! 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST
! 98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST
! 99 | 509 | 00099_update9 | Fri Apr 10 00:00:00 1970 PST
! 100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST
! 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST
! 103 | 303 | 00103_update3 | Sun Jan 04 00:00:00 1970 PST
! 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST
! 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST
! 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST
! 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST
! 109 | 509 | 00109_update9 | Sat Jan 10 00:00:00 1970 PST
! 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST
! 111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST
! 113 | 303 | 00113_update3 | Wed Jan 14 00:00:00 1970 PST
! 114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST
! 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST
! 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST
! 118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST
! 119 | 509 | 00119_update9 | Tue Jan 20 00:00:00 1970 PST
! 120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST
! 121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST
! 123 | 303 | 00123_update3 | Sat Jan 24 00:00:00 1970 PST
! 124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST
! 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST
! 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST
! 128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST
! 129 | 509 | 00129_update9 | Fri Jan 30 00:00:00 1970 PST
! 130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST
! 131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST
! 133 | 303 | 00133_update3 | Tue Feb 03 00:00:00 1970 PST
! 134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST
! 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST
! 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST
! 138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST
! 139 | 509 | 00139_update9 | Mon Feb 09 00:00:00 1970 PST
! 140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST
! 141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST
! 143 | 303 | 00143_update3 | Fri Feb 13 00:00:00 1970 PST
! 144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST
! 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST
! 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST
! 148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST
! 149 | 509 | 00149_update9 | Thu Feb 19 00:00:00 1970 PST
! 150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST
! 151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST
! 153 | 303 | 00153_update3 | Mon Feb 23 00:00:00 1970 PST
! 154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST
! 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST
! 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST
! 158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST
! 159 | 509 | 00159_update9 | Sun Mar 01 00:00:00 1970 PST
! 160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST
! 161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST
! 163 | 303 | 00163_update3 | Thu Mar 05 00:00:00 1970 PST
! 164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST
! 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST
! 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST
! 168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST
! 169 | 509 | 00169_update9 | Wed Mar 11 00:00:00 1970 PST
! 170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST
! 171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST
! 173 | 303 | 00173_update3 | Sun Mar 15 00:00:00 1970 PST
! 174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST
! 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST
! 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST
! 178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST
! 179 | 509 | 00179_update9 | Sat Mar 21 00:00:00 1970 PST
! 180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST
! 181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST
! 183 | 303 | 00183_update3 | Wed Mar 25 00:00:00 1970 PST
! 184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST
! 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST
! 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST
! 188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST
! 189 | 509 | 00189_update9 | Tue Mar 31 00:00:00 1970 PST
! 190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST
! 191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST
! 193 | 303 | 00193_update3 | Sat Apr 04 00:00:00 1970 PST
! 194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST
! 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST
! 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST
! 198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST
! 199 | 509 | 00199_update9 | Fri Apr 10 00:00:00 1970 PST
! 200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST
! 201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST
! 203 | 303 | 00203_update3 | Sun Jan 04 00:00:00 1970 PST
! 204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST
! 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST
! 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST
! 208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST
! 209 | 509 | 00209_update9 | Sat Jan 10 00:00:00 1970 PST
! 210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST
! 211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST
! 213 | 303 | 00213_update3 | Wed Jan 14 00:00:00 1970 PST
! 214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST
! 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST
! 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST
! 218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST
! 219 | 509 | 00219_update9 | Tue Jan 20 00:00:00 1970 PST
! 220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST
! 221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST
! 223 | 303 | 00223_update3 | Sat Jan 24 00:00:00 1970 PST
! 224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST
! 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST
! 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST
! 228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST
! 229 | 509 | 00229_update9 | Fri Jan 30 00:00:00 1970 PST
! 230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST
! 231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST
! 233 | 303 | 00233_update3 | Tue Feb 03 00:00:00 1970 PST
! 234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST
! 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST
! 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST
! 238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST
! 239 | 509 | 00239_update9 | Mon Feb 09 00:00:00 1970 PST
! 240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST
! 241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST
! 243 | 303 | 00243_update3 | Fri Feb 13 00:00:00 1970 PST
! 244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST
! 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST
! 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST
! 248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST
! 249 | 509 | 00249_update9 | Thu Feb 19 00:00:00 1970 PST
! 250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST
! 251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST
! 253 | 303 | 00253_update3 | Mon Feb 23 00:00:00 1970 PST
! 254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST
! 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST
! 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST
! 258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST
! 259 | 509 | 00259_update9 | Sun Mar 01 00:00:00 1970 PST
! 260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST
! 261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST
! 263 | 303 | 00263_update3 | Thu Mar 05 00:00:00 1970 PST
! 264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST
! 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST
! 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST
! 268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST
! 269 | 509 | 00269_update9 | Wed Mar 11 00:00:00 1970 PST
! 270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST
! 271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST
! 273 | 303 | 00273_update3 | Sun Mar 15 00:00:00 1970 PST
! 274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST
! 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST
! 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST
! 278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST
! 279 | 509 | 00279_update9 | Sat Mar 21 00:00:00 1970 PST
! 280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST
! 281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST
! 283 | 303 | 00283_update3 | Wed Mar 25 00:00:00 1970 PST
! 284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST
! 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST
! 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST
! 288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST
! 289 | 509 | 00289_update9 | Tue Mar 31 00:00:00 1970 PST
! 290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST
! 291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST
! 293 | 303 | 00293_update3 | Sat Apr 04 00:00:00 1970 PST
! 294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST
! 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST
! 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST
! 298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST
! 299 | 509 | 00299_update9 | Fri Apr 10 00:00:00 1970 PST
! 300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST
! 301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST
! 303 | 303 | 00303_update3 | Sun Jan 04 00:00:00 1970 PST
! 304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST
! 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST
! 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST
! 308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST
! 309 | 509 | 00309_update9 | Sat Jan 10 00:00:00 1970 PST
! 310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST
! 311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST
! 313 | 303 | 00313_update3 | Wed Jan 14 00:00:00 1970 PST
! 314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST
! 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST
! 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST
! 318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST
! 319 | 509 | 00319_update9 | Tue Jan 20 00:00:00 1970 PST
! 320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST
! 321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST
! 323 | 303 | 00323_update3 | Sat Jan 24 00:00:00 1970 PST
! 324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST
! 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST
! 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST
! 328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST
! 329 | 509 | 00329_update9 | Fri Jan 30 00:00:00 1970 PST
! 330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST
! 331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST
! 333 | 303 | 00333_update3 | Tue Feb 03 00:00:00 1970 PST
! 334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST
! 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST
! 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST
! 338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST
! 339 | 509 | 00339_update9 | Mon Feb 09 00:00:00 1970 PST
! 340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST
! 341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST
! 343 | 303 | 00343_update3 | Fri Feb 13 00:00:00 1970 PST
! 344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST
! 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST
! 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST
! 348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST
! 349 | 509 | 00349_update9 | Thu Feb 19 00:00:00 1970 PST
! 350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST
! 351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST
! 353 | 303 | 00353_update3 | Mon Feb 23 00:00:00 1970 PST
! 354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST
! 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST
! 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST
! 358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST
! 359 | 509 | 00359_update9 | Sun Mar 01 00:00:00 1970 PST
! 360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST
! 361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST
! 363 | 303 | 00363_update3 | Thu Mar 05 00:00:00 1970 PST
! 364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST
! 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST
! 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST
! 368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST
! 369 | 509 | 00369_update9 | Wed Mar 11 00:00:00 1970 PST
! 370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST
! 371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST
! 373 | 303 | 00373_update3 | Sun Mar 15 00:00:00 1970 PST
! 374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST
! 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST
! 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST
! 378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST
! 379 | 509 | 00379_update9 | Sat Mar 21 00:00:00 1970 PST
! 380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST
! 381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST
! 383 | 303 | 00383_update3 | Wed Mar 25 00:00:00 1970 PST
! 384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST
! 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST
! 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST
! 388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST
! 389 | 509 | 00389_update9 | Tue Mar 31 00:00:00 1970 PST
! 390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST
! 391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST
! 393 | 303 | 00393_update3 | Sat Apr 04 00:00:00 1970 PST
! 394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST
! 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST
! 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST
! 398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST
! 399 | 509 | 00399_update9 | Fri Apr 10 00:00:00 1970 PST
! 400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST
! 401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST
! 403 | 303 | 00403_update3 | Sun Jan 04 00:00:00 1970 PST
! 404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST
! 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST
! 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST
! 408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST
! 409 | 509 | 00409_update9 | Sat Jan 10 00:00:00 1970 PST
! 410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST
! 411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST
! 413 | 303 | 00413_update3 | Wed Jan 14 00:00:00 1970 PST
! 414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST
! 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST
! 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST
! 418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST
! 419 | 509 | 00419_update9 | Tue Jan 20 00:00:00 1970 PST
! 420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST
! 421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST
! 423 | 303 | 00423_update3 | Sat Jan 24 00:00:00 1970 PST
! 424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST
! 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST
! 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST
! 428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST
! 429 | 509 | 00429_update9 | Fri Jan 30 00:00:00 1970 PST
! 430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST
! 431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST
! 433 | 303 | 00433_update3 | Tue Feb 03 00:00:00 1970 PST
! 434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST
! 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST
! 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST
! 438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST
! 439 | 509 | 00439_update9 | Mon Feb 09 00:00:00 1970 PST
! 440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST
! 441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST
! 443 | 303 | 00443_update3 | Fri Feb 13 00:00:00 1970 PST
! 444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST
! 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST
! 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST
! 448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST
! 449 | 509 | 00449_update9 | Thu Feb 19 00:00:00 1970 PST
! 450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST
! 451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST
! 453 | 303 | 00453_update3 | Mon Feb 23 00:00:00 1970 PST
! 454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST
! 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST
! 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST
! 458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST
! 459 | 509 | 00459_update9 | Sun Mar 01 00:00:00 1970 PST
! 460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST
! 461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST
! 463 | 303 | 00463_update3 | Thu Mar 05 00:00:00 1970 PST
! 464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST
! 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST
! 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST
! 468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST
! 469 | 509 | 00469_update9 | Wed Mar 11 00:00:00 1970 PST
! 470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST
! 471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST
! 473 | 303 | 00473_update3 | Sun Mar 15 00:00:00 1970 PST
! 474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST
! 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST
! 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST
! 478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST
! 479 | 509 | 00479_update9 | Sat Mar 21 00:00:00 1970 PST
! 480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST
! 481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST
! 483 | 303 | 00483_update3 | Wed Mar 25 00:00:00 1970 PST
! 484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST
! 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST
! 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST
! 488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST
! 489 | 509 | 00489_update9 | Tue Mar 31 00:00:00 1970 PST
! 490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST
! 491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST
! 493 | 303 | 00493_update3 | Sat Apr 04 00:00:00 1970 PST
! 494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST
! 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST
! 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST
! 498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST
! 499 | 509 | 00499_update9 | Fri Apr 10 00:00:00 1970 PST
! 500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST
! 501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST
! 503 | 303 | 00503_update3 | Sun Jan 04 00:00:00 1970 PST
! 504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST
! 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST
! 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST
! 508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST
! 509 | 509 | 00509_update9 | Sat Jan 10 00:00:00 1970 PST
! 510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST
! 511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST
! 513 | 303 | 00513_update3 | Wed Jan 14 00:00:00 1970 PST
! 514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST
! 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST
! 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST
! 518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST
! 519 | 509 | 00519_update9 | Tue Jan 20 00:00:00 1970 PST
! 520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST
! 521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST
! 523 | 303 | 00523_update3 | Sat Jan 24 00:00:00 1970 PST
! 524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST
! 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST
! 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST
! 528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST
! 529 | 509 | 00529_update9 | Fri Jan 30 00:00:00 1970 PST
! 530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST
! 531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST
! 533 | 303 | 00533_update3 | Tue Feb 03 00:00:00 1970 PST
! 534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST
! 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST
! 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST
! 538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST
! 539 | 509 | 00539_update9 | Mon Feb 09 00:00:00 1970 PST
! 540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST
! 541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST
! 543 | 303 | 00543_update3 | Fri Feb 13 00:00:00 1970 PST
! 544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST
! 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST
! 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST
! 548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST
! 549 | 509 | 00549_update9 | Thu Feb 19 00:00:00 1970 PST
! 550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST
! 551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST
! 553 | 303 | 00553_update3 | Mon Feb 23 00:00:00 1970 PST
! 554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST
! 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST
! 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST
! 558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST
! 559 | 509 | 00559_update9 | Sun Mar 01 00:00:00 1970 PST
! 560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST
! 561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST
! 563 | 303 | 00563_update3 | Thu Mar 05 00:00:00 1970 PST
! 564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST
! 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST
! 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST
! 568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST
! 569 | 509 | 00569_update9 | Wed Mar 11 00:00:00 1970 PST
! 570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST
! 571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST
! 573 | 303 | 00573_update3 | Sun Mar 15 00:00:00 1970 PST
! 574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST
! 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST
! 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST
! 578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST
! 579 | 509 | 00579_update9 | Sat Mar 21 00:00:00 1970 PST
! 580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST
! 581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST
! 583 | 303 | 00583_update3 | Wed Mar 25 00:00:00 1970 PST
! 584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST
! 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST
! 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST
! 588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST
! 589 | 509 | 00589_update9 | Tue Mar 31 00:00:00 1970 PST
! 590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST
! 591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST
! 593 | 303 | 00593_update3 | Sat Apr 04 00:00:00 1970 PST
! 594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST
! 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST
! 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST
! 598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST
! 599 | 509 | 00599_update9 | Fri Apr 10 00:00:00 1970 PST
! 600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST
! 601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST
! 603 | 303 | 00603_update3 | Sun Jan 04 00:00:00 1970 PST
! 604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST
! 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST
! 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST
! 608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST
! 609 | 509 | 00609_update9 | Sat Jan 10 00:00:00 1970 PST
! 610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST
! 611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST
! 613 | 303 | 00613_update3 | Wed Jan 14 00:00:00 1970 PST
! 614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST
! 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST
! 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST
! 618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST
! 619 | 509 | 00619_update9 | Tue Jan 20 00:00:00 1970 PST
! 620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST
! 621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST
! 623 | 303 | 00623_update3 | Sat Jan 24 00:00:00 1970 PST
! 624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST
! 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST
! 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST
! 628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST
! 629 | 509 | 00629_update9 | Fri Jan 30 00:00:00 1970 PST
! 630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST
! 631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST
! 633 | 303 | 00633_update3 | Tue Feb 03 00:00:00 1970 PST
! 634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST
! 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST
! 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST
! 638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST
! 639 | 509 | 00639_update9 | Mon Feb 09 00:00:00 1970 PST
! 640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST
! 641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST
! 643 | 303 | 00643_update3 | Fri Feb 13 00:00:00 1970 PST
! 644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST
! 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST
! 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST
! 648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST
! 649 | 509 | 00649_update9 | Thu Feb 19 00:00:00 1970 PST
! 650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST
! 651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST
! 653 | 303 | 00653_update3 | Mon Feb 23 00:00:00 1970 PST
! 654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST
! 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST
! 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST
! 658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST
! 659 | 509 | 00659_update9 | Sun Mar 01 00:00:00 1970 PST
! 660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST
! 661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST
! 663 | 303 | 00663_update3 | Thu Mar 05 00:00:00 1970 PST
! 664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST
! 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST
! 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST
! 668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST
! 669 | 509 | 00669_update9 | Wed Mar 11 00:00:00 1970 PST
! 670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST
! 671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST
! 673 | 303 | 00673_update3 | Sun Mar 15 00:00:00 1970 PST
! 674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST
! 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST
! 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST
! 678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST
! 679 | 509 | 00679_update9 | Sat Mar 21 00:00:00 1970 PST
! 680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST
! 681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST
! 683 | 303 | 00683_update3 | Wed Mar 25 00:00:00 1970 PST
! 684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST
! 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST
! 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST
! 688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST
! 689 | 509 | 00689_update9 | Tue Mar 31 00:00:00 1970 PST
! 690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST
! 691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST
! 693 | 303 | 00693_update3 | Sat Apr 04 00:00:00 1970 PST
! 694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST
! 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST
! 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST
! 698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST
! 699 | 509 | 00699_update9 | Fri Apr 10 00:00:00 1970 PST
! 700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST
! 701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST
! 703 | 303 | 00703_update3 | Sun Jan 04 00:00:00 1970 PST
! 704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST
! 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST
! 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST
! 708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST
! 709 | 509 | 00709_update9 | Sat Jan 10 00:00:00 1970 PST
! 710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST
! 711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST
! 713 | 303 | 00713_update3 | Wed Jan 14 00:00:00 1970 PST
! 714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST
! 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST
! 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST
! 718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST
! 719 | 509 | 00719_update9 | Tue Jan 20 00:00:00 1970 PST
! 720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST
! 721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST
! 723 | 303 | 00723_update3 | Sat Jan 24 00:00:00 1970 PST
! 724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST
! 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST
! 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST
! 728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST
! 729 | 509 | 00729_update9 | Fri Jan 30 00:00:00 1970 PST
! 730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST
! 731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST
! 733 | 303 | 00733_update3 | Tue Feb 03 00:00:00 1970 PST
! 734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST
! 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST
! 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST
! 738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST
! 739 | 509 | 00739_update9 | Mon Feb 09 00:00:00 1970 PST
! 740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST
! 741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST
! 743 | 303 | 00743_update3 | Fri Feb 13 00:00:00 1970 PST
! 744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST
! 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST
! 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST
! 748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST
! 749 | 509 | 00749_update9 | Thu Feb 19 00:00:00 1970 PST
! 750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST
! 751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST
! 753 | 303 | 00753_update3 | Mon Feb 23 00:00:00 1970 PST
! 754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST
! 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST
! 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST
! 758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST
! 759 | 509 | 00759_update9 | Sun Mar 01 00:00:00 1970 PST
! 760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST
! 761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST
! 763 | 303 | 00763_update3 | Thu Mar 05 00:00:00 1970 PST
! 764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST
! 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST
! 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST
! 768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST
! 769 | 509 | 00769_update9 | Wed Mar 11 00:00:00 1970 PST
! 770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST
! 771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST
! 773 | 303 | 00773_update3 | Sun Mar 15 00:00:00 1970 PST
! 774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST
! 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST
! 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST
! 778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST
! 779 | 509 | 00779_update9 | Sat Mar 21 00:00:00 1970 PST
! 780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST
! 781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST
! 783 | 303 | 00783_update3 | Wed Mar 25 00:00:00 1970 PST
! 784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST
! 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST
! 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST
! 788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST
! 789 | 509 | 00789_update9 | Tue Mar 31 00:00:00 1970 PST
! 790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST
! 791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST
! 793 | 303 | 00793_update3 | Sat Apr 04 00:00:00 1970 PST
! 794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST
! 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST
! 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST
! 798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST
! 799 | 509 | 00799_update9 | Fri Apr 10 00:00:00 1970 PST
! 800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST
! 801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST
! 803 | 303 | 00803_update3 | Sun Jan 04 00:00:00 1970 PST
! 804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST
! 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST
! 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST
! 808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST
! 809 | 509 | 00809_update9 | Sat Jan 10 00:00:00 1970 PST
! 810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST
! 811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST
! 813 | 303 | 00813_update3 | Wed Jan 14 00:00:00 1970 PST
! 814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST
! 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST
! 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST
! 818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST
! 819 | 509 | 00819_update9 | Tue Jan 20 00:00:00 1970 PST
! 820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST
! 821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST
! 823 | 303 | 00823_update3 | Sat Jan 24 00:00:00 1970 PST
! 824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST
! 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST
! 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST
! 828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST
! 829 | 509 | 00829_update9 | Fri Jan 30 00:00:00 1970 PST
! 830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST
! 831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST
! 833 | 303 | 00833_update3 | Tue Feb 03 00:00:00 1970 PST
! 834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST
! 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST
! 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST
! 838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST
! 839 | 509 | 00839_update9 | Mon Feb 09 00:00:00 1970 PST
! 840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST
! 841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST
! 843 | 303 | 00843_update3 | Fri Feb 13 00:00:00 1970 PST
! 844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST
! 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST
! 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST
! 848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST
! 849 | 509 | 00849_update9 | Thu Feb 19 00:00:00 1970 PST
! 850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST
! 851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST
! 853 | 303 | 00853_update3 | Mon Feb 23 00:00:00 1970 PST
! 854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST
! 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST
! 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST
! 858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST
! 859 | 509 | 00859_update9 | Sun Mar 01 00:00:00 1970 PST
! 860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST
! 861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST
! 863 | 303 | 00863_update3 | Thu Mar 05 00:00:00 1970 PST
! 864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST
! 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST
! 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST
! 868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST
! 869 | 509 | 00869_update9 | Wed Mar 11 00:00:00 1970 PST
! 870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST
! 871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST
! 873 | 303 | 00873_update3 | Sun Mar 15 00:00:00 1970 PST
! 874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST
! 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST
! 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST
! 878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST
! 879 | 509 | 00879_update9 | Sat Mar 21 00:00:00 1970 PST
! 880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST
! 881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST
! 883 | 303 | 00883_update3 | Wed Mar 25 00:00:00 1970 PST
! 884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST
! 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST
! 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST
! 888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST
! 889 | 509 | 00889_update9 | Tue Mar 31 00:00:00 1970 PST
! 890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST
! 891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST
! 893 | 303 | 00893_update3 | Sat Apr 04 00:00:00 1970 PST
! 894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST
! 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST
! 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST
! 898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST
! 899 | 509 | 00899_update9 | Fri Apr 10 00:00:00 1970 PST
! 900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST
! 901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST
! 903 | 303 | 00903_update3 | Sun Jan 04 00:00:00 1970 PST
! 904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST
! 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST
! 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST
! 908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST
! 909 | 509 | 00909_update9 | Sat Jan 10 00:00:00 1970 PST
! 910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST
! 911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST
! 913 | 303 | 00913_update3 | Wed Jan 14 00:00:00 1970 PST
! 914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST
! 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST
! 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST
! 918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST
! 919 | 509 | 00919_update9 | Tue Jan 20 00:00:00 1970 PST
! 920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST
! 921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST
! 923 | 303 | 00923_update3 | Sat Jan 24 00:00:00 1970 PST
! 924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST
! 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST
! 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST
! 928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST
! 929 | 509 | 00929_update9 | Fri Jan 30 00:00:00 1970 PST
! 930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST
! 931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST
! 933 | 303 | 00933_update3 | Tue Feb 03 00:00:00 1970 PST
! 934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST
! 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST
! 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST
! 938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST
! 939 | 509 | 00939_update9 | Mon Feb 09 00:00:00 1970 PST
! 940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST
! 941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST
! 943 | 303 | 00943_update3 | Fri Feb 13 00:00:00 1970 PST
! 944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST
! 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST
! 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST
! 948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST
! 949 | 509 | 00949_update9 | Thu Feb 19 00:00:00 1970 PST
! 950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST
! 951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST
! 953 | 303 | 00953_update3 | Mon Feb 23 00:00:00 1970 PST
! 954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST
! 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST
! 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST
! 958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST
! 959 | 509 | 00959_update9 | Sun Mar 01 00:00:00 1970 PST
! 960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST
! 961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST
! 963 | 303 | 00963_update3 | Thu Mar 05 00:00:00 1970 PST
! 964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST
! 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST
! 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST
! 968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST
! 969 | 509 | 00969_update9 | Wed Mar 11 00:00:00 1970 PST
! 970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST
! 971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST
! 973 | 303 | 00973_update3 | Sun Mar 15 00:00:00 1970 PST
! 974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST
! 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST
! 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST
! 978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST
! 979 | 509 | 00979_update9 | Sat Mar 21 00:00:00 1970 PST
! 980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST
! 981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST
! 983 | 303 | 00983_update3 | Wed Mar 25 00:00:00 1970 PST
! 984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST
! 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST
! 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST
! 988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST
! 989 | 509 | 00989_update9 | Tue Mar 31 00:00:00 1970 PST
! 990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST
! 991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST
! 993 | 303 | 00993_update3 | Sat Apr 04 00:00:00 1970 PST
! 994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST
! 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST
! 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST
! 998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST
! 999 | 509 | 00999_update9 | Fri Apr 10 00:00:00 1970 PST
! 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST
! 1001 | 101 | 0000100001 |
! 1003 | 403 | 0000300003_update3 |
! 1004 | 104 | 0000400004 |
! 1006 | 106 | 0000600006 |
! 1007 | 507 | 0000700007_update7 |
! 1008 | 108 | 0000800008 |
! 1009 | 609 | 0000900009_update9 |
! 1010 | 100 | 0001000010 |
! 1011 | 101 | 0001100011 |
! 1013 | 403 | 0001300013_update3 |
! 1014 | 104 | 0001400014 |
! 1016 | 106 | 0001600016 |
! 1017 | 507 | 0001700017_update7 |
! 1018 | 108 | 0001800018 |
! 1019 | 609 | 0001900019_update9 |
! 1020 | 100 | 0002000020 |
! 1101 | 201 | aaa |
! 1103 | 503 | ccc_update3 |
! 1104 | 204 | ddd |
! (819 rows)
!
! EXPLAIN (verbose, costs off)
! INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
! Insert on public.ft2
! Output: (tableoid)::regclass
! Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
! -> Result
! Output: 9999, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum
! (5 rows)
!
! INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
! tableoid
! ----------
! ft2
! (1 row)
!
! EXPLAIN (verbose, costs off)
! UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
! Update on public.ft2
! Output: (tableoid)::regclass
! Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1
! -> Foreign Scan on public.ft2
! Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
! Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
! (6 rows)
!
! UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
! tableoid
! ----------
! ft2
! (1 row)
!
! EXPLAIN (verbose, costs off)
! DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
! QUERY PLAN
! ------------------------------------------------------------------------------------
! Delete on public.ft2
! Output: (tableoid)::regclass
! Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
! -> Foreign Scan on public.ft2
! Output: ctid
! Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
! (6 rows)
!
! DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
! tableoid
! ----------
! ft2
! (1 row)
!
! -- Test that trigger on remote table works as expected
! CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
! BEGIN
! NEW.c3 = NEW.c3 || '_trig_update';
! RETURN NEW;
! END;
! $$ LANGUAGE plpgsql;
! CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
! ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
! INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+-----------------+----+----+----+------------+----
! 1208 | 818 | fff_trig_update | | | | ft2 |
! (1 row)
!
! INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+-----------------+----+----+------+------------+----
! 1218 | 818 | ggg_trig_update | | | (--; | ft2 |
! (1 row)
!
! UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+------------------------+------------------------------+--------------------------+----+------------+-----
! 8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 18 | 608 | 00018_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 28 | 608 | 00028_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 38 | 608 | 00038_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 48 | 608 | 00048_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 58 | 608 | 00058_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 68 | 608 | 00068_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 78 | 608 | 00078_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 88 | 608 | 00088_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 98 | 608 | 00098_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 108 | 608 | 00108_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 118 | 608 | 00118_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 128 | 608 | 00128_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 138 | 608 | 00138_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 148 | 608 | 00148_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 158 | 608 | 00158_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 168 | 608 | 00168_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 178 | 608 | 00178_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 188 | 608 | 00188_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 198 | 608 | 00198_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 208 | 608 | 00208_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 218 | 608 | 00218_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 228 | 608 | 00228_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 238 | 608 | 00238_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 248 | 608 | 00248_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 258 | 608 | 00258_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 268 | 608 | 00268_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 278 | 608 | 00278_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 288 | 608 | 00288_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 298 | 608 | 00298_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 308 | 608 | 00308_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 318 | 608 | 00318_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 328 | 608 | 00328_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 338 | 608 | 00338_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 348 | 608 | 00348_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 358 | 608 | 00358_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 368 | 608 | 00368_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 378 | 608 | 00378_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 388 | 608 | 00388_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 398 | 608 | 00398_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 408 | 608 | 00408_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 418 | 608 | 00418_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 428 | 608 | 00428_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 438 | 608 | 00438_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 448 | 608 | 00448_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 458 | 608 | 00458_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 468 | 608 | 00468_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 478 | 608 | 00478_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 488 | 608 | 00488_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 498 | 608 | 00498_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 508 | 608 | 00508_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 518 | 608 | 00518_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 528 | 608 | 00528_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 538 | 608 | 00538_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 548 | 608 | 00548_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 558 | 608 | 00558_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 568 | 608 | 00568_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 578 | 608 | 00578_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 588 | 608 | 00588_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 598 | 608 | 00598_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 608 | 608 | 00608_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 618 | 608 | 00618_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 628 | 608 | 00628_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 638 | 608 | 00638_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 648 | 608 | 00648_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 658 | 608 | 00658_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 668 | 608 | 00668_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 678 | 608 | 00678_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 688 | 608 | 00688_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 698 | 608 | 00698_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 708 | 608 | 00708_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 718 | 608 | 00718_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 728 | 608 | 00728_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 738 | 608 | 00738_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 748 | 608 | 00748_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 758 | 608 | 00758_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 768 | 608 | 00768_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 778 | 608 | 00778_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 788 | 608 | 00788_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 798 | 608 | 00798_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 808 | 608 | 00808_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 818 | 608 | 00818_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 828 | 608 | 00828_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 838 | 608 | 00838_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 848 | 608 | 00848_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 858 | 608 | 00858_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 868 | 608 | 00868_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 878 | 608 | 00878_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 888 | 608 | 00888_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 898 | 608 | 00898_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 908 | 608 | 00908_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
! 918 | 608 | 00918_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
! 928 | 608 | 00928_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
! 938 | 608 | 00938_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
! 948 | 608 | 00948_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
! 958 | 608 | 00958_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
! 968 | 608 | 00968_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
! 978 | 608 | 00978_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
! 988 | 608 | 00988_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
! 998 | 608 | 00998_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
! 1008 | 708 | 0000800008_trig_update | | | | ft2 |
! 1018 | 708 | 0001800018_trig_update | | | | ft2 |
! (102 rows)
!
! -- Test errors thrown on remote side during update
! ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
! INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
! ERROR: duplicate key value violates unique constraint "t1_pkey"
! DETAIL: Key ("C 1")=(11) already exists.
! CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
! INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works
! INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported
! ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
! INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported
! ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
! INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
! ERROR: new row for relation "T 1" violates check constraint "c2positive"
! DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null).
! CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
! UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
! ERROR: new row for relation "T 1" violates check constraint "c2positive"
! DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
! CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
! -- Test savepoint/rollback behavior
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 0 | 100
! 1 | 100
! 4 | 100
! 6 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 0 | 100
! 1 | 100
! 4 | 100
! 6 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! begin;
! update ft2 set c2 = 42 where c2 = 0;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 4 | 100
! 6 | 100
! 42 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! savepoint s1;
! update ft2 set c2 = 44 where c2 = 4;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! release savepoint s1;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! savepoint s2;
! update ft2 set c2 = 46 where c2 = 6;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 42 | 100
! 44 | 100
! 46 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! rollback to savepoint s2;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! release savepoint s2;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! savepoint s3;
! update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
! ERROR: new row for relation "T 1" violates check constraint "c2positive"
! DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo).
! CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
! rollback to savepoint s3;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! release savepoint s3;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! -- none of the above is committed yet remotely
! select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 0 | 100
! 1 | 100
! 4 | 100
! 6 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! commit;
! select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
! c2 | count
! -----+-------
! 1 | 100
! 6 | 100
! 42 | 100
! 44 | 100
! 100 | 2
! 101 | 2
! 104 | 2
! 106 | 2
! 201 | 1
! 204 | 1
! 303 | 100
! 403 | 2
! 407 | 100
! (13 rows)
!
! -- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs
! -- FIRST behavior here.
! -- ORDER BY DESC NULLS LAST options
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS LAST, "C 1" ASC NULLS LAST
! (5 rows)
!
! SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+--------------------+------------------------------+--------------------------+------+------------+-----
! 960 | 42 | 00960_trig_update | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0 | 0 | foo
! 970 | 42 | 00970_trig_update | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0 | 0 | foo
! 980 | 42 | 00980_trig_update | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0 | 0 | foo
! 990 | 42 | 00990_trig_update | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0 | 0 | foo
! 1000 | 42 | 01000_trig_update | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo
! 1218 | 818 | ggg_trig_update | | | (--; | ft2 |
! 1001 | 101 | 0000100001 | | | | ft2 |
! 1003 | 403 | 0000300003_update3 | | | | ft2 |
! 1004 | 104 | 0000400004 | | | | ft2 |
! 1006 | 106 | 0000600006 | | | | ft2 |
! (10 rows)
!
! -- ORDER BY DESC NULLS FIRST options
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS FIRST, "C 1" ASC NULLS LAST
! (5 rows)
!
! SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+-----------------+------------------------------+--------------------------+----+------------+-----
! 1020 | 100 | 0002000020 | | | | ft2 |
! 1101 | 201 | aaa | | | | ft2 |
! 1103 | 503 | ccc_update3 | | | | ft2 |
! 1104 | 204 | ddd | | | | ft2 |
! 1208 | 818 | fff_trig_update | | | | ft2 |
! 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | ft2 | foo
! 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9 | ft2 | foo
! 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9 | ft2 | foo
! 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9 | ft2 | foo
! 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9 | ft2 | foo
! (10 rows)
!
! -- ORDER BY ASC NULLS FIRST options
! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------
! Limit
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! -> Foreign Scan on public.ft1
! Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 ASC NULLS FIRST, "C 1" ASC NULLS LAST
! (5 rows)
!
! SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
! c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
! ------+-----+-------------------+------------------------------+--------------------------+------+------------+-----
! 1020 | 100 | 0002000020 | | | | ft2 |
! 1101 | 201 | aaa | | | | ft2 |
! 1103 | 503 | ccc_update3 | | | | ft2 |
! 1104 | 204 | ddd | | | | ft2 |
! 1208 | 818 | fff_trig_update | | | | ft2 |
! 1218 | 818 | ggg_trig_update | | | (--; | ft2 |
! 10 | 42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo
! 20 | 42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0 | 0 | foo
! 30 | 42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0 | 0 | foo
! 40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo
! (10 rows)
!
! -- ===================================================================
! -- test check constraints
! -- ===================================================================
! -- Consistent check constraints provide consistent results
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
! EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
! QUERY PLAN
! -------------------------------------------------------------------
! Aggregate
! Output: count(*)
! -> Foreign Scan on public.ft1
! Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0))
! (4 rows)
!
! SELECT count(*) FROM ft1 WHERE c2 < 0;
! count
! -------
! 0
! (1 row)
!
! SET constraint_exclusion = 'on';
! EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
! QUERY PLAN
! --------------------------------
! Aggregate
! Output: count(*)
! -> Result
! One-Time Filter: false
! (4 rows)
!
! SELECT count(*) FROM ft1 WHERE c2 < 0;
! count
! -------
! 0
! (1 row)
!
! RESET constraint_exclusion;
! -- check constraint is enforced on the remote side, not locally
! INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
! ERROR: new row for relation "T 1" violates check constraint "c2positive"
! DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null).
! CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
! UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
! ERROR: new row for relation "T 1" violates check constraint "c2positive"
! DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
! CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
! -- But inconsistent check constraints provide inconsistent results
! ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
! EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
! QUERY PLAN
! --------------------------------------------------------------------
! Aggregate
! Output: count(*)
! -> Foreign Scan on public.ft1
! Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 >= 0))
! (4 rows)
!
! SELECT count(*) FROM ft1 WHERE c2 >= 0;
! count
! -------
! 821
! (1 row)
!
! SET constraint_exclusion = 'on';
! EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
! QUERY PLAN
! --------------------------------
! Aggregate
! Output: count(*)
! -> Result
! One-Time Filter: false
! (4 rows)
!
! SELECT count(*) FROM ft1 WHERE c2 >= 0;
! count
! -------
! 0
! (1 row)
!
! RESET constraint_exclusion;
! -- local check constraint is not actually enforced
! INSERT INTO ft1(c1, c2) VALUES(1111, 2);
! UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
! ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
! -- ===================================================================
! -- test serial columns (ie, sequence-based defaults)
! -- ===================================================================
! create table loc1 (f1 serial, f2 text);
! create foreign table rem1 (f1 serial, f2 text)
! server loopback options(table_name 'loc1');
! select pg_catalog.setval('rem1_f1_seq', 10, false);
! setval
! --------
! 10
! (1 row)
!
! insert into loc1(f2) values('hi');
! insert into rem1(f2) values('hi remote');
! insert into loc1(f2) values('bye');
! insert into rem1(f2) values('bye remote');
! select * from loc1;
! f1 | f2
! ----+------------
! 1 | hi
! 10 | hi remote
! 2 | bye
! 11 | bye remote
! (4 rows)
!
! select * from rem1;
! f1 | f2
! ----+------------
! 1 | hi
! 10 | hi remote
! 2 | bye
! 11 | bye remote
! (4 rows)
!
! -- ===================================================================
! -- test local triggers
! -- ===================================================================
! -- Trigger functions "borrowed" from triggers regress test.
! CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$
! BEGIN
! RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %',
! TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
! RETURN NULL;
! END;$$;
! CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
! CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1
! FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
! CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger
! LANGUAGE plpgsql AS $$
!
! declare
! oldnew text[];
! relid text;
! argstr text;
! begin
!
! relid := TG_relid::regclass;
! argstr := '';
! for i in 0 .. TG_nargs - 1 loop
! if i > 0 then
! argstr := argstr || ', ';
! end if;
! argstr := argstr || TG_argv[i];
! end loop;
!
! RAISE NOTICE '%(%) % % % ON %',
! tg_name, argstr, TG_when, TG_level, TG_OP, relid;
! oldnew := '{}'::text[];
! if TG_OP != 'INSERT' then
! oldnew := array_append(oldnew, format('OLD: %s', OLD));
! end if;
!
! if TG_OP != 'DELETE' then
! oldnew := array_append(oldnew, format('NEW: %s', NEW));
! end if;
!
! RAISE NOTICE '%', array_to_string(oldnew, ',');
!
! if TG_OP = 'DELETE' then
! return OLD;
! else
! return NEW;
! end if;
! end;
! $$;
! -- Test basic functionality
! CREATE TRIGGER trig_row_before
! BEFORE INSERT OR UPDATE OR DELETE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
! CREATE TRIGGER trig_row_after
! AFTER INSERT OR UPDATE OR DELETE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
! delete from rem1;
! NOTICE: trigger_func(<NULL>) called: action = DELETE, when = BEFORE, level = STATEMENT
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
! NOTICE: OLD: (1,hi)
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
! NOTICE: OLD: (10,"hi remote")
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
! NOTICE: OLD: (2,bye)
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
! NOTICE: OLD: (11,"bye remote")
! NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
! NOTICE: OLD: (1,hi)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
! NOTICE: OLD: (10,"hi remote")
! NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
! NOTICE: OLD: (2,bye)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
! NOTICE: OLD: (11,"bye remote")
! NOTICE: trigger_func(<NULL>) called: action = DELETE, when = AFTER, level = STATEMENT
! insert into rem1 values(1,'insert');
! NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
! NOTICE: NEW: (1,insert)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
! NOTICE: NEW: (1,insert)
! NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
! update rem1 set f2 = 'update' where f1 = 1;
! NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = BEFORE, level = STATEMENT
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
! NOTICE: OLD: (1,insert),NEW: (1,update)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
! NOTICE: OLD: (1,insert),NEW: (1,update)
! NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = AFTER, level = STATEMENT
! update rem1 set f2 = f2 || f2;
! NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = BEFORE, level = STATEMENT
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
! NOTICE: OLD: (1,update),NEW: (1,updateupdate)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
! NOTICE: OLD: (1,update),NEW: (1,updateupdate)
! NOTICE: trigger_func(<NULL>) called: action = UPDATE, when = AFTER, level = STATEMENT
! -- cleanup
! DROP TRIGGER trig_row_before ON rem1;
! DROP TRIGGER trig_row_after ON rem1;
! DROP TRIGGER trig_stmt_before ON rem1;
! DROP TRIGGER trig_stmt_after ON rem1;
! DELETE from rem1;
! -- Test WHEN conditions
! CREATE TRIGGER trig_row_before_insupd
! BEFORE INSERT OR UPDATE ON rem1
! FOR EACH ROW
! WHEN (NEW.f2 like '%update%')
! EXECUTE PROCEDURE trigger_data(23,'skidoo');
! CREATE TRIGGER trig_row_after_insupd
! AFTER INSERT OR UPDATE ON rem1
! FOR EACH ROW
! WHEN (NEW.f2 like '%update%')
! EXECUTE PROCEDURE trigger_data(23,'skidoo');
! -- Insert or update not matching: nothing happens
! INSERT INTO rem1 values(1, 'insert');
! UPDATE rem1 set f2 = 'test';
! -- Insert or update matching: triggers are fired
! INSERT INTO rem1 values(2, 'update');
! NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON rem1
! NOTICE: NEW: (2,update)
! NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON rem1
! NOTICE: NEW: (2,update)
! UPDATE rem1 set f2 = 'update update' where f1 = '2';
! NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON rem1
! NOTICE: OLD: (2,update),NEW: (2,"update update")
! NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON rem1
! NOTICE: OLD: (2,update),NEW: (2,"update update")
! CREATE TRIGGER trig_row_before_delete
! BEFORE DELETE ON rem1
! FOR EACH ROW
! WHEN (OLD.f2 like '%update%')
! EXECUTE PROCEDURE trigger_data(23,'skidoo');
! CREATE TRIGGER trig_row_after_delete
! AFTER DELETE ON rem1
! FOR EACH ROW
! WHEN (OLD.f2 like '%update%')
! EXECUTE PROCEDURE trigger_data(23,'skidoo');
! -- Trigger is fired for f1=2, not for f1=1
! DELETE FROM rem1;
! NOTICE: trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON rem1
! NOTICE: OLD: (2,"update update")
! NOTICE: trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON rem1
! NOTICE: OLD: (2,"update update")
! -- cleanup
! DROP TRIGGER trig_row_before_insupd ON rem1;
! DROP TRIGGER trig_row_after_insupd ON rem1;
! DROP TRIGGER trig_row_before_delete ON rem1;
! DROP TRIGGER trig_row_after_delete ON rem1;
! -- Test various RETURN statements in BEFORE triggers.
! CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$
! BEGIN
! NEW.f2 := NEW.f2 || ' triggered !';
! RETURN NEW;
! END
! $$ language plpgsql;
! CREATE TRIGGER trig_row_before_insupd
! BEFORE INSERT OR UPDATE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
! -- The new values should have 'triggered' appended
! INSERT INTO rem1 values(1, 'insert');
! SELECT * from loc1;
! f1 | f2
! ----+--------------------
! 1 | insert triggered !
! (1 row)
!
! INSERT INTO rem1 values(2, 'insert') RETURNING f2;
! f2
! --------------------
! insert triggered !
! (1 row)
!
! SELECT * from loc1;
! f1 | f2
! ----+--------------------
! 1 | insert triggered !
! 2 | insert triggered !
! (2 rows)
!
! UPDATE rem1 set f2 = '';
! SELECT * from loc1;
! f1 | f2
! ----+--------------
! 1 | triggered !
! 2 | triggered !
! (2 rows)
!
! UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
! f2
! --------------------
! skidoo triggered !
! skidoo triggered !
! (2 rows)
!
! SELECT * from loc1;
! f1 | f2
! ----+--------------------
! 1 | skidoo triggered !
! 2 | skidoo triggered !
! (2 rows)
!
! DELETE FROM rem1;
! -- Add a second trigger, to check that the changes are propagated correctly
! -- from trigger to trigger
! CREATE TRIGGER trig_row_before_insupd2
! BEFORE INSERT OR UPDATE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
! INSERT INTO rem1 values(1, 'insert');
! SELECT * from loc1;
! f1 | f2
! ----+--------------------------------
! 1 | insert triggered ! triggered !
! (1 row)
!
! INSERT INTO rem1 values(2, 'insert') RETURNING f2;
! f2
! --------------------------------
! insert triggered ! triggered !
! (1 row)
!
! SELECT * from loc1;
! f1 | f2
! ----+--------------------------------
! 1 | insert triggered ! triggered !
! 2 | insert triggered ! triggered !
! (2 rows)
!
! UPDATE rem1 set f2 = '';
! SELECT * from loc1;
! f1 | f2
! ----+--------------------------
! 1 | triggered ! triggered !
! 2 | triggered ! triggered !
! (2 rows)
!
! UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
! f2
! --------------------------------
! skidoo triggered ! triggered !
! skidoo triggered ! triggered !
! (2 rows)
!
! SELECT * from loc1;
! f1 | f2
! ----+--------------------------------
! 1 | skidoo triggered ! triggered !
! 2 | skidoo triggered ! triggered !
! (2 rows)
!
! DROP TRIGGER trig_row_before_insupd ON rem1;
! DROP TRIGGER trig_row_before_insupd2 ON rem1;
! DELETE from rem1;
! INSERT INTO rem1 VALUES (1, 'test');
! -- Test with a trigger returning NULL
! CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$
! BEGIN
! RETURN NULL;
! END
! $$ language plpgsql;
! CREATE TRIGGER trig_null
! BEFORE INSERT OR UPDATE OR DELETE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trig_null();
! -- Nothing should have changed.
! INSERT INTO rem1 VALUES (2, 'test2');
! SELECT * from loc1;
! f1 | f2
! ----+------
! 1 | test
! (1 row)
!
! UPDATE rem1 SET f2 = 'test2';
! SELECT * from loc1;
! f1 | f2
! ----+------
! 1 | test
! (1 row)
!
! DELETE from rem1;
! SELECT * from loc1;
! f1 | f2
! ----+------
! 1 | test
! (1 row)
!
! DROP TRIGGER trig_null ON rem1;
! DELETE from rem1;
! -- Test a combination of local and remote triggers
! CREATE TRIGGER trig_row_before
! BEFORE INSERT OR UPDATE OR DELETE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
! CREATE TRIGGER trig_row_after
! AFTER INSERT OR UPDATE OR DELETE ON rem1
! FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
! CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1
! FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
! INSERT INTO rem1(f2) VALUES ('test');
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
! NOTICE: NEW: (12,test)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
! NOTICE: NEW: (12,"test triggered !")
! UPDATE rem1 SET f2 = 'testo';
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
! NOTICE: OLD: (12,"test triggered !"),NEW: (12,testo)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
! NOTICE: OLD: (12,"test triggered !"),NEW: (12,"testo triggered !")
! -- Test returning a system attribute
! INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
! NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
! NOTICE: NEW: (13,test)
! NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
! NOTICE: NEW: (13,"test triggered !")
! ctid
! --------
! (0,27)
! (1 row)
!
! -- ===================================================================
! -- test inheritance features
! -- ===================================================================
! CREATE TABLE a (aa TEXT);
! CREATE TABLE loct (aa TEXT, bb TEXT);
! CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
! SERVER loopback OPTIONS (table_name 'loct');
! INSERT INTO a(aa) VALUES('aaa');
! INSERT INTO a(aa) VALUES('aaaa');
! INSERT INTO a(aa) VALUES('aaaaa');
! INSERT INTO b(aa) VALUES('bbb');
! INSERT INTO b(aa) VALUES('bbbb');
! INSERT INTO b(aa) VALUES('bbbbb');
! SELECT tableoid::regclass, * FROM a;
! tableoid | aa
! ----------+-------
! a | aaa
! a | aaaa
! a | aaaaa
! b | bbb
! b | bbbb
! b | bbbbb
! (6 rows)
!
! SELECT tableoid::regclass, * FROM b;
! tableoid | aa | bb
! ----------+-------+----
! b | bbb |
! b | bbbb |
! b | bbbbb |
! (3 rows)
!
! SELECT tableoid::regclass, * FROM ONLY a;
! tableoid | aa
! ----------+-------
! a | aaa
! a | aaaa
! a | aaaaa
! (3 rows)
!
! UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%';
! SELECT tableoid::regclass, * FROM a;
! tableoid | aa
! ----------+--------
! a | aaa
! a | zzzzzz
! a | zzzzzz
! b | bbb
! b | bbbb
! b | bbbbb
! (6 rows)
!
! SELECT tableoid::regclass, * FROM b;
! tableoid | aa | bb
! ----------+-------+----
! b | bbb |
! b | bbbb |
! b | bbbbb |
! (3 rows)
!
! SELECT tableoid::regclass, * FROM ONLY a;
! tableoid | aa
! ----------+--------
! a | aaa
! a | zzzzzz
! a | zzzzzz
! (3 rows)
!
! UPDATE b SET aa = 'new';
! SELECT tableoid::regclass, * FROM a;
! tableoid | aa
! ----------+--------
! a | aaa
! a | zzzzzz
! a | zzzzzz
! b | new
! b | new
! b | new
! (6 rows)
!
! SELECT tableoid::regclass, * FROM b;
! tableoid | aa | bb
! ----------+-----+----
! b | new |
! b | new |
! b | new |
! (3 rows)
!
! SELECT tableoid::regclass, * FROM ONLY a;
! tableoid | aa
! ----------+--------
! a | aaa
! a | zzzzzz
! a | zzzzzz
! (3 rows)
!
! UPDATE a SET aa = 'newtoo';
! SELECT tableoid::regclass, * FROM a;
! tableoid | aa
! ----------+--------
! a | newtoo
! a | newtoo
! a | newtoo
! b | newtoo
! b | newtoo
! b | newtoo
! (6 rows)
!
! SELECT tableoid::regclass, * FROM b;
! tableoid | aa | bb
! ----------+--------+----
! b | newtoo |
! b | newtoo |
! b | newtoo |
! (3 rows)
!
! SELECT tableoid::regclass, * FROM ONLY a;
! tableoid | aa
! ----------+--------
! a | newtoo
! a | newtoo
! a | newtoo
! (3 rows)
!
! DELETE FROM a;
! SELECT tableoid::regclass, * FROM a;
! tableoid | aa
! ----------+----
! (0 rows)
!
! SELECT tableoid::regclass, * FROM b;
! tableoid | aa | bb
! ----------+----+----
! (0 rows)
!
! SELECT tableoid::regclass, * FROM ONLY a;
! tableoid | aa
! ----------+----
! (0 rows)
!
! DROP TABLE a CASCADE;
! NOTICE: drop cascades to foreign table b
! DROP TABLE loct;
! -- Check SELECT FOR UPDATE/SHARE with an inherited source table
! create table loct1 (f1 int, f2 int, f3 int);
! create table loct2 (f1 int, f2 int, f3 int);
! create table foo (f1 int, f2 int);
! create foreign table foo2 (f3 int) inherits (foo)
! server loopback options (table_name 'loct1');
! create table bar (f1 int, f2 int);
! create foreign table bar2 (f3 int) inherits (bar)
! server loopback options (table_name 'loct2');
! insert into foo values(1,1);
! insert into foo values(3,3);
! insert into foo2 values(2,2,2);
! insert into foo2 values(4,4,4);
! insert into bar values(1,11);
! insert into bar values(2,22);
! insert into bar values(6,66);
! insert into bar2 values(3,33,33);
! insert into bar2 values(4,44,44);
! insert into bar2 values(7,77,77);
! explain (verbose, costs off)
! select * from bar where f1 in (select f1 from foo) for update;
! QUERY PLAN
! ----------------------------------------------------------------------------------------------
! LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
! -> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
! Hash Cond: (bar.f1 = foo.f1)
! -> Append
! -> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
! -> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
! -> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! Group Key: foo.f1
! -> Append
! -> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
! (22 rows)
!
! select * from bar where f1 in (select f1 from foo) for update;
! f1 | f2
! ----+----
! 1 | 11
! 2 | 22
! 3 | 33
! 4 | 44
! (4 rows)
!
! explain (verbose, costs off)
! select * from bar where f1 in (select f1 from foo) for share;
! QUERY PLAN
! ----------------------------------------------------------------------------------------------
! LockRows
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
! -> Hash Join
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid, foo.ctid, foo.*, foo.tableoid
! Hash Cond: (bar.f1 = foo.f1)
! -> Append
! -> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
! -> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
! -> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! Group Key: foo.f1
! -> Append
! -> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
! (22 rows)
!
! select * from bar where f1 in (select f1 from foo) for share;
! f1 | f2
! ----+----
! 1 | 11
! 2 | 22
! 3 | 33
! 4 | 44
! (4 rows)
!
! -- Check UPDATE with inherited target and an inherited source table
! explain (verbose, costs off)
! update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Update on public.bar
! Update on public.bar
! Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
! -> Hash Join
! Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
! Hash Cond: (bar.f1 = foo.f1)
! -> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid
! -> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! Group Key: foo.f1
! -> Append
! -> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
! -> Hash Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid
! Hash Cond: (bar2.f1 = foo.f1)
! -> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
! -> Hash
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> HashAggregate
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! Group Key: foo.f1
! -> Append
! -> Seq Scan on public.foo
! Output: foo.ctid, foo.*, foo.tableoid, foo.f1
! -> Foreign Scan on public.foo2
! Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
! (37 rows)
!
! update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
! select tableoid::regclass, * from bar order by 1,2;
! tableoid | f1 | f2
! ----------+----+-----
! bar | 1 | 111
! bar | 2 | 122
! bar | 6 | 66
! bar2 | 3 | 133
! bar2 | 4 | 144
! bar2 | 7 | 77
! (6 rows)
!
! -- Check UPDATE with inherited target and an appendrel subquery
! explain (verbose, costs off)
! update bar set f2 = f2 + 100
! from
! ( select f1 from foo union all select f1+3 from foo ) ss
! where bar.f1 = ss.f1;
! QUERY PLAN
! --------------------------------------------------------------------------------------
! Update on public.bar
! Update on public.bar
! Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
! -> Hash Join
! Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
! Hash Cond: (foo.f1 = bar.f1)
! -> Append
! -> Seq Scan on public.foo
! Output: ROW(foo.f1), foo.f1
! -> Foreign Scan on public.foo2
! Output: ROW(foo2.f1), foo2.f1
! Remote SQL: SELECT f1 FROM public.loct1
! -> Seq Scan on public.foo foo_1
! Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
! -> Foreign Scan on public.foo2 foo2_1
! Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
! Remote SQL: SELECT f1 FROM public.loct1
! -> Hash
! Output: bar.f1, bar.f2, bar.ctid
! -> Seq Scan on public.bar
! Output: bar.f1, bar.f2, bar.ctid
! -> Merge Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
! Merge Cond: (bar2.f1 = foo.f1)
! -> Sort
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
! Sort Key: bar2.f1
! -> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
! -> Sort
! Output: (ROW(foo.f1)), foo.f1
! Sort Key: foo.f1
! -> Append
! -> Seq Scan on public.foo
! Output: ROW(foo.f1), foo.f1
! -> Foreign Scan on public.foo2
! Output: ROW(foo2.f1), foo2.f1
! Remote SQL: SELECT f1 FROM public.loct1
! -> Seq Scan on public.foo foo_1
! Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
! -> Foreign Scan on public.foo2 foo2_1
! Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
! Remote SQL: SELECT f1 FROM public.loct1
! (45 rows)
!
! update bar set f2 = f2 + 100
! from
! ( select f1 from foo union all select f1+3 from foo ) ss
! where bar.f1 = ss.f1;
! select tableoid::regclass, * from bar order by 1,2;
! tableoid | f1 | f2
! ----------+----+-----
! bar | 1 | 211
! bar | 2 | 222
! bar | 6 | 166
! bar2 | 3 | 233
! bar2 | 4 | 244
! bar2 | 7 | 177
! (6 rows)
!
! -- Test forcing the remote server to produce sorted data for a merge join,
! -- but the foreign table is an inheritance child.
! truncate table loct1;
! truncate table only foo;
! \set num_rows_foo 2000
! insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2);
! insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2);
! SET enable_hashjoin to false;
! SET enable_nestloop to false;
! alter foreign table foo2 options (use_remote_estimate 'true');
! create index i_loct1_f1 on loct1(f1);
! create index i_foo_f1 on foo(f1);
! analyze foo;
! analyze loct1;
! -- inner join; expressions in the clauses appear in the equivalence class list
! explain (verbose, costs off)
! select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------
! Limit
! Output: foo.f1, loct1.f1, foo.f2
! -> Sort
! Output: foo.f1, loct1.f1, foo.f2
! Sort Key: foo.f2
! -> Merge Join
! Output: foo.f1, loct1.f1, foo.f2
! Merge Cond: (foo.f1 = loct1.f1)
! -> Merge Append
! Sort Key: foo.f1
! -> Index Scan using i_foo_f1 on public.foo
! Output: foo.f1, foo.f2
! -> Foreign Scan on public.foo2
! Output: foo2.f1, foo2.f2
! Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST
! -> Index Only Scan using i_loct1_f1 on public.loct1
! Output: loct1.f1
! (17 rows)
!
! select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
! f1 | f1
! ----+----
! 20 | 20
! 22 | 22
! 24 | 24
! 26 | 26
! 28 | 28
! 30 | 30
! 32 | 32
! 34 | 34
! 36 | 36
! 38 | 38
! (10 rows)
!
! -- outer join; expressions in the clauses do not appear in equivalence class
! -- list but no output change as compared to the previous query
! explain (verbose, costs off)
! select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------
! Limit
! Output: foo.f1, loct1.f1, foo.f2
! -> Sort
! Output: foo.f1, loct1.f1, foo.f2
! Sort Key: foo.f2
! -> Merge Left Join
! Output: foo.f1, loct1.f1, foo.f2
! Merge Cond: (foo.f1 = loct1.f1)
! -> Merge Append
! Sort Key: foo.f1
! -> Index Scan using i_foo_f1 on public.foo
! Output: foo.f1, foo.f2
! -> Foreign Scan on public.foo2
! Output: foo2.f1, foo2.f2
! Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST
! -> Index Only Scan using i_loct1_f1 on public.loct1
! Output: loct1.f1
! (17 rows)
!
! select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
! f1 | f1
! ----+----
! 10 | 10
! 11 |
! 12 | 12
! 13 |
! 14 | 14
! 15 |
! 16 | 16
! 17 |
! 18 | 18
! 19 |
! (10 rows)
!
! RESET enable_hashjoin;
! RESET enable_nestloop;
! -- Test that WHERE CURRENT OF is not supported
! begin;
! declare c cursor for select * from bar where f1 = 7;
! fetch from c;
! f1 | f2
! ----+-----
! 7 | 177
! (1 row)
!
! update bar set f2 = null where current of c;
! ERROR: WHERE CURRENT OF is not supported for this table type
! rollback;
! drop table foo cascade;
! NOTICE: drop cascades to foreign table foo2
! drop table bar cascade;
! NOTICE: drop cascades to foreign table bar2
! drop table loct1;
! drop table loct2;
! -- ===================================================================
! -- test IMPORT FOREIGN SCHEMA
! -- ===================================================================
! CREATE SCHEMA import_source;
! CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL);
! CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX");
! CREATE TYPE typ1 AS (m1 int, m2 varchar);
! CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1);
! CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42));
! CREATE TABLE import_source."x 5" (c1 float8);
! ALTER TABLE import_source."x 5" DROP COLUMN c1;
! CREATE SCHEMA import_dest1;
! IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1;
! \det+ import_dest1.*
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------------+-------+----------+-------------------------------------------------+-------------
! import_dest1 | t1 | loopback | (schema_name 'import_source', table_name 't1') |
! import_dest1 | t2 | loopback | (schema_name 'import_source', table_name 't2') |
! import_dest1 | t3 | loopback | (schema_name 'import_source', table_name 't3') |
! import_dest1 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') |
! import_dest1 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') |
! (5 rows)
!
! \d import_dest1.*
! Foreign table "import_dest1.t1"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+-----------+--------------------
! c1 | integer | | (column_name 'c1')
! c2 | character varying | not null | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't1')
!
! Foreign table "import_dest1.t2"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+---------------+--------------------
! c1 | integer | | (column_name 'c1')
! c2 | character varying | | (column_name 'c2')
! c3 | text | collate POSIX | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't2')
!
! Foreign table "import_dest1.t3"
! Column | Type | Modifiers | FDW Options
! --------+--------------------------+-----------+--------------------
! c1 | timestamp with time zone | | (column_name 'c1')
! c2 | typ1 | | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't3')
!
! Foreign table "import_dest1.x 4"
! Column | Type | Modifiers | FDW Options
! --------+-----------------------+-----------+---------------------
! c1 | double precision | | (column_name 'c1')
! C 2 | text | | (column_name 'C 2')
! c3 | character varying(42) | | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 4')
!
! Foreign table "import_dest1.x 5"
! Column | Type | Modifiers | FDW Options
! --------+------+-----------+-------------
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 5')
!
! -- Options
! CREATE SCHEMA import_dest2;
! IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2
! OPTIONS (import_default 'true');
! \det+ import_dest2.*
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------------+-------+----------+-------------------------------------------------+-------------
! import_dest2 | t1 | loopback | (schema_name 'import_source', table_name 't1') |
! import_dest2 | t2 | loopback | (schema_name 'import_source', table_name 't2') |
! import_dest2 | t3 | loopback | (schema_name 'import_source', table_name 't3') |
! import_dest2 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') |
! import_dest2 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') |
! (5 rows)
!
! \d import_dest2.*
! Foreign table "import_dest2.t1"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+-----------+--------------------
! c1 | integer | | (column_name 'c1')
! c2 | character varying | not null | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't1')
!
! Foreign table "import_dest2.t2"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+---------------+--------------------
! c1 | integer | default 42 | (column_name 'c1')
! c2 | character varying | | (column_name 'c2')
! c3 | text | collate POSIX | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't2')
!
! Foreign table "import_dest2.t3"
! Column | Type | Modifiers | FDW Options
! --------+--------------------------+---------------+--------------------
! c1 | timestamp with time zone | default now() | (column_name 'c1')
! c2 | typ1 | | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't3')
!
! Foreign table "import_dest2.x 4"
! Column | Type | Modifiers | FDW Options
! --------+-----------------------+-----------+---------------------
! c1 | double precision | | (column_name 'c1')
! C 2 | text | | (column_name 'C 2')
! c3 | character varying(42) | | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 4')
!
! Foreign table "import_dest2.x 5"
! Column | Type | Modifiers | FDW Options
! --------+------+-----------+-------------
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 5')
!
! CREATE SCHEMA import_dest3;
! IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3
! OPTIONS (import_collate 'false', import_not_null 'false');
! \det+ import_dest3.*
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------------+-------+----------+-------------------------------------------------+-------------
! import_dest3 | t1 | loopback | (schema_name 'import_source', table_name 't1') |
! import_dest3 | t2 | loopback | (schema_name 'import_source', table_name 't2') |
! import_dest3 | t3 | loopback | (schema_name 'import_source', table_name 't3') |
! import_dest3 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') |
! import_dest3 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') |
! (5 rows)
!
! \d import_dest3.*
! Foreign table "import_dest3.t1"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+-----------+--------------------
! c1 | integer | | (column_name 'c1')
! c2 | character varying | | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't1')
!
! Foreign table "import_dest3.t2"
! Column | Type | Modifiers | FDW Options
! --------+-------------------+-----------+--------------------
! c1 | integer | | (column_name 'c1')
! c2 | character varying | | (column_name 'c2')
! c3 | text | | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't2')
!
! Foreign table "import_dest3.t3"
! Column | Type | Modifiers | FDW Options
! --------+--------------------------+-----------+--------------------
! c1 | timestamp with time zone | | (column_name 'c1')
! c2 | typ1 | | (column_name 'c2')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 't3')
!
! Foreign table "import_dest3.x 4"
! Column | Type | Modifiers | FDW Options
! --------+-----------------------+-----------+---------------------
! c1 | double precision | | (column_name 'c1')
! C 2 | text | | (column_name 'C 2')
! c3 | character varying(42) | | (column_name 'c3')
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 4')
!
! Foreign table "import_dest3.x 5"
! Column | Type | Modifiers | FDW Options
! --------+------+-----------+-------------
! Server: loopback
! FDW Options: (schema_name 'import_source', table_name 'x 5')
!
! -- Check LIMIT TO and EXCEPT
! CREATE SCHEMA import_dest4;
! IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch)
! FROM SERVER loopback INTO import_dest4;
! \det+ import_dest4.*
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------------+-------+----------+------------------------------------------------+-------------
! import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') |
! (1 row)
!
! IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch)
! FROM SERVER loopback INTO import_dest4;
! \det+ import_dest4.*
! List of foreign tables
! Schema | Table | Server | FDW Options | Description
! --------------+-------+----------+-------------------------------------------------+-------------
! import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') |
! import_dest4 | t2 | loopback | (schema_name 'import_source', table_name 't2') |
! import_dest4 | t3 | loopback | (schema_name 'import_source', table_name 't3') |
! import_dest4 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') |
! (4 rows)
!
! -- Assorted error cases
! IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4;
! ERROR: relation "t1" already exists
! CONTEXT: importing foreign table "t1"
! IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4;
! ERROR: schema "nonesuch" is not present on foreign server "loopback"
! IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere;
! ERROR: schema "notthere" does not exist
! IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere;
! ERROR: server "nowhere" does not exist
! -- Check case of a type present only on the remote server.
! -- We can fake this by dropping the type locally in our transaction.
! CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue');
! CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors");
! CREATE SCHEMA import_dest5;
! BEGIN;
! DROP TYPE "Colors" CASCADE;
! NOTICE: drop cascades to table import_source.t5 column Col
! IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
! FROM SERVER loopback INTO import_dest5; -- ERROR
! ERROR: type "public.Colors" does not exist
! LINE 4: "Col" public."Colors" OPTIONS (column_name 'Col')
! ^
! QUERY: CREATE FOREIGN TABLE t5 (
! c1 integer OPTIONS (column_name 'c1'),
! c2 text OPTIONS (column_name 'c2') COLLATE pg_catalog."C",
! "Col" public."Colors" OPTIONS (column_name 'Col')
! ) SERVER loopback
! OPTIONS (schema_name 'import_source', table_name 't5');
! CONTEXT: importing foreign table "t5"
! ROLLBACK;
! BEGIN;
! CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' );
! SELECT count(*)
! FROM pg_foreign_server
! WHERE srvname = 'fetch101'
! AND srvoptions @> array['fetch_size=101'];
! count
! -------
! 1
! (1 row)
!
! ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' );
! SELECT count(*)
! FROM pg_foreign_server
! WHERE srvname = 'fetch101'
! AND srvoptions @> array['fetch_size=101'];
! count
! -------
! 0
! (1 row)
!
! SELECT count(*)
! FROM pg_foreign_server
! WHERE srvname = 'fetch101'
! AND srvoptions @> array['fetch_size=202'];
! count
! -------
! 1
! (1 row)
!
! CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' );
! SELECT COUNT(*)
! FROM pg_foreign_table
! WHERE ftrelid = 'table30000'::regclass
! AND ftoptions @> array['fetch_size=30000'];
! count
! -------
! 1
! (1 row)
!
! ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000');
! SELECT COUNT(*)
! FROM pg_foreign_table
! WHERE ftrelid = 'table30000'::regclass
! AND ftoptions @> array['fetch_size=30000'];
! count
! -------
! 0
! (1 row)
!
! SELECT COUNT(*)
! FROM pg_foreign_table
! WHERE ftrelid = 'table30000'::regclass
! AND ftoptions @> array['fetch_size=60000'];
! count
! -------
! 1
! (1 row)
!
! ROLLBACK;
! -- Cleanup
! DROP OWNED BY view_owner;
! DROP USER view_owner;
--- 187,200 ----
-- To exercise multiple code paths, we use local stats on ft1
-- and remote-estimate mode on ft2.
ANALYZE ft1;
+ ERROR: cache lookup failed for foreign server 1609863456
ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
-- ===================================================================
-- simple queries
-- ===================================================================
-- single table without alias
EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
tablefunc.diffstext/plain; charset=UTF-8; name=tablefunc.diffsDownload
*** /home/vpopov/Projects/pwdtest.new/postgresql/contrib/tablefunc/expected/tablefunc.out 2016-03-15 14:32:12.945978099 +0300
--- /home/vpopov/Projects/pwdtest.new/postgresql/contrib/tablefunc/results/tablefunc.out 2016-03-15 16:58:19.297568781 +0300
***************
*** 4,427 ****
-- no easy way to do this for regression testing
--
SELECT avg(normal_rand)::int FROM normal_rand(100, 250, 0.2);
! avg
! -----
! 250
! (1 row)
!
! --
! -- crosstab()
! --
! CREATE TABLE ct(id int, rowclass text, rowid text, attribute text, val text);
! \copy ct from 'data/ct.data'
! SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;');
! row_name | category_1 | category_2
! ----------+------------+------------
! test1 | val2 | val3
! test2 | val6 | val7
! | val10 | val11
! (3 rows)
!
! SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3
! ----------+------------+------------+------------
! test1 | val2 | val3 |
! test2 | val6 | val7 |
! | val10 | val11 |
! (3 rows)
!
! SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3 | category_4
! ----------+------------+------------+------------+------------
! test1 | val2 | val3 | |
! test2 | val6 | val7 | |
! | val10 | val11 | |
! (3 rows)
!
! SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
! row_name | category_1 | category_2
! ----------+------------+------------
! test1 | val1 | val2
! test2 | val5 | val6
! | val9 | val10
! (3 rows)
!
! SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3
! ----------+------------+------------+------------
! test1 | val1 | val2 | val3
! test2 | val5 | val6 | val7
! | val9 | val10 | val11
! (3 rows)
!
! SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3 | category_4
! ----------+------------+------------+------------+------------
! test1 | val1 | val2 | val3 | val4
! test2 | val5 | val6 | val7 | val8
! | val9 | val10 | val11 | val12
! (3 rows)
!
! SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;');
! row_name | category_1 | category_2
! ----------+------------+------------
! test3 | val1 | val2
! test4 | val4 | val5
! (2 rows)
!
! SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3
! ----------+------------+------------+------------
! test3 | val1 | val2 |
! test4 | val4 | val5 |
! (2 rows)
!
! SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3 | category_4
! ----------+------------+------------+------------+------------
! test3 | val1 | val2 | |
! test4 | val4 | val5 | |
! (2 rows)
!
! SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;');
! row_name | category_1 | category_2
! ----------+------------+------------
! test3 | val1 | val2
! test4 | val4 | val5
! (2 rows)
!
! SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3
! ----------+------------+------------+------------
! test3 | val1 | val2 | val3
! test4 | val4 | val5 | val6
! (2 rows)
!
! SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;');
! row_name | category_1 | category_2 | category_3 | category_4
! ----------+------------+------------+------------+------------
! test3 | val1 | val2 | val3 |
! test4 | val4 | val5 | val6 |
! (2 rows)
!
! SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text);
! rowid | att1 | att2
! -------+------+-------
! test1 | val1 | val2
! test2 | val5 | val6
! | val9 | val10
! (3 rows)
!
! SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text);
! rowid | att1 | att2 | att3
! -------+------+-------+-------
! test1 | val1 | val2 | val3
! test2 | val5 | val6 | val7
! | val9 | val10 | val11
! (3 rows)
!
! SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text);
! rowid | att1 | att2 | att3 | att4
! -------+------+-------+-------+-------
! test1 | val1 | val2 | val3 | val4
! test2 | val5 | val6 | val7 | val8
! | val9 | val10 | val11 | val12
! (3 rows)
!
! -- check it works with OUT parameters, too
! CREATE FUNCTION crosstab_out(text,
! OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text)
! RETURNS setof record
! AS '$libdir/tablefunc','crosstab'
! LANGUAGE C STABLE STRICT;
! SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
! rowid | att1 | att2 | att3
! -------+------+-------+-------
! test1 | val1 | val2 | val3
! test2 | val5 | val6 | val7
! | val9 | val10 | val11
! (3 rows)
!
! --
! -- hash based crosstab
! --
! create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text);
! insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42');
! insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS');
! -- the next line is intentionally left commented and is therefore a "missing" attribute
! -- insert into cth values(DEFAULT,'test1','01 March 2003','test_startdate','28 February 2003');
! insert into cth values(DEFAULT,'test1','01 March 2003','volts','2.6987');
! insert into cth values(DEFAULT,'test2','02 March 2003','temperature','53');
! insert into cth values(DEFAULT,'test2','02 March 2003','test_result','FAIL');
! insert into cth values(DEFAULT,'test2','02 March 2003','test_startdate','01 March 2003');
! insert into cth values(DEFAULT,'test2','02 March 2003','volts','3.1234');
! -- next group tests for NULL rowids
! insert into cth values(DEFAULT,NULL,'25 October 2007','temperature','57');
! insert into cth values(DEFAULT,NULL,'25 October 2007','test_result','PASS');
! insert into cth values(DEFAULT,NULL,'25 October 2007','test_startdate','24 October 2007');
! insert into cth values(DEFAULT,NULL,'25 October 2007','volts','1.41234');
! -- return attributes as plain text
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text);
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+--------------------------+-------------+-------------+-----------------+---------
! test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
! test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | 01 March 2003 | 3.1234
! | Thu Oct 25 00:00:00 2007 | 57 | PASS | 24 October 2007 | 1.41234
! (3 rows)
!
! -- this time without rowdt
! SELECT * FROM crosstab(
! 'SELECT rowid, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1')
! AS c(rowid text, temperature text, test_result text, test_startdate text, volts text);
! rowid | temperature | test_result | test_startdate | volts
! -------+-------------+-------------+-----------------+---------
! test1 | 42 | PASS | | 2.6987
! test2 | 53 | FAIL | 01 March 2003 | 3.1234
! | 57 | PASS | 24 October 2007 | 1.41234
! (3 rows)
!
! -- convert attributes to specific datatypes
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+--------------------------+-------------+-------------+--------------------------+---------
! test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
! test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
! | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234
! (3 rows)
!
! -- source query and category query out of sync
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth WHERE attribute IN (''temperature'',''test_result'',''test_startdate'') ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp);
! rowid | rowdt | temperature | test_result | test_startdate
! -------+--------------------------+-------------+-------------+--------------------------
! test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS |
! test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003
! | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007
! (3 rows)
!
! -- if category query generates no rows, get expected error
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
! ERROR: provided "categories" SQL must return 1 column of at least one row
! -- if category query generates more than one column, get expected error
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2')
! AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
! ERROR: provided "categories" SQL must return 1 column of at least one row
! -- if source query returns zero rows, get zero rows returned
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text);
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+-------+-------------+-------------+----------------+-------
! (0 rows)
!
! -- if source query returns zero rows, get zero rows returned even if category query generates no rows
! SELECT * FROM crosstab(
! 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1')
! AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text);
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+-------+-------------+-------------+----------------+-------
! (0 rows)
!
! -- check it works with a named result rowtype
! create type my_crosstab_result as (
! rowid text, rowdt timestamp,
! temperature int4, test_result text, test_startdate timestamp, volts float8);
! CREATE FUNCTION crosstab_named(text, text)
! RETURNS setof my_crosstab_result
! AS '$libdir/tablefunc','crosstab_hash'
! LANGUAGE C STABLE STRICT;
! SELECT * FROM crosstab_named(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+--------------------------+-------------+-------------+--------------------------+---------
! test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
! test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
! | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234
! (3 rows)
!
! -- check it works with OUT parameters
! CREATE FUNCTION crosstab_out(text, text,
! OUT rowid text, OUT rowdt timestamp,
! OUT temperature int4, OUT test_result text,
! OUT test_startdate timestamp, OUT volts float8)
! RETURNS setof record
! AS '$libdir/tablefunc','crosstab_hash'
! LANGUAGE C STABLE STRICT;
! SELECT * FROM crosstab_out(
! 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
! 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
! rowid | rowdt | temperature | test_result | test_startdate | volts
! -------+--------------------------+-------------+-------------+--------------------------+---------
! test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
! test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
! | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234
! (3 rows)
!
! --
! -- connectby
! --
! -- test connectby with text based hierarchy
! CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int);
! \copy connectby_text from 'data/connectby_text.data'
! -- with branch, without orderby
! SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text);
! keyid | parent_keyid | level | branch
! -------+--------------+-------+---------------------
! row2 | | 0 | row2
! row4 | row2 | 1 | row2~row4
! row6 | row4 | 2 | row2~row4~row6
! row8 | row6 | 3 | row2~row4~row6~row8
! row5 | row2 | 1 | row2~row5
! row9 | row5 | 2 | row2~row5~row9
! (6 rows)
!
! -- without branch, without orderby
! SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int);
! keyid | parent_keyid | level
! -------+--------------+-------
! row2 | | 0
! row4 | row2 | 1
! row6 | row4 | 2
! row8 | row6 | 3
! row5 | row2 | 1
! row9 | row5 | 2
! (6 rows)
!
! -- with branch, with orderby
! SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos;
! keyid | parent_keyid | level | branch | pos
! -------+--------------+-------+---------------------+-----
! row2 | | 0 | row2 | 1
! row5 | row2 | 1 | row2~row5 | 2
! row9 | row5 | 2 | row2~row5~row9 | 3
! row4 | row2 | 1 | row2~row4 | 4
! row6 | row4 | 2 | row2~row4~row6 | 5
! row8 | row6 | 3 | row2~row4~row6~row8 | 6
! (6 rows)
!
! -- without branch, with orderby
! SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos;
! keyid | parent_keyid | level | pos
! -------+--------------+-------+-----
! row2 | | 0 | 1
! row5 | row2 | 1 | 2
! row9 | row5 | 2 | 3
! row4 | row2 | 1 | 4
! row6 | row4 | 2 | 5
! row8 | row6 | 3 | 6
! (6 rows)
!
! -- test connectby with int based hierarchy
! CREATE TABLE connectby_int(keyid int, parent_keyid int);
! \copy connectby_int from 'data/connectby_int.data'
! -- with branch
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text);
! keyid | parent_keyid | level | branch
! -------+--------------+-------+---------
! 2 | | 0 | 2
! 4 | 2 | 1 | 2~4
! 6 | 4 | 2 | 2~4~6
! 8 | 6 | 3 | 2~4~6~8
! 5 | 2 | 1 | 2~5
! 9 | 5 | 2 | 2~5~9
! (6 rows)
!
! -- without branch
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
! keyid | parent_keyid | level
! -------+--------------+-------
! 2 | | 0
! 4 | 2 | 1
! 6 | 4 | 2
! 8 | 6 | 3
! 5 | 2 | 1
! 9 | 5 | 2
! (6 rows)
!
! -- recursion detection
! INSERT INTO connectby_int VALUES(10,9);
! INSERT INTO connectby_int VALUES(11,10);
! INSERT INTO connectby_int VALUES(9,11);
! -- should fail due to infinite recursion
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text);
! ERROR: infinite recursion detected
! -- infinite recursion failure avoided by depth limit
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') AS t(keyid int, parent_keyid int, level int, branch text);
! keyid | parent_keyid | level | branch
! -------+--------------+-------+-------------
! 2 | | 0 | 2
! 4 | 2 | 1 | 2~4
! 6 | 4 | 2 | 2~4~6
! 8 | 6 | 3 | 2~4~6~8
! 5 | 2 | 1 | 2~5
! 9 | 5 | 2 | 2~5~9
! 10 | 9 | 3 | 2~5~9~10
! 11 | 10 | 4 | 2~5~9~10~11
! (8 rows)
!
! -- should fail as first two columns must have the same type
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text);
! ERROR: invalid return type
! DETAIL: First two columns must be the same type.
! -- should fail as key field datatype should match return datatype
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text);
! ERROR: invalid return type
! DETAIL: SQL key field type double precision does not match return key field type integer.
! -- tests for values using custom queries
! -- query with one column - failed
! SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
! ERROR: invalid return type
! DETAIL: Query must return at least two columns.
! -- query with two columns first value as NULL
! SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
! keyid | parent_keyid | level
! -------+--------------+-------
! 2 | | 0
! | 1 | 1
! (2 rows)
!
! -- query with two columns second value as NULL
! SELECT * FROM connectby('connectby_int', '1::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
! ERROR: infinite recursion detected
! -- query with two columns, both values as NULL
! SELECT * FROM connectby('connectby_int', 'NULL::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
! keyid | parent_keyid | level
! -------+--------------+-------
! 2 | | 0
! | | 1
! (2 rows)
!
! -- test for falsely detected recursion
! DROP TABLE connectby_int;
! CREATE TABLE connectby_int(keyid int, parent_keyid int);
! INSERT INTO connectby_int VALUES(11,NULL);
! INSERT INTO connectby_int VALUES(10,11);
! INSERT INTO connectby_int VALUES(111,11);
! INSERT INTO connectby_int VALUES(1,111);
! -- this should not fail due to recursion detection
! SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '11', 0, '-') AS t(keyid int, parent_keyid int, level int, branch text);
! keyid | parent_keyid | level | branch
! -------+--------------+-------+----------
! 11 | | 0 | 11
! 10 | 11 | 1 | 11-10
! 111 | 11 | 1 | 11-111
! 1 | 111 | 2 | 11-111-1
! (4 rows)
!
--- 4,10 ----
-- no easy way to do this for regression testing
--
SELECT avg(normal_rand)::int FROM normal_rand(100, 250, 0.2);
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
======================================================================
On Tue, Mar 15, 2016 at 3:46 PM, Valery Popov wrote:
make installcheck-world failed on several contrib modules:
dblink, file_fdw, hstore, pgcrypto, pgstattuple, postgres_fdw, tablefunc.
The tests results are attached.
Documentation looks good.
Where may be a problem with make check-world and make installcheck-world
results?
I cannot reproduce this, and my guess is that the binaries of those
contrib/ modules are not up to date for the installed instance of
Postgres you are running the tests on. Particularly I find this
portion doubtful:
SELECT avg(normal_rand)::int FROM normal_rand(100, 250, 0.2);
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
The set of patches I am proposing here does not go through those code
paths, and this is likely an aggregate failure.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Michael,
On 3/14/16 7:07 PM, Michael Paquier wrote:
On Mon, Mar 14, 2016 at 5:06 PM, Michael Paquier <michael.paquier@gmail.com> wrote:
On Mon, Mar 14, 2016 at 4:32 PM, David Steele <david@pgmasters.net> wrote:
Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.And here they are. I have addressed the documentation and the
whitespaces reported up to now at the same time.
For this first review I would like to focus on the user visible changes
introduced in 0001-0002.
First I created two new users with each type of supported verifier:
postgres=# create user test with password 'test';
CREATE ROLE
postgres=# create user testu with unencrypted password 'testu'
valid until '2017-01-01';
CREATE ROLE
1) I see that rolvaliduntil is still in pg_authid:
postgres=# select oid, rolname, rolvaliduntil from pg_authid;
oid | rolname | rolvaliduntil
-------+---------+------------------------
10 | vagrant |
16387 | test |
16388 | testu | 2017-01-01 00:00:00+00
I think that's OK if we now define it to be "role validity" (it's still
password validity in the patched docs). I would also like to see a
validuntil column in pg_auth_verifiers so we can track password
expiration for each verifier separately. For now I think it's enough to
copy the same validity both places since there can only be one verifier.
2) I don't think the column naming in pg_auth_verifiers is consistent
with other catalogs:
postgres=# select * from pg_auth_verifiers;
roleid | verimet | verival
--------+---------+-------------------------------------
16387 | m | md505a671c66aefea124cc08b76ea6d30bb
16388 | p | testu
System catalogs generally use a 3 character prefix so I would expect the
columns to be (if we pick avr as a prefix):
avrrole
avrmethod
avrverifier
avrvaliduntil
I'm not a big fan in abbreviating too much so you can see I've expanded
the names a bit.
3) rolpassword is still in pg_shadow even though it is not useful anymore:
postgres=# select usename, passwd, valuntil from pg_shadow;
usename | passwd | valuntil
---------+----------+------------------------
vagrant | ******** |
test | ******** |
testu | ******** | 2017-01-01 00:00:00+00
If anyone is actually using this column in a meaningful way they are in
for a nasty surprise when trying use the value in passwd as a verifier.
I would prefer to drop the column entirely and produce a clear error.
Perhaps a better option would be to drop pg_shadow entirely since it
seems to have no further purpose in life.
Thanks,
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi!
On 03/15/2016 06:59 PM, Michael Paquier wrote:
The set of patches I am proposing here does not go through those code
paths, and this is likely an aggregate failure.
Michael, you were right. It was incorrect installation of contrib binaries.
Now all tests pass OK, both check-world and installcheck-world,
Thanks.
--
Regards,
Valery Popov
Postgres Professional http://www.postgrespro.com
The Russian Postgres Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 15, 2016 at 6:38 PM, David Steele <david@pgmasters.net> wrote:
Hi Michael,
On 3/14/16 7:07 PM, Michael Paquier wrote:
On Mon, Mar 14, 2016 at 5:06 PM, Michael Paquier <michael.paquier@gmail.com> wrote:
On Mon, Mar 14, 2016 at 4:32 PM, David Steele <david@pgmasters.net> wrote:
Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.And here they are. I have addressed the documentation and the
whitespaces reported up to now at the same time.For this first review I would like to focus on the user visible changes
introduced in 0001-0002.
Thanks for the input!
1) I see that rolvaliduntil is still in pg_authid:
I think that's OK if we now define it to be "role validity" (it's still
password validity in the patched docs). I would also like to see a
validuntil column in pg_auth_verifiers so we can track password
expiration for each verifier separately. For now I think it's enough to
copy the same validity both places since there can only be one verifier.
FWIW, this is an intentional change, and my goal is to focus on only
the protocol aging for now. We will need to move rolvaliduntil to
pg_auth_verifiers if we want to allow rolling updates of password
verifiers for a given role, but that's a different patch, and we need
to think about the SQL interface carefully. This infrastructure makes
the move easier by the way to do that, and honestly I don't really see
what we gain now by copying the same value to two different system
catalogs.
2) I don't think the column naming in pg_auth_verifiers is consistent
with other catalogs:
postgres=# select * from pg_auth_verifiers;
roleid | verimet | verival
--------+---------+-------------------------------------
16387 | m | md505a671c66aefea124cc08b76ea6d30bb
16388 | p | testuSystem catalogs generally use a 3 character prefix so I would expect the
columns to be (if we pick avr as a prefix):
OK, this makes sense.
avrrole
avrmethod
avrverifier
Assuming "ver" is the prefix, we get: verroleid, vermethod, vervalue.
I kind of like those ones, more than with "avr" as prefix actually.
Other ideas are of course welcome.
I'm not a big fan in abbreviating too much so you can see I've expanded
the names a bit.
Sure.
3) rolpassword is still in pg_shadow even though it is not useful anymore:
postgres=# select usename, passwd, valuntil from pg_shadow;usename | passwd | valuntil
---------+----------+------------------------
vagrant | ******** |
test | ******** |
testu | ******** | 2017-01-01 00:00:00+00If anyone is actually using this column in a meaningful way they are in
for a nasty surprise when trying use the value in passwd as a verifier.
I would prefer to drop the column entirely and produce a clear error.Perhaps a better option would be to drop pg_shadow entirely since it
seems to have no further purpose in life.
We discussed that on the previous thread and the conclusion was to
keep pg_shadow, but to clobber the password value with "***",
explaining this choice:
/messages/by-id/6174.1455501497@sss.pgh.pa.us
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 3/16/16 9:00 AM, Michael Paquier wrote:
On Tue, Mar 15, 2016 at 6:38 PM, David Steele <david@pgmasters.net> wrote:
1) I see that rolvaliduntil is still in pg_authid:
I think that's OK if we now define it to be "role validity" (it's still
password validity in the patched docs). I would also like to see a
validuntil column in pg_auth_verifiers so we can track password
expiration for each verifier separately. For now I think it's enough to
copy the same validity both places since there can only be one verifier.FWIW, this is an intentional change, and my goal is to focus on only
the protocol aging for now. We will need to move rolvaliduntil to
pg_auth_verifiers if we want to allow rolling updates of password
verifiers for a given role, but that's a different patch, and we need
to think about the SQL interface carefully. This infrastructure makes
the move easier by the way to do that, and honestly I don't really see
what we gain now by copying the same value to two different system
catalogs.
Here's my thinking. If validuntil is moved to pg_auth_verifiers now
then people can start using it there. That will make it less traumatic
when/if validuntil in pg_authid is removed later. The field in
pg_authid could be deprecated in this release to let people know not to
use it.
Or, as I suggested it could be recast as role validity, which right now
happens to be the same as password validity.
2) I don't think the column naming in pg_auth_verifiers is consistent
with other catalogs:
postgres=# select * from pg_auth_verifiers;
roleid | verimet | verival
--------+---------+-------------------------------------
16387 | m | md505a671c66aefea124cc08b76ea6d30bb
16388 | p | testuSystem catalogs generally use a 3 character prefix so I would expect the
columns to be (if we pick avr as a prefix):OK, this makes sense.
avrrole
avrmethod
avrverifierAssuming "ver" is the prefix, we get: verroleid, vermethod, vervalue.
I kind of like those ones, more than with "avr" as prefix actually.
Other ideas are of course welcome.
ver is fine as a prefix.
3) rolpassword is still in pg_shadow even though it is not useful anymore:
postgres=# select usename, passwd, valuntil from pg_shadow;usename | passwd | valuntil
---------+----------+------------------------
vagrant | ******** |
test | ******** |
testu | ******** | 2017-01-01 00:00:00+00If anyone is actually using this column in a meaningful way they are in
for a nasty surprise when trying use the value in passwd as a verifier.
I would prefer to drop the column entirely and produce a clear error.Perhaps a better option would be to drop pg_shadow entirely since it
seems to have no further purpose in life.We discussed that on the previous thread and the conclusion was to
keep pg_shadow, but to clobber the password value with "***",
explaining this choice:
/messages/by-id/6174.1455501497@sss.pgh.pa.us
Ah, I missed that one.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Michael,
On 3/14/16 7:07 PM, Michael Paquier wrote:
On Mon, Mar 14, 2016 at 5:06 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Mon, Mar 14, 2016 at 4:32 PM, David Steele <david@pgmasters.net> wrote:
Could you provide an updated set of patches for review? Meanwhile I am
marking this as "waiting for author".Sure. I'll provide them shortly with all the comments addressed. Up to
now I just had a couple of comments about docs and whitespaces, so I
didn't really bother sending a new set, but this meritates a rebase.And here they are. I have addressed the documentation and the
whitespaces reported up to now at the same time.
Here's my full review of this patch set.
First let me thank you for submitting this patch for the current CF. I
feel a bit guilty that I requested it and am only now posting a full
review. In my defense I can only say that being CFM has been rather
more work than I was expecting, but I'm sure you know the feeling.
* [PATCH 1/9] Add facility to store multiple password verifiers
This is a pretty big patch but I went through it carefully and found
nothing to complain about. Your attention to detail is impressive as
always.
Be sure to update the column names for pg_auth_verifiers as we discussed
in [1]/messages/by-id/CAB7nPqSGm-9c4yFULt4GS9TzoSuz8XbO-K7TGGGw08sztfG2Uw@mail.gmail.com.
* [PATCH 2/9] Introduce password_protocols
diff --git a/src/test/regress/expected/password.out
b/src/test/regress/expected/password.out
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
So that makes sense but you get the same result if you do:
postgres=# alter user role_passwd5 password 'foo';
ERROR: specified password protocol not allowed
DETAIL: List of authorized protocols is specified by password_protocols.
I don't think this makes sense - if I have explicitly set
password_protocols to 'plain' and I don't specify a verifier for alter
user then it seems like it should work. If nothing else the error
message lacks information needed to identify the problem.
* [PATCH 3/9] Add pg_auth_verifiers_sanitize
This function is just a little scary but since password_protocols
defaults to 'plain,md5' I can live with it.
* [PATCH 4/9] Remove password verifiers for unsupported protocols in
pg_upgrade
Same as above - it will always be important for password_protocols to
default to *all* protocols to avoid data being dropped during the
pg_upgrade by accident. You've done that here (and later in the SCRAM
patch) so I'm satisfied but it bears watching.
What I would do is add some extra comments in the GUC code to make it
clear to always update the default when adding new verifiers.
* [PATCH 5/9] Move sha1.c to src/common
This looks fine to me and is a good reuse of code.
* [PATCH 6/9] Refactor sendAuthRequest
I tested this across different client versions and it seems to work fine.
* [PATCH 7/9] Refactor RandomSalt to handle salts of different lengths
A simple enough refactor.
* [PATCH 8/9] Move encoding routines to src/common/
A bit surprising that these functions were never used by any front end code.
* Subject: [PATCH 9/9] SCRAM authentication
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
@@ -1616,18 +1619,34 @@ FlattenPasswordIdentifiers(List *verifiers, char
*rolname)
* instances of Postgres, an md5 hash passed as a plain verifier
* should still be treated as an MD5 entry.
*/
- if (spec->veriftype == AUTH_VERIFIER_MD5 &&
- !isMD5(spec->value))
+ switch (spec->veriftype)
{
- char encrypted_passwd[MD5_PASSWD_LEN + 1];
- if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname),
- encrypted_passwd))
- elog(ERROR, "password encryption failed");
- spec->value = pstrdup(encrypted_passwd);
+ case AUTH_VERIFIER_MD5:
It seems like this case statement should have been introduced in patch
0001. Were you just trying to avoid churn in the code unless SCRAM is
committed?
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
+
+static char *
+read_attr_value(char **input, char attr)
+{
Numerous functions like the above in auth-scram.c do not have comments.
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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;
+ }
Why is that? Is it because gss auth should be expected in this case or
some limitation of SCRAM? Anyway, it wasn't clear to me why this would
be true so some comments here would be good.
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen);
+}
Same in scram-common.c WRT comments.
diff --git a/src/include/common/scram-common.h
b/src/include/common/scram-common.h
+extern void scram_ClientOrServerKey(const char *password, const char
*salt, int saltlen, int iterations, const char *keystr, uint8 *result);
My, that's a very long line!
* A few general things:
Most of the new scram modules are seriously in need of better comments -
I pointed out a few but all the new files suffer from this lack.
The strings "plain", "md5", and "scram" are used often enough that I
think it would be nice if they were constants. I feel the same way
about verifier methods 'm', 'p', 's' -- perhaps more so because they
aren't very verbose.
It looks like this will need a bit of work if the GSSAPI patch goes in
(and vice versa). Not a problem but you'll need to be prepared to do
that quickly in the event - time is flying.
--
-David
david@pgmasters.net
[1]: /messages/by-id/CAB7nPqSGm-9c4yFULt4GS9TzoSuz8XbO-K7TGGGw08sztfG2Uw@mail.gmail.com
/messages/by-id/CAB7nPqSGm-9c4yFULt4GS9TzoSuz8XbO-K7TGGGw08sztfG2Uw@mail.gmail.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Mar 18, 2016 at 3:16 AM, David Steele <david@pgmasters.net> wrote:
Here's my full review of this patch set.
Thanks!
First let me thank you for submitting this patch for the current CF. I
feel a bit guilty that I requested it and am only now posting a full
review. In my defense I can only say that being CFM has been rather
more work than I was expecting, but I'm sure you know the feeling.
I get the idea. That's a very draining activity and I can see what you
are doing. That's impressive. Really.
* [PATCH 1/9] Add facility to store multiple password verifiers
This is a pretty big patch but I went through it carefully and found
nothing to complain about. Your attention to detail is impressive as
always.Be sure to update the column names for pg_auth_verifiers as we discussed
in [1].
Done. I have added as well the block of 0009 you pointed out into this
patch for clarity.
* [PATCH 2/9] Introduce password_protocols
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out +SET password_protocols = 'plain'; +ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok +ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error +ERROR: specified password protocol not allowed +DETAIL: List of authorized protocols is specified by password_protocols.So that makes sense but you get the same result if you do:
postgres=# alter user role_passwd5 password 'foo';
ERROR: specified password protocol not allowed
DETAIL: List of authorized protocols is specified by password_protocols.I don't think this makes sense - if I have explicitly set
password_protocols to 'plain' and I don't specify a verifier for alter
user then it seems like it should work. If nothing else the error
message lacks information needed to identify the problem.
Hm. The problem here is the interaction between the new
password_protocols and the existing password_encryption.
password_protocols involves that password_encryption should not
contain elements not listed in it, in short password_protocols @>
password_encryption. So I think that the GUC callbacks checking the
validity of those parameter values should check that each other are
not set to incorrect values. One thing to simplify those validity
checks would be to make password_protocols a PGC_POSTMASTER, aka it
needs a restart to be updated. This sacrifices a large portion of the
regression tests though... Do others have thoughts to share? I have
not updated the patch yet, and I would personally let both parameters
as they are now, aka password_protocols as PGC_SUSET and
password_encryption as PGC_USERSET, and check their validity when they
are updated, but I am not alone here (hopefully).
* [PATCH 3/9] Add pg_auth_verifiers_sanitize
This function is just a little scary but since password_protocols
defaults to 'plain,md5' I can live with it.
Another thing that I thought about was to integrate as part of
pg_upgrade_support part. That's no big deal to do it this way as well,
though I thought that it could be useful for admins. So extra ideas
are welcome. That's superuser-only anyway... And a critical part to
manage old protocol deprecation.
* [PATCH 4/9] Remove password verifiers for unsupported protocols in
pg_upgradeSame as above - it will always be important for password_protocols to
default to *all* protocols to avoid data being dropped during the
pg_upgrade by accident. You've done that here (and later in the SCRAM
patch) so I'm satisfied but it bears watching.
We could have an extra keyword like "all" to all mapping to all the
existing protocols, but I find listing the protocols explicitly a more
verbose and simple concept, that's why I chose that.
What I would do is add some extra comments in the GUC code to make it
clear to always update the default when adding new verifiers.
Good idea.
* [PATCH 5/9] Move sha1.c to src/common
This looks fine to me and is a good reuse of code.
Yes.
* [PATCH 6/9] Refactor sendAuthRequest
I tested this across different client versions and it seems to work fine.
OK, cool!
* [PATCH 7/9] Refactor RandomSalt to handle salts of different lengths
A simple enough refactor.
That's something we should do as an independent change I think.
* [PATCH 8/9] Move encoding routines to src/common/
A bit surprising that these functions were never used by any front end code.
Perhaps there are some client tools that copy-paste it. I cannot be
sure. At least it seems to me that this is useful enough as an
independent change.
* Subject: [PATCH 9/9] SCRAM authentication
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c @@ -1616,18 +1619,34 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname) * instances of Postgres, an md5 hash passed as a plain verifier * should still be treated as an MD5 entry. */ - if (spec->veriftype == AUTH_VERIFIER_MD5 && - !isMD5(spec->value)) + switch (spec->veriftype) { - char encrypted_passwd[MD5_PASSWD_LEN + 1]; - if (!pg_md5_encrypt(spec->value, rolname, strlen(rolname), - encrypted_passwd)) - elog(ERROR, "password encryption failed"); - spec->value = pstrdup(encrypted_passwd); + case AUTH_VERIFIER_MD5:It seems like this case statement should have been introduced in patch
0001. Were you just trying to avoid churn in the code unless SCRAM is
committed?
Yeah, right. I have now plugged this portion into 0001.
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c + +static char * +read_attr_value(char **input, char attr) +{Numerous functions like the above in auth-scram.c do not have comments.
Noted. I have done nothing on that yet though :) And I am lowering the
priority for 0009 in this CF to keep focus on the core machinery
instead, as well as other patches that need feedback.
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c + else if (strcmp(token->string, "scram") == 0) + { + 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; + }Why is that? Is it because gss auth should be expected in this case or
some limitation of SCRAM? Anyway, it wasn't clear to me why this would
be true so some comments here would be good.
The username is part of the identifier used as part of the protocol,
so we cannot rely on mappings of db_user_namespace.
diff --git a/src/common/scram-common.c b/src/common/scram-common.c +void +scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen) +{ + SHA1Update(&ctx->sha1ctx, (const uint8 *) str, slen); +}Same in scram-common.c WRT comments.
OK, noted. I have not updated those comments yet though. At this stage
of the game considering 0009 for integration is a rather difficult
task, and I suspect enough work with the underlying patches. For 9.6,
I would be happy enough if we got the basic infra in core.
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h +extern void scram_ClientOrServerKey(const char *password, const char *salt, int saltlen, int iterations, const char *keystr, uint8 *result);My, that's a very long line!
Oops. Sorry.
* A few general things:
Most of the new scram modules are seriously in need of better comments -
I pointed out a few but all the new files suffer from this lack.
Indeed. Honestly, as you say, time flies, and by the time of the
feature freeze I am thinking that the only sane target for the CF
would be to focus on 0001~0004. That's the basic infrastructure I
think we need anyway. 0005~0008 are things that I think are useful
taken independently and are simple refactoring, so they could be
considered with the time frame we have. 0009 is a bit too complex. I
expect enough comments on the first patches to keep my time busy until
the end of this CF without that, that's still useful for testing by
the way.
The strings "plain", "md5", and "scram" are used often enough that I
think it would be nice if they were constants.
This makes sense. So I switched the code this way. Note that for md5 I
think that it makes sense to use a #define variable when referring to
the verifier method, not when referring to the prefix of a md5
verifier. Those full names are added in pg_auth_verifiers.h.
I feel the same way
about verifier methods 'm', 'p', 's' -- perhaps more so because they
aren't very verbose.
I am thinking of the verifier abbreviations in the system catalog in a
way similar to pg_class' relkind, explaining the one-character
identifier, so I wish letting them as-is.
It looks like this will need a bit of work if the GSSAPI patch goes in
(and vice versa). Not a problem but you'll need to be prepared to do
that quickly in the event - time is flying.
That's not an issue for me to rebase this set of patches. The only
conflicts that I anticipate are on 0009, but I don't have high hopes
to get this portion integrating into core for 9.6, the rest of the
patches is complicated enough, and everyone bandwidth is limited.
--
Michael
Attachments:
0001-Add-facility-to-store-multiple-password-verifiers.patchbinary/octet-stream; name=0001-Add-facility-to-store-multiple-password-verifiers.patchDownload
From 664348a3afc0d35e227ec43a5558a87e53fe7333 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 18 Mar 2016 22:15:46 +0900
Subject: [PATCH 1/9] Add facility to store multiple password verifiers
This commit adds a new cluster-wide catalog table called pg_auth_verifiers
extending the existing one-password value per role approach into a facility
able to store multiple passwords formats for one user. This makes easier to
add additional password format support in the future and is a requirement
for the additional of SCRAM-SHA1.
CREATE ROLE and ALTER ROLE are extended with PASSWORD VERIFIERS that allow
a user to set a list of password identifiers at will, something particularly
useful for pg_dump that makes use of it with this commit.
password_encryption is transformed into a list able to use "md5" or "plain",
or even both when CREATE/ALTER ROLE uses neither ENCRYPTED/UNENCRYPTED.
pg_shadown, which had up to now the concept of storing the password plain
or md5 value is now clobbered. Users and client applications are advised to
switch to pg_auth_verifiers instead.
The password check hook has been redesigned to be able to check a list
of passwords instead of a single entry, and the related contrib module
passwordcheck/ is updated respecting the new format.
Extra facility for protocol aging and upgrades will be added in a different
commit.
Regression tests and documentation are added accordingly.
---
contrib/passwordcheck/passwordcheck.c | 138 ++++++-----
doc/src/sgml/catalogs.sgml | 103 +++++---
doc/src/sgml/config.sgml | 17 +-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/catalog.c | 4 +
src/backend/catalog/system_views.sql | 2 +-
src/backend/commands/user.c | 332 +++++++++++++++++---------
src/backend/libpq/crypt.c | 72 ++++--
src/backend/nodes/copyfuncs.c | 14 ++
src/backend/nodes/equalfuncs.c | 12 +
src/backend/parser/gram.y | 98 +++++++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 23 +-
src/backend/utils/cache/syscache.c | 23 ++
src/backend/utils/misc/guc.c | 66 ++++-
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/bin/initdb/initdb.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 77 +++++-
src/include/catalog/indexing.h | 5 +
src/include/catalog/pg_auth_verifiers.h | 68 ++++++
src/include/catalog/pg_authid.h | 8 +-
src/include/commands/user.h | 11 +-
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 11 +
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/password.out | 104 ++++++++
src/test/regress/expected/roleattributes.out | 192 +++++++--------
src/test/regress/expected/rules.out | 2 +-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 72 ++++++
34 files changed, 1130 insertions(+), 367 deletions(-)
create mode 100644 src/include/catalog/pg_auth_verifiers.h
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index b4c1ce0..13ad053 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,11 @@
#include <crack.h>
#endif
+#include "catalog/pg_auth_verifiers.h"
#include "commands/user.h"
#include "fmgr.h"
#include "libpq/md5.h"
+#include "nodes/parsenodes.h"
PG_MODULE_MAGIC;
@@ -50,87 +52,93 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ List *passwordVerifiers,
Datum validuntil_time,
bool validuntil_null)
{
int namelen = strlen(username);
- int pwdlen = strlen(password);
+ int pwdlen;
char encrypted[MD5_PASSWD_LEN + 1];
int i;
bool pwd_has_letter,
pwd_has_nonletter;
+ ListCell *l;
- switch (password_type)
+ foreach(l, passwordVerifiers)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
- /*
- * For unencrypted passwords we can perform better checks
- */
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ switch (spec->veriftype)
+ {
+ case AUTH_VERIFIER_MD5:
+
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ if (!pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ if (strcmp(spec->value, encrypted) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ break;
+
+ case AUTH_VERIFIER_PLAIN:
+
/*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
+ * For unencrypted passwords we can perform better checks
*/
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must contain both letters and nonletters")));
+ pwdlen = strlen(spec->value);
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(spec->value, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
+ /*
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
+ */
+ if (isalpha((unsigned char) spec->value[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(spec->value, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
+ break;
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
+ default:
+ elog(ERROR, "unrecognized password type: %d", spec->veriftype);
+ break;
+ }
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 951f59b..cee0c42 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1161,13 +1161,6 @@
</para>
<para>
- Since this catalog contains passwords, it must not be publicly readable.
- <link linkend="view-pg-roles"><structname>pg_roles</structname></link>
- is a publicly readable view on
- <structname>pg_authid</structname> that blanks out the password field.
- </para>
-
- <para>
<xref linkend="user-manag"> contains detailed information about user and
privilege management.
</para>
@@ -1270,21 +1263,6 @@
</row>
<row>
- <entry><structfield>rolpassword</structfield></entry>
- <entry><type>text</type></entry>
- <entry>
- Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
- </entry>
- </row>
-
- <row>
<entry><structfield>rolvaliduntil</structfield></entry>
<entry><type>timestamptz</type></entry>
<entry>Password expiry time (only used for password authentication);
@@ -1296,6 +1274,77 @@
</sect1>
+ <sect1 id="catalog-pg-auth-verifiers">
+ <title><structname>pg_auth_verifiers</structname></title>
+
+ <indexterm zone="catalog-pg-auth-verifiers">
+ <primary>pg_auth_verifiers</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_auth_verifiers</structname> contains password
+ information for database authorization identifiers (roles).
+ </para>
+
+ <para>
+ Since this catalog contains passwords, it must not be publicly readable.
+ </para>
+
+ <para>
+ Because user identities are cluster-wide,
+ <structname>pg_auth_verifiers</structname>
+ is shared across all databases of a cluster: there is only one
+ copy of <structname>pg_auth_verifiers</structname> per cluster, not
+ one per database.
+ </para>
+
+ <table>
+ <title><structname>pg_auth_verifiers</> Columns</title>
+
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>oid</structfield></entry>
+ <entry><type>verroleid</type></entry>
+ <entry>Role identifier OID</entry>
+ </row>
+
+ <row>
+ <entry><structfield>vermethod</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry>
+ <literal>p</> = plain format,
+ <literal>m</> = MD5-encrypted
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>text</structfield></entry>
+ <entry><type>vervalue</type></entry>
+ <entry>
+ Password (possibly encrypted with format defined in
+ <structfield>vermethod</>). If the password
+ is MD5-encrypted, this column will begin with the string <literal>md5</>
+ followed by a 32-character hexadecimal MD5 hash. The MD5 hash
+ will be of the user's password concatenated to their user name.
+ For example, if user <literal>joe</> has password <literal>xyzzy</>,
+ <productname>PostgreSQL</> will store the md5 hash of
+ <literal>xyzzyjoe</>.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
<sect1 id="catalog-pg-auth-members">
<title><structname>pg_auth_members</structname></title>
@@ -9299,9 +9348,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry><structfield>passwd</structfield></entry>
<entry><type>text</type></entry>
<entry></entry>
- <entry>Password (possibly encrypted); null if none. See
- <link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>
- for details of how encrypted passwords are stored.</entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
@@ -9771,9 +9818,9 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</row>
<row>
- <entry><structfield>passwd</structfield></entry>
- <entry><type>text</type></entry>
- <entry>Not the password (always reads as <literal>********</>)</entry>
+ <entry><structfield>passwd</structfield></entry>
+ <entry><type>text</type></entry>
+ <entry>Not the password (always reads as <literal>********</>)</entry>
</row>
<row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7695ec1..80fc479 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,20 +1163,29 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>string</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
+ Specifies a comma-separated list of password encryption formats.
+ Supported formats are <literal>plain</> and <literal>md5</>.
+ </para>
+
+ <para>
When a password is specified in <xref
linkend="sql-createuser"> or
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
- <literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ <literal>UNENCRYPTED</>, this parameter determines the list of
+ encryption formats this password is to be stored as.
+ </para>
+
+ <para>
+ The default is <literal>md5</> (encrypt the password with MD5
+ encryption).
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..d58431f 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD VERIFIERS ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' [, ...] )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -211,9 +212,9 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
roles having the <literal>LOGIN</literal> attribute, but you
can nonetheless define one for roles without it.) If you do
not plan to use password authentication you can omit this
- option. If no password is specified, the password will be set
- to null and password authentication will always fail for that
- user. A null password can optionally be written explicitly as
+ option. If no password is specified, no password will be set
+ and password authentication will always fail for that user.
+ A null password can optionally be written explicitly as
<literal>PASSWORD NULL</literal>.
</para>
</listitem>
@@ -245,6 +246,22 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD VERIFIERS</literal> ( <replaceable class="PARAMETER">verifier_type</replaceable> = '<replaceable class="PARAMETER">password</replaceable>' )</term>
+ <listitem>
+ <para>
+ Sets the list of password verifiers for the role. Currently only
+ <literal>md5</>, for MD5-encrypted format, and <literal>plain</>
+ for unencrypted format, can be specified as verifier format type
+ for <literal>verifier_type</>. If the password defined with
+ <literal>plain</> type is already in MD5-encrypted format
+ the password will be stored as MD5-encrypted. If the password defined
+ is in plain-format and that <literal>verifier_type</> is set
+ to <literal>md5</>, the password will be stored as MD5-encrypted.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..2e695b8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -35,8 +35,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
- pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
+ pg_auth_verifiers.h pg_authid.h pg_auth_members.h pg_shdepend.h \
+ pg_shdescription.h pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index bead2c1..7406d2f 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -27,6 +27,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -218,6 +219,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthVerifRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == PLTemplateRelationId ||
@@ -231,6 +233,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes (see indexing.h) */
if (relationId == AuthIdRolnameIndexId ||
relationId == AuthIdOidIndexId ||
+ relationId == AuthVerifRoleMethodIndexId ||
+ relationId == AuthVerifMethodRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == DatabaseNameIndexId ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fef67bd..f1a719e 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,7 +40,7 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ '********'::text as passwd,
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 4baeaa2..e8f23e6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -21,9 +21,11 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#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"
@@ -42,9 +44,6 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
-extern bool Password_encryption;
-
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -54,6 +53,10 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static void FlattenPasswordIdentifiers(List *verifiers, char *rolname);
+static void InsertPasswordIdentifiers(Oid roleid, List *verifiers,
+ char *rolname);
+static void DeletePasswordVerifiers(Oid roleid);
/* Check if current user has createrole privileges */
@@ -78,9 +81,7 @@ CreateRole(CreateRoleStmt *stmt)
Oid roleid;
ListCell *item;
ListCell *option;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -96,7 +97,7 @@ CreateRole(CreateRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
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;
@@ -128,19 +129,13 @@ CreateRole(CreateRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -248,8 +243,8 @@ CreateRole(CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -340,12 +335,16 @@ CreateRole(CreateRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
+ */
+ FlattenPasswordIdentifiers(passwordVerifiers, stmt->role);
+
+ /*
+ * Call the password checking hook if there is one defined.
*/
- if (check_password_hook && password)
+ if (check_password_hook && passwordVerifiers != NIL)
(*check_password_hook) (stmt->role,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -365,24 +364,6 @@ 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_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -411,6 +392,10 @@ CreateRole(CreateRoleStmt *stmt)
roleid = simple_heap_insert(pg_authid_rel, tuple);
CatalogUpdateIndexes(pg_authid_rel, tuple);
+ /* store password verifiers */
+ if (passwordVerifiers)
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, stmt->role);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -479,9 +464,7 @@ AlterRole(AlterRoleStmt *stmt)
Form_pg_authid authform;
ListCell *option;
char *rolename = NULL;
- char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ List *passwordVerifiers = NIL; /* password verifiers */
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -494,7 +477,7 @@ AlterRole(AlterRoleStmt *stmt)
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
int bypassrls = -1;
- DefElem *dpassword = NULL;
+ DefElem *dpasswordVerifiers = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
@@ -512,19 +495,13 @@ AlterRole(AlterRoleStmt *stmt)
{
DefElem *defel = (DefElem *) lfirst(option);
- if (strcmp(defel->defname, "password") == 0 ||
- strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ if (strcmp(defel->defname, "passwordVerifiers") == 0)
{
- if (dpassword)
+ if (dpasswordVerifiers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
- dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
- else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ dpasswordVerifiers = defel;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -612,8 +589,8 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
+ if (dpasswordVerifiers && dpasswordVerifiers->arg)
+ passwordVerifiers = (List *) dpasswordVerifiers->arg;
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
@@ -687,7 +664,7 @@ AlterRole(AlterRoleStmt *stmt)
!dconnlimit &&
!rolemembers &&
!validUntil &&
- dpassword &&
+ dpasswordVerifiers &&
roleid == GetUserId()))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -712,12 +689,16 @@ AlterRole(AlterRoleStmt *stmt)
}
/*
- * Call the password checking hook if there is one defined
+ * Flatten list of password verifiers.
*/
- if (check_password_hook && password)
+ FlattenPasswordIdentifiers(passwordVerifiers, rolename);
+
+ /*
+ * Call the password checking hook if there is one defined.
+ */
+ if (check_password_hook && passwordVerifiers)
(*check_password_hook) (rolename,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ passwordVerifiers,
validUntil_datum,
validUntil_null);
@@ -773,30 +754,6 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
- /* password */
- if (password)
- {
- 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;
- }
-
/* valid until */
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -821,6 +778,21 @@ AlterRole(AlterRoleStmt *stmt)
heap_freetuple(new_tuple);
/*
+ * Update password verifiers. The old entries are completely cleaned up
+ * and are updated by what is wanted through this command.
+ */
+ if (passwordVerifiers)
+ {
+ DeletePasswordVerifiers(roleid);
+ CommandCounterIncrement();
+ InsertPasswordIdentifiers(roleid, passwordVerifiers, rolename);
+ }
+
+ /* remove password verifiers */
+ if (dpasswordVerifiers && dpasswordVerifiers->arg == NULL)
+ DeletePasswordVerifiers(roleid);
+
+ /*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
@@ -1070,8 +1042,10 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
/*
- * Remove any comments or security labels on this role.
+ * Remove any comments, password verifiers or security labels on this
+ * role.
*/
+ DeletePasswordVerifiers(roleid);
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
@@ -1106,11 +1080,11 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ authtuple;
TupleDesc dsc;
- Relation rel;
- Datum datum;
- bool isnull;
+ Relation pg_authid_rel,
+ pg_auth_verifiers_rel;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1118,8 +1092,10 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
- rel = heap_open(AuthIdRelationId, RowExclusiveLock);
- dsc = RelationGetDescr(rel);
+ pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+
+ dsc = RelationGetDescr(pg_authid_rel);
oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
if (!HeapTupleIsValid(oldtuple))
@@ -1179,22 +1155,10 @@ 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);
-
- if (!isnull && isMD5(TextDatumGetCString(datum)))
- {
- /* 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;
-
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
- }
-
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
- simple_heap_update(rel, &oldtuple->t_self, newtuple);
+ simple_heap_update(pg_authid_rel, &oldtuple->t_self, newtuple);
- CatalogUpdateIndexes(rel, newtuple);
+ CatalogUpdateIndexes(pg_authid_rel, newtuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
@@ -1202,10 +1166,23 @@ RenameRole(const char *oldname, const char *newname)
ReleaseSysCache(oldtuple);
+ /* look for md5 entry in pg_auth_verifiers and remove it if it exists */
+ authtuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(AUTH_VERIFIER_MD5));
+ if (HeapTupleIsValid(authtuple))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &authtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+ ReleaseSysCache(authtuple);
+ }
+
/*
- * Close pg_authid, but keep lock till commit.
+ * Close pg_authid and pg_auth_verifiers, but keep lock till commit.
*/
- heap_close(rel, NoLock);
+ heap_close(pg_auth_verifiers_rel, NoLock);
+ heap_close(pg_authid_rel, NoLock);
return address;
}
@@ -1611,3 +1588,144 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
heap_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * FlattenPasswordIdentifiers
+ * Make list of password verifier types and values consistent with input.
+ */
+static void
+FlattenPasswordIdentifiers(List *verifiers, char *rolname)
+{
+ ListCell *l;
+
+ /*
+ * This machinery is here for for sanity checks and backward
+ * compatibility with pre-9.6 instances of Postgres, an md5
+ * hash passed as a plain verifier should still be treated as
+ * an MD5 entry.
+ */
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ if (spec->value == NULL)
+ continue;
+
+ switch (spec->veriftype)
+ {
+ case AUTH_VERIFIER_MD5:
+ /*
+ * Check if given value for an MD5 verifier is adapted and
+ * do conversion as needed. If an MD5 password is provided
+ * but that the verifier has a plain format switch type of
+ * verifier accordingly.
+ */
+ if (!isMD5(spec->value))
+ {
+ char encrypted_passwd[MD5_PASSWD_LEN + 1];
+
+ if (!pg_md5_encrypt(spec->value, rolname,
+ strlen(rolname),
+ encrypted_passwd))
+ elog(ERROR, "password encryption failed");
+ spec->value = pstrdup(encrypted_passwd);
+ }
+ break;
+
+ case AUTH_VERIFIER_PLAIN:
+ if (isMD5(spec->value))
+ spec->veriftype = AUTH_VERIFIER_MD5;
+ break;
+
+ default:
+ Assert(0); /* should not happen */
+ }
+ }
+}
+
+/*
+ * InsertPasswordIdentifiers
+ * Add list of given identifiers into pg_auth_verifiers for given role.
+ */
+static void
+InsertPasswordIdentifiers(Oid roleid, List *verifiers, char *rolname)
+{
+ ListCell *l;
+ Relation pg_auth_verifiers_rel;
+ TupleDesc pg_auth_verifiers_dsc;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ pg_auth_verifiers_dsc = RelationGetDescr(pg_auth_verifiers_rel);
+
+ foreach(l, verifiers)
+ {
+ Datum new_record[Natts_pg_auth_verifiers];
+ bool new_record_nulls[Natts_pg_auth_verifiers];
+ HeapTuple tuple;
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+
+ /* Move on if no verifier value define */
+ if (spec->value == NULL)
+ continue;
+
+ /* Build tuple and insert it */
+ MemSet(new_record, 0, sizeof(new_record));
+ MemSet(new_record_nulls, false, sizeof(new_record_nulls));
+
+ new_record[Anum_pg_auth_verifiers_verroleid - 1] = ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_verifiers_vermethod - 1] =
+ CharGetDatum(spec->veriftype);
+
+ new_record[Anum_pg_auth_verifiers_vervalue - 1] =
+ CStringGetTextDatum(spec->value);
+
+ tuple = heap_form_tuple(pg_auth_verifiers_dsc,
+ new_record, new_record_nulls);
+
+ simple_heap_insert(pg_auth_verifiers_rel, tuple);
+ CatalogUpdateIndexes(pg_auth_verifiers_rel, tuple);
+
+ /* CCI after each change */
+ CommandCounterIncrement();
+ }
+
+ /* Keep locks until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
+
+/*
+ * DeletePasswordVerifiers
+ * Remove all password identifiers for given role.
+ */
+static void
+DeletePasswordVerifiers(Oid roleid)
+{
+ Relation pg_auth_verifiers_rel;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+ HeapTuple tmp_tuple;
+
+ pg_auth_verifiers_rel = heap_open(AuthVerifRelationId, RowExclusiveLock);
+ /*
+ * Remove role entries from pg_auth_verifiers table. All the tuples that
+ * are similar to the role dropped need to be removed.
+ */
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_verifiers_verroleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_verifiers_rel,
+ AuthVerifRoleMethodIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ simple_heap_delete(pg_auth_verifiers_rel, &tmp_tuple->t_self);
+ }
+
+ systable_endscan(sscan);
+
+ /* keep lock until the end of transaction */
+ heap_close(pg_auth_verifiers_rel, NoLock);
+}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d79f5a2..e41b837 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -20,14 +20,46 @@
#include <crypt.h>
#endif
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#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_auth_verifiers tuple, for given authentication
+ * method.
+ */
+static char *
+get_role_verifier(Oid roleid, const char method)
+{
+ HeapTuple tuple;
+ Datum verifier_datum;
+ char *verifier;
+ bool isnull;
+
+ /* Now attempt to grab the verifier value */
+ tuple = SearchSysCache2(AUTHVERIFROLEMETH,
+ ObjectIdGetDatum(roleid),
+ CharGetDatum(method));
+ if (!HeapTupleIsValid(tuple))
+ return NULL; /* no verifier available */
+
+ verifier_datum = SysCacheGetAttr(AUTHVERIFROLEMETH, tuple,
+ Anum_pg_auth_verifiers_vervalue,
+ &isnull);
+ verifier = TextDatumGetCString(verifier_datum);
+
+ ReleaseSysCache(tuple);
+
+ return verifier;
+}
/*
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
@@ -39,13 +71,15 @@ 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;
bool isnull;
+ Oid roleid;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -56,16 +90,24 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
return STATUS_ERROR; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ verifier_is_md5 = true;
+
+ roleid = HeapTupleGetOid(roleTup);
+ verifier = get_role_verifier(roleid, AUTH_VERIFIER_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(roleid, AUTH_VERIFIER_PLAIN);
+
+ if (verifier == NULL)
+ {
+ *logdetail = psprintf(_("User \"%s\" has no password assigned for authentication method \"%s\"."),
+ role, AUTH_VERIFIER_FULL_MD5);
+ ReleaseSysCache(roleTup);
+ return STATUS_ERROR;
+ }
}
- shadow_pass = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
@@ -74,7 +116,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (*verifier == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
@@ -92,10 +134,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))
{
@@ -108,7 +150,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))
@@ -130,7 +172,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);
@@ -143,7 +185,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index df7c2fa..a2cb4b5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2696,6 +2696,17 @@ _copyRoleSpec(const RoleSpec *from)
return newnode;
}
+static AuthVerifierSpec *
+_copyAuthVerifierSpec(const AuthVerifierSpec *from)
+{
+ AuthVerifierSpec *newnode = makeNode(AuthVerifierSpec);
+
+ COPY_SCALAR_FIELD(veriftype);
+ COPY_STRING_FIELD(value);
+
+ return newnode;
+}
+
static Query *
_copyQuery(const Query *from)
{
@@ -5011,6 +5022,9 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_AuthVerifierSpec:
+ retval = _copyAuthVerifierSpec(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9c3959..8087c39 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2594,6 +2594,15 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalAuthVerifierSpec(const AuthVerifierSpec *a, const AuthVerifierSpec *b)
+{
+ COMPARE_SCALAR_FIELD(veriftype);
+ COMPARE_STRING_FIELD(value);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3338,6 +3347,9 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_AuthVerifierSpec:
+ retval = _equalAuthVerifierSpec(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b9aeb31..e1039c6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,15 +51,18 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
+#include "commands/user.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
#include "parser/parser.h"
#include "parser/parse_expr.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/numeric.h"
@@ -145,6 +148,7 @@ static Node *makeNullAConst(int location);
static Node *makeAConst(Value *v, int location);
static Node *makeBoolAConst(bool state, int location);
static Node *makeRoleSpec(RoleSpecType type, int location);
+static Node *makeAuthVerifierSpec(char type, char *password);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
@@ -370,6 +374,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
+ auth_verifier_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -497,6 +502,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> createdb_opt_name
%type <node> var_value zone_value
%type <node> auth_ident RoleSpec opt_granted_by
+%type <node> AuthVerifierSpec
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
@@ -638,7 +644,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
@@ -918,25 +924,86 @@ AlterOptRoleList:
| /* EMPTY */ { $$ = NIL; }
;
+auth_verifier_list:
+ AuthVerifierSpec
+ { $$ = list_make1((Node*)$1); }
+ | auth_verifier_list ',' AuthVerifierSpec
+ { $$ = lappend($1, (Node *)$3); }
+
+AuthVerifierSpec:
+ NonReservedWord '=' Sconst
+ {
+ char type;
+
+ if (strcmp($1, AUTH_VERIFIER_FULL_MD5) == 0)
+ type = AUTH_VERIFIER_MD5;
+ else if (strcmp($1, AUTH_VERIFIER_FULL_PLAIN) == 0)
+ type = AUTH_VERIFIER_PLAIN;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized authorization verifier option \"%s\"", $1),
+ parser_errposition(@1)));
+ $$ = (Node *) makeAuthVerifierSpec(type, $3);
+ }
+
AlterOptRoleElem:
PASSWORD Sconst
{
- $$ = makeDefElem("password",
- (Node *)makeString($2));
+ char *rawstring = pstrdup(Password_encryption);
+ List *elemlist;
+ ListCell *l;
+ List *result = NIL;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l);
+ char veriftype;
+ AuthVerifierSpec *n;
+
+ if (strcmp(meth_name, AUTH_VERIFIER_FULL_MD5) == 0)
+ veriftype = AUTH_VERIFIER_MD5;
+ else if (strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0)
+ veriftype = AUTH_VERIFIER_PLAIN;
+ else
+ Assert(false); /* should not happen */
+ n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(veriftype, $2);
+ result = lappend(result, (Node *)n);
+ }
+ pfree(rawstring);
+ list_free(elemlist);
+
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *) result);
}
| PASSWORD NULL_P
{
- $$ = makeDefElem("password", NULL);
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, NULL);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| ENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("encryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_MD5, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
}
| UNENCRYPTED PASSWORD Sconst
{
- $$ = makeDefElem("unencryptedPassword",
- (Node *)makeString($3));
+ AuthVerifierSpec *n = (AuthVerifierSpec *)
+ makeAuthVerifierSpec(AUTH_VERIFIER_PLAIN, $3);
+ $$ = makeDefElem("passwordVerifiers",
+ (Node *)list_make1((Node *)n));
+ }
+ | PASSWORD VERIFIERS '(' auth_verifier_list ')'
+ {
+ $$ = makeDefElem("passwordVerifiers", (Node *)$4);
}
| INHERIT
{
@@ -13902,6 +13969,7 @@ unreserved_keyword:
| VALIDATOR
| VALUE_P
| VARYING
+ | VERIFIERS
| VERSION_P
| VIEW
| VIEWS
@@ -14296,6 +14364,20 @@ makeRoleSpec(RoleSpecType type, int location)
return (Node *) spec;
}
+/* makeAuthVerifierSpec
+ * Create a AuthVerifierSpec for the given type.
+ */
+static Node *
+makeAuthVerifierSpec(char type, char *password)
+{
+ AuthVerifierSpec *spec = makeNode(AuthVerifierSpec);
+
+ spec->veriftype = type;
+ spec->value = password;
+
+ return (Node *) spec;
+}
+
/* check_qualified_name --- check the result of qualified_name production
*
* It's easiest to let the grammar production for qualified_name allow
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index e929616..bb83df2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1073,6 +1073,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
case AUTHMEMMEMROLE:
+ case AUTHVERIFROLEMETH:
/*
* Protect authentication lookups occurring before relcache has
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 130c06d..c1b6330 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -45,6 +45,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -98,6 +99,7 @@ static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
+static const FormData_pg_attribute Desc_pg_auth_verifiers[Natts_pg_auth_verifiers] = {Schema_pg_auth_verifiers};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -1567,8 +1569,8 @@ LookupOpclassInfo(Oid operatorClassOid,
* catalogs.
*
* formrdesc is currently used for: pg_database, pg_authid, pg_auth_members,
- * pg_shseclabel, pg_class, pg_attribute, pg_proc, and pg_type
- * (see RelationCacheInitializePhase2/3).
+ * pg_auth_verifiers pg_shseclabel, pg_class, pg_attribute, pg_proc, and
+ * pg_type (see RelationCacheInitializePhase2/3).
*
* Note that these catalogs can't have constraints (except attnotnull),
* default values, rules, or triggers, since we don't cope with any of that.
@@ -2871,6 +2873,7 @@ RelationBuildLocalRelation(const char *relname,
case DatabaseRelationId:
case AuthIdRelationId:
case AuthMemRelationId:
+ case AuthVerifRelationId:
case RelationRelationId:
case AttributeRelationId:
case ProcedureRelationId:
@@ -3257,10 +3260,12 @@ RelationCacheInitializePhase2(void)
true, Natts_pg_authid, Desc_pg_authid);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
false, Natts_pg_auth_members, Desc_pg_auth_members);
+ formrdesc("pg_auth_verifiers", AuthVerifRelation_Rowtype_Id, true,
+ false, Natts_pg_auth_verifiers, Desc_pg_auth_verifiers);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
false, Natts_pg_shseclabel, Desc_pg_shseclabel);
-#define NUM_CRITICAL_SHARED_RELS 4 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -3380,10 +3385,10 @@ RelationCacheInitializePhase3(void)
* initial lookup of MyDatabaseId, without which we'll never find any
* non-shared catalogs at all. Autovacuum calls InitPostgres with a
* database OID, so it instead depends on DatabaseOidIndexId. We also
- * need to nail up some indexes on pg_authid and pg_auth_members for use
- * during client authentication. SharedSecLabelObjectIndexId isn't
- * critical for the core system, but authentication hooks might be
- * interested in it.
+ * need to nail up some indexes on pg_authid, pg_auth_verifiers and
+ * pg_auth_members for use during client authentication.
+ * SharedSecLabelObjectIndexId isn't critical for the core system, but
+ * authentication hooks might be interested in it.
*/
if (!criticalSharedRelcachesBuilt)
{
@@ -3397,10 +3402,12 @@ RelationCacheInitializePhase3(void)
AuthIdRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
+ load_critical_index(AuthVerifRoleMethodIndexId,
+ AuthVerifRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..17390b4 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -28,6 +28,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_verifiers.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -248,6 +249,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthVerifRelationId, /* AUTHVERIFMETHROLE */
+ AuthVerifMethodRoleIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_vermethod,
+ Anum_pg_auth_verifiers_verroleid,
+ 0,
+ 0
+ },
+ 4
+ },
+ {AuthVerifRelationId, /* AUTHVERIFROLEMETH */
+ AuthVerifRoleMethodIndexId,
+ 2,
+ {
+ Anum_pg_auth_verifiers_verroleid,
+ Anum_pg_auth_verifiers_vermethod,
+ 0,
+ 0
+ },
+ 4
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a325943..ae880cb 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -32,6 +32,7 @@
#include "access/twophase.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/pg_auth_verifiers.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/vacuum.h"
@@ -179,6 +180,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
+static bool check_password_encryption(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -422,8 +425,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -435,6 +436,8 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
+char *Password_encryption;
+
char *cluster_name = "";
char *ConfigFileName;
char *HbaFileName;
@@ -1309,17 +1312,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3395,6 +3387,19 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password encryption methods."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines how the password is to be encrypted."),
+ GUC_LIST_INPUT
+ },
+ &Password_encryption,
+ "md5",
+ check_password_encryption, NULL, NULL
+ },
+
+ {
{"ssl_cert_file", PGC_POSTMASTER, CONN_AUTH_SECURITY,
gettext_noop("Location of the SSL server certificate file."),
NULL
@@ -10229,6 +10234,41 @@ check_cluster_name(char **newval, void **extra, GucSource source)
return true;
}
+static bool
+check_password_encryption(char **newval, void **extra, GucSource source)
+{
+ char *rawstring = pstrdup(*newval); /* get copy of list string */
+ List *elemlist;
+ ListCell *l;
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ pfree(rawstring);
+ list_free(elemlist);
+ Assert(false);
+ return false; /* GUC machinery should have already complained */
+ }
+
+ /* Check that only supported formats are listed */
+ foreach(l, elemlist)
+ {
+ char *encryption_name = (char *) lfirst(l);
+
+ if (strcmp(encryption_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
+ strcmp(encryption_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
+ {
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ return true;
+}
+
static const char *
show_unix_socket_permissions(void)
{
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 773b4e8..bff25ca 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -87,7 +87,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = 'md5'
#db_user_namespace = off
#row_security = on
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ed3ba7b..5315db7 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1530,10 +1530,11 @@ setup_auth(FILE *cmdfd)
const char *const * line;
static const char *const pg_authid_setup[] = {
/*
- * The authid table shouldn't be readable except through views, to
- * ensure passwords are not publicly visible.
+ * The authorization tables shouldn't be readable except through
+ * views, to ensure password data are not publicly visible.
*/
"REVOKE ALL on pg_authid FROM public;\n\n",
+ "REVOKE ALL on pg_auth_verifiers FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index be6b4a8..4bf36c4 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,8 +663,22 @@ dumpRoles(PGconn *conn)
i_is_current_user;
int i;
- /* note: rolconfig is dumped later */
- if (server_version >= 90500)
+ /*
+ * Note: rolconfig is dumped later. In 9.6 and above, password
+ * information is dumped later on.
+ */
+ if (server_version >= 90600)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, "
+ "null::text as rolpassword, "
+ "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, "
@@ -869,6 +883,65 @@ dumpRoles(PGconn *conn)
PQclear(res);
+ /*
+ * Dump password configuration for all roles.
+ */
+ if (server_version >= 90600)
+ {
+ char *current_user = NULL;
+ bool first_elt = true;
+ res = executeQuery(conn,
+ "SELECT a.rolname, v.vermethod, v.vervalue "
+ "FROM pg_auth_verifiers AS v "
+ "LEFT JOIN pg_authid AS a ON (v.verroleid = a.oid) "
+ "ORDER BY rolname;");
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ char *user_name = PQgetvalue(res, i, 0);
+ char verifier_meth = *PQgetvalue(res, i, 1);
+ char *verifier_value = PQgetvalue(res, i, 2);
+
+ /* Switch to new ALTER ROLE query when a different user is found */
+ if (current_user == NULL ||
+ strcmp(user_name, current_user) != 0)
+ {
+ /* Finish last query */
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+
+ resetPQExpBuffer(buf);
+
+ if (current_user)
+ pg_free(current_user);
+ current_user = pg_strdup(user_name);
+ first_elt = true;
+ appendPQExpBuffer(buf, "ALTER ROLE %s PASSWORD VERIFIERS (",
+ current_user);
+ }
+
+ if (first_elt)
+ first_elt = false;
+ else
+ appendPQExpBufferStr(buf, ", ");
+
+ if (verifier_meth == 'm')
+ appendPQExpBufferStr(buf, "md5 = ");
+ else if (verifier_meth == 'p')
+ appendPQExpBufferStr(buf, "plain = ");
+ appendStringLiteralConn(buf, verifier_value, conn);
+ }
+ if (current_user != NULL)
+ {
+ appendPQExpBufferStr(buf, ");\n");
+ fprintf(OPF, "%s", buf->data);
+ }
+ }
+
+
fprintf(OPF, "\n\n");
destroyPQExpBuffer(buf);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ab2c1a8..5a9dbd6 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -97,6 +97,11 @@ DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, on pg_auth_members
DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, on pg_auth_members using btree(member oid_ops, roleid oid_ops));
#define AuthMemMemRoleIndexId 2695
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_role_method_index, 3337, on pg_auth_verifiers using btree(verroleid oid_ops, vermethod char_ops));
+#define AuthVerifRoleMethodIndexId 3337
+DECLARE_UNIQUE_INDEX(pg_auth_verifiers_method_role_index, 3338, on pg_auth_verifiers using btree(vermethod char_ops, verroleid oid_ops));
+#define AuthVerifMethodRoleIndexId 3338
+
DECLARE_UNIQUE_INDEX(pg_cast_oid_index, 2660, on pg_cast using btree(oid oid_ops));
#define CastOidIndexId 2660
DECLARE_UNIQUE_INDEX(pg_cast_source_target_index, 2661, on pg_cast using btree(castsource oid_ops, casttarget oid_ops));
diff --git a/src/include/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
new file mode 100644
index 0000000..86461d7
--- /dev/null
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_verifiers.h
+ * definition of the system "authorization password hashes" relation
+ * (pg_auth_verifiers) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_auth_verifiers.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_VERIFIERS_H
+#define PG_AUTH_VERIFIERS_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_auth_verifiers definition. cpp turns this into
+ * typedef struct FormData_pg_auth_verifiers
+ * ----------------
+ */
+#define AuthVerifRelationId 3335
+#define AuthVerifRelation_Rowtype_Id 3336
+
+CATALOG(pg_auth_verifiers,3335) BKI_SHARED_RELATION BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(3336) BKI_SCHEMA_MACRO
+{
+ Oid verroleid; /* OID of the role using this *
+ * verifier */
+ char vermethod; /* Method used to generate the hash *
+ * See AUTH_VERIFIER_xxx below */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text vervalue BKI_FORCE_NOT_NULL; /* Hash value */
+#endif
+} FormData_pg_auth_verifiers;
+
+/* ----------------
+ * Form_pg_auth_verifiers corresponds to a pointer to a tuple with
+ * the format of pg_auth_verifiers relation.
+ * ----------------
+ */
+typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
+
+/* ----------------
+ * compiler constants for pg_auth_verifiers
+ * ----------------
+ */
+#define Natts_pg_auth_verifiers 3
+#define Anum_pg_auth_verifiers_verroleid 1
+#define Anum_pg_auth_verifiers_vermethod 2
+#define Anum_pg_auth_verifiers_vervalue 3
+
+/* catalog-level verifier identifiers */
+#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
+#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+
+/* full-name verifier identifiers */
+#define AUTH_VERIFIER_FULL_PLAIN "plain"
+#define AUTH_VERIFIER_FULL_MD5 "md5"
+
+#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index c163083..35f74d0 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -56,7 +56,6 @@ 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 */
timestamptz rolvaliduntil; /* password expiration time, if any */
#endif
} FormData_pg_authid;
@@ -75,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* compiler constants for pg_authid
* ----------------
*/
-#define Natts_pg_authid 11
+#define Natts_pg_authid 10
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
@@ -85,8 +84,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_rolvaliduntil 11
+#define Anum_pg_authid_rolvaliduntil 10
/* ----------------
* initial contents of pg_authid
@@ -95,7 +93,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* user choices.
* ----------------
*/
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_));
#define BOOTSTRAP_SUPERUSERID 10
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..636e8ac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,12 +14,13 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
+/* GUC parameter */
+extern char *Password_encryption;
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
-
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username,
+ List *passwordVerifiers,
+ Datum validuntil_time,
+ bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 42c9582..ccd05e0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -449,6 +449,7 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_AuthVerifierSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2fd0629..a14969c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -312,6 +312,17 @@ typedef struct RoleSpec
} RoleSpec;
/*
+ * AuthVerifierSpec - a password verifier with a some dedicated values.
+ */
+typedef struct AuthVerifierSpec
+{
+ NodeTag type;
+ char veriftype; /* type of this verifier, as listed in *
+ * pg_auth_verifiers.h */
+ char *value; /* value specified by user */
+} AuthVerifierSpec;
+
+/*
* FuncCall - a function or aggregate invocation
*
* agg_order (if not NIL) indicates we saw 'foo(... ORDER BY ...)', or if
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6e1e820..56635d5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -416,6 +416,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/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..300ebaa 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,8 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHVERIFMETHROLE,
+ AUTHVERIFROLEMETH,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..5e6f04d
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,104 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+SET password_encryption = true; -- error
+ERROR: invalid value for parameter "password_encryption": "true"
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+ rolname | vermethod | substr
+--------------+-----------+--------
+ role_passwd1 | p | rol
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd3 | p | rol
+(4 rows)
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.vermethod;
+ rolname | vermethod | substr
+------------------+-----------+--------
+ role_passwd3_new | p | rol
+(1 row)
+
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+ rolname | vermethod | substr
+--------------+-----------+--------
+ role_passwd1 | p | foo
+ role_passwd2 | m | md5
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ERROR: unrecognized authorization verifier option "unexistent_verif"
+LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
+ ^
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+ rolname | vermethod | substr
+--------------+-----------+--------
+ role_passwd1 | m | md5
+ role_passwd2 | p | foo
+ role_passwd3 | m | md5
+ role_passwd4 | m | md5
+(4 rows)
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+-- all entries should have been removed
+SELECT a.rolname, v.vermethod
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.vermethod;
+ rolname | vermethod
+---------+-----------
+(0 rows)
+
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index aa5f42a..66ea550 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE test_def_superuser;
SELECT * FROM pg_authid WHERE rolname = 'test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH NOSUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_superuser WITH SUPERUSER;
SELECT * FROM pg_authid WHERE rolname = 'test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_superuser | t | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE test_def_inherit;
SELECT * FROM pg_authid WHERE rolname = 'test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH INHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_inherit WITH NOINHERIT;
SELECT * FROM pg_authid WHERE rolname = 'test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_inherit | f | f | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE test_def_createrole;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH NOCREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createrole WITH CREATEROLE;
SELECT * FROM pg_authid WHERE rolname = 'test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createrole | f | t | t | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE test_def_createdb;
SELECT * FROM pg_authid WHERE rolname = 'test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH NOCREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_createdb WITH CREATEDB;
SELECT * FROM pg_authid WHERE rolname = 'test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_createdb | f | t | f | t | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE test_def_role_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_role_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER test_def_user_canlogin;
SELECT * FROM pg_authid WHERE rolname = 'test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH LOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER test_user_canlogin WITH NOLOGIN;
SELECT * FROM pg_authid WHERE rolname = 'test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE test_def_replication;
SELECT * FROM pg_authid WHERE rolname = 'test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE test_replication WITH NOREPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_replication WITH REPLICATION;
SELECT * FROM pg_authid WHERE rolname = 'test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_replication | f | t | f | f | f | t | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE test_def_bypassrls;
SELECT * FROM pg_authid WHERE rolname = 'test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH NOBYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | f | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE test_bypassrls WITH BYPASSRLS;
SELECT * FROM pg_authid WHERE rolname = 'test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- test_bypassrls | f | t | f | f | f | f | t | -1 | |
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 79f9b23..f69e026 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1631,7 +1631,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,
+ '********'::text AS passwd,
(pg_authid.rolvaliduntil)::abstime AS valuntil,
s.setconfig AS useconfig
FROM (pg_authid
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..d4b3f36 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -91,6 +91,7 @@ pg_amproc|t
pg_attrdef|t
pg_attribute|t
pg_auth_members|t
+pg_auth_verifiers|t
pg_authid|t
pg_cast|t
pg_class|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index bec0316..bbe969b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7e9b319..cd724b6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -111,6 +111,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: alter_generic
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..c6addf9
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,72 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- error
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'md5,plain'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE role_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'md5,plain';
+CREATE ROLE role_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = '';
+CREATE ROLE role_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE role_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+
+-- Rename a role
+ALTER ROLE role_passwd3 RENAME TO role_passwd3_new;
+-- md5 entry should have been removed
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname = 'role_passwd3_new'
+ ORDER BY a.rolname, v.vermethod;
+ALTER ROLE role_passwd3_new RENAME TO role_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE role_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE role_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE role_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE role_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+
+-- PASSWORD VERIFIERS
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
+ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
+ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
+ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%'
+ ORDER BY a.rolname, v.vermethod;
+
+DROP ROLE role_passwd1;
+DROP ROLE role_passwd2;
+DROP ROLE role_passwd3;
+DROP ROLE role_passwd4;
+DROP ROLE role_passwd5;
+
+-- all entries should have been removed
+SELECT a.rolname, v.vermethod
+ FROM pg_auth_verifiers v
+ LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
+ WHERE a.rolname LIKE 'role_passwd%' ORDER BY a.rolname, v.vermethod;
--
2.7.3
0002-Introduce-password_protocols.patchbinary/octet-stream; name=0002-Introduce-password_protocols.patchDownload
From fa98d5230405a8db734e359e73535a71b2e8e8d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 18 Mar 2016 21:57:36 +0900
Subject: [PATCH 2/9] Introduce password_protocols
This new superuser GUC parameters specifies a list of supported password
protocols in Postgres backend. This is useful for system maintainers to
prevent the creation of password using a protocol thought as unsafe in
certain deployments.
The current default is 'plain,md5', authorizing the creation of both plain
passwords and MD5-encrypted passwords in the system.
---
doc/src/sgml/config.sgml | 27 ++++++++++++++++
src/backend/commands/user.c | 46 +++++++++++++++++++++++++++
src/backend/utils/misc/guc.c | 29 +++++++++++++----
src/backend/utils/misc/postgresql.conf.sample | 2 ++
src/include/commands/user.h | 3 +-
src/test/regress/expected/password.out | 28 ++++++++++++++++
src/test/regress/sql/password.sql | 20 ++++++++++++
7 files changed, 147 insertions(+), 8 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 80fc479..a11beab 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1190,6 +1190,33 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-password-protocols" xreflabel="password_protocols">
+ <term><varname>password_protocols</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>password_protocols</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies a comma-separated list of supported password formats by
+ the server. Supported formats are currently <literal>plain</> and
+ <literal>md5</>.
+ </para>
+
+ <para>
+ When a password is specified in <xref linkend="sql-createuser"> or
+ <xref linkend="sql-alterrole">, this parameter determines if the
+ password specified is authorized to be stored or not, returning
+ an error message to caller if it is not.
+ </para>
+
+ <para>
+ The default is <literal>plain,md5</>, meaning that MD5-encrypted
+ passwords and plain passwords are both accepted.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-krb-server-keyfile" xreflabel="krb_server_keyfile">
<term><varname>krb_server_keyfile</varname> (<type>string</type>)
<indexterm>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index e8f23e6..95583e6 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1597,6 +1597,8 @@ static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
{
ListCell *l;
+ char *rawstring;
+ List *elemlist;
/*
* This machinery is here for for sanity checks and backward
@@ -1641,6 +1643,50 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
Assert(0); /* should not happen */
}
}
+
+ /*
+ * Now that the list of verifiers is built and consistent with the input
+ * values, check that the list of verifiers specified is actually
+ * supported by server or not.
+ */
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ /*
+ * This is O(N ^ 2), but the small number of elements in the list of
+ * protocols supported is not worth complicating this code.
+ */
+ foreach(l, verifiers)
+ {
+ AuthVerifierSpec *spec = (AuthVerifierSpec *) lfirst(l);
+ ListCell *l2;
+ bool found_match = false;
+
+ foreach(l2, elemlist)
+ {
+ char *meth_name = (char *) lfirst(l2);
+
+ if ((strcmp(meth_name, AUTH_VERIFIER_FULL_MD5) == 0 &&
+ spec->veriftype == AUTH_VERIFIER_MD5) ||
+ (strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0 &&
+ spec->veriftype == AUTH_VERIFIER_PLAIN))
+ {
+ found_match = true;
+ break;
+ }
+ }
+
+ if (!found_match)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("specified password protocol not allowed"),
+ errdetail("List of authorized protocols is specified by password_protocols.")));
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ae880cb..ab2bc3f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -180,8 +180,8 @@ static void assign_pgstat_temp_directory(const char *newval, void *extra);
static bool check_application_name(char **newval, void **extra, GucSource source);
static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
-static bool check_password_encryption(char **newval, void **extra,
- GucSource source);
+static bool check_password_methods(char **newval, void **extra,
+ GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
@@ -437,6 +437,7 @@ int temp_file_limit = -1;
int num_temp_buffers = 1024;
char *Password_encryption;
+char *password_protocols;
char *cluster_name = "";
char *ConfigFileName;
@@ -3396,7 +3397,21 @@ static struct config_string ConfigureNamesString[] =
},
&Password_encryption,
"md5",
- check_password_encryption, NULL, NULL
+ check_password_methods, NULL, NULL
+ },
+
+ {
+ {"password_protocols", PGC_SUSET, CONN_AUTH_SECURITY,
+ gettext_noop("List of password protocols supported."),
+ gettext_noop("The list of password protocols specified by this "
+ "parameter determines what are the authorized methods "
+ "on the server when running CREATE USER or ALTER "
+ "USER."),
+ GUC_LIST_INPUT
+ },
+ &password_protocols,
+ "plain,md5",
+ check_password_methods, NULL, NULL
},
{
@@ -10235,7 +10250,7 @@ check_cluster_name(char **newval, void **extra, GucSource source)
}
static bool
-check_password_encryption(char **newval, void **extra, GucSource source)
+check_password_methods(char **newval, void **extra, GucSource source)
{
char *rawstring = pstrdup(*newval); /* get copy of list string */
List *elemlist;
@@ -10253,10 +10268,10 @@ check_password_encryption(char **newval, void **extra, GucSource source)
/* Check that only supported formats are listed */
foreach(l, elemlist)
{
- char *encryption_name = (char *) lfirst(l);
+ char *method_name = (char *) lfirst(l);
- if (strcmp(encryption_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
- strcmp(encryption_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
+ if (strcmp(method_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
+ strcmp(method_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index bff25ca..a93cbe7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -88,6 +88,8 @@
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
#password_encryption = 'md5'
+#password_protocols = 'plain,md5' # comma-separated list of supported
+ # password protocols.
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 636e8ac..7a73bc5 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -14,8 +14,9 @@
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
-/* GUC parameter */
+/* GUC parameters */
extern char *Password_encryption;
+extern char *password_protocols;
typedef void (*check_password_hook_type) (const char *username,
List *passwordVerifiers,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 5e6f04d..7f18799 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -9,6 +9,14 @@ ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+ERROR: invalid value for parameter "password_protocols": "novalue"
+SET password_protocols = true; -- error
+ERROR: invalid value for parameter "password_protocols": "true"
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -88,6 +96,26 @@ SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
role_passwd4 | m | md5
(4 rows)
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+ERROR: specified password protocol not allowed
+DETAIL: List of authorized protocols is specified by password_protocols.
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index c6addf9..e973c5e 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -9,6 +9,13 @@ SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
SET password_encryption = 'md5,plain'; -- ok
+-- Tests for GUC password_protocols
+SET password_protocols = 'novalue'; -- error
+SET password_protocols = true; -- error
+SET password_protocols = 'md5'; -- ok
+SET password_protocols = 'plain'; -- ok
+SET password_protocols = 'md5,plain'; -- ok
+
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -59,6 +66,19 @@ SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
WHERE a.rolname LIKE 'role_passwd%'
ORDER BY a.rolname, v.vermethod;
+-- entries for password_protocols
+SET password_protocols = 'md5,plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo', plain = 'foo'); -- ok
+SET password_protocols = 'md5';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- ok
+SET password_protocols = 'plain';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- ok
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+SET password_protocols = '';
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (plain = 'foo'); -- error
+ALTER ROLE role_passwd5 PASSWORD VERIFIERS (md5 = 'foo'); -- error
+
DROP ROLE role_passwd1;
DROP ROLE role_passwd2;
DROP ROLE role_passwd3;
--
2.7.3
0003-Add-pg_auth_verifiers_sanitize.patchbinary/octet-stream; name=0003-Add-pg_auth_verifiers_sanitize.patchDownload
From aae1dd9e30eb6671005a8782a72ea733a42f07c5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 18 Mar 2016 22:15:14 +0900
Subject: [PATCH 3/9] Add pg_auth_verifiers_sanitize
This function is aimed at being used by pg_upgrade and system administers
to filter out password verifiers that are based on protocols not defined
on the system per the list given by password_protocols.
---
doc/src/sgml/func.sgml | 34 +++++++++++++++++++++
src/backend/commands/user.c | 71 +++++++++++++++++++++++++++++++++++++++++++
src/include/catalog/pg_proc.h | 4 +++
src/include/commands/user.h | 2 ++
4 files changed, 111 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 000489d..3129da6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -19050,6 +19050,40 @@ SELECT (pg_stat_file('filename')).modification;
</sect2>
+ <sect2 id="functions-password">
+ <title>Password Functions</title>
+
+ <para>
+ The functions shown in <xref linkend="functions-password-table"> manage
+ system passwords.
+ </para>
+
+ <table id="functions-password-table">
+ <title>Password Functions</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <literal><function>pg_auth_verifiers_sanitize()</function></literal>
+ </entry>
+ <entry><type>integer</type></entry>
+ <entry>remove password verifier entries in
+ <link linkend="catalog-pg-auth-verifiers"></> for password protocols
+ not listed in <xref linkend="guc-password-protocols">. This function
+ is limited to superusers. The return result is the number of entries
+ removed from the table.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
</sect1>
<sect1 id="functions-trigger">
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 95583e6..1f94721 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1775,3 +1775,74 @@ DeletePasswordVerifiers(Oid roleid)
/* keep lock until the end of transaction */
heap_close(pg_auth_verifiers_rel, NoLock);
}
+
+/*
+ * pg_auth_sanitize
+ *
+ * Scan through pg_auth_verifiers and remove all the password verifiers
+ * that are not part of the list of supported protocols as defined by
+ * password_protocols. Returns to caller the number of entries removed.
+ */
+Datum
+pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
+{
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tup;
+ char *rawstring;
+ List *elemlist;
+ int res = 0;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to sanitize \"pg_auth_verifiers\""))));
+
+ rawstring = pstrdup(password_protocols);
+
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ Assert(false); /* should not happen */
+
+ rel = heap_open(AuthVerifRelationId, AccessShareLock);
+ scan = heap_beginscan_catalog(rel, 0, NULL);
+ while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Form_pg_auth_verifiers authform =
+ (Form_pg_auth_verifiers) GETSTRUCT(tup);
+ ListCell *l;
+ bool remove_entry = true;
+
+ foreach(l, elemlist)
+ {
+ char *meth_name = lfirst(l);
+
+ /* Check for protocol matches */
+ if (authform->vermethod == AUTH_VERIFIER_MD5 &&
+ strcmp(meth_name, AUTH_VERIFIER_FULL_MD5) == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ else if (authform->vermethod == AUTH_VERIFIER_PLAIN &&
+ strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0)
+ {
+ remove_entry = false;
+ break;
+ }
+ }
+
+ if (remove_entry)
+ {
+ simple_heap_delete(rel, &tup->t_self);
+ res++;
+ }
+ }
+
+ heap_endscan(scan);
+ heap_close(rel, NoLock);
+
+ pfree(rawstring);
+ list_free(elemlist);
+
+ PG_RETURN_INT32(res);
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ceb8129..caf2ad9 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5196,6 +5196,10 @@ DESCR("for use by pg_upgrade");
DATA(insert OID = 3591 ( binary_upgrade_create_empty_extension PGNSP PGUID 12 1 0 0 0 f f f f f f v r 7 0 2278 "25 25 16 25 1028 1009 1009" _null_ _null_ _null_ _null_ _null_ binary_upgrade_create_empty_extension _null_ _null_ _null_ ));
DESCR("for use by pg_upgrade");
+/* commands/user.h */
+DATA(insert OID = 3339 ( pg_auth_verifiers_sanitize PGNSP PGUID 12 1 0 0 0 f f f f t f v u 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_auth_verifiers_sanitize _null_ _null_ _null_ ));
+DESCR("sanitize entries of pg_auth_verifiers using password_protocols");
+
/* replication/origin.h */
DATA(insert OID = 6003 ( pg_replication_origin_create PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 26 "25" _null_ _null_ _null_ _null_ _null_ pg_replication_origin_create _null_ _null_ _null_ ));
DESCR("create a replication origin");
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a73bc5..4277d5f 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -35,4 +35,6 @@ extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
extern List *roleSpecsToIds(List *memberNames);
+extern Datum pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS);
+
#endif /* USER_H */
--
2.7.3
0004-Remove-password-verifiers-for-unsupported-protocols-.patchbinary/octet-stream; name=0004-Remove-password-verifiers-for-unsupported-protocols-.patchDownload
From 30e94ceeed3c8ab65d9c07ce0a47b7fdddc6efb7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 14:53:37 +0900
Subject: [PATCH 4/9] Remove password verifiers for unsupported protocols in
pg_upgrade
This uses pg_auth_verifiers_sanitize to perform the cleanup in
pg_auth_verifiers that has been introduced previously.
---
src/bin/pg_upgrade/pg_upgrade.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 4f5361a..390d8ee 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -260,6 +260,18 @@ prepare_new_cluster(void)
new_cluster.bindir, cluster_conn_opts(&new_cluster),
log_opts.verbose ? "--verbose" : "");
check_ok();
+
+ /*
+ * In order to ensure that the freshly-deployed cluster has no outdated
+ * password verifier entries, sanitize pg_auth_verifiers using the
+ * in-core function aimed at this purpose.
+ */
+ prep_status("Removing password verifiers for unsupported protocols");
+ exec_prog(UTILITY_LOG_FILE, NULL, true,
+ "\"%s/psql\" " EXEC_PSQL_ARGS
+ " %s -c \"SELECT pg_auth_verifiers_sanitize()\"",
+ new_cluster.bindir, cluster_conn_opts(&new_cluster));
+ check_ok();
}
--
2.7.3
0005-Move-sha1.c-to-src-common.patchbinary/octet-stream; name=0005-Move-sha1.c-to-src-common.patchDownload
From b4635e3fa83ac97c7ccf1af1d619973b046cbef3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:33:27 +0900
Subject: [PATCH 5/9] Move sha1.c to src/common
This set of routines taken from pgcrypto will be used on both backend
and frontend for authentication purposes.
---
contrib/pgcrypto/Makefile | 4 ++--
contrib/pgcrypto/internal.c | 2 +-
src/common/Makefile | 3 ++-
{contrib/pgcrypto => src/common}/sha1.c | 4 ++--
{contrib/pgcrypto => src/include/common}/sha1.h | 2 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
6 files changed, 9 insertions(+), 8 deletions(-)
rename {contrib/pgcrypto => src/common}/sha1.c (99%)
rename {contrib/pgcrypto => src/include/common}/sha1.h (98%)
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/src/common/Makefile b/src/common/Makefile
index f7a4a4d..16052de 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,7 +37,8 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o pg_lzcompress.o \
- pgfnames.o psprintf.o relpath.o rmtree.o string.o username.o wait_error.o
+ pgfnames.o psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
+ wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/contrib/pgcrypto/sha1.c b/src/common/sha1.c
similarity index 99%
rename from contrib/pgcrypto/sha1.c
rename to src/common/sha1.c
index 0e753ce..4d9a325 100644
--- a/contrib/pgcrypto/sha1.c
+++ b/src/common/sha1.c
@@ -28,7 +28,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * contrib/pgcrypto/sha1.c
+ * src/common/sha1.c
*/
/*
* FIPS pub 180-1: Secure Hash Algorithm (SHA-1)
@@ -40,7 +40,7 @@
#include <sys/param.h>
-#include "sha1.h"
+#include "common/sha1.h"
/* constant table */
static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
diff --git a/contrib/pgcrypto/sha1.h b/src/include/common/sha1.h
similarity index 98%
rename from contrib/pgcrypto/sha1.h
rename to src/include/common/sha1.h
index 2f61e45..3cccc76 100644
--- a/contrib/pgcrypto/sha1.h
+++ b/src/include/common/sha1.h
@@ -1,4 +1,4 @@
-/* contrib/pgcrypto/sha1.h */
+/* src/include/common/sha1.h */
/* $KAME: sha1.h,v 1.4 2000/02/22 14:01:18 itojun Exp $ */
/*
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 949077a..f7e803b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -107,7 +107,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c exec.c pg_lzcompress.c pgfnames.c
- psprintf.c relpath.c rmtree.c string.c username.c wait_error.c);
+ psprintf.c relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
--
2.7.3
0006-Refactor-sendAuthRequest.patchbinary/octet-stream; name=0006-Refactor-sendAuthRequest.patchDownload
From d9444b3ebd4bde42013ccaa8783a8a91480429d4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 3 Aug 2015 15:42:49 +0900
Subject: [PATCH 6/9] 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 7f1ae8c..c061bf0 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);
}
@@ -1673,7 +1672,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)
{
@@ -1948,7 +1947,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)
@@ -2308,7 +2307,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.7.3
0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchbinary/octet-stream; name=0007-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From a6e0044c6f930beb2777f3fb8ba779be742f2224 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 11 Aug 2015 20:43:26 +0900
Subject: [PATCH 7/9] Refactor RandomSalt to handle salts of different lengths
---
src/backend/postmaster/postmaster.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index b16fc28..525155b 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -404,7 +404,7 @@ static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -2339,7 +2339,7 @@ ConnCreate(int serverFd)
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
- RandomSalt(port->md5Salt);
+ RandomSalt(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
@@ -5079,23 +5079,21 @@ StartupPacketTimeoutHandler(void)
* RandomSalt
*/
static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
{
long rand;
+ int i;
/*
* We use % 255, sacrificing one possible byte value, so as to ensure that
* all bits of the random() value participate in the result. While at it,
* add one to avoid generating any null bytes.
*/
- rand = PostmasterRandom();
- md5Salt[0] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[1] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[2] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[3] = (rand % 255) + 1;
+ for (i = 0; i < len; i++)
+ {
+ rand = PostmasterRandom();
+ md5Salt[i] = (rand % 255) + 1;
+ }
}
/*
--
2.7.3
0008-Move-encoding-routines-to-src-common.patchbinary/octet-stream; name=0008-Move-encoding-routines-to-src-common.patchDownload
From 353223ba6058e67c15383ad1b1e34472a2b09ffa Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 23 Feb 2016 15:42:35 +0900
Subject: [PATCH 8/9] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA1, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +----------------------------
src/common/Makefile | 6 +-
src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
src/include/common/encode.h | 30 +++
src/tools/msvc/Mkvcbuild.pm | 5 +-
5 files changed, 169 insertions(+), 636 deletions(-)
copy src/{backend/utils/adt => common}/encode.c (72%)
create mode 100644 src/include/common/encode.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..76747bf 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/common/Makefile b/src/common/Makefile
index 16052de..2fb88ff 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -36,9 +36,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o pg_lzcompress.o \
- pgfnames.o psprintf.o relpath.o rmtree.o sha1.o string.o username.o \
- wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
+ pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o sha1.o \
+ string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index d833efc..4a07089 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
* encode.c
- * Various data encoding/decoding things.
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the \
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..8166376
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ * encode.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* COMMON_ENCODE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f7e803b..60d379a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,8 +106,9 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c pg_lzcompress.c pgfnames.c
- psprintf.c relpath.c rmtree.c sha1.c string.c username.c wait_error.c);
+ config_info.c controldata_utils.c encode.c exec.c pg_lzcompress.c
+ pgfnames.c psprintf.c relpath.c rmtree.c sha1.c string.c username.c
+ wait_error.c);
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
--
2.7.3
0009-SCRAM-authentication.patchbinary/octet-stream; name=0009-SCRAM-authentication.patchDownload
From 1ea61a1dff31a87ab4849d2738ed0e414dee73b2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Tue, 18 Aug 2015 13:29:50 +0900
Subject: [PATCH 9/9] SCRAM authentication
---
contrib/passwordcheck/passwordcheck.c | 4 +
doc/src/sgml/catalogs.sgml | 3 +-
doc/src/sgml/config.sgml | 17 +-
doc/src/sgml/protocol.sgml | 148 ++++++-
src/backend/commands/user.c | 34 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 682 ++++++++++++++++++++++++++++++++
src/backend/libpq/auth.c | 117 ++++++
src/backend/libpq/crypt.c | 4 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 2 +-
src/backend/parser/gram.y | 4 +
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/adt/varlena.c | 1 +
src/backend/utils/misc/guc.c | 5 +-
src/bin/pg_dump/pg_dumpall.c | 2 +
src/common/Makefile | 4 +-
src/common/scram-common.c | 170 ++++++++
src/include/catalog/pg_auth_verifiers.h | 2 +
src/include/common/scram-common.h | 45 +++
src/include/libpq/auth.h | 5 +
src/include/libpq/crypt.h | 1 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 3 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 ++
src/include/utils/builtins.h | 2 -
src/interfaces/libpq/.gitignore | 3 +
src/interfaces/libpq/Makefile | 7 +-
src/interfaces/libpq/fe-auth-scram.c | 386 ++++++++++++++++++
src/interfaces/libpq/fe-auth.c | 96 +++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 51 +++
src/interfaces/libpq/libpq-int.h | 5 +
src/test/regress/expected/password.out | 13 +-
src/test/regress/sql/password.sql | 9 +-
36 files changed, 1845 insertions(+), 34 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 13ad053..57f7f49 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -135,6 +135,10 @@ check_password(const char *username,
#endif
break;
+ case AUTH_VERIFIER_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
+
default:
elog(ERROR, "unrecognized password type: %d", spec->veriftype);
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index cee0c42..20018e4 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1323,7 +1323,8 @@
<entry><type>char</type></entry>
<entry>
<literal>p</> = plain format,
- <literal>m</> = MD5-encrypted
+ <literal>m</> = MD5-encrypted,
+ <literal>s</> = SCRAM-SHA1-encrypted
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a11beab..5d8b72e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1171,7 +1171,8 @@ include_dir 'conf.d'
<listitem>
<para>
Specifies a comma-separated list of password encryption formats.
- Supported formats are <literal>plain</> and <literal>md5</>.
+ Supported formats are <literal>plain</>,<literal>md5</> and
+ <literal>scram</>.
</para>
<para>
@@ -1199,8 +1200,8 @@ include_dir 'conf.d'
<listitem>
<para>
Specifies a comma-separated list of supported password formats by
- the server. Supported formats are currently <literal>plain</> and
- <literal>md5</>.
+ the server. Supported formats are currently <literal>plain</>,
+ <literal>md5</> and <literal>scram</>.
</para>
<para>
@@ -1211,8 +1212,8 @@ include_dir 'conf.d'
</para>
<para>
- The default is <literal>plain,md5</>, meaning that MD5-encrypted
- passwords and plain passwords are both accepted.
+ The default is <literal>plain,md5,scram</>, meaning that MD5-encrypted
+ passwords, plain passwords, and SCRAM-encrypted passwords are accepted.
</para>
</listitem>
</varlistentry>
@@ -1286,8 +1287,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 522128e..e1238d7 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>
@@ -2578,6 +2607,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>
@@ -4340,7 +4478,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 1f94721..c5bb357 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,6 +31,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"
@@ -1591,7 +1592,9 @@ DelRoleMems(const char *rolename, Oid roleid,
/*
* FlattenPasswordIdentifiers
- * Make list of password verifier types and values consistent with input.
+ * Make list of password verifier types and values consistent with the output
+ * wanted, and adapt the specifier value if possible, informing user in case of
+ * incorrect verifier used.
*/
static void
FlattenPasswordIdentifiers(List *verifiers, char *rolname)
@@ -1622,7 +1625,11 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
* but that the verifier has a plain format switch type of
* verifier accordingly.
*/
- if (!isMD5(spec->value))
+ if (is_scram_verifier(spec->value))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid MD5 verifier: SCRAM verifier found")));
+ else if (!isMD5(spec->value))
{
char encrypted_passwd[MD5_PASSWD_LEN + 1];
@@ -1635,10 +1642,21 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
break;
case AUTH_VERIFIER_PLAIN:
- if (isMD5(spec->value))
+ if (is_scram_verifier(spec->value))
+ spec->veriftype = AUTH_VERIFIER_SCRAM;
+ else if (isMD5(spec->value))
spec->veriftype = AUTH_VERIFIER_MD5;
break;
+ case AUTH_VERIFIER_SCRAM:
+ if (isMD5(spec->value))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid SCRAM verifier: MD5 verifier found")));
+ else if (!is_scram_verifier(spec->value))
+ spec->value = scram_build_verifier(rolname, spec->value, 0);
+ break;
+
default:
Assert(0); /* should not happen */
}
@@ -1671,7 +1689,9 @@ FlattenPasswordIdentifiers(List *verifiers, char *rolname)
if ((strcmp(meth_name, AUTH_VERIFIER_FULL_MD5) == 0 &&
spec->veriftype == AUTH_VERIFIER_MD5) ||
(strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0 &&
- spec->veriftype == AUTH_VERIFIER_PLAIN))
+ spec->veriftype == AUTH_VERIFIER_PLAIN) ||
+ (strcmp(meth_name, AUTH_VERIFIER_FULL_SCRAM) == 0 &&
+ spec->veriftype == AUTH_VERIFIER_SCRAM))
{
found_match = true;
break;
@@ -1829,6 +1849,12 @@ pg_auth_verifiers_sanitize(PG_FUNCTION_ARGS)
remove_entry = false;
break;
}
+ else if (authform->vermethod == AUTH_VERIFIER_SCRAM &&
+ strcmp(meth_name, "scram") == 0)
+ {
+ remove_entry = false;
+ break;
+ }
}
if (remove_entry)
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..0d53348
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,682 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_auth_verifiers 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/encode.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"
+
+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;
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+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 */
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+
+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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index c061bf0..8fb9c62 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,15 +21,19 @@
#include <arpa/inet.h>
#include <unistd.h>
+#include "access/htup_details.h"
+#include "catalog/pg_auth_verifiers.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/ip.h"
#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"
+#include "utils/syscache.h"
/*----------------------------------------------------------------
@@ -185,6 +189,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -246,6 +256,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 +534,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, "");
@@ -690,6 +705,108 @@ 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;
+ HeapTuple roleTup;
+
+ /*
+ * 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")));
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(port->user_name));
+ if (!HeapTupleIsValid(roleTup))
+ return STATUS_ERROR;
+
+ /* lookup verifier */
+ verifier = get_role_verifier(HeapTupleGetOid(roleTup), AUTH_VERIFIER_SCRAM);
+ if (verifier == NULL)
+ {
+ ReleaseSysCache(roleTup);
+ return STATUS_ERROR;
+ }
+
+ ReleaseSysCache(roleTup);
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA1_NAME,
+ strlen(SCRAM_SHA1_NAME) + 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;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index e41b837..494149f 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -33,10 +33,10 @@
#include "utils/timestamp.h"
/*
- * Get verifier stored in pg_auth_verifiers tuple, for given authentication
+ * Get verifier stored in pg_auth_verifiers, for given authentication
* method.
*/
-static char *
+char *
get_role_verifier(Oid roleid, const char method)
{
HeapTuple tuple;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 28f9fb5..df0cc1d 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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/parser/gram.y b/src/backend/parser/gram.y
index e1039c6..4421f55 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -939,6 +939,8 @@ AuthVerifierSpec:
type = AUTH_VERIFIER_MD5;
else if (strcmp($1, AUTH_VERIFIER_FULL_PLAIN) == 0)
type = AUTH_VERIFIER_PLAIN;
+ else if (strcmp($1, AUTH_VERIFIER_FULL_SCRAM) == 0)
+ type = AUTH_VERIFIER_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -968,6 +970,8 @@ AlterOptRoleElem:
veriftype = AUTH_VERIFIER_MD5;
else if (strcmp(meth_name, AUTH_VERIFIER_FULL_PLAIN) == 0)
veriftype = AUTH_VERIFIER_PLAIN;
+ else if (strcmp(meth_name, AUTH_VERIFIER_FULL_SCRAM) == 0)
+ veriftype = AUTH_VERIFIER_SCRAM;
else
Assert(false); /* should not happen */
n = (AuthVerifierSpec *)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 525155b..00e95bb 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2340,6 +2340,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 94599cc..862d315 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode.h"
#include "lib/hyperloglog.h"
#include "libpq/md5.h"
#include "libpq/pqformat.h"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ab2bc3f..7cd64c0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3410,7 +3410,7 @@ static struct config_string ConfigureNamesString[] =
GUC_LIST_INPUT
},
&password_protocols,
- "plain,md5",
+ "plain,md5,scram",
check_password_methods, NULL, NULL
},
@@ -10271,7 +10271,8 @@ check_password_methods(char **newval, void **extra, GucSource source)
char *method_name = (char *) lfirst(l);
if (strcmp(method_name, AUTH_VERIFIER_FULL_MD5) != 0 &&
- strcmp(method_name, AUTH_VERIFIER_FULL_PLAIN) != 0)
+ strcmp(method_name, AUTH_VERIFIER_FULL_PLAIN) != 0 &&
+ strcmp(method_name, AUTH_VERIFIER_FULL_SCRAM) != 0)
{
pfree(rawstring);
list_free(elemlist);
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 4bf36c4..9ab1e1c 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -932,6 +932,8 @@ dumpRoles(PGconn *conn)
appendPQExpBufferStr(buf, "md5 = ");
else if (verifier_meth == 'p')
appendPQExpBufferStr(buf, "plain = ");
+ else if (verifier_meth == 's')
+ appendPQExpBufferStr(buf, "scram = ");
appendStringLiteralConn(buf, verifier_value, conn);
}
if (current_user != NULL)
diff --git a/src/common/Makefile b/src/common/Makefile
index 2fb88ff..7b891c6 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,8 +37,8 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
- pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o sha1.o \
- string.o username.o wait_error.o
+ pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
+ scram-common.o sha1.o string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..a17387e
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,170 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (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"
+#include "utils/memutils.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 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)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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/catalog/pg_auth_verifiers.h b/src/include/catalog/pg_auth_verifiers.h
index 86461d7..82da354 100644
--- a/src/include/catalog/pg_auth_verifiers.h
+++ b/src/include/catalog/pg_auth_verifiers.h
@@ -60,9 +60,11 @@ typedef FormData_pg_auth_verifiers *Form_pg_auth_verifiers;
/* catalog-level verifier identifiers */
#define AUTH_VERIFIER_PLAIN 'p' /* plain verifier */
#define AUTH_VERIFIER_MD5 'm' /* md5 verifier */
+#define AUTH_VERIFIER_SCRAM 's' /* SCRAM verifier */
/* full-name verifier identifiers */
#define AUTH_VERIFIER_FULL_PLAIN "plain"
#define AUTH_VERIFIER_FULL_MD5 "md5"
+#define AUTH_VERIFIER_FULL_SCRAM "scram"
#endif /* PG_AUTH_VERIFIERS_H */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..3d99bc8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+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 3cd06b7..5a02534 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 5725bb4..93eec02 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(Oid roleid, char method);
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/libpq-be.h b/src/include/libpq/libpq-be.h
index 5d07b78..c5663f4 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..b9af4c4
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+/* Name of SCRAM-SHA1 per IANA */
+#define SCRAM_SHA1_NAME "SCRAM-SHA-1"
+
+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);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 59a00bb..7f09eef 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -156,8 +156,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..225cfe4 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,8 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha1.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 1b292d2..cf5c813 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,8 @@ 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/
+OBJS += encode.o scram-common.o sha1.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode.c 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..ebbd1db
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,386 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/encode.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;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+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;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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 != SCRAM_SALT_LEN)
+ 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,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ 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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ 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, SCRAM_CLIENT_KEY_NAME, 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index cd863a5..91e952b 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -428,6 +429,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(auth_mechanism, SCRAM_SHA1_NAME) == 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 +765,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 9d11654..f779fb2 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 5ad4755..6cd38bb 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,48 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if (msgLength > 4 &&
+ (areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT))
+ {
+ 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 SASL 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
@@ -3042,6 +3084,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 6c9bbf7..087c731 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 */
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 7f18799..8181f11 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -8,7 +8,8 @@ SET password_encryption = true; -- error
ERROR: invalid value for parameter "password_encryption": "true"
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
ERROR: invalid value for parameter "password_protocols": "novalue"
@@ -16,7 +17,8 @@ SET password_protocols = true; -- error
ERROR: invalid value for parameter "password_protocols": "true"
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
CREATE ROLE role_passwd1 PASSWORD 'role_pwd1';
@@ -83,6 +85,11 @@ LINE 1: ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif...
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ERROR: invalid MD5 verifier: SCRAM verifier found
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ERROR: invalid SCRAM verifier: MD5 verifier found
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
@@ -93,7 +100,7 @@ SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
role_passwd1 | m | md5
role_passwd2 | p | foo
role_passwd3 | m | md5
- role_passwd4 | m | md5
+ role_passwd4 | s | XxC
(4 rows)
-- entries for password_protocols
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index e973c5e..b5f1008 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -7,14 +7,16 @@ SET password_encryption = 'novalue'; -- error
SET password_encryption = true; -- error
SET password_encryption = 'md5'; -- ok
SET password_encryption = 'plain'; -- ok
-SET password_encryption = 'md5,plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+SET password_encryption = 'md5,plain,scram'; -- ok
-- Tests for GUC password_protocols
SET password_protocols = 'novalue'; -- error
SET password_protocols = true; -- error
SET password_protocols = 'md5'; -- ok
SET password_protocols = 'plain'; -- ok
-SET password_protocols = 'md5,plain'; -- ok
+SET password_protocols = 'scram'; -- ok
+SET password_protocols = 'md5,plain,scram'; -- ok
-- consistency of password entries
SET password_encryption = 'plain';
@@ -60,6 +62,9 @@ ALTER ROLE role_passwd1 PASSWORD VERIFIERS (unexistent_verif = 'foo'); -- error
ALTER ROLE role_passwd1 PASSWORD VERIFIERS (md5 = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- ok, as md5
ALTER ROLE role_passwd2 PASSWORD VERIFIERS (plain = 'foo'); -- ok, as plain
ALTER ROLE role_passwd3 PASSWORD VERIFIERS (md5 = 'foo'); -- ok, as md5
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (md5 = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (scram = 'md5deaeed29b1cf796ea981d53e82cd5856'); -- error
+ALTER ROLE role_passwd4 PASSWORD VERIFIERS (plain = 'XxCnrdnT4B0z1A==:4096:2713dffd3535173b4e346f4a498e4fb197a210fc:07065f00b3a74de04d0ea4295b18ea959ef2ca94'); -- ok, as scram
SELECT a.rolname, v.vermethod, substr(v.vervalue, 1, 3)
FROM pg_auth_verifiers v
LEFT JOIN pg_authid a ON (v.verroleid = a.oid)
--
2.7.3
On Fri, Mar 18, 2016 at 9:31 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
That's not an issue for me to rebase this set of patches. The only
conflicts that I anticipate are on 0009, but I don't have high hopes
to get this portion integrating into core for 9.6, the rest of the
patches is complicated enough, and everyone bandwidth is limited.
I really think we ought to consider pushing this whole thing out to
9.7. I don't see how we're going to get all of this into 9.6, and
these are big, user-facing changes that I don't think we should rush
into under time pressure. I think it'd be better to do this early in
the 9.7 cycle so that it has time to settle before the time crunch at
the end. I predict this is going to have a lot of loose ends that are
going to take months to settle, and we don't have that time right now.
And I'd rather see all of the changes in one release than split them
across two releases.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Mar 19, 2016 at 12:28 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Mar 18, 2016 at 9:31 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:That's not an issue for me to rebase this set of patches. The only
conflicts that I anticipate are on 0009, but I don't have high hopes
to get this portion integrating into core for 9.6, the rest of the
patches is complicated enough, and everyone bandwidth is limited.I really think we ought to consider pushing this whole thing out to
9.7. I don't see how we're going to get all of this into 9.6, and
these are big, user-facing changes that I don't think we should rush
into under time pressure. I think it'd be better to do this early in
the 9.7 cycle so that it has time to settle before the time crunch at
the end. I predict this is going to have a lot of loose ends that are
going to take months to settle, and we don't have that time right now.
And I'd rather see all of the changes in one release than split them
across two releases.
FWIW, the catalog separation is not that much a complicated patch, and
that's really a change independent on SCRAM, the main matter being to
manage critical index and relation entries correctly and it does not
touch the authentication code, which is what IMO is the sensitive
part. The catalog separation opens the door as well to multiple
verifiers for the same protocol for a single role, facilitating
password rolling policies, which is a feature that has been asked a
lot. Nothing prevents the development of moving validuntil into
pg_auth_verifiers in parallel of the SCRAM for the 9.7 release cycle,
though it would facilitate it to have some basic infra in place. Just
my 2c.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert, all,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Fri, Mar 18, 2016 at 9:31 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:That's not an issue for me to rebase this set of patches. The only
conflicts that I anticipate are on 0009, but I don't have high hopes
to get this portion integrating into core for 9.6, the rest of the
patches is complicated enough, and everyone bandwidth is limited.I really think we ought to consider pushing this whole thing out to
9.7. I don't see how we're going to get all of this into 9.6, and
these are big, user-facing changes that I don't think we should rush
into under time pressure. I think it'd be better to do this early in
the 9.7 cycle so that it has time to settle before the time crunch at
the end. I predict this is going to have a lot of loose ends that are
going to take months to settle, and we don't have that time right now.
I'm not sure that I agree with the above. This patch has been through
the ringer multiple times regarding the user-facing bits and, by and
large, the results appear reasonable. Further, getting a better auth
method into PG is something which I do view as a priority considering
the concerns and complaints that have been, justifiably, raised against
our current password-based authentication support.
This isn't a new patch set either, it was submitted initially over the
summer after it was pointed out, over a year ago, that people actually
do care about the problems with our current implementation (amusingly, I
recall having pointed out the same 5+ years ago, but only did so to this
list).
I've been following along on this patch set and asked David to spend
time reviewing it as I feel that it's stil got a chance for 9.6, since
it's been through multiple CF rounds and has had a fair bit of
discussion, review, and consideration.
And I'd rather see all of the changes in one release than split them
across two releases.
I agree with this. If we aren't going to get SCRAM into 9.6 then the
rest is just breaking things with little benefit. I'm optomistic that
we will be able to include SCRAM support in 9.6, but if that ends up not
being feasible then we need to put all of the changes to the next
release.
I do think that if we push this off to 9.7 then we're going to have
SCRAM *plus* a bunch of other changes around password policies in that
release, and it'd be better to introduce SCRAM independently of the
other changes.
All that said, this is just my voice from having followed this thread
and discussing it with David and I'm not trying to force anything. It'd
certainly be nice to have and to be able to tell people that we do have
a strong and recognized approach to password-based authentication in PG,
but I've long been telling everyone that they should be using GSSAPI
and/or SSL and can continue to do so for another year if necessary.
Thanks!
Stephen
On Fri, Mar 18, 2016 at 2:12 PM, Stephen Frost <sfrost@snowman.net> wrote:
I'm not sure that I agree with the above. This patch has been through
the ringer multiple times regarding the user-facing bits and, by and
large, the results appear reasonable. Further, getting a better auth
method into PG is something which I do view as a priority considering
the concerns and complaints that have been, justifiably, raised against
our current password-based authentication support.This isn't a new patch set either, it was submitted initially over the
summer after it was pointed out, over a year ago, that people actually
do care about the problems with our current implementation (amusingly, I
recall having pointed out the same 5+ years ago, but only did so to this
list).
I am not disputing the importance of the topic, and I do realize that
the patch has been around in some form since March. However, I don't
think there's been a whole heck of a lot in terms of detailed
code-level review, and I think that's pretty important for something
that necessarily involves wire protocol changes. Doing that with the
level of detail and care that it seems to me to require seems like an
almost-impossible task. Most of the major features I've committed
this CommitFest are patches where I've personally done multiple rounds
of review on over the last several months, and in many cases, other
people have been doing code reviews for months before that. I'm not
denying that this patch has prompted a good deal of discussion and
what I would call design review, but detailed code review? I just
haven't seen much of that.
And I'd rather see all of the changes in one release than split them
across two releases.I agree with this. If we aren't going to get SCRAM into 9.6 then the
rest is just breaking things with little benefit. I'm optomistic that
we will be able to include SCRAM support in 9.6, but if that ends up not
being feasible then we need to put all of the changes to the next
release.
OK, glad we agree on that.
I do think that if we push this off to 9.7 then we're going to have
SCRAM *plus* a bunch of other changes around password policies in that
release, and it'd be better to introduce SCRAM independently of the
other changes.
Well, for my part, I'd be happy enough to do all of that in a release
cycle - maybe SCRAM at the beginning and those other changes a little
later on. I don't see that as a real conflict, and in fact, sometimes
when you do several things like that in a single cycle, people start
to see whatever the common theme is - security, say - as part of the
message of that release a little more than they would if a feature
lands here and another there. That's not all a bad thing.
All that said, this is just my voice from having followed this thread
and discussing it with David and I'm not trying to force anything. It'd
certainly be nice to have and to be able to tell people that we do have
a strong and recognized approach to password-based authentication in PG,
but I've long been telling everyone that they should be using GSSAPI
and/or SSL and can continue to do so for another year if necessary.
I agree it's unfortunate, but IMHO that's kinda where we are at. If
Heikki were still involved and had been working on this, I strongly
suspect it would have been committed already. But he's not, and it's
not clear when or if he's coming back, and I cannot imagine how we are
going to begin and complete pushing in a feature of this magnitude in
the three weeks before feature freeze without a lot of collateral
damage. That is an opinion, not a fact, but it's one I feel pretty
confident about.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Mar 19, 2016 at 3:52 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Mar 18, 2016 at 2:12 PM, Stephen Frost <sfrost@snowman.net> wrote:
I'm not sure that I agree with the above. This patch has been through
the ringer multiple times regarding the user-facing bits and, by and
large, the results appear reasonable. Further, getting a better auth
method into PG is something which I do view as a priority considering
the concerns and complaints that have been, justifiably, raised against
our current password-based authentication support.This isn't a new patch set either, it was submitted initially over the
summer after it was pointed out, over a year ago, that people actually
do care about the problems with our current implementation (amusingly, I
recall having pointed out the same 5+ years ago, but only did so to this
list).I am not disputing the importance of the topic, and I do realize that
the patch has been around in some form since March. However, I don't
think there's been a whole heck of a lot in terms of detailed
code-level review, and I think that's pretty important for something
that necessarily involves wire protocol changes.
Yep, that's the desert here, though there are surely a lot of people
would like a way to get out of md5 and get into something more modern
(see STIG), and many companies want to get something, my company
included, though this is really a complicated task, and there are few
people who could really help out here I guess.
Doing that with the
level of detail and care that it seems to me to require seems like an
almost-impossible task. Most of the major features I've committed
this CommitFest are patches where I've personally done multiple rounds
of review on over the last several months, and in many cases, other
people have been doing code reviews for months before that. I'm not
denying that this patch has prompted a good deal of discussion and
what I would call design review, but detailed code review? I just
haven't seen much of that.
There has been none, as well as no real discussion regarding what we
want to do. The current result, particularly for the management of
protocol aging, is based on things I wrote by myself which negate the
many negative opinions received up to now for the past patches (mainly
the feedback was "I don't like that", without real output or fresh
ideas during discussion to explain why that's the case).
And I'd rather see all of the changes in one release than split them
across two releases.I agree with this. If we aren't going to get SCRAM into 9.6 then the
rest is just breaking things with little benefit. I'm optimistic that
we will be able to include SCRAM support in 9.6, but if that ends up not
being feasible then we need to put all of the changes to the next
release.OK, glad we agree on that.
Speaking as a co-author of the stuff of this thread, the two main
patches are 0001, introducing pg_auth_verifiers and 0009, adding
SCRAM-SHA1. The rest is just refactoring and addition of a couple of
utilities to manage the protocol aging, which are really
straight-forward, and all the user-visible changes are introduced by
0001. While I really like the shape of 0001, 0009 is not there yet,
and really requires more time than 3 weeks, that's more than what I
can do by feature freeze of 9.6. So if the conclusion is if there is
no SCRAM, all the other changes don't make much sense, let's bump it
to 9.7. There is honestly still interest from here, and I would guess
that the only thing I could do on top of having patches for the first
CF of 9.7 is discussing the topic at the dev unconference of PGCon.
I do think that if we push this off to 9.7 then we're going to have
SCRAM *plus* a bunch of other changes around password policies in that
release, and it'd be better to introduce SCRAM independently of the
other changes.Well, for my part, I'd be happy enough to do all of that in a release
cycle - maybe SCRAM at the beginning and those other changes a little
later on. I don't see that as a real conflict, and in fact, sometimes
when you do several things like that in a single cycle, people start
to see whatever the common theme is - security, say - as part of the
message of that release a little more than they would if a feature
lands here and another there. That's not all a bad thing.
Having a centralized theme for a given release cycle is not a bad
thing, I agree. And I'd like to think that the same discussion is not
going to happen again in one year...
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Mar 19, 2016 at 8:30 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Doing that with the
level of detail and care that it seems to me to require seems like an
almost-impossible task. Most of the major features I've committed
this CommitFest are patches where I've personally done multiple rounds
of review on over the last several months, and in many cases, other
people have been doing code reviews for months before that. I'm not
denying that this patch has prompted a good deal of discussion and
what I would call design review, but detailed code review? I just
haven't seen much of that.There has been none, as well as no real discussion regarding what we
want to do. The current result, particularly for the management of
protocol aging, is based on things I wrote by myself which negate the
many negative opinions received up to now for the past patches (mainly
the feedback was "I don't like that", without real output or fresh
ideas during discussion to explain why that's the case).
Well, I said before and I'll say again that I don't like the idea of
multiple password verifiers. I think that's an accident waiting to
happen, and I'm not prepared to put in the amount of time and energy
that it would take to get that feature committed despite not wanting
it myself, or for being responsible for it afterwards. I'd prefer we
didn't do it at all, although I'm not going to dig in my heels. I
might be willing to deal with SCRAM itself, but this whole area is not
my strongest suit. So ideally some other committer would be willing
to pick this up.
But the problem isn't even just that somebody has to hit the final
commit button - as we've both said, there's a woeful lack of any
meaningful review on this thread, and this sort of change really needs
quite a lot of review. This has implications for
backward-compatibility, for connectors that don't use libpq, etc.
Really, I'm not even sure we have consensus on the direction. I mean,
Heikki's proposal to adopt SCRAM sounds good enough at a broad level,
but I don't really know what the alternatives are, I'm mostly just
taking his word for it, and like you say, there's been a fair amount
of miscellaneous negativity floating around.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Mar 21, 2016 at 11:07 PM, Robert Haas <robertmhaas@gmail.com> wrote:
Well, I said before and I'll say again that I don't like the idea of
multiple password verifiers. I think that's an accident waiting to
happen, and I'm not prepared to put in the amount of time and energy
that it would take to get that feature committed despite not wanting
it myself, or for being responsible for it afterwards. I'd prefer we
didn't do it at all, although I'm not going to dig in my heels. I
might be willing to deal with SCRAM itself, but this whole area is not
my strongest suit. So ideally some other committer would be willing
to pick this up.
I won't bet my hand on that.
But the problem isn't even just that somebody has to hit the final
commit button - as we've both said, there's a woeful lack of any
meaningful review on this thread, and this sort of change really needs
quite a lot of review.
Yep.
This has implications for
backward-compatibility, for connectors that don't use libpq, etc.
Really, I'm not even sure we have consensus on the direction. I mean,
Heikki's proposal to adopt SCRAM sounds good enough at a broad level,
but I don't really know what the alternatives are, I'm mostly just
taking his word for it, and like you say, there's been a fair amount
of miscellaneous negativity floating around.
PAKE or J-PAKE are other alternatives I have in mind.
I have marked the patch as returned with feedback.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 22, 2016 at 2:48 PM, Michael Paquier <michael.paquier@gmail.com>
wrote:
On Mon, Mar 21, 2016 at 11:07 PM, Robert Haas <robertmhaas@gmail.com>
wrote:Well, I said before and I'll say again that I don't like the idea of
multiple password verifiers. I think that's an accident waiting to
happen, and I'm not prepared to put in the amount of time and energy
that it would take to get that feature committed despite not wanting
it myself, or for being responsible for it afterwards. I'd prefer we
didn't do it at all, although I'm not going to dig in my heels. I
might be willing to deal with SCRAM itself, but this whole area is not
my strongest suit. So ideally some other committer would be willing
to pick this up.I won't bet my hand on that.
In principle I'd be happy to look at it, but I doubt that I will have
enough time to get it done within this CF unfortunately. Thus I'd rather
not commit to doing it.. It kind of fell off my radar too long ago, as I
was originally planning to look at it back in the autumn, but failed.
So basically, if somebody else has the cycles to do it in time for 9.6,
please do.
I have marked the patch as returned with feedback.
Yeah, unfortunately I think that's probably right. Let's focus on things
that have a better chance of making it.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
----[This is a rather informal user-review]----
Here are some thoughts and experiences on using the new features, I
focused on testing the basic funcionality of setting password_encryption
to scram and then generating some users with passwords. After that, I
took a look at the documentation, specifically all those parts that
mentioned "md5", but not SCRAM, so i took some time to write those down
and add my thoughts on them.
We're quite keen on seeing these features in a future release, so I
suggest that we add these patches to the next commitfest asap in order
to keep the discussion on this topic flowing.
For those of you who like to put the authentication method itself up for
discussion, I'd like to add that it seems fairly simple to insert code
for new authentication mechanisms.
In conclusion I think these patches are very useful.
My remarks follow below.
Kind regards,
Julian Markwort
julian.markwort@uni-muenster.de
Things I noticed:
1.
when using either
CREATE ROLE
ALTER ROLE
with the parameter
ENCRYPTED
md5 encryption is always assumed (I've come to realize that
UNENCRYPTED always equals plain and, in the past, ENCRYPTED equaled md5
since there were no other options)
I don't know if this is intended behaviour. Maybe this option
should be omitted (or marked as deprecated in the documentation) from
the CREATE/ALTER functions (since without this Option, the
password_encryption from pg_conf.hba is used)
or maybe it should have it's own parameter like
CREATE ROLE testuser WITH LOGIN ENCRYPTED 'SCRAM' PASSWORD 'test';
so that the desired encryption is used.
From my point of view, this would be the sensible thing to do,
especially if different verifiers should be allowed (as proposed by
these patches).
In either case, a bit of text explaining the (UN)ENCRYPTED option
should be added to the documentation of the CREATE/ALTER ROLE functions.
2.
Documentation
III.
17. Server Setup and Operation
17.2. Creating a Database Cluster: maybe list SCRAM as a
possible method for securing the db-admin
19. Client Authentication
19.1. The pg_hba.conf File: SCRAM is not listed in the list
of available auth_methods to be specified in pg_conf.hba
19.3 Authentication Methods
19.3.2 Password Authentication: SCRAM would belong to
the same category as md5 and password, as they are all password-based.
20. Database Roles
20.2. Role Attributes: password : list SCRAM as
authentication method as well
VI.
ALTER ROLE: is SCRAM also dependent on the role name for
salting? if so, add warning.
(it doesn't seem that way, however I'm curious as
to why the function FlattenPasswordIdentifiers in
src/backend/commands/user.c called by AlterRole passes rolname to
scram_build_verifier(), when that function does absolutely nothing with
this argument?)
CREATE ROLE: can SCRAM also be used in the list of PASSWORD
VERIFIERS?
VII.
49. System Catalogs:
49.9 pg_auth_verifiers: Column names and types are mixed up
in description for column vervalue:
explain some basic stuff about md5
maybe as well?
remark: the statements about the
composition of the string that is md5-hashed are contradictory.
(concatenating "bar" to "foo"
results in foobar, not the other way round, as it is implied in the
explanation of the md5 hashing), this however, is not really linked to
the changes introduced with these patches.
remark: naming inconsistency: md5
vervalues are stored "md5*" why don't we take the same approach and use
it on SCRAM hashes (i.e. "scram*" ).
(if this is a general convention
thing, please ignore this comment, however I couldn't find anything in
the relevant RFC's while skimming through them).
50. Frontend/Backend Protocol
50.2.1 Start-up: add explanation for
"AuthenticationSCRAMPassword" authentication request message. (?)
50.5 message formats see 50.2.1
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 30, 2016 at 1:44 AM, Julian Markwort
<julian.markwort@uni-muenster.de> wrote:
----[This is a rather informal user-review]----
Here are some thoughts and experiences on using the new features, I focused
on testing the basic funcionality of setting password_encryption to scram
and then generating some users with passwords. After that, I took a look at
the documentation, specifically all those parts that mentioned "md5", but
not SCRAM, so i took some time to write those down and add my thoughts on
them.We're quite keen on seeing these features in a future release, so I suggest
that we add these patches to the next commitfest asap in order to keep the
discussion on this topic flowing.For those of you who like to put the authentication method itself up for
discussion, I'd like to add that it seems fairly simple to insert code for
new authentication mechanisms.
In conclusion I think these patches are very useful.
The reception of the concept of multiple password verifiers for a
single role was rather... cold. So except if a committer pushes hard
for it is never going to show up. There is clear consensus that SCRAM
is something needed though, so we may as well just focus on that.
Things I noticed:
1.
when using either
CREATE ROLE
ALTER ROLE
with the parameter
ENCRYPTED
md5 encryption is always assumed (I've come to realize that UNENCRYPTED
always equals plain and, in the past, ENCRYPTED equaled md5 since there were
no other options)
Yes, that's to match the current behavior, and make something fully
backward-compatible. Switching to md5 + scram may have made sense as
well though.
I don't know if this is intended behaviour.
This is an intended behavior.
Maybe this option should be
omitted (or marked as deprecated in the documentation) from the CREATE/ALTER
functions (since without this Option, the password_encryption from
pg_conf.hba is used)
or maybe it should have it's own parameter like
CREATE ROLE testuser WITH LOGIN ENCRYPTED 'SCRAM' PASSWORD 'test';
so that the desired encryption is used.
From my point of view, this would be the sensible thing to do,
especially if different verifiers should be allowed (as proposed by these
patches).
The extension PASSWORD VERIFIERS is aimed at covering this need. The
grammar of those queries is not a fixed thing though.
In either case, a bit of text explaining the (UN)ENCRYPTED option should
be added to the documentation of the CREATE/ALTER ROLE functions.
It is specified here;
http://www.postgresql.org/docs/devel/static/sql-createrole.html
And the patch does not ignore that.
2.
Documentation
III.
17. Server Setup and Operation
17.2. Creating a Database Cluster: maybe list SCRAM as a
possible method for securing the db-admin
Indeed.
19. Client Authentication
19.1. The pg_hba.conf File: SCRAM is not listed in the list of
available auth_methods to be specified in pg_conf.hba
19.3 Authentication Methods
19.3.2 Password Authentication: SCRAM would belong to the
same category as md5 and password, as they are all password-based.20. Database Roles
20.2. Role Attributes: password : list SCRAM as authentication
method as well
Indeed.
VI.
ALTER ROLE: is SCRAM also dependent on the role name for salting? if
so, add warning.
No.
(it doesn't seem that way, however I'm curious as to why
the function FlattenPasswordIdentifiers in src/backend/commands/user.c
called by AlterRole passes rolname to scram_build_verifier(), when that
function does absolutely nothing with this argument?)
Yeah, this argument could be removed.
CREATE ROLE: can SCRAM also be used in the list of PASSWORD
VERIFIERS?
Yes.
VII.
49. System Catalogs:
49.9 pg_auth_verifiers: Column names and types are mixed up
in description for column vervalue:
Yes, things are messed up a bit there. Thanks for noticing.
remark: naming inconsistency: md5
vervalues are stored "md5*" why don't we take the same approach and use it
on SCRAM hashes (i.e. "scram*" ).
Perhaps this makes sense if there is no pg_auth_verifiers.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 30, 2016 at 9:46 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Things I noticed:
1.
when using either
CREATE ROLE
ALTER ROLE
with the parameter
ENCRYPTED
md5 encryption is always assumed (I've come to realize that UNENCRYPTED
always equals plain and, in the past, ENCRYPTED equaled md5 since there were
no other options)Yes, that's to match the current behavior, and make something fully
backward-compatible. Switching to md5 + scram may have made sense as
well though.
I think we're not going to have much luck getting people to switch
over to SCRAM if the default remains MD5. Perhaps there should be a
GUC for this - and we can initially set that GUC to md5, allowing
people who are ready to adopt SCRAM to change it. And then in a later
release we can change the default, once we're pretty confident that
most connectors have added support for the new authentication method.
This is going to take a long time to roll out. Alternatively, we
could control it strictly through DDL.
Note that the existing behavior is pretty wonky:
alter user rhaas unencrypted password 'foo'; -> rolpassword foo
alter user rhaas encrypted password 'foo'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
alter user rhaas encrypted password
'md5e748797a605a1c95f3d6b5f140b2d528'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
alter user rhaas unencrypted password
'md5e748797a605a1c95f3d6b5f140b2d528'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
So basically the use of the ENCRYPTED keyword means "if it does
already seem to be the sort of MD5 blob we're expecting, turn it into
that". And we just rely on the format to distinguish between an MD5
verifier and an unencrypted password. Personally, I think a good
start here, and I think you may have something like this in the patch
already, would be to split rolpassword into two columns, say
rolencryption and rolpassword. rolencryption says how the password
verifier is encrypted and rolpassword contains the verifier itself.
Initially, rolencryption will be 'plain' or 'md5', but later we can
add 'scram' as another choice, or maybe it'll be more specific like
'scram-hmac-doodad'. And then maybe introduce syntax like this:
alter user rhaas set password 'raw-unencrypted-passwordt' using
'verifier-method';
alter user rhaas set password verifier 'verifier-goes-here' using
'verifier-method';
That might require making verifier a key word, which would be good to
avoid. Perhaps we could use "password validator" instead?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 03/30/2016 06:14 PM, Robert Haas wrote:
So basically the use of the ENCRYPTED keyword means "if it does
already seem to be the sort of MD5 blob we're expecting, turn it into
that".
If it does NOT already seem to be... I guess?
And we just rely on the format to distinguish between an MD5 verifier
and an unencrypted password. Personally, I think a good start here,
and I think you may have something like this in the patch already,
would be to split rolpassword into two columns, say rolencryption and
rolpassword.
This inches closer to Michael's suggestion to have multiple verifiers
per pg_authid user ...
rolencryption says how the password verifier is encrypted and
rolpassword contains the verifier itself. Initially, rolencryption
will be 'plain' or 'md5', but later we can add 'scram' as another
choice, or maybe it'll be more specific like 'scram-hmac-doodad'.
May I suggest using "{" <scheme>["."<encoding>] "}" just like Dovecot does?
e.g. "{md5.hex}e748797a605a1c95f3d6b5f140b2d528"
where no "{ ... }" prefix means just fallback to the old method of
trying to guess what the blob contains?
This would invalidate PLAIN passwords beginning with "{", though,
so some measures would be needed.
And then maybe introduce syntax like this: alter user rhaas set
password 'raw-unencrypted-passwordt' using 'verifier-method'; alter
user rhaas set password verifier 'verifier-goes-here' using
'verifier-method'; That might require making verifier a key word,
which would be good to avoid. Perhaps we could use "password
validator" instead?
I'd like USING best ... though by prepending the schema for ENCRYPTED,
the required information is already conveyed within the verifier, so no
need to specify it again :)
Just my .02€
/ J.L.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 30, 2016 at 12:31 PM, José Luis Tallón
<jltallon@adv-solutions.net> wrote:
On 03/30/2016 06:14 PM, Robert Haas wrote:
So basically the use of the ENCRYPTED keyword means "if it does already
seem to be the sort of MD5 blob we're expecting, turn it into that".If it does NOT already seem to be... I guess?
Yes, that's what I meant. Sorry.
rolencryption says how the password verifier is encrypted and rolpassword
contains the verifier itself. Initially, rolencryption will be 'plain' or
'md5', but later we can add 'scram' as another choice, or maybe it'll be
more specific like 'scram-hmac-doodad'.May I suggest using "{" <scheme>["."<encoding>] "}" just like Dovecot does?
Doesn't seem very SQL-ish to me... I think we should normalize.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Mar 31, 2016 at 1:14 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Mar 30, 2016 at 9:46 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:Things I noticed:
1.
when using either
CREATE ROLE
ALTER ROLE
with the parameter
ENCRYPTED
md5 encryption is always assumed (I've come to realize that UNENCRYPTED
always equals plain and, in the past, ENCRYPTED equaled md5 since there were
no other options)Yes, that's to match the current behavior, and make something fully
backward-compatible. Switching to md5 + scram may have made sense as
well though.I think we're not going to have much luck getting people to switch
over to SCRAM if the default remains MD5. Perhaps there should be a
GUC for this - and we can initially set that GUC to md5, allowing
people who are ready to adopt SCRAM to change it. And then in a later
release we can change the default, once we're pretty confident that
most connectors have added support for the new authentication method.
This is going to take a long time to roll out.
Alternatively, we could control it strictly through DDL.
This maps quite a lot with the existing password_encryption, so adding
a GUC to control only the format of protocols only for ENCRYPTED is
disturbing, say password_encryption_encrypted. I'd rather keep
ENCRYPTED to md5 as default when password_encryption is 'on', switch
to scram a couple of releases later, and extend the DDL grammar with
something like PROTOCOL {'md5' | 'plain' | 'scram'}, which can be used
instead of UNENCRYPTED | ENCRYPTED as an additional keyword. Smooth
transition to a more-extensive system.
Note that the existing behavior is pretty wonky:
alter user rhaas unencrypted password 'foo'; -> rolpassword foo
alter user rhaas encrypted password 'foo'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
alter user rhaas encrypted password
'md5e748797a605a1c95f3d6b5f140b2d528'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
alter user rhaas unencrypted password
'md5e748797a605a1c95f3d6b5f140b2d528'; -> rolpassword
md5e748797a605a1c95f3d6b5f140b2d528
I actually wrote some regression tests for that. Those are upthread as
part of 0001, have for example a look at password.sql.
So basically the use of the ENCRYPTED keyword means "if it does
already seem to be the sort of MD5 blob we're expecting, turn it into
that". And we just rely on the format to distinguish between an MD5
verifier and an unencrypted password. Personally, I think a good
start here, and I think you may have something like this in the patch
already, would be to split rolpassword into two columns, say
rolencryption and rolpassword. rolencryption says how the password
verifier is encrypted and rolpassword contains the verifier itself.
The patch has something like that. And doing this split is not that
complicated to be honest. Surely that would be clearer than relying on
the prefix of the identifier to see if it is md5 or not.
Initially, rolencryption will be 'plain' or 'md5', but later we can
add 'scram' as another choice, or maybe it'll be more specific like
'scram-hmac-doodad'. And then maybe introduce syntax like this:alter user rhaas set password 'raw-unencrypted-passwordt' using
'verifier-method';
alter user rhaas set password verifier 'verifier-goes-here' using
'verifier-method';That might require making verifier a key word, which would be good to
avoid. Perhaps we could use "password validator" instead?
Yes, that matches what I wrote above. At this point putting that back
on board and discuss it openly at PGCon is the best course of action
IMO.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
So, the consensus so far seems to be: We don't want the support for
multiple password verifiers per user. At least not yet. Let's get SCRAM
working first, in a way that a user can only have SCRAM or an MD5 hash
stored in the database, not both. We can add support for multiple
verifiers per user, password aging, etc. later. Hopefully we'll make
some progress on those before 9.7 is released, too, but let's treat them
as separate issues and focus on SCRAM.
I took a quick look at the patch set now again, and except that it needs
to have the multiple password verifier support refactored out, I think
it's in a pretty good shape. I don't like the pg_upgrade changes and its
support function, that also seems like an orthogonal or add-on feature
that would be better discussed separately. I think pg_upgrade should
just do the upgrade with as little change to the system as possible, and
let the admin reset/rehash/deprecate the passwords separately, when she
wants to switch all users to SCRAM. So I suggest that we rip out those
changes from the patch set as well.
In related news, RFC 7677 that describes a new SCRAM-SHA-256
authentication mechanism, was published in November 2015. It's identical
to SCRAM-SHA-1, which is what this patch set implements, except that
SHA-1 has been replaced with SHA-256. Perhaps we should forget about
SCRAM-SHA-1 and jump straight to SCRAM-SHA-256.
RFC 7677 also adds some verbiage, in response to vulnerabilities that
have been found with the "tls-unique" channel binding mechanism:
To be secure, either SCRAM-SHA-256-PLUS and SCRAM-SHA-1-PLUS MUST be
used over a TLS channel that has had the session hash extension
[RFC7627] negotiated, or session resumption MUST NOT have been used.
So that doesn't affect details of the protocol per se, but once we
implement channel binding, we need to check for those conditions somehow
(or make sure that OpenSSL checks for them).
Michael, do you plan to submit a new version of this patch set for the
next commitfest? I'd like to get this committed early in the 9.7 release
cycle, so that we have time to work on all the add-on stuff before the
release.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jul 3, 2016 at 4:54 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
I took a quick look at the patch set now again, and except that it needs to
have the multiple password verifier support refactored out, I think it's in
a pretty good shape. I don't like the pg_upgrade changes and its support
function, that also seems like an orthogonal or add-on feature that would be
better discussed separately. I think pg_upgrade should just do the upgrade
with as little change to the system as possible, and let the admin
reset/rehash/deprecate the passwords separately, when she wants to switch
all users to SCRAM. So I suggest that we rip out those changes from the
patch set as well.
That's as well what I recall from the consensus at PGCon: only focus
on the protocol addition and storage of the scram verifier. It was not
mentioned directly but that's what I guess should be done. So no
complains here.
In related news, RFC 7677 that describes a new SCRAM-SHA-256 authentication
mechanism, was published in November 2015. It's identical to SCRAM-SHA-1,
which is what this patch set implements, except that SHA-1 has been replaced
with SHA-256. Perhaps we should forget about SCRAM-SHA-1 and jump straight
to SCRAM-SHA-256.
That's to consider. I don't thing switching to that is much complicated.
RFC 7677 also adds some verbiage, in response to vulnerabilities that have
been found with the "tls-unique" channel binding mechanism:To be secure, either SCRAM-SHA-256-PLUS and SCRAM-SHA-1-PLUS MUST be
used over a TLS channel that has had the session hash extension
[RFC7627] negotiated, or session resumption MUST NOT have been used.So that doesn't affect details of the protocol per se, but once we implement
channel binding, we need to check for those conditions somehow (or make sure
that OpenSSL checks for them).
Yes.
Michael, do you plan to submit a new version of this patch set for the next
commitfest? I'd like to get this committed early in the 9.7 release cycle,
so that we have time to work on all the add-on stuff before the release.
Thanks. That's good news! Yes, I am still on track to submit a patch for CF1.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 7/2/16 6:32 PM, Michael Paquier wrote:
On Sun, Jul 3, 2016 at 4:54 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Michael, do you plan to submit a new version of this patch set for the next
commitfest? I'd like to get this committed early in the 9.7 release cycle,
so that we have time to work on all the add-on stuff before the release.Thanks. That's good news! Yes, I am still on track to submit a patch for CF1.
And I'm on board for reviews, testing, and whatever else I can help with.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 7/2/16 3:54 PM, Heikki Linnakangas wrote:
In related news, RFC 7677 that describes a new SCRAM-SHA-256
authentication mechanism, was published in November 2015. It's identical
to SCRAM-SHA-1, which is what this patch set implements, except that
SHA-1 has been replaced with SHA-256. Perhaps we should forget about
SCRAM-SHA-1 and jump straight to SCRAM-SHA-256.
I think a global change from SHA-1 to SHA-256 is in the air already, so
if we're going to release something brand new in 2017 or so, it should
be SHA-256.
I suspect this would be a relatively simple change, so I wouldn't mind
seeing a SHA-1-based variant in CF1 to get things rolling.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 4, 2016 at 6:34 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 7/2/16 3:54 PM, Heikki Linnakangas wrote:
In related news, RFC 7677 that describes a new SCRAM-SHA-256
authentication mechanism, was published in November 2015. It's identical
to SCRAM-SHA-1, which is what this patch set implements, except that
SHA-1 has been replaced with SHA-256. Perhaps we should forget about
SCRAM-SHA-1 and jump straight to SCRAM-SHA-256.I think a global change from SHA-1 to SHA-256 is in the air already, so if
we're going to release something brand new in 2017 or so, it should be
SHA-256.I suspect this would be a relatively simple change, so I wouldn't mind
seeing a SHA-1-based variant in CF1 to get things rolling.
I'd just move this thing to SHA256, we are likely going to use that at the end.
As I am coming back into that, I would as well suggest do the
following, that the current set of patches is clearly missing:
- Put the HMAC infrastructure stuff of pgcrypto into src/common/. It
is a bit a shame to not reuse what is currently available, then I
would suggest to reuse that with HMAC_SCRAM_SHAXXX as label.
- Move *all* the SHA-related things of pgcrypto to src/common,
including SHA1, SHA224 and SHA256. px_memset is a simple wrapper on
top of memset, we should clean up that first.
Any other things to consider that I am forgetting?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 4, 2016 at 12:54 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
As I am coming back into that, I would as well suggest do the
following, that the current set of patches is clearly missing:
- Put the HMAC infrastructure stuff of pgcrypto into src/common/. It
is a bit a shame to not reuse what is currently available, then I
would suggest to reuse that with HMAC_SCRAM_SHAXXX as label.
- Move *all* the SHA-related things of pgcrypto to src/common,
including SHA1, SHA224 and SHA256. px_memset is a simple wrapper on
top of memset, we should clean up that first.
Any other things to consider that I am forgetting?
After looking more into that, I have come up with PG-like equivalents
of things in openssl/sha.h:
pg_shaXX_init(pg_shaXX_ctx *ctx, data);
pg_shaXX_update(pg_shaXX_ctx *ctx, uint8 *data, size_t len);
pg_shaXX_final(uint8 *dest, pg_shaXX_ctx *ctx);
Then think about shaXX as 1, 224, 256, 384 and 512.
Hence all those functions, moved to src/common, finish with the
following shape, take an init() one:
#ifdef USE_SSL
#define <openssl/sha.h>
#endif
void
pg_shaXX_init(pg_shaXX_ctx *ctx)
{
#ifdef USE_SSL
SHAXX_Init((SHAXX_CTX *) ctx);
#else
//Here does the OpenBSD stuff, now part of pgcrypto
#endif
}
And that's really ugly, all the OpenBSD things that are used by
pgcrypto when the code is not built with --with-openssl gather into a
single place with parts wrapped around USE_SSL. A less ugly solution
would be to split that into two files, and one or the other gets
included in OBJS depending on if the build is done with or without
OpenSSL. We do a rather similar thing with fe/be-secure-openssl.c.
Another possibility is that we could say that SCRAM is designed to
work with TLS, as mentioned a bit upthread via the RFC, so we would
not support it in builds compiled without OpenSSL. I think that would
be a shame, but it would simplify all this refactoring juggling.
So, 3 possibilities here:
1) Use a single file src/common/sha.c that includes a set of functions
using USE_SSL
2) Have two files in src/common, one when build is used with OpenSSL,
and the second one when built-in methods are used
3) Disable the use of SCRAM when OpenSSL is not present in the build.
Opinions? My heart goes for 2) because 1) is ugly, and 3) is not
appealing in terms of flexibility.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 5, 2016 at 10:06 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:
On Mon, Jul 4, 2016 at 12:54 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:As I am coming back into that, I would as well suggest do the
following, that the current set of patches is clearly missing:
- Put the HMAC infrastructure stuff of pgcrypto into src/common/. It
is a bit a shame to not reuse what is currently available, then I
would suggest to reuse that with HMAC_SCRAM_SHAXXX as label.
- Move *all* the SHA-related things of pgcrypto to src/common,
including SHA1, SHA224 and SHA256. px_memset is a simple wrapper on
top of memset, we should clean up that first.
Any other things to consider that I am forgetting?After looking more into that, I have come up with PG-like equivalents
of things in openssl/sha.h:
pg_shaXX_init(pg_shaXX_ctx *ctx, data);
pg_shaXX_update(pg_shaXX_ctx *ctx, uint8 *data, size_t len);
pg_shaXX_final(uint8 *dest, pg_shaXX_ctx *ctx);
Then think about shaXX as 1, 224, 256, 384 and 512.Hence all those functions, moved to src/common, finish with the
following shape, take an init() one:
#ifdef USE_SSL
#define <openssl/sha.h>
#endif
void
pg_shaXX_init(pg_shaXX_ctx *ctx)
{
#ifdef USE_SSL
SHAXX_Init((SHAXX_CTX *) ctx);
#else
//Here does the OpenBSD stuff, now part of pgcrypto
#endif
}And that's really ugly, all the OpenBSD things that are used by
pgcrypto when the code is not built with --with-openssl gather into a
single place with parts wrapped around USE_SSL. A less ugly solution
would be to split that into two files, and one or the other gets
included in OBJS depending on if the build is done with or without
OpenSSL. We do a rather similar thing with fe/be-secure-openssl.c.
FWIW, the main reason for be-secure-openssl.c is that we could have support
for another external SSL library. The idea was never to have a builtin
replacement for it :)
However, is there something that's fundamentally better with the OpenSSL
implementation? Or should we just keep *just* the #else branch in the code,
the part we've imported from OpenBSD?
TLS is complex, we don't want to do that in that case. But just the sha
functions isn't *that* complex, is it?
Another possibility is that we could say that SCRAM is designed to
work with TLS, as mentioned a bit upthread via the RFC, so we would
not support it in builds compiled without OpenSSL. I think that would
be a shame, but it would simplify all this refactoring juggling.So, 3 possibilities here:
1) Use a single file src/common/sha.c that includes a set of functions
using USE_SSL
2) Have two files in src/common, one when build is used with OpenSSL,
and the second one when built-in methods are used
3) Disable the use of SCRAM when OpenSSL is not present in the build.Opinions? My heart goes for 2) because 1) is ugly, and 3) is not
appealing in terms of flexibility.
I really dislike #3 - we want everybody to start using this...
I'm not sure how common a build without openssl is in the real world
though. RPMs, DEBs, Windows installers etc all build with OpenSSL. But we
probably don't want to make it mandatory, no...
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On Tue, Jul 5, 2016 at 5:50 PM, Magnus Hagander <magnus@hagander.net> wrote:
On Tue, Jul 5, 2016 at 10:06 AM, Michael Paquier <michael.paquier@gmail.com> wrote:
However, is there something that's fundamentally better with the OpenSSL
implementation? Or should we just keep *just* the #else branch in the code,
the part we've imported from OpenBSD?
Good question. I think that we want both, giving priority to OpenSSL
if it is there. Usually their things prove to have more entropy, but I
didn't look at their code to be honest. If we only use the OpenBSD
stuff, it would be a good idea to refresh the in-core code. This is
from OpenBSD of 2002.
TLS is complex, we don't want to do that in that case. But just the sha
functions isn't *that* complex, is it?
No, they are not.
Another possibility is that we could say that SCRAM is designed to
work with TLS, as mentioned a bit upthread via the RFC, so we would
not support it in builds compiled without OpenSSL. I think that would
be a shame, but it would simplify all this refactoring juggling.So, 3 possibilities here:
1) Use a single file src/common/sha.c that includes a set of functions
using USE_SSL
2) Have two files in src/common, one when build is used with OpenSSL,
and the second one when built-in methods are used
3) Disable the use of SCRAM when OpenSSL is not present in the build.Opinions? My heart goes for 2) because 1) is ugly, and 3) is not
appealing in terms of flexibility.I really dislike #3 - we want everybody to start using this...
OK, after hacking that for a bit I have finished with option 2 and the
set of PG-like set of routines, the use of USE_SSL in the file
containing all the SHA functions of OpenBSD has proved to be really
ugly, but with a split things are really clear to the eye. The stuff I
got builds on OSX, Linux and MSVC. pgcrypto cannot link directly to
libpgcommon.a, so I am making it compile directly with the source
files, as it is doing on HEAD.
I'm not sure how common a build without openssl is in the real world though.
RPMs, DEBs, Windows installers etc all build with OpenSSL. But we probably
don't want to make it mandatory, no...
I don't think that it is this much common to have an enterprise-class
build of Postgres without SSL, but each company has always its own
reasons, so things could exist.
And I continue to move on... Thanks for the feedback.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 6, 2016 at 4:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
OK, after hacking that for a bit I have finished with option 2 and the
set of PG-like set of routines, the use of USE_SSL in the file
containing all the SHA functions of OpenBSD has proved to be really
ugly, but with a split things are really clear to the eye. The stuff I
got builds on OSX, Linux and MSVC. pgcrypto cannot link directly to
libpgcommon.a, so I am making it compile directly with the source
files, as it is doing on HEAD.
Btw, attached is the patch I did for this part if there is any interest in it.
Also, while working on the rest, I am not adding a new column to
pg_auth_id to identify the password verifier type. That's just to keep
the patch at a bare minimum size. Are there issues with that?
--
Michael
Attachments:
0001-Refactor-SHA-functions-and-move-them-to-src-common.patchtext/x-diff; charset=US-ASCII; name=0001-Refactor-SHA-functions-and-move-them-to-src-common.patchDownload
From 6101ffb3baf12cbb13a20812b1d8d10350683ff7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 6 Jul 2016 16:09:31 +0900
Subject: [PATCH 1/3] Refactor SHA functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA functions,
allowing to link to either OpenSSL or the in-core stuff taken from OpenBSD
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 11 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 +-
contrib/pgcrypto/internal.c | 32 +-
contrib/pgcrypto/sha1.c | 341 ---------
contrib/pgcrypto/sha1.h | 75 --
contrib/pgcrypto/sha2.h | 100 ---
src/common/Makefile | 6 +
contrib/pgcrypto/sha2.c => src/common/sha.c | 1101 +++++++++++++++++----------
src/common/sha_openssl.c | 120 +++
src/include/common/sha.h | 95 +++
src/tools/msvc/Mkvcbuild.pm | 11 +-
13 files changed, 1012 insertions(+), 978 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
delete mode 100644 contrib/pgcrypto/sha2.h
rename contrib/pgcrypto/sha2.c => src/common/sha.c (54%)
create mode 100644 src/common/sha_openssl.c
create mode 100644 src/include/common/sha.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..582110e 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha.c
+/sha_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..492184d 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 internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c random.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
@@ -22,6 +22,12 @@ SRCS = pgcrypto.c px.c px-hmac.c px-crypt.c \
pgp-pubdec.c pgp-pubenc.c pgp-pubkey.c pgp-s2k.c \
pgp-pgsql.c
+ifeq ($(with_openssl),yes)
+SRCS += sha_openssl.c
+else
+SRCS += sha.c
+endif
+
MODULE_big = pgcrypto
OBJS = $(SRCS:.c=.o) $(WIN32RES)
@@ -59,6 +65,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha.c sha_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..6bc6faf 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..3868fd2 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..5e3fc30 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -33,9 +33,10 @@
#include <time.h>
+#include "common/sha.h"
+
#include "px.h"
#include "md5.h"
-#include "sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
@@ -63,15 +64,6 @@
#define MD5_DIGEST_LENGTH 16
#endif
-#ifndef SHA1_DIGEST_LENGTH
-#ifdef SHA1_RESULTLEN
-#define SHA1_DIGEST_LENGTH SHA1_RESULTLEN
-#else
-#define SHA1_DIGEST_LENGTH 20
-#endif
-#endif
-
-#define SHA1_BLOCK_SIZE 64
#define MD5_BLOCK_SIZE 64
static void init_md5(PX_MD *h);
@@ -152,43 +144,43 @@ int_md5_free(PX_MD *h)
static unsigned
int_sha1_len(PX_MD *h)
{
- return SHA1_DIGEST_LENGTH;
+ return PG_SHA1_DIGEST_LENGTH;
}
static unsigned
int_sha1_block_len(PX_MD *h)
{
- return SHA1_BLOCK_SIZE;
+ return PG_SHA1_BLOCK_LENGTH;
}
static void
int_sha1_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Update(ctx, data, dlen);
+ pg_sha1_update(ctx, data, dlen);
}
static void
int_sha1_reset(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Init(ctx);
+ pg_sha1_init(ctx);
}
static void
int_sha1_finish(PX_MD *h, uint8 *dst)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Final(dst, ctx);
+ pg_sha1_final(ctx, dst);
}
static void
int_sha1_free(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -220,7 +212,7 @@ init_md5(PX_MD *md)
static void
init_sha1(PX_MD *md)
{
- SHA1_CTX *ctx;
+ pg_sha1_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
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 2f61e45..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 *);
-
-/* compatibility 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/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 72b7369..c27e25e 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,6 +40,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o keywords.o \
pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha_openssl.o
+else
+OBJS_COMMON += sha.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha.c
similarity index 54%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha.c
index 231f9df..0809671 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha.c
@@ -1,10 +1,22 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha.c
+ * Set of SHA functions for SHA-1, SHA-224, SHA-256, SHA-384 and
+ * SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
- * FILE: sha2.c
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -15,14 +27,14 @@
* 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 copyright holder nor the names of contributors
+ * 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 AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ * 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 AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ * 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)
@@ -31,109 +43,341 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
- *
- * contrib/pgcrypto/sha2.c
+ * src/common/sha.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>
*/
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha.h"
-/*
- * UNROLLED TRANSFORM LOOP NOTE:
- * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
- * loop version for the hash transform rounds (defined using macros
- * later in this file). Either define on the command line, for example:
- *
- * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
- *
- * or define below:
- *
- * #define SHA2_UNROLL_TRANSFORM
- *
- */
+/* constant table */
+static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
-/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
+#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) (ctx->h.b32[(n)])
+#define COUNT (ctx->count)
+#define BCOUNT (ctx->c.b64[0] / 8)
+#define W(n) (ctx->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctx->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+static void pg_sha1_step(pg_sha1_ctx *ctx);
+static void pg_sha1_pad(pg_sha1_ctx *ctx);
+
+static void
+pg_sha1_step(pg_sha1_ctx *ctx)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
-/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
-#define REVERSE32(w,x) { \
- uint32 tmp = (w); \
- tmp = (tmp >> 16) | (tmp << 16); \
- (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+ pg_sha1_ctx tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctx->m.b8[0], 64);
+ ctx->m.b8[0] = tctxt.m.b8[3];
+ ctx->m.b8[1] = tctxt.m.b8[2];
+ ctx->m.b8[2] = tctxt.m.b8[1];
+ ctx->m.b8[3] = tctxt.m.b8[0];
+ ctx->m.b8[4] = tctxt.m.b8[7];
+ ctx->m.b8[5] = tctxt.m.b8[6];
+ ctx->m.b8[6] = tctxt.m.b8[5];
+ ctx->m.b8[7] = tctxt.m.b8[4];
+ ctx->m.b8[8] = tctxt.m.b8[11];
+ ctx->m.b8[9] = tctxt.m.b8[10];
+ ctx->m.b8[10] = tctxt.m.b8[9];
+ ctx->m.b8[11] = tctxt.m.b8[8];
+ ctx->m.b8[12] = tctxt.m.b8[15];
+ ctx->m.b8[13] = tctxt.m.b8[14];
+ ctx->m.b8[14] = tctxt.m.b8[13];
+ ctx->m.b8[15] = tctxt.m.b8[12];
+ ctx->m.b8[16] = tctxt.m.b8[19];
+ ctx->m.b8[17] = tctxt.m.b8[18];
+ ctx->m.b8[18] = tctxt.m.b8[17];
+ ctx->m.b8[19] = tctxt.m.b8[16];
+ ctx->m.b8[20] = tctxt.m.b8[23];
+ ctx->m.b8[21] = tctxt.m.b8[22];
+ ctx->m.b8[22] = tctxt.m.b8[21];
+ ctx->m.b8[23] = tctxt.m.b8[20];
+ ctx->m.b8[24] = tctxt.m.b8[27];
+ ctx->m.b8[25] = tctxt.m.b8[26];
+ ctx->m.b8[26] = tctxt.m.b8[25];
+ ctx->m.b8[27] = tctxt.m.b8[24];
+ ctx->m.b8[28] = tctxt.m.b8[31];
+ ctx->m.b8[29] = tctxt.m.b8[30];
+ ctx->m.b8[30] = tctxt.m.b8[29];
+ ctx->m.b8[31] = tctxt.m.b8[28];
+ ctx->m.b8[32] = tctxt.m.b8[35];
+ ctx->m.b8[33] = tctxt.m.b8[34];
+ ctx->m.b8[34] = tctxt.m.b8[33];
+ ctx->m.b8[35] = tctxt.m.b8[32];
+ ctx->m.b8[36] = tctxt.m.b8[39];
+ ctx->m.b8[37] = tctxt.m.b8[38];
+ ctx->m.b8[38] = tctxt.m.b8[37];
+ ctx->m.b8[39] = tctxt.m.b8[36];
+ ctx->m.b8[40] = tctxt.m.b8[43];
+ ctx->m.b8[41] = tctxt.m.b8[42];
+ ctx->m.b8[42] = tctxt.m.b8[41];
+ ctx->m.b8[43] = tctxt.m.b8[40];
+ ctx->m.b8[44] = tctxt.m.b8[47];
+ ctx->m.b8[45] = tctxt.m.b8[46];
+ ctx->m.b8[46] = tctxt.m.b8[45];
+ ctx->m.b8[47] = tctxt.m.b8[44];
+ ctx->m.b8[48] = tctxt.m.b8[51];
+ ctx->m.b8[49] = tctxt.m.b8[50];
+ ctx->m.b8[50] = tctxt.m.b8[49];
+ ctx->m.b8[51] = tctxt.m.b8[48];
+ ctx->m.b8[52] = tctxt.m.b8[55];
+ ctx->m.b8[53] = tctxt.m.b8[54];
+ ctx->m.b8[54] = tctxt.m.b8[53];
+ ctx->m.b8[55] = tctxt.m.b8[52];
+ ctx->m.b8[56] = tctxt.m.b8[59];
+ ctx->m.b8[57] = tctxt.m.b8[58];
+ ctx->m.b8[58] = tctxt.m.b8[57];
+ ctx->m.b8[59] = tctxt.m.b8[56];
+ ctx->m.b8[60] = tctxt.m.b8[63];
+ ctx->m.b8[61] = tctxt.m.b8[62];
+ ctx->m.b8[62] = tctxt.m.b8[61];
+ ctx->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(&ctx->m.b8[0], 0, 64);
}
-#define REVERSE64(w,x) { \
- uint64 tmp = (w); \
- tmp = (tmp >> 32) | (tmp << 32); \
- tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
- ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
- (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
- ((tmp & 0x0000ffff0000ffffULL) << 16); \
+
+static void
+pg_sha1_pad(pg_sha1_ctx *ctx)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctx->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ pg_sha1_step(ctx);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctx->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctx->c.b8[0]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[7]);
+#else
+ PUTPAD(ctx->c.b8[7]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[0]);
+#endif
}
-#endif /* not bigendian */
-/*
- * Macro for incrementally adding the unsigned 64-bit integer n to the
- * unsigned 128-bit integer (represented using a two-element array of
- * 64-bit words):
- */
-#define ADDINC128(w,n) { \
- (w)[0] += (uint64)(n); \
- if ((w)[0] < (n)) { \
- (w)[1]++; \
- } \
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(pg_sha1_ctx));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
}
-/*** THE SIX LOGICAL FUNCTIONS ****************************************/
-/*
- * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
- *
- * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
- * S is a ROTATION) because the SHA-256/384/512 description document
- * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
- * uses this same "backwards" definition.
- */
-/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
-#define R(b,x) ((x) >> (b))
-/* 32-bit Rotate-right (used in SHA-256): */
-#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
-/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
-#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
-/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
-#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
-#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+ input = (const uint8 *) input0;
+ off = 0;
-/* Four of six logical functions used in SHA-256: */
-#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
-#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
-#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
-#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctx->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctx->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ pg_sha1_step(ctx);
+ off += copysiz;
+ }
+}
-/* Four of six logical functions used in SHA-384 and SHA-512: */
-#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
-#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
-#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
-#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) dest;
+ pg_sha1_pad(ctx);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctx->h.b8[0], 20);
+#else
+ digest[0] = ctx->h.b8[3];
+ digest[1] = ctx->h.b8[2];
+ digest[2] = ctx->h.b8[1];
+ digest[3] = ctx->h.b8[0];
+ digest[4] = ctx->h.b8[7];
+ digest[5] = ctx->h.b8[6];
+ digest[6] = ctx->h.b8[5];
+ digest[7] = ctx->h.b8[4];
+ digest[8] = ctx->h.b8[11];
+ digest[9] = ctx->h.b8[10];
+ digest[10] = ctx->h.b8[9];
+ digest[11] = ctx->h.b8[8];
+ digest[12] = ctx->h.b8[15];
+ digest[13] = ctx->h.b8[14];
+ digest[14] = ctx->h.b8[13];
+ digest[15] = ctx->h.b8[12];
+ digest[16] = ctx->h.b8[19];
+ digest[17] = ctx->h.b8[18];
+ digest[18] = ctx->h.b8[17];
+ digest[19] = ctx->h.b8[16];
+#endif
+}
-/*** INTERNAL FUNCTION PROTOTYPES *************************************/
-/* NOTE: These should not be accessed directly from outside this
- * library -- they are intended for private internal visibility/use
- * only.
+/*
+ * UNROLLED TRANSFORM LOOP NOTE:
+ * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
+ * loop version for the hash transform rounds (defined using macros
+ * later in this file). Either define on the command line, for example:
+ *
+ * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
+ *
+ * or define below:
+ *
+ * #define SHA2_UNROLL_TRANSFORM
+ *
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
+/*** SHA-256/384/512 Various Length Definitions ***********************/
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -248,16 +492,124 @@ static const uint64 sha512_initial_hash_value[8] = {
0x5be0cd19137e2179ULL
};
+/*** ENDIAN REVERSAL MACROS *******************************************/
+#ifndef WORDS_BIGENDIAN
+#define REVERSE32(w,x) { \
+ uint32 tmp = (w); \
+ tmp = (tmp >> 16) | (tmp << 16); \
+ (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+}
+#define REVERSE64(w,x) { \
+ uint64 tmp = (w); \
+ tmp = (tmp >> 32) | (tmp << 32); \
+ tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
+ ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
+ (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
+ ((tmp & 0x0000ffff0000ffffULL) << 16); \
+}
+#endif /* not bigendian */
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+/*
+ * Macro for incrementally adding the unsigned 64-bit integer n to the
+ * unsigned 128-bit integer (represented using a two-element array of
+ * 64-bit words):
+ */
+#define ADDINC128(w,n) { \
+ (w)[0] += (uint64)(n); \
+ if ((w)[0] < (n)) { \
+ (w)[1]++; \
+ } \
+}
+
+/*** THE SIX LOGICAL FUNCTIONS ****************************************/
+/*
+ * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
+ *
+ * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
+ * S is a ROTATION) because the SHA-256/384/512 description document
+ * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
+ * uses this same "backwards" definition.
+ */
+/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
+#define R(b,x) ((x) >> (b))
+/* 32-bit Rotate-right (used in SHA-256): */
+#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
+/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
+#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+
+/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
+#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+/* Four of six logical functions used in SHA-256: */
+#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
+#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
+#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
+#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+
+/* Four of six logical functions used in SHA-384 and SHA-512: */
+#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
+#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
+#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
+#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+
+/*** INTERNAL FUNCTION PROTOTYPES *************************************/
+/* NOTE: These should not be accessed directly from outside this
+ * library -- they are intended for private internal visibility/use
+ * only.
+ */
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
+
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -287,7 +639,7 @@ SHA256_Init(SHA256_CTX *context)
} while(0)
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +655,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,14 +695,14 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -358,7 +710,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +727,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +785,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -616,7 +829,7 @@ SHA512_Init(SHA512_CTX *context)
} while(0)
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +842,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,14 +882,14 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -684,7 +897,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +911,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +972,82 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +1056,148 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +1207,42 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/* Interface routines for SHA-384 */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1252,41 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+/* Interface routines for SHA-224 */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1295,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha_openssl.c b/src/common/sha_openssl.c
new file mode 100644
index 0000000..c6aac90
--- /dev/null
+++ b/src/common/sha_openssl.c
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA
+ * functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha.h"
+
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ SHA1_Init((SHA_CTX *) ctx);
+}
+
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ SHA1_Update((SHA_CTX *) ctx, input0, len);
+}
+
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ SHA1_Final(dest, (SHA_CTX *) ctx);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha.h b/src/include/common/sha.h
new file mode 100644
index 0000000..6db54b4
--- /dev/null
+++ b/src/include/common/sha.h
@@ -0,0 +1,95 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha.h
+ * Generic headers for SHA functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _PG_SHA_H_
+#define _PG_SHA_H_
+
+/*** SHA-1/224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA1_BLOCK_LENGTH 64
+#define PG_SHA1_DIGEST_LENGTH 20
+#define PG_SHA1_DIGEST_STRING_LENGTH (PG_SHA1_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+typedef struct pg_sha1_ctx
+{
+ 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;
+} pg_sha1_ctx;
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+
+/* Interface routines for SHA-1/224/256/384/512 */
+extern void pg_sha1_init(pg_sha1_ctx *ctx);
+extern void pg_sha1_update(pg_sha1_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest);
+
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index fe905d3..02a3fc7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
restricted_token.c));
@@ -443,13 +452,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.9.0
* Michael Paquier (michael.paquier@gmail.com) wrote:
On Tue, Jul 5, 2016 at 5:50 PM, Magnus Hagander <magnus@hagander.net> wrote:
On Tue, Jul 5, 2016 at 10:06 AM, Michael Paquier <michael.paquier@gmail.com> wrote:
However, is there something that's fundamentally better with the OpenSSL
implementation? Or should we just keep *just* the #else branch in the code,
the part we've imported from OpenBSD?Good question. I think that we want both, giving priority to OpenSSL
if it is there. Usually their things prove to have more entropy, but I
didn't look at their code to be honest. If we only use the OpenBSD
stuff, it would be a good idea to refresh the in-core code. This is
from OpenBSD of 2002.
I agree that we definitely want to use the OpenSSL functions when they
are available.
I'm not sure how common a build without openssl is in the real world though.
RPMs, DEBs, Windows installers etc all build with OpenSSL. But we probably
don't want to make it mandatory, no...I don't think that it is this much common to have an enterprise-class
build of Postgres without SSL, but each company has always its own
reasons, so things could exist.
I agree that it's useful to have the support if PG isn't built with
OpenSSL for some reason.
Thanks!
Stephen
On Thu, Jul 7, 2016 at 7:51 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Michael Paquier (michael.paquier@gmail.com) wrote:
I'm not sure how common a build without openssl is in the real world though.
RPMs, DEBs, Windows installers etc all build with OpenSSL. But we probably
don't want to make it mandatory, no...I don't think that it is this much common to have an enterprise-class
build of Postgres without SSL, but each company has always its own
reasons, so things could exist.I agree that it's useful to have the support if PG isn't built with
OpenSSL for some reason.
OK, I am doing that at the end.
And also while moving on...
On another topic, here are some ideas to extend CREATE/ALTER ROLE to
support SCRAM password directly:
1) protocol PASSWORD value, where protocol is { MD5 | PLAIN | SCRAM }, giving:
CREATE ROLE foorole SCRAM PASSWORD value;
2) PASSWORD (protocol) value.
3) Just add SCRAM PASSWORD
My mind is thinking about 1) as being the cleanest solution as this
does not touch the defaults, which may change a couple of releases
later. Other opinions?
Note that I am also switching password_encryption to an enum, able to
use as values on, off, md5, plain, scram. Of course, on => md5, off =>
plain to preserve the default.
Other things that I am making conservative:
- ENCRYPTED PASSWORD still implies MD5-encrypted password
- UNENCRYPTED PASSWORD still implies plain text password
- PASSWORD used alone depends on the value of password_encryption
So it would be possible to move to scram by default by setting
password_encryption to 'scram'.
Objections are welcome, I am moving into something respecting the
default behavior as much as possible.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 15, 2016 at 9:30 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
OK, I am doing that at the end.
And also while moving on...
On another topic, here are some ideas to extend CREATE/ALTER ROLE to
support SCRAM password directly:
1) protocol PASSWORD value, where protocol is { MD5 | PLAIN | SCRAM }, giving:
CREATE ROLE foorole SCRAM PASSWORD value;
2) PASSWORD (protocol) value.
3) Just add SCRAM PASSWORD
My mind is thinking about 1) as being the cleanest solution as this
does not touch the defaults, which may change a couple of releases
later. Other opinions?
I can't really understand what you are saying here, but I'm going to
be -1 on adding SCRAM as a parser keyword. Let's pick a syntax like
"PASSWORD SConst USING SConst" or "PASSWORD SConst ENCRYPTED WITH
SConst".
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier wrote:
On Wed, Jul 6, 2016 at 4:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:OK, after hacking that for a bit I have finished with option 2 and the
set of PG-like set of routines, the use of USE_SSL in the file
containing all the SHA functions of OpenBSD has proved to be really
ugly, but with a split things are really clear to the eye. The stuff I
got builds on OSX, Linux and MSVC. pgcrypto cannot link directly to
libpgcommon.a, so I am making it compile directly with the source
files, as it is doing on HEAD.Btw, attached is the patch I did for this part if there is any interest in it.
After quickly eyeballing your patch, I agree with the decision of going
with (2), even if my gut initially told me that (1) would be better
because it'd require less makefile trickery.
I'm surprised that you say pgcrypto cannot link libpgcommon directly.
Is there some insurmountable problem there? I notice your MSVC patch
uses libpgcommon while the Makefile symlinks the files.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 20, 2016 at 02:12:57PM -0400, Alvaro Herrera wrote:
Michael Paquier wrote:
On Wed, Jul 6, 2016 at 4:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:OK, after hacking that for a bit I have finished with option 2 and the
set of PG-like set of routines, the use of USE_SSL in the file
containing all the SHA functions of OpenBSD has proved to be really
ugly, but with a split things are really clear to the eye. The stuff I
got builds on OSX, Linux and MSVC. pgcrypto cannot link directly to
libpgcommon.a, so I am making it compile directly with the source
files, as it is doing on HEAD.Btw, attached is the patch I did for this part if there is any interest in it.
After quickly eyeballing your patch, I agree with the decision of going
with (2), even if my gut initially told me that (1) would be better
because it'd require less makefile trickery.I'm surprised that you say pgcrypto cannot link libpgcommon directly.
Is there some insurmountable problem there? I notice your MSVC patch
uses libpgcommon while the Makefile symlinks the files.
People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jul 21, 2016 at 12:15 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Jul 15, 2016 at 9:30 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:OK, I am doing that at the end.
And also while moving on...
On another topic, here are some ideas to extend CREATE/ALTER ROLE to
support SCRAM password directly:
1) protocol PASSWORD value, where protocol is { MD5 | PLAIN | SCRAM }, giving:
CREATE ROLE foorole SCRAM PASSWORD value;
2) PASSWORD (protocol) value.
3) Just add SCRAM PASSWORD
My mind is thinking about 1) as being the cleanest solution as this
does not touch the defaults, which may change a couple of releases
later. Other opinions?I can't really understand what you are saying here, but I'm going to
be -1 on adding SCRAM as a parser keyword. Let's pick a syntax like
"PASSWORD SConst USING SConst" or "PASSWORD SConst ENCRYPTED WITH
SConst".
No, I do not mean to make SCRAM or MD5 keywords. While hacking that, I
got at some point in the mood of using "PASSWORD Sconst Sconst" but
that's ugly. Sticking a keyword in between makes more sense, and USING
is a good idea. I haven't thought of this one.
By the way, the core patch does not have any grammar extension. The
grammar extension will be on top of it and the core patch can just
activate scram passwords using password_encryption. That's user
unfriendly, but as the patch is large I try to cut it in as many
pieces as necessary.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jul 21, 2016 at 5:25 AM, David Fetter <david@fetter.org> wrote:
On Wed, Jul 20, 2016 at 02:12:57PM -0400, Alvaro Herrera wrote:
Michael Paquier wrote:
On Wed, Jul 6, 2016 at 4:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:OK, after hacking that for a bit I have finished with option 2 and the
set of PG-like set of routines, the use of USE_SSL in the file
containing all the SHA functions of OpenBSD has proved to be really
ugly, but with a split things are really clear to the eye. The stuff I
got builds on OSX, Linux and MSVC. pgcrypto cannot link directly to
libpgcommon.a, so I am making it compile directly with the source
files, as it is doing on HEAD.Btw, attached is the patch I did for this part if there is any interest in it.
After quickly eyeballing your patch, I agree with the decision of going
with (2), even if my gut initially told me that (1) would be better
because it'd require less makefile trickery.
Yeah, I thought the same thing as well when putting my hands in the
dirt... But the in the end (2) is really less ugly.
I'm surprised that you say pgcrypto cannot link libpgcommon directly.
Is there some insurmountable problem there? I notice your MSVC patch
uses libpgcommon while the Makefile symlinks the files.
I am running into some weird things when linking both on OSX... But I
am not done with it completely yet. I'll adjust that a bit more when
producing the set of patches that will be published. So let's see.
People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.
Do you have any references? I don't see that as a problem.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 20, 2016 at 7:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.Do you have any references? I don't see that as a problem.
I don't have a link to previous discussion handy, but I definitely
recall that it's been discussed. I don't think that would mean that
libpgcrypto couldn't depend on libpgcommon, but the reverse direction
would make libpgcrypto essentially mandatory which I don't think is a
direction we want to go for both technical and legal reasons.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 7/21/16 12:19 PM, Robert Haas wrote:
On Wed, Jul 20, 2016 at 7:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.Do you have any references? I don't see that as a problem.
I don't have a link to previous discussion handy, but I definitely
recall that it's been discussed. I don't think that would mean that
libpgcrypto couldn't depend on libpgcommon, but the reverse direction
would make libpgcrypto essentially mandatory which I don't think is a
direction we want to go for both technical and legal reasons.
I searched a few different ways and finally came up with this post from Tom:
/messages/by-id/11392.1389991321@sss.pgh.pa.us
It's the only thing I could find, but thought it might jog something
loose for somebody else.
I know that export controls have been an issue for crypto in the past
but have no idea what the current state of that is.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Steele <david@pgmasters.net> writes:
On 7/21/16 12:19 PM, Robert Haas wrote:
On Wed, Jul 20, 2016 at 7:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.
Do you have any references? I don't see that as a problem.
I don't have a link to previous discussion handy, but I definitely
recall that it's been discussed. I don't think that would mean that
libpgcrypto couldn't depend on libpgcommon, but the reverse direction
would make libpgcrypto essentially mandatory which I don't think is a
direction we want to go for both technical and legal reasons.
I searched a few different ways and finally came up with this post from Tom:
/messages/by-id/11392.1389991321@sss.pgh.pa.us
It's the only thing I could find, but thought it might jog something
loose for somebody else.
Way back when, like fifteen years ago, there absolutely were US export
control restrictions on software containing crypto. I believe the US has
figured out that that was silly, but I'm not sure everyplace else has.
(And if you've been reading the news you will notice that legal
restrictions on crypto are back in vogue, so it would not be wise to
assume that the question is dead and buried.) So our project policy
since at least the turn of the century has been that any crypto facility
has to be in a separable extension, where it would be fairly easy for
a packager to delete it if they need to ship a crypto-free version.
Note that "crypto" for this purpose generally means reversible encryption;
I've never heard that one-way hashes are illegal anywhere. So password
hashing such as md5 is fine in core, and a stronger hash would be too.
But pulling in pgcrypto lock, stock, and barrel is not OK.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 22, 2016 at 2:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Way back when, like fifteen years ago, there absolutely were US export
control restrictions on software containing crypto. I believe the US has
figured out that that was silly, but I'm not sure everyplace else has.
England is these days legally running a battle against data
encryption. I have not heard how this is evolving these days.
(And if you've been reading the news you will notice that legal
restrictions on crypto are back in vogue, so it would not be wise to
assume that the question is dead and buried.) So our project policy
since at least the turn of the century has been that any crypto facility
has to be in a separable extension, where it would be fairly easy for
a packager to delete it if they need to ship a crypto-free version.
Note that "crypto" for this purpose generally means reversible encryption;
I've never heard that one-way hashes are illegal anywhere. So password
hashing such as md5 is fine in core, and a stronger hash would be too.
But pulling in pgcrypto lock, stock, and barrel is not OK.
So it would be an issue if pgcrypto.so links directly to libpqcommon?
Because that's not what I am doing now, perhaps fortunately. I moved
the sha functions to src/common. But actually but thinking more about
that, I don't need to do so because the routines of SCRAM shared
between the frontend and the backend just need to be part of libpq so
they could just be part of backend/libpq like md5.
Tom, if I get it correctly, it would not be an issue if the SHA
functions are directly part of the compiled backend like md5, right?
Because I would like to just change my set of patches to have the SHA
and the encoding functions in src/backend/libpq instead of src/common,
and then have pgcrypto be compiled with a link to those files. That's
a cleaner design btw, more in line with what is done for md5..
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Jul 22, 2016 at 2:31 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Note that "crypto" for this purpose generally means reversible encryption;
I've never heard that one-way hashes are illegal anywhere. So password
hashing such as md5 is fine in core, and a stronger hash would be too.
But pulling in pgcrypto lock, stock, and barrel is not OK.
So it would be an issue if pgcrypto.so links directly to libpqcommon?
No, I don't see why that'd be an issue. What we can't do is have
libpgcommon depending on pgcrypto.so, or containing anything more than
one-way-hash functionality itself.
Because I would like to just change my set of patches to have the SHA
and the encoding functions in src/backend/libpq instead of src/common,
and then have pgcrypto be compiled with a link to those files. That's
a cleaner design btw, more in line with what is done for md5..
I'm confused. We need that code in both libpq and backend, no?
src/common is the place for stuff of that description.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 22, 2016 at 8:48 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
Because I would like to just change my set of patches to have the SHA
and the encoding functions in src/backend/libpq instead of src/common,
and then have pgcrypto be compiled with a link to those files. That's
a cleaner design btw, more in line with what is done for md5..I'm confused. We need that code in both libpq and backend, no?
src/common is the place for stuff of that description.
Not necessarily. src/interfaces/libpq/Makefile uses a set of files
like md5.c which is located in the backend code and directly compiles
libpq.so with them, so one possibility would be to do the same for
sha.c: locate the file in src/backend/libpq/ and then fetch the file
directly when compiling libpq's shared library.
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so. Here is what I mean:
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -43,6 +43,14 @@ 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/
+OBJS += encode.o scram-common.o
+
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Jul 22, 2016 at 8:48 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm confused. We need that code in both libpq and backend, no?
src/common is the place for stuff of that description.
Not necessarily. src/interfaces/libpq/Makefile uses a set of files
like md5.c which is located in the backend code and directly compiles
libpq.so with them, so one possibility would be to do the same for
sha.c: locate the file in src/backend/libpq/ and then fetch the file
directly when compiling libpq's shared library.
Meh. That seems like a hack left over from before we had src/common.
Having said that, src/interfaces/libpq/ does have some special
requirements, because it needs the code compiled with -fpic (on most
hardware), which means it can't just use the client-side libpgcommon.a
builds. So maybe it's not worth improving this.
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.
Well, it could link source files from there just as easily as from the
backend. Not object files, though.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 22, 2016 at 9:02 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.Well, it could link source files from there just as easily as from the
backend. Not object files, though.
OK. I'll just keep things the current way then :)
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 22 July 2016 at 01:31, Tom Lane <tgl@sss.pgh.pa.us> wrote:
David Steele <david@pgmasters.net> writes:
On 7/21/16 12:19 PM, Robert Haas wrote:
On Wed, Jul 20, 2016 at 7:42 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:People have, in the past, expressed concerns about linking in
pgcrypto. Apparently, in some countries, it's a legal problem.Do you have any references? I don't see that as a problem.
I don't have a link to previous discussion handy, but I definitely
recall that it's been discussed. I don't think that would mean that
libpgcrypto couldn't depend on libpgcommon, but the reverse direction
would make libpgcrypto essentially mandatory which I don't think is a
direction we want to go for both technical and legal reasons.I searched a few different ways and finally came up with this post from
Tom:
/messages/by-id/11392.1389991321@sss.pgh.pa.us
It's the only thing I could find, but thought it might jog something
loose for somebody else.Way back when, like fifteen years ago, there absolutely were US export
control restrictions on software containing crypto. I believe the US has
figured out that that was silly, but I'm not sure everyplace else has.
Australia has recently enacted laws that are reminiscent of the US's
defunct crypto export control laws, but they add penalties for *teaching*
encryption too. Yup, you can be charged for talking about it. Of course
they'll only actually USE those new powers to Stop The Terrorist Threat,
they promise...
http://www.defence.gov.au/deco/DTC.asp
Unless recently amended, they even failed to exclude academic institutions.
I haven't been following it closely because, frankly, it's too ridiculous
to pay much attention to, and I don't work directly with crypto anyway. But
it's far from the only such colossally ignorant and idiotic law floating
around.
Despite the technical frustrations involved, we should keep crypto
implementations in a separate library. I agree with Tom that one-way hashes
are not a practical concern, even if the laws are probably written too
poorly to draw a distinction.
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
On Fri, Jul 22, 2016 at 9:06 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Fri, Jul 22, 2016 at 9:02 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.Well, it could link source files from there just as easily as from the
backend. Not object files, though.OK. I'll just keep things the current way then :)
Note: I have put more energy into that and I think that I will be able
to publish a new patch set pretty soon, like at the beginning of next
week.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 22, 2016 at 3:43 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Fri, Jul 22, 2016 at 9:06 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Fri, Jul 22, 2016 at 9:02 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.Well, it could link source files from there just as easily as from the
backend. Not object files, though.OK. I'll just keep things the current way then :)
Note: I have put more energy into that and I think that I will be able
to publish a new patch set pretty soon, like at the beginning of next
week.
Ok, here is the real deal. As discussed at PGcon, I have shaved off
from the set of patches the following things:
- No separate catalog pg_auth_verifier
- No additional column in pg_authid to determine the password type.
All the logic used check if the password string has a wanted format.
We do that for MD5 now, this set does it for SCRAM.
- Removal of the pg_upgrade stuff.
- Removal of password_protocols, so we don't care anymore about protocol aging.
In short, the SCRAM verifiers get stored in rolpassword.
And here is what this set of patches does:
- Implementation of SCRAM-SHA-256, and not SHA1. I have moved to the
one that makes the most sense considering the current situation based
on RFC 5802 and 7677.
- No channel binding support. I guess that this could be added later on.
- password_encryption is now an enum, and gains three values: md5,
plain and scram. true => md5, false => plain for backward
compatibility
- Grammar of CREATE/ALTER ROLE is extended with PASSWORD val USING
protocol, that's a separate patch applying on top of the core patch
for SASL.
I have noticed as well a couple of bugs in the previous set(s) of patches:
- valid_until was not checked for SCRAM
- When using ENCRYPTED or UNENCRYPTED, already encrypted password
should be used as-is. The same is applied to PASSWORD USING protocol
to ease dump and reload. That's actually what is used for MD5.
And here is a detail of the patches:
- 0001, refactoring of SHA functions into src/common.
- 0002, refactoring for sendAuthRequest
- 0003, Refactoring for RandomSalt to accomodate with the salt used by
scram (length of 10 bytes, md5 is 4).
- 0004, move encoding routines to src/common/
- 0005, make password_encryption an enum
- 0006, refactor some code in CREATE/ALTER role code paths related the
use of password_encryption
- 0007, refactor some code to have a single routine to fetch password
and valid_until from pg_authid
- 0008, The core implementation of SCRAM-SHA-256, with the SASL
communication protocol. if you want to use SCRAM with that, things go
with password_encryption = 'scram'.
- 0009, addition of PASSWORD val USING protocol
- 0010. regression tests for passwords. Not sure how useful they would
be. But they helped me a bit.
I am adding an entry in the next CF. Comments are welcome.
--
Michael
Attachments:
0004-Move-encoding-routines-to-src-common.patchapplication/x-patch; name=0004-Move-encoding-routines-to-src-common.patchDownload
From 5e2d6423e372a1b4b5e6e93b12482a7faac1b37e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 13:35:26 +0900
Subject: [PATCH 04/10] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA1, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +----------------------------
src/backend/utils/adt/varlena.c | 1 +
src/common/Makefile | 6 +-
src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
src/include/common/encode.h | 30 +++
src/include/utils/builtins.h | 2 -
src/tools/msvc/Mkvcbuild.pm | 2 +-
7 files changed, 168 insertions(+), 637 deletions(-)
copy src/{backend/utils/adt => common}/encode.c (72%)
create mode 100644 src/include/common/encode.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..76747bf 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index a869e85..6fec1f7 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode.h"
#include "lib/hyperloglog.h"
#include "libpq/md5.h"
#include "libpq/pqformat.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index c27e25e..186fcd0 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -36,9 +36,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o keywords.o \
- pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
+ keywords.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index d833efc..4a07089 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
* encode.c
- * Various data encoding/decoding things.
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the \
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..8166376
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ * encode.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* COMMON_ENCODE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..5791347 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -156,8 +156,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 02a3fc7..b6908c2 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c keywords.c
+ config_info.c controldata_utils.c encode.c exec.c keywords.c
pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.9.2
0005-Switch-password_encryption-to-a-enum.patchapplication/x-patch; name=0005-Switch-password_encryption-to-a-enum.patchDownload
From 320a2a13f48da1cd5b89daa0745903a0171f1890 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 13:59:43 +0900
Subject: [PATCH 05/10] Switch password_encryption to a enum
This makes this parameter more extensible in order to add support for
future password-based authentication protocols.
---
doc/src/sgml/config.sgml | 16 +++++++++--
src/backend/commands/user.c | 22 +++++++--------
src/backend/utils/misc/guc.c | 40 ++++++++++++++++++---------
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/include/commands/user.h | 11 ++++++--
5 files changed, 60 insertions(+), 31 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index c9e0ec2..278a6ca 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,7 +1163,7 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>enum</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
@@ -1175,8 +1175,18 @@ include_dir 'conf.d'
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
<literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ password is to be encrypted.
+ </para>
+
+ <para>
+ A value set to <literal>on</> or <literal>md5</> corresponds to a
+ MD5-encrypted password, <literal>off</> or <literal>plain</>
+ corresponds to an unencrypted password.
+ </para>
+
+ <para>
+ The default is <literal>on</> (encrypt the password with MD5
+ encryption).
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index b6ea950..bbf4dcc 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -44,7 +44,7 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
-extern bool Password_encryption;
+int Password_encryption = PASSWORD_TYPE_MD5;
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -80,7 +80,7 @@ CreateRole(CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
@@ -139,9 +139,9 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -357,7 +357,7 @@ CreateRole(CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -380,7 +380,7 @@ CreateRole(CreateRoleStmt *stmt)
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
@@ -492,7 +492,7 @@ AlterRole(AlterRoleStmt *stmt)
ListCell *option;
char *rolename = NULL;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
@@ -537,9 +537,9 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -732,7 +732,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -791,7 +791,7 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6ac5184..869babe 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -34,6 +34,7 @@
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "commands/trigger.h"
@@ -393,6 +394,20 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
{NULL, 0, false}
};
+static const struct config_enum_entry password_encryption_options[] = {
+ {"off", PASSWORD_TYPE_PLAINTEXT, false},
+ {"on", PASSWORD_TYPE_MD5, false},
+ {"md5", PASSWORD_TYPE_MD5, false},
+ {"plain", PASSWORD_TYPE_PLAINTEXT, false},
+ {"true", PASSWORD_TYPE_MD5, true},
+ {"false", PASSWORD_TYPE_PLAINTEXT, true},
+ {"yes", PASSWORD_TYPE_MD5, true},
+ {"no", PASSWORD_TYPE_PLAINTEXT, true},
+ {"1", PASSWORD_TYPE_MD5, true},
+ {"0", PASSWORD_TYPE_PLAINTEXT, true},
+ {NULL, 0, false}
+};
+
/*
* Options for enum values stored in other modules
*/
@@ -423,8 +438,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -1310,17 +1323,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3806,6 +3808,18 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("Encrypt passwords."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines whether the password is to be encrypted.")
+ },
+ &Password_encryption,
+ PASSWORD_TYPE_MD5, password_encryption_options,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 6d0666c..c61ed8d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = on # on, off, md5 or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..3acbcbd 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -15,9 +15,14 @@
#include "nodes/parsenodes.h"
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
+/* Types of password */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern int Password_encryption;
typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
--
2.9.2
0006-Refactor-decision-making-of-password-encryption-into.patchapplication/x-patch; name=0006-Refactor-decision-making-of-password-encryption-into.patchDownload
From b37399becffe1e7f086b35c0576ddea97c72dc57 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:13:59 +0900
Subject: [PATCH 06/10] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 81 +++++++++++++++++++++++++++++++--------------
1 file changed, 57 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index bbf4dcc..60bf64d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,47 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = password;
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = password;
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +124,7 @@ CreateRole(CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -380,17 +423,12 @@ CreateRole(CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -493,7 +531,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -791,17 +829,12 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
--
2.9.2
0007-Create-generic-routine-to-fetch-password-and-valid-u.patchapplication/x-patch; name=0007-Create-generic-routine-to-fetch-password-and-valid-u.patchDownload
From fdfd3700a0d88617e542abe517fdee53da8840ae Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:40:15 +0900
Subject: [PATCH 07/10] Create generic routine to fetch password and valid
until values for a role
This is used now for the MD5-encrypted case and the plain text, and this
is going to be used as well for SCRAM-SHA256. That's as well useful for
any new password-based protocols.
---
src/backend/libpq/crypt.c | 59 +++++++++++++++++++++++++++++++++++------------
src/include/libpq/crypt.h | 2 ++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d79f5a2..183c549 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,49 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
--
2.9.2
0008-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-patch; name=0008-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From 18ee956317377a2eb452150eb64b1fc853c3315d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 15:09:12 +0900
Subject: [PATCH 08/10] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 4 +
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 9 +-
doc/src/sgml/protocol.sgml | 147 +++++-
doc/src/sgml/ref/create_role.sgml | 14 +-
src/backend/commands/user.c | 16 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 712 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 132 +++++
src/backend/libpq/crypt.c | 1 +
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/parser/gram.y | 8 +-
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 51 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 26 +
src/interfaces/libpq/.gitignore | 4 +
src/interfaces/libpq/Makefile | 13 +-
src/interfaces/libpq/fe-auth-scram.c | 418 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
32 files changed, 1942 insertions(+), 42 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index b4c1ce0..f08bd0d 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -128,6 +128,10 @@ check_password(const char *username,
#endif
break;
+ case PASSWORD_TYPE_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
+
default:
elog(ERROR, "unrecognized password type: %d", password_type);
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0689cc9..bfcc75b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1289,13 +1289,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 278a6ca..8b8e909 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1181,7 +1181,8 @@ include_dir 'conf.d'
<para>
A value set to <literal>on</> or <literal>md5</> corresponds to a
MD5-encrypted password, <literal>off</> or <literal>plain</>
- corresponds to an unencrypted password.
+ corresponds to an unencrypted password. Setting this parameter to
+ <literal>scram</> will encrypt the password with SCRAM-SHA-256.
</para>
<para>
@@ -1260,8 +1261,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 9c96d8f..56c5213 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>
@@ -2578,6 +2607,114 @@ 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>
@@ -4340,7 +4477,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 60bf64d..1fc4245 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"
@@ -69,8 +70,8 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -80,11 +81,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = password;
else
{
@@ -100,6 +101,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
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..fadebfc
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,712 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode.h"
+#include "common/scram-common.h"
+#include "common/sha.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+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 */
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 126e337..d5d875e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,9 +27,11 @@
#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"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -201,6 +203,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -262,6 +270,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;
@@ -542,6 +551,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, "");
@@ -717,6 +730,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * 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);
+
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 183c549..21751cf 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -84,6 +84,7 @@ get_role_details(const char *role,
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
+
}
return STATUS_OK;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 1b4bbce..55974af 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1184,6 +1184,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index edf4516..b6e4618 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -626,10 +626,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
- SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
- SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
- STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
+ SAVEPOINT SCHEMA SCRAM SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+ SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE
+ SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+ START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2c1c76a..78a3ce7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2343,6 +2343,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 869babe..9c146b1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -398,6 +398,7 @@ static const struct config_enum_entry password_encryption_options[] = {
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"true", PASSWORD_TYPE_MD5, true},
{"false", PASSWORD_TYPE_PLAINTEXT, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c61ed8d..4a5211c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on # on, off, md5 or plain
+#password_encryption = on # on, off, md5, plain or scram
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 186fcd0..9fa6201 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -38,7 +38,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o encode.o exec.o \
keywords.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 3acbcbd..d72ecc7 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -19,7 +19,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..f3beea4
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index 5d07b78..2a397a3 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..c2354f0
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..8558f58 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,9 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha.c
+/sha_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 1b292d2..8f9221e 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,14 @@ 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/
+OBJS += encode.o scram-common.o
+
+ifeq ($(with_openssl),yes)
+OBJS += sha_openssl.o
+else
+OBJS += sha.o
+endif
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -102,6 +110,9 @@ ip.c md5.c: % : $(backend_src)/libpq/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode.c scram-common.c sha.c sha_openssl.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..529b833
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,418 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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");
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+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;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+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 != SCRAM_SALT_LEN)
+ 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;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d237262..4e2696d 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq/md5.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 9b2839b..590e0b7 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 1183323..b59335f 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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.9.2
0009-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-patch; name=0009-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From f7332f59b99777dd4fe689e0d85cae9defea2f92 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:00:32 +0900
Subject: [PATCH 09/10] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
doc/src/sgml/ref/alter_role.sgml | 2 ++
doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
src/backend/commands/user.c | 72 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 6 ++++
4 files changed, 94 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..b25d2c0 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the wanted protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+ an unencrypted password. If the presented password string is already
+ in MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+ as-is.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 1fc4245..054cc45 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -178,7 +178,8 @@ CreateRole(CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -186,9 +187,41 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -296,8 +329,6 @@ CreateRole(CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -571,6 +602,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -579,9 +611,41 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -669,8 +733,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b6e4618..b94dccd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -932,6 +932,12 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)));
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.9.2
0010-Add-regression-tests-for-passwords.patchapplication/x-patch; name=0010-Add-regression-tests-for-passwords.patchDownload
From 6deccb11942f8aa7d7acc23ff6c09441b9fdeae2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:55:49 +0900
Subject: [PATCH 10/10] Add regression tests for passwords
---
src/test/regress/expected/password.out | 101 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..a90f323
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: off, on, md5, scram, plain.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------
+ regress_passwd1 |
+ regress_passwd2 |
+ regress_passwd3 |
+ regress_passwd4 |
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4ebad04..a1df64c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5c7038d..165299f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.9.2
0001-Refactor-SHA-functions-and-move-them-to-src-common.patchapplication/x-patch; name=0001-Refactor-SHA-functions-and-move-them-to-src-common.patchDownload
From 68fc5556968bfae26ad0fe1c1e5778ca1a891b69 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Sun, 24 Jul 2016 13:39:18 +0900
Subject: [PATCH 01/10] Refactor SHA functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA functions,
allowing to link to either OpenSSL or the in-core stuff taken from OpenBSD
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 11 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 +-
contrib/pgcrypto/internal.c | 32 +-
contrib/pgcrypto/sha1.c | 341 ---------
contrib/pgcrypto/sha1.h | 75 --
contrib/pgcrypto/sha2.h | 100 ---
src/common/Makefile | 6 +
contrib/pgcrypto/sha2.c => src/common/sha.c | 1101 +++++++++++++++++----------
src/common/sha_openssl.c | 120 +++
src/include/common/sha.h | 107 +++
src/tools/msvc/Mkvcbuild.pm | 11 +-
13 files changed, 1024 insertions(+), 978 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
delete mode 100644 contrib/pgcrypto/sha2.h
rename contrib/pgcrypto/sha2.c => src/common/sha.c (54%)
create mode 100644 src/common/sha_openssl.c
create mode 100644 src/include/common/sha.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..582110e 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha.c
+/sha_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..492184d 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 internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c random.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
@@ -22,6 +22,12 @@ SRCS = pgcrypto.c px.c px-hmac.c px-crypt.c \
pgp-pubdec.c pgp-pubenc.c pgp-pubkey.c pgp-s2k.c \
pgp-pgsql.c
+ifeq ($(with_openssl),yes)
+SRCS += sha_openssl.c
+else
+SRCS += sha.c
+endif
+
MODULE_big = pgcrypto
OBJS = $(SRCS:.c=.o) $(WIN32RES)
@@ -59,6 +65,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha.c sha_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..6bc6faf 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..3868fd2 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..5e3fc30 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -33,9 +33,10 @@
#include <time.h>
+#include "common/sha.h"
+
#include "px.h"
#include "md5.h"
-#include "sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
@@ -63,15 +64,6 @@
#define MD5_DIGEST_LENGTH 16
#endif
-#ifndef SHA1_DIGEST_LENGTH
-#ifdef SHA1_RESULTLEN
-#define SHA1_DIGEST_LENGTH SHA1_RESULTLEN
-#else
-#define SHA1_DIGEST_LENGTH 20
-#endif
-#endif
-
-#define SHA1_BLOCK_SIZE 64
#define MD5_BLOCK_SIZE 64
static void init_md5(PX_MD *h);
@@ -152,43 +144,43 @@ int_md5_free(PX_MD *h)
static unsigned
int_sha1_len(PX_MD *h)
{
- return SHA1_DIGEST_LENGTH;
+ return PG_SHA1_DIGEST_LENGTH;
}
static unsigned
int_sha1_block_len(PX_MD *h)
{
- return SHA1_BLOCK_SIZE;
+ return PG_SHA1_BLOCK_LENGTH;
}
static void
int_sha1_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Update(ctx, data, dlen);
+ pg_sha1_update(ctx, data, dlen);
}
static void
int_sha1_reset(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Init(ctx);
+ pg_sha1_init(ctx);
}
static void
int_sha1_finish(PX_MD *h, uint8 *dst)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Final(dst, ctx);
+ pg_sha1_final(ctx, dst);
}
static void
int_sha1_free(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -220,7 +212,7 @@ init_md5(PX_MD *md)
static void
init_sha1(PX_MD *md)
{
- SHA1_CTX *ctx;
+ pg_sha1_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
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 2f61e45..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 *);
-
-/* compatibility 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/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 72b7369..c27e25e 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,6 +40,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o keywords.o \
pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha_openssl.o
+else
+OBJS_COMMON += sha.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha.c
similarity index 54%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha.c
index 231f9df..0809671 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha.c
@@ -1,10 +1,22 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha.c
+ * Set of SHA functions for SHA-1, SHA-224, SHA-256, SHA-384 and
+ * SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
- * FILE: sha2.c
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -15,14 +27,14 @@
* 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 copyright holder nor the names of contributors
+ * 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 AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ * 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 AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ * 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)
@@ -31,109 +43,341 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
- *
- * contrib/pgcrypto/sha2.c
+ * src/common/sha.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>
*/
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha.h"
-/*
- * UNROLLED TRANSFORM LOOP NOTE:
- * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
- * loop version for the hash transform rounds (defined using macros
- * later in this file). Either define on the command line, for example:
- *
- * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
- *
- * or define below:
- *
- * #define SHA2_UNROLL_TRANSFORM
- *
- */
+/* constant table */
+static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
-/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
+#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) (ctx->h.b32[(n)])
+#define COUNT (ctx->count)
+#define BCOUNT (ctx->c.b64[0] / 8)
+#define W(n) (ctx->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctx->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+static void pg_sha1_step(pg_sha1_ctx *ctx);
+static void pg_sha1_pad(pg_sha1_ctx *ctx);
+
+static void
+pg_sha1_step(pg_sha1_ctx *ctx)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
-/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
-#define REVERSE32(w,x) { \
- uint32 tmp = (w); \
- tmp = (tmp >> 16) | (tmp << 16); \
- (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+ pg_sha1_ctx tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctx->m.b8[0], 64);
+ ctx->m.b8[0] = tctxt.m.b8[3];
+ ctx->m.b8[1] = tctxt.m.b8[2];
+ ctx->m.b8[2] = tctxt.m.b8[1];
+ ctx->m.b8[3] = tctxt.m.b8[0];
+ ctx->m.b8[4] = tctxt.m.b8[7];
+ ctx->m.b8[5] = tctxt.m.b8[6];
+ ctx->m.b8[6] = tctxt.m.b8[5];
+ ctx->m.b8[7] = tctxt.m.b8[4];
+ ctx->m.b8[8] = tctxt.m.b8[11];
+ ctx->m.b8[9] = tctxt.m.b8[10];
+ ctx->m.b8[10] = tctxt.m.b8[9];
+ ctx->m.b8[11] = tctxt.m.b8[8];
+ ctx->m.b8[12] = tctxt.m.b8[15];
+ ctx->m.b8[13] = tctxt.m.b8[14];
+ ctx->m.b8[14] = tctxt.m.b8[13];
+ ctx->m.b8[15] = tctxt.m.b8[12];
+ ctx->m.b8[16] = tctxt.m.b8[19];
+ ctx->m.b8[17] = tctxt.m.b8[18];
+ ctx->m.b8[18] = tctxt.m.b8[17];
+ ctx->m.b8[19] = tctxt.m.b8[16];
+ ctx->m.b8[20] = tctxt.m.b8[23];
+ ctx->m.b8[21] = tctxt.m.b8[22];
+ ctx->m.b8[22] = tctxt.m.b8[21];
+ ctx->m.b8[23] = tctxt.m.b8[20];
+ ctx->m.b8[24] = tctxt.m.b8[27];
+ ctx->m.b8[25] = tctxt.m.b8[26];
+ ctx->m.b8[26] = tctxt.m.b8[25];
+ ctx->m.b8[27] = tctxt.m.b8[24];
+ ctx->m.b8[28] = tctxt.m.b8[31];
+ ctx->m.b8[29] = tctxt.m.b8[30];
+ ctx->m.b8[30] = tctxt.m.b8[29];
+ ctx->m.b8[31] = tctxt.m.b8[28];
+ ctx->m.b8[32] = tctxt.m.b8[35];
+ ctx->m.b8[33] = tctxt.m.b8[34];
+ ctx->m.b8[34] = tctxt.m.b8[33];
+ ctx->m.b8[35] = tctxt.m.b8[32];
+ ctx->m.b8[36] = tctxt.m.b8[39];
+ ctx->m.b8[37] = tctxt.m.b8[38];
+ ctx->m.b8[38] = tctxt.m.b8[37];
+ ctx->m.b8[39] = tctxt.m.b8[36];
+ ctx->m.b8[40] = tctxt.m.b8[43];
+ ctx->m.b8[41] = tctxt.m.b8[42];
+ ctx->m.b8[42] = tctxt.m.b8[41];
+ ctx->m.b8[43] = tctxt.m.b8[40];
+ ctx->m.b8[44] = tctxt.m.b8[47];
+ ctx->m.b8[45] = tctxt.m.b8[46];
+ ctx->m.b8[46] = tctxt.m.b8[45];
+ ctx->m.b8[47] = tctxt.m.b8[44];
+ ctx->m.b8[48] = tctxt.m.b8[51];
+ ctx->m.b8[49] = tctxt.m.b8[50];
+ ctx->m.b8[50] = tctxt.m.b8[49];
+ ctx->m.b8[51] = tctxt.m.b8[48];
+ ctx->m.b8[52] = tctxt.m.b8[55];
+ ctx->m.b8[53] = tctxt.m.b8[54];
+ ctx->m.b8[54] = tctxt.m.b8[53];
+ ctx->m.b8[55] = tctxt.m.b8[52];
+ ctx->m.b8[56] = tctxt.m.b8[59];
+ ctx->m.b8[57] = tctxt.m.b8[58];
+ ctx->m.b8[58] = tctxt.m.b8[57];
+ ctx->m.b8[59] = tctxt.m.b8[56];
+ ctx->m.b8[60] = tctxt.m.b8[63];
+ ctx->m.b8[61] = tctxt.m.b8[62];
+ ctx->m.b8[62] = tctxt.m.b8[61];
+ ctx->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(&ctx->m.b8[0], 0, 64);
}
-#define REVERSE64(w,x) { \
- uint64 tmp = (w); \
- tmp = (tmp >> 32) | (tmp << 32); \
- tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
- ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
- (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
- ((tmp & 0x0000ffff0000ffffULL) << 16); \
+
+static void
+pg_sha1_pad(pg_sha1_ctx *ctx)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctx->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ pg_sha1_step(ctx);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctx->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctx->c.b8[0]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[7]);
+#else
+ PUTPAD(ctx->c.b8[7]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[0]);
+#endif
}
-#endif /* not bigendian */
-/*
- * Macro for incrementally adding the unsigned 64-bit integer n to the
- * unsigned 128-bit integer (represented using a two-element array of
- * 64-bit words):
- */
-#define ADDINC128(w,n) { \
- (w)[0] += (uint64)(n); \
- if ((w)[0] < (n)) { \
- (w)[1]++; \
- } \
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(pg_sha1_ctx));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
}
-/*** THE SIX LOGICAL FUNCTIONS ****************************************/
-/*
- * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
- *
- * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
- * S is a ROTATION) because the SHA-256/384/512 description document
- * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
- * uses this same "backwards" definition.
- */
-/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
-#define R(b,x) ((x) >> (b))
-/* 32-bit Rotate-right (used in SHA-256): */
-#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
-/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
-#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
-/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
-#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
-#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+ input = (const uint8 *) input0;
+ off = 0;
-/* Four of six logical functions used in SHA-256: */
-#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
-#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
-#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
-#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctx->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctx->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ pg_sha1_step(ctx);
+ off += copysiz;
+ }
+}
-/* Four of six logical functions used in SHA-384 and SHA-512: */
-#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
-#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
-#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
-#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) dest;
+ pg_sha1_pad(ctx);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctx->h.b8[0], 20);
+#else
+ digest[0] = ctx->h.b8[3];
+ digest[1] = ctx->h.b8[2];
+ digest[2] = ctx->h.b8[1];
+ digest[3] = ctx->h.b8[0];
+ digest[4] = ctx->h.b8[7];
+ digest[5] = ctx->h.b8[6];
+ digest[6] = ctx->h.b8[5];
+ digest[7] = ctx->h.b8[4];
+ digest[8] = ctx->h.b8[11];
+ digest[9] = ctx->h.b8[10];
+ digest[10] = ctx->h.b8[9];
+ digest[11] = ctx->h.b8[8];
+ digest[12] = ctx->h.b8[15];
+ digest[13] = ctx->h.b8[14];
+ digest[14] = ctx->h.b8[13];
+ digest[15] = ctx->h.b8[12];
+ digest[16] = ctx->h.b8[19];
+ digest[17] = ctx->h.b8[18];
+ digest[18] = ctx->h.b8[17];
+ digest[19] = ctx->h.b8[16];
+#endif
+}
-/*** INTERNAL FUNCTION PROTOTYPES *************************************/
-/* NOTE: These should not be accessed directly from outside this
- * library -- they are intended for private internal visibility/use
- * only.
+/*
+ * UNROLLED TRANSFORM LOOP NOTE:
+ * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
+ * loop version for the hash transform rounds (defined using macros
+ * later in this file). Either define on the command line, for example:
+ *
+ * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
+ *
+ * or define below:
+ *
+ * #define SHA2_UNROLL_TRANSFORM
+ *
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
+/*** SHA-256/384/512 Various Length Definitions ***********************/
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -248,16 +492,124 @@ static const uint64 sha512_initial_hash_value[8] = {
0x5be0cd19137e2179ULL
};
+/*** ENDIAN REVERSAL MACROS *******************************************/
+#ifndef WORDS_BIGENDIAN
+#define REVERSE32(w,x) { \
+ uint32 tmp = (w); \
+ tmp = (tmp >> 16) | (tmp << 16); \
+ (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+}
+#define REVERSE64(w,x) { \
+ uint64 tmp = (w); \
+ tmp = (tmp >> 32) | (tmp << 32); \
+ tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
+ ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
+ (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
+ ((tmp & 0x0000ffff0000ffffULL) << 16); \
+}
+#endif /* not bigendian */
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+/*
+ * Macro for incrementally adding the unsigned 64-bit integer n to the
+ * unsigned 128-bit integer (represented using a two-element array of
+ * 64-bit words):
+ */
+#define ADDINC128(w,n) { \
+ (w)[0] += (uint64)(n); \
+ if ((w)[0] < (n)) { \
+ (w)[1]++; \
+ } \
+}
+
+/*** THE SIX LOGICAL FUNCTIONS ****************************************/
+/*
+ * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
+ *
+ * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
+ * S is a ROTATION) because the SHA-256/384/512 description document
+ * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
+ * uses this same "backwards" definition.
+ */
+/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
+#define R(b,x) ((x) >> (b))
+/* 32-bit Rotate-right (used in SHA-256): */
+#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
+/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
+#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+
+/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
+#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+/* Four of six logical functions used in SHA-256: */
+#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
+#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
+#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
+#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+
+/* Four of six logical functions used in SHA-384 and SHA-512: */
+#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
+#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
+#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
+#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+
+/*** INTERNAL FUNCTION PROTOTYPES *************************************/
+/* NOTE: These should not be accessed directly from outside this
+ * library -- they are intended for private internal visibility/use
+ * only.
+ */
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
+
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -287,7 +639,7 @@ SHA256_Init(SHA256_CTX *context)
} while(0)
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +655,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,14 +695,14 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -358,7 +710,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +727,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +785,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -616,7 +829,7 @@ SHA512_Init(SHA512_CTX *context)
} while(0)
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +842,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,14 +882,14 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -684,7 +897,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +911,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +972,82 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +1056,148 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +1207,42 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/* Interface routines for SHA-384 */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1252,41 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+/* Interface routines for SHA-224 */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1295,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha_openssl.c b/src/common/sha_openssl.c
new file mode 100644
index 0000000..c6aac90
--- /dev/null
+++ b/src/common/sha_openssl.c
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA
+ * functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha.h"
+
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ SHA1_Init((SHA_CTX *) ctx);
+}
+
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ SHA1_Update((SHA_CTX *) ctx, input0, len);
+}
+
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ SHA1_Final(dest, (SHA_CTX *) ctx);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha.h b/src/include/common/sha.h
new file mode 100644
index 0000000..5434e6a
--- /dev/null
+++ b/src/include/common/sha.h
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha.h
+ * Generic headers for SHA functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _PG_SHA_H_
+#define _PG_SHA_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA-1/224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA1_BLOCK_LENGTH 64
+#define PG_SHA1_DIGEST_LENGTH 20
+#define PG_SHA1_DIGEST_STRING_LENGTH (PG_SHA1_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA_CTX pg_sha1_ctx;
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha1_ctx
+{
+ 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;
+} pg_sha1_ctx;
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA-1/224/256/384/512 */
+extern void pg_sha1_init(pg_sha1_ctx *ctx);
+extern void pg_sha1_update(pg_sha1_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest);
+
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index fe905d3..02a3fc7 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
restricted_token.c));
@@ -443,13 +452,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.9.2
0002-Refactor-sendAuthRequest.patchapplication/x-patch; name=0002-Refactor-sendAuthRequest.patchDownload
From c1448e306278620b7bc13f1fb7c2e8422f879e34 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Sat, 23 Jul 2016 22:53:28 +0900
Subject: [PATCH 02/10] Refactor sendAuthRequest
This will be used by the upcoming patch to add support for SCRAM, and
improves a bit the readability of the code.
---
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 7d8fc3e..126e337 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);
@@ -498,7 +499,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);
@@ -507,7 +508,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);
@@ -531,12 +532,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;
@@ -583,7 +585,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);
}
@@ -593,7 +595,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;
@@ -602,27 +604,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);
@@ -934,7 +917,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);
}
@@ -1179,7 +1170,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);
}
@@ -1807,7 +1806,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)
{
@@ -2137,7 +2136,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)
@@ -2497,7 +2496,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.9.2
0003-Refactor-RandomSalt-to-handle-salts-of-different-len.patchapplication/x-patch; name=0003-Refactor-RandomSalt-to-handle-salts-of-different-len.patchDownload
From 0ff01ebb7782421d5ff77fbbe9d55d6e334762fe Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Sat, 23 Jul 2016 22:54:37 +0900
Subject: [PATCH 03/10] Refactor RandomSalt to handle salts of different
lengths
This will be used as well to generate a salt value for SCRAM, which
has a different length than the MD5 one which is 4 bytes long.
---
src/backend/postmaster/postmaster.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 19d11e0..2c1c76a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -404,7 +404,7 @@ static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
static long PostmasterRandom(void);
-static void RandomSalt(char *md5Salt);
+static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -2342,7 +2342,7 @@ ConnCreate(int serverFd)
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
- RandomSalt(port->md5Salt);
+ RandomSalt(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
@@ -5083,23 +5083,21 @@ StartupPacketTimeoutHandler(void)
* RandomSalt
*/
static void
-RandomSalt(char *md5Salt)
+RandomSalt(char *md5Salt, int len)
{
long rand;
+ int i;
/*
* We use % 255, sacrificing one possible byte value, so as to ensure that
* all bits of the random() value participate in the result. While at it,
* add one to avoid generating any null bytes.
*/
- rand = PostmasterRandom();
- md5Salt[0] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[1] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[2] = (rand % 255) + 1;
- rand = PostmasterRandom();
- md5Salt[3] = (rand % 255) + 1;
+ for (i = 0; i < len; i++)
+ {
+ rand = PostmasterRandom();
+ md5Salt[i] = (rand % 255) + 1;
+ }
}
/*
--
2.9.2
On 07/22/2016 03:02 AM, Tom Lane wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
On Fri, Jul 22, 2016 at 8:48 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm confused. We need that code in both libpq and backend, no?
src/common is the place for stuff of that description.Not necessarily. src/interfaces/libpq/Makefile uses a set of files
like md5.c which is located in the backend code and directly compiles
libpq.so with them, so one possibility would be to do the same for
sha.c: locate the file in src/backend/libpq/ and then fetch the file
directly when compiling libpq's shared library.Meh. That seems like a hack left over from before we had src/common.
Having said that, src/interfaces/libpq/ does have some special
requirements, because it needs the code compiled with -fpic (on most
hardware), which means it can't just use the client-side libpgcommon.a
builds. So maybe it's not worth improving this.
src/common/Makefile says:
# This makefile generates two outputs:
#
# libpgcommon.a - contains object files with FRONTEND defined,
# for use by client application and libraries
#
# libpgcommon_srv.a - contains object files without FRONTEND defined,
# for use only by the backend binaries
It claims that libpcommon.a can be used by libraries, but without -fPIC,
that's a lie.
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.Well, it could link source files from there just as easily as from the
backend. Not object files, though.
I think that's the way to go (and that's what Michael's latest patch
did). But let's update the comment in the Makefile, explaining that you
can also copy or symlink source files directly from src/common as
needed, for instance for shared libraries.
Let's take the opportunity and also move src/backend/libpq/ip.c and
md5.c into src/common. It would be weird to have sha.c in src/common,
but md5.c in src/backend/libpq. Looking at ip.c, it could be split into
two: some of the functions in ip.c are clearly not needed in the client,
like enumerating all interfaces.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 18, 2016 at 9:28 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
# This makefile generates two outputs:
#
# libpgcommon.a - contains object files with FRONTEND defined,
# for use by client application and libraries
#
# libpgcommon_srv.a - contains object files without FRONTEND
defined,
# for use only by the backend binariesIt claims that libpcommon.a can be used by libraries, but without -fPIC,
that's a lie.
Yes.
One thing about my current set of patches is that I have begun adding
files from src/common/ to libpq's list of files. As that would be new
I am wondering if I should avoid doing so.Well, it could link source files from there just as easily as from the
backend. Not object files, though.I think that's the way to go (and that's what Michael's latest patch did).
But let's update the comment in the Makefile, explaining that you can also
copy or symlink source files directly from src/common as needed, for
instance for shared libraries.
Updating that is a good idea.
Let's take the opportunity and also move src/backend/libpq/ip.c and md5.c
into src/common. It would be weird to have sha.c in src/common, but md5.c in
src/backend/libpq. Looking at ip.c, it could be split into two: some of the
functions in ip.c are clearly not needed in the client, like enumerating all
interfaces.
It would be definitely better to do all that before even moving sha.c.
For the current ip.c, I don't have a better idea than putting in
src/common/ip.c the set of routines used by both the frontend and
backend, and have fe_ip.c the new file that has the frontend-only
things. Need a patch?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 08/18/2016 03:45 PM, Michael Paquier wrote:
On Thu, Aug 18, 2016 at 9:28 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Let's take the opportunity and also move src/backend/libpq/ip.c and md5.c
into src/common. It would be weird to have sha.c in src/common, but md5.c in
src/backend/libpq. Looking at ip.c, it could be split into two: some of the
functions in ip.c are clearly not needed in the client, like enumerating all
interfaces.It would be definitely better to do all that before even moving sha.c.
Agreed.
For the current ip.c, I don't have a better idea than putting in
src/common/ip.c the set of routines used by both the frontend and
backend, and have fe_ip.c the new file that has the frontend-only
things. Need a patch?
Yes, please. I don't think there's anything there that's needed by only
the frontend, but some of the functions are needed by only the backend.
So I think we'll end up with src/common/ip.c, and
src/backend/libpq/be-ip.c. (Not sure about those names, pick something
that makes sense, given what's left in the files.)
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 19, 2016 at 1:51 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 08/18/2016 03:45 PM, Michael Paquier wrote:
On Thu, Aug 18, 2016 at 9:28 PM, Heikki Linnakangas <hlinnaka@iki.fi>
wrote:
For the current ip.c, I don't have a better idea than putting in
src/common/ip.c the set of routines used by both the frontend and
backend, and have fe_ip.c the new file that has the frontend-only
things. Need a patch?Yes, please. I don't think there's anything there that's needed by only the
frontend, but some of the functions are needed by only the backend. So I
think we'll end up with src/common/ip.c, and src/backend/libpq/be-ip.c. (Not
sure about those names, pick something that makes sense, given what's left
in the files.)
OK, so let's do that first correctly. Attached are two patches:
- 0001 moves md5 to src/common
- 0002 that does the same for ip.c.
By the way, it seems to me that having be-ip.c is not that much worth
it. I am noticing that only pg_range_sockaddr could be marked as
backend-only. pg_foreach_ifaddr is being used as well by
tools/ifaddrs/, and this one calls as well pg_sockaddr_cidr_mask. Or
is there still some utility in having src/tools/ifaddrs? If not we
could move pg_sockaddr_cidr_mask and pg_foreach_ifaddr to be
backend-only. With pg_range_sockaddr that would make half the routines
to be marked as backend-only.
I have not rebased the whole series yet of SCRAM... I'll do that after
we agree on those two patches with the two commits you have already
done cleaned up of course (thanks btw for those ones!).
--
Michael
Attachments:
0001-Bundle-md5.c-into-src-common.patchtext/x-patch; charset=US-ASCII; name=0001-Bundle-md5.c-into-src-common.patchDownload
From bdc5c5b6d6361a5e5d2de5b5fd749ceb93045aa0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 19 Aug 2016 11:57:49 +0900
Subject: [PATCH 1/2] Bundle md5.c into src/common/
---
contrib/passwordcheck/passwordcheck.c | 2 +-
src/backend/commands/user.c | 2 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth.c | 2 +-
src/backend/libpq/crypt.c | 2 +-
src/backend/utils/adt/varlena.c | 2 +-
src/common/Makefile | 2 +-
src/{backend/libpq => common}/md5.c | 11 +++++++----
src/include/{libpq => common}/md5.h | 2 +-
src/interfaces/libpq/Makefile | 9 +++++++--
src/interfaces/libpq/fe-auth.c | 2 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
12 files changed, 24 insertions(+), 16 deletions(-)
rename src/{backend/libpq => common}/md5.c (98%)
rename src/include/{libpq => common}/md5.h (96%)
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index b4c1ce0..b38d1e3 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -20,9 +20,9 @@
#include <crack.h>
#endif
+#include "common/md5.h"
#include "commands/user.h"
#include "fmgr.h"
-#include "libpq/md5.h"
PG_MODULE_MAGIC;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index b6ea950..821dce3 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -29,7 +29,7 @@
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "libpq/md5.h"
+#include "common/md5.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..82d424f 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -14,7 +14,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 \
+OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o pqcomm.o \
pqformat.o pqmq.o pqsignal.o
ifeq ($(with_openssl),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index fc8b99b..fd4bc4b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,12 +21,12 @@
#include <arpa/inet.h>
#include <unistd.h>
+#include "common/md5.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
-#include "libpq/md5.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d79f5a2..d84a180 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -21,8 +21,8 @@
#endif
#include "catalog/pg_authid.h"
+#include "common/md5.h"
#include "libpq/crypt.h"
-#include "libpq/md5.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index bf7c0cd..582d3e4 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,8 +21,8 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/md5.h"
#include "lib/hyperloglog.h"
-#include "libpq/md5.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "parser/scansup.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index 72b7369..e245867 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -37,7 +37,7 @@ override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o keywords.o \
- pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
+ md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
diff --git a/src/backend/libpq/md5.c b/src/common/md5.c
similarity index 98%
rename from src/backend/libpq/md5.c
rename to src/common/md5.c
index 5af54e6..6dad165 100644
--- a/src/backend/libpq/md5.c
+++ b/src/common/md5.c
@@ -14,13 +14,16 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/backend/libpq/md5.c
+ * src/common/md5.c
*/
-/* This is intended to be used in both frontend and backend, so use c.h */
-#include "c.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-#include "libpq/md5.h"
+#include "common/md5.h"
/*
diff --git a/src/include/libpq/md5.h b/src/include/common/md5.h
similarity index 96%
rename from src/include/libpq/md5.h
rename to src/include/common/md5.h
index f3eec8b..4a04320 100644
--- a/src/include/libpq/md5.h
+++ b/src/include/common/md5.h
@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * src/include/libpq/md5.h
+ * src/include/common/md5.h
*
*-------------------------------------------------------------------------
*/
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0b4065e..f9b1aa9 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -40,9 +40,11 @@ OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# backend/libpq
-OBJS += ip.o md5.o
+OBJS += ip.o
# utils/mb
OBJS += encnames.o wchar.o
+# common
+OBJS += md5.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -96,7 +98,10 @@ backend_src = $(top_srcdir)/src/backend
chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
-ip.c md5.c: % : $(backend_src)/libpq/%
+ip.c: % : $(backend_src)/libpq/%
+ rm -f $@ && $(LN_S) $< .
+
+md5.c: % : $(top_srcdir)/src/common/%
rm -f $@ && $(LN_S) $< .
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d237262..9b233e3 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -40,7 +40,7 @@
#include "libpq-fe.h"
#include "fe-auth.h"
-#include "libpq/md5.h"
+#include "common/md5.h"
#ifdef ENABLE_GSS
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index da4d984..4f359d4 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -111,7 +111,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c exec.c keywords.c
- pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
+ md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
our @pgcommonfrontendfiles = (
--
2.9.3
0002-Move-ip.c-from-src-backend-libpq-to-src-common.patchtext/x-patch; charset=US-ASCII; name=0002-Move-ip.c-from-src-backend-libpq-to-src-common.patchDownload
From 615757a1e926495097aaff016fb29bc427ee2de7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Fri, 19 Aug 2016 15:45:19 +0900
Subject: [PATCH 2/2] Move ip.c from src/backend/libpq to src/common
---
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth.c | 2 +-
src/backend/libpq/hba.c | 2 +-
src/backend/libpq/pqcomm.c | 2 +-
src/backend/postmaster/pgstat.c | 2 +-
src/backend/postmaster/postmaster.c | 2 +-
src/backend/utils/adt/network.c | 2 +-
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/common/Makefile | 2 +-
src/{backend/libpq => common}/ip.c | 11 +++++++----
src/include/{libpq => common}/ip.h | 5 ++---
src/interfaces/libpq/Makefile | 9 ++-------
src/interfaces/libpq/fe-connect.c | 2 +-
src/tools/ifaddrs/Makefile | 8 +++-----
src/tools/ifaddrs/test_ifaddrs.c | 2 +-
src/tools/msvc/Mkvcbuild.pm | 2 +-
16 files changed, 26 insertions(+), 31 deletions(-)
rename src/{backend/libpq => common}/ip.c (99%)
rename src/include/{libpq => common}/ip.h (95%)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 82d424f..645fddb 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -14,7 +14,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 pqcomm.o \
+OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o pqcomm.o \
pqformat.o pqmq.o pqsignal.o
ifeq ($(with_openssl),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index fd4bc4b..d907e6b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -21,10 +21,10 @@
#include <arpa/inet.h>
#include <unistd.h>
+#include "common/ip.h"
#include "common/md5.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
-#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 1b4bbce..6f71342 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -26,7 +26,7 @@
#include <unistd.h>
#include "catalog/pg_collation.h"
-#include "libpq/ip.h"
+#include "common/ip.h"
#include "libpq/libpq.h"
#include "postmaster/postmaster.h"
#include "regex/regex.h"
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index ba42753..857f529 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -89,7 +89,7 @@
#include <mstcpip.h>
#endif
-#include "libpq/ip.h"
+#include "common/ip.h"
#include "libpq/libpq.h"
#include "miscadmin.h"
#include "storage/ipc.h"
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 8fa9edb..4df876d 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -38,7 +38,7 @@
#include "access/xact.h"
#include "catalog/pg_database.h"
#include "catalog/pg_proc.h"
-#include "libpq/ip.h"
+#include "common/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 05f3f14..23c221f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -99,9 +99,9 @@
#include "access/xlog.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_control.h"
+#include "common/ip.h"
#include "lib/ilist.h"
#include "libpq/auth.h"
-#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
diff --git a/src/backend/utils/adt/network.c b/src/backend/utils/adt/network.c
index 1f8469a..729e8aa 100644
--- a/src/backend/utils/adt/network.c
+++ b/src/backend/utils/adt/network.c
@@ -14,7 +14,7 @@
#include "access/hash.h"
#include "catalog/pg_type.h"
-#include "libpq/ip.h"
+#include "common/ip.h"
#include "libpq/libpq-be.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1bba5fa..5d1ccf5 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -16,8 +16,8 @@
#include "access/htup_details.h"
#include "catalog/pg_type.h"
+#include "common/ip.h"
#include "funcapi.h"
-#include "libpq/ip.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/proc.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index e245867..ea0729c 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -36,7 +36,7 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o keywords.o \
+OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
diff --git a/src/backend/libpq/ip.c b/src/common/ip.c
similarity index 99%
rename from src/backend/libpq/ip.c
rename to src/common/ip.c
index 9591ed2..f2a4a1a 100644
--- a/src/backend/libpq/ip.c
+++ b/src/common/ip.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * src/backend/libpq/ip.c
+ * src/common/ip.c
*
* This file and the IPV6 implementation were initially provided by
* Nigel Kukard <nkukard@lbsd.net>, Linux Based Systems Design
@@ -17,8 +17,11 @@
*-------------------------------------------------------------------------
*/
-/* This is intended to be used in both frontend and backend, so use c.h */
-#include "c.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <unistd.h>
#include <sys/types.h>
@@ -32,7 +35,7 @@
#include <arpa/inet.h>
#include <sys/file.h>
-#include "libpq/ip.h"
+#include "common/ip.h"
static int range_sockaddr_AF_INET(const struct sockaddr_in * addr,
diff --git a/src/include/libpq/ip.h b/src/include/common/ip.h
similarity index 95%
rename from src/include/libpq/ip.h
rename to src/include/common/ip.h
index ce9bc6e..7b91b0a 100644
--- a/src/include/libpq/ip.h
+++ b/src/include/common/ip.h
@@ -3,12 +3,11 @@
* ip.h
* Definitions for IPv6-aware network access.
*
- * These definitions are used by both frontend and backend code. Be careful
- * what you include here!
+ * These definitions are used by both frontend and backend code.
*
* Copyright (c) 2003-2016, PostgreSQL Global Development Group
*
- * src/include/libpq/ip.h
+ * src/include/common/ip.h
*
*-------------------------------------------------------------------------
*/
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index f9b1aa9..c0e133b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -39,12 +39,10 @@ OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
-# backend/libpq
-OBJS += ip.o
# utils/mb
OBJS += encnames.o wchar.o
# common
-OBJS += md5.o
+OBJS += ip.o md5.o
ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
@@ -98,10 +96,7 @@ backend_src = $(top_srcdir)/src/backend
chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
-ip.c: % : $(backend_src)/libpq/%
- rm -f $@ && $(LN_S) $< .
-
-md5.c: % : $(top_srcdir)/src/common/%
+ip.c md5.c: % : $(top_srcdir)/src/common/%
rm -f $@ && $(LN_S) $< .
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 76b61bd..9668b52 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -72,7 +72,7 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
PQExpBuffer errorMessage);
#endif
-#include "libpq/ip.h"
+#include "common/ip.h"
#include "mb/pg_wchar.h"
#ifndef FD_CLOEXEC
diff --git a/src/tools/ifaddrs/Makefile b/src/tools/ifaddrs/Makefile
index 231f388..6d22a2a 100644
--- a/src/tools/ifaddrs/Makefile
+++ b/src/tools/ifaddrs/Makefile
@@ -12,16 +12,14 @@ subdir = src/tools/ifaddrs
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-libpq_backend_dir = $(top_builddir)/src/backend/libpq
-
-override CPPFLAGS := -I$(libpq_backend_dir) $(CPPFLAGS)
+override CPPFLAGS := -DFRONTEND -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
OBJS = test_ifaddrs.o
all: test_ifaddrs
-test_ifaddrs: test_ifaddrs.o $(libpq_backend_dir)/ip.o
- $(CC) $(CFLAGS) test_ifaddrs.o $(libpq_backend_dir)/ip.o $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+test_ifaddrs: test_ifaddrs.o
+ $(CC) $(CFLAGS) test_ifaddrs.o $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
clean distclean maintainer-clean:
rm -f test_ifaddrs$(X) $(OBJS)
diff --git a/src/tools/ifaddrs/test_ifaddrs.c b/src/tools/ifaddrs/test_ifaddrs.c
index 48d184c..deaf2c6 100644
--- a/src/tools/ifaddrs/test_ifaddrs.c
+++ b/src/tools/ifaddrs/test_ifaddrs.c
@@ -12,7 +12,7 @@
#include <netinet/in.h>
#include <sys/socket.h>
-#include "libpq/ip.h"
+#include "common/ip.h"
static void
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4f359d4..3837a30 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c keywords.c
+ config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.9.3
On 08/19/2016 09:46 AM, Michael Paquier wrote:
On Fri, Aug 19, 2016 at 1:51 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 08/18/2016 03:45 PM, Michael Paquier wrote:
On Thu, Aug 18, 2016 at 9:28 PM, Heikki Linnakangas <hlinnaka@iki.fi>
wrote:
For the current ip.c, I don't have a better idea than putting in
src/common/ip.c the set of routines used by both the frontend and
backend, and have fe_ip.c the new file that has the frontend-only
things. Need a patch?Yes, please. I don't think there's anything there that's needed by only the
frontend, but some of the functions are needed by only the backend. So I
think we'll end up with src/common/ip.c, and src/backend/libpq/be-ip.c. (Not
sure about those names, pick something that makes sense, given what's left
in the files.)OK, so let's do that first correctly. Attached are two patches:
- 0001 moves md5 to src/common
- 0002 that does the same for ip.c.
By the way, it seems to me that having be-ip.c is not that much worth
it. I am noticing that only pg_range_sockaddr could be marked as
backend-only. pg_foreach_ifaddr is being used as well by
tools/ifaddrs/, and this one calls as well pg_sockaddr_cidr_mask. Or
is there still some utility in having src/tools/ifaddrs? If not we
could move pg_sockaddr_cidr_mask and pg_foreach_ifaddr to be
backend-only. With pg_range_sockaddr that would make half the routines
to be marked as backend-only.
I decided to split ip.c anyway. I'd like to keep the files in
src/common/ip.c as small as possible, so I think it makes sense to be
quite surgical when moving things there. I kept the pg_foreach_ifaddr()
function in src/backend/libpq/ifaddr.c (I renamed the file to avoid
confusion with the ip.c that got moved), even though it means that
test_ifaddr will have to continue to copy the file directly from
src/backend/libpq. I'm OK with that, because test_ifaddrs is just a
little test program that mimics the backend's behaviour of enumerating
interfaces. I don't consider it to be a "real" frontend application.
Pushed, after splitting. Thanks! Now let's move on to the more
substantial patches.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 2, 2016 at 7:57 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
I decided to split ip.c anyway. I'd like to keep the files in
src/common/ip.c as small as possible, so I think it makes sense to be quite
surgical when moving things there. I kept the pg_foreach_ifaddr() function
in src/backend/libpq/ifaddr.c (I renamed the file to avoid confusion with
the ip.c that got moved), even though it means that test_ifaddr will have to
continue to copy the file directly from src/backend/libpq. I'm OK with that,
because test_ifaddrs is just a little test program that mimics the backend's
behaviour of enumerating interfaces. I don't consider it to be a "real"
frontend application.Pushed, after splitting. Thanks! Now let's move on to the more substantial
patches.
Before I send a new series of patches... There is one thing that I am
still troubled with: the compilation of pgcrypto. First from
contrib/pgcrypto/Makefile I am noticing the following issue with this
block:
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
How is that correct if src/Makefile.global is not loaded first?
Variables like with_openssl are still not loaded at that point.
Then, as per patch 0001 there are two files holding the SHA routines:
sha.c with the interface taken from OpenBSD, and sha_openssl.c that
uses the interface of OpenSSL. And when compiling pgcrypto, the choice
of file is made depending on the value of $(with_openssl).
As far as I know, the list of OBJS needs to be completely defined
before loading contrib-global.mk, but I fail to see how we can do that
with USE_PGXS=1... Or would it be fine to error if pgcrypto is
compiled with USE_PGXS?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael Paquier <michael.paquier@gmail.com> writes:
Before I send a new series of patches... There is one thing that I am
still troubled with: the compilation of pgcrypto. First from
contrib/pgcrypto/Makefile I am noticing the following issue with this
block:
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
How is that correct if src/Makefile.global is not loaded first?
Variables like with_openssl are still not loaded at that point.
Um, you do know that Make treats "=" definitions of variables as,
essentially, macro definitions? The fact that with_openssl isn't
set yet doesn't necessarily mean these definitions are wrong.
Is it actually not working for you, or are you just not understanding
why it works?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 2, 2016 at 10:59 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Michael Paquier <michael.paquier@gmail.com> writes:
Before I send a new series of patches... There is one thing that I am
still troubled with: the compilation of pgcrypto. First from
contrib/pgcrypto/Makefile I am noticing the following issue with this
block:
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
How is that correct if src/Makefile.global is not loaded first?
Variables like with_openssl are still not loaded at that point.Um, you do know that Make treats "=" definitions of variables as,
essentially, macro definitions? The fact that with_openssl isn't
set yet doesn't necessarily mean these definitions are wrong.
Is it actually not working for you, or are you just not understanding
why it works?
Oops right. I was trying to use an ifeq on $with_openssl, and that did
not work but just using that would go correctly...
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Sep 2, 2016 at 10:23 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Fri, Sep 2, 2016 at 7:57 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
I decided to split ip.c anyway. I'd like to keep the files in
src/common/ip.c as small as possible, so I think it makes sense to be quite
surgical when moving things there. I kept the pg_foreach_ifaddr() function
in src/backend/libpq/ifaddr.c (I renamed the file to avoid confusion with
the ip.c that got moved), even though it means that test_ifaddr will have to
continue to copy the file directly from src/backend/libpq. I'm OK with that,
because test_ifaddrs is just a little test program that mimics the backend's
behaviour of enumerating interfaces. I don't consider it to be a "real"
frontend application.Pushed, after splitting. Thanks! Now let's move on to the more substantial
patches.
Thanks for the push.
Before I send a new series of patches... There is one thing that I am
still troubled with: the compilation of pgcrypto. First from
contrib/pgcrypto/Makefile I am noticing the following issue with this
block:
CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS))
CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS))
CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST))
How is that correct if src/Makefile.global is not loaded first?
Variables like with_openssl are still not loaded at that point.Then, as per patch 0001 there are two files holding the SHA routines:
sha.c with the interface taken from OpenBSD, and sha_openssl.c that
uses the interface of OpenSSL. And when compiling pgcrypto, the choice
of file is made depending on the value of $(with_openssl).
So I have solved my identity crisis here by just using INT_SRCS and
OSSL_SRCS to list the correct files holding the SHA files. Thanks Tom
for the hint. I need to study more my Makefile-fu.
Attached is a new series:
- 0001, refactoring of SHA functions into src/common.
- 0002, move encoding routines to src/common/
- 0003, make password_encryption an enum
- 0004, refactor some code in CREATE/ALTER role code paths related the
use of password_encryption
- 0005, refactor some code to have a single routine to fetch password
and valid_until from pg_authid
- 0006, The core implementation of SCRAM-SHA-256, with the SASL
communication protocol. if you want to use SCRAM with that, things go
with password_encryption = 'scram'. I have spotted here a bug with the
MSVC build on the way.
- 0007, addition of PASSWORD val USING protocol
- 0008. regression tests for passwords. Those do not trigger the
internal sha routines, which lead to inconsistent results.
--
Michael
Attachments:
0001-Refactor-SHA-functions-and-move-them-to-src-common.patchapplication/x-patch; name=0001-Refactor-SHA-functions-and-move-them-to-src-common.patchDownload
From 9c220e96d8a2efbe377021fffd5c87fb9b4ea595 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Sat, 3 Sep 2016 20:15:31 +0900
Subject: [PATCH 1/8] Refactor SHA functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA functions,
allowing to link to either OpenSSL or the in-core stuff taken from OpenBSD
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 10 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 +-
contrib/pgcrypto/internal.c | 32 +-
contrib/pgcrypto/sha1.c | 341 ---------
contrib/pgcrypto/sha1.h | 75 --
contrib/pgcrypto/sha2.h | 100 ---
src/common/Makefile | 6 +
contrib/pgcrypto/sha2.c => src/common/sha.c | 1101 +++++++++++++++++----------
src/common/sha_openssl.c | 120 +++
src/include/common/sha.h | 107 +++
src/tools/msvc/Mkvcbuild.pm | 11 +-
13 files changed, 1021 insertions(+), 980 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
delete mode 100644 contrib/pgcrypto/sha2.h
rename contrib/pgcrypto/sha2.c => src/common/sha.c (54%)
create mode 100644 src/common/sha_openssl.c
create mode 100644 src/include/common/sha.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..582110e 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha.c
+/sha_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..195fc73 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,10 +1,11 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
- fortuna.c random.c pgp-mpi-internal.c imath.c
+INT_SRCS = md5.c internal.c internal-sha2.c blf.c rijndael.c \
+ fortuna.c random.c pgp-mpi-internal.c imath.c \
+ sha.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +60,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha.c sha_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..6bc6faf 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..3868fd2 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index cb8ba26..5e3fc30 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -33,9 +33,10 @@
#include <time.h>
+#include "common/sha.h"
+
#include "px.h"
#include "md5.h"
-#include "sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
@@ -63,15 +64,6 @@
#define MD5_DIGEST_LENGTH 16
#endif
-#ifndef SHA1_DIGEST_LENGTH
-#ifdef SHA1_RESULTLEN
-#define SHA1_DIGEST_LENGTH SHA1_RESULTLEN
-#else
-#define SHA1_DIGEST_LENGTH 20
-#endif
-#endif
-
-#define SHA1_BLOCK_SIZE 64
#define MD5_BLOCK_SIZE 64
static void init_md5(PX_MD *h);
@@ -152,43 +144,43 @@ int_md5_free(PX_MD *h)
static unsigned
int_sha1_len(PX_MD *h)
{
- return SHA1_DIGEST_LENGTH;
+ return PG_SHA1_DIGEST_LENGTH;
}
static unsigned
int_sha1_block_len(PX_MD *h)
{
- return SHA1_BLOCK_SIZE;
+ return PG_SHA1_BLOCK_LENGTH;
}
static void
int_sha1_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Update(ctx, data, dlen);
+ pg_sha1_update(ctx, data, dlen);
}
static void
int_sha1_reset(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Init(ctx);
+ pg_sha1_init(ctx);
}
static void
int_sha1_finish(PX_MD *h, uint8 *dst)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Final(dst, ctx);
+ pg_sha1_final(ctx, dst);
}
static void
int_sha1_free(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -220,7 +212,7 @@ init_md5(PX_MD *md)
static void
init_sha1(PX_MD *md)
{
- SHA1_CTX *ctx;
+ pg_sha1_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
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 2f61e45..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 *);
-
-/* compatibility 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/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index a5fa649..f1cce0f 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha_openssl.o
+else
+OBJS_COMMON += sha.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha.c
similarity index 54%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha.c
index 231f9df..0809671 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha.c
@@ -1,10 +1,22 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha.c
+ * Set of SHA functions for SHA-1, SHA-224, SHA-256, SHA-384 and
+ * SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
- * FILE: sha2.c
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -15,14 +27,14 @@
* 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 copyright holder nor the names of contributors
+ * 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 AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ * 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 AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ * 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)
@@ -31,109 +43,341 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
- *
- * contrib/pgcrypto/sha2.c
+ * src/common/sha.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>
*/
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha.h"
-/*
- * UNROLLED TRANSFORM LOOP NOTE:
- * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
- * loop version for the hash transform rounds (defined using macros
- * later in this file). Either define on the command line, for example:
- *
- * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
- *
- * or define below:
- *
- * #define SHA2_UNROLL_TRANSFORM
- *
- */
+/* constant table */
+static uint32 _K[] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};
-/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
+#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) (ctx->h.b32[(n)])
+#define COUNT (ctx->count)
+#define BCOUNT (ctx->c.b64[0] / 8)
+#define W(n) (ctx->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctx->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+static void pg_sha1_step(pg_sha1_ctx *ctx);
+static void pg_sha1_pad(pg_sha1_ctx *ctx);
+
+static void
+pg_sha1_step(pg_sha1_ctx *ctx)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
-/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
-#define REVERSE32(w,x) { \
- uint32 tmp = (w); \
- tmp = (tmp >> 16) | (tmp << 16); \
- (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+ pg_sha1_ctx tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctx->m.b8[0], 64);
+ ctx->m.b8[0] = tctxt.m.b8[3];
+ ctx->m.b8[1] = tctxt.m.b8[2];
+ ctx->m.b8[2] = tctxt.m.b8[1];
+ ctx->m.b8[3] = tctxt.m.b8[0];
+ ctx->m.b8[4] = tctxt.m.b8[7];
+ ctx->m.b8[5] = tctxt.m.b8[6];
+ ctx->m.b8[6] = tctxt.m.b8[5];
+ ctx->m.b8[7] = tctxt.m.b8[4];
+ ctx->m.b8[8] = tctxt.m.b8[11];
+ ctx->m.b8[9] = tctxt.m.b8[10];
+ ctx->m.b8[10] = tctxt.m.b8[9];
+ ctx->m.b8[11] = tctxt.m.b8[8];
+ ctx->m.b8[12] = tctxt.m.b8[15];
+ ctx->m.b8[13] = tctxt.m.b8[14];
+ ctx->m.b8[14] = tctxt.m.b8[13];
+ ctx->m.b8[15] = tctxt.m.b8[12];
+ ctx->m.b8[16] = tctxt.m.b8[19];
+ ctx->m.b8[17] = tctxt.m.b8[18];
+ ctx->m.b8[18] = tctxt.m.b8[17];
+ ctx->m.b8[19] = tctxt.m.b8[16];
+ ctx->m.b8[20] = tctxt.m.b8[23];
+ ctx->m.b8[21] = tctxt.m.b8[22];
+ ctx->m.b8[22] = tctxt.m.b8[21];
+ ctx->m.b8[23] = tctxt.m.b8[20];
+ ctx->m.b8[24] = tctxt.m.b8[27];
+ ctx->m.b8[25] = tctxt.m.b8[26];
+ ctx->m.b8[26] = tctxt.m.b8[25];
+ ctx->m.b8[27] = tctxt.m.b8[24];
+ ctx->m.b8[28] = tctxt.m.b8[31];
+ ctx->m.b8[29] = tctxt.m.b8[30];
+ ctx->m.b8[30] = tctxt.m.b8[29];
+ ctx->m.b8[31] = tctxt.m.b8[28];
+ ctx->m.b8[32] = tctxt.m.b8[35];
+ ctx->m.b8[33] = tctxt.m.b8[34];
+ ctx->m.b8[34] = tctxt.m.b8[33];
+ ctx->m.b8[35] = tctxt.m.b8[32];
+ ctx->m.b8[36] = tctxt.m.b8[39];
+ ctx->m.b8[37] = tctxt.m.b8[38];
+ ctx->m.b8[38] = tctxt.m.b8[37];
+ ctx->m.b8[39] = tctxt.m.b8[36];
+ ctx->m.b8[40] = tctxt.m.b8[43];
+ ctx->m.b8[41] = tctxt.m.b8[42];
+ ctx->m.b8[42] = tctxt.m.b8[41];
+ ctx->m.b8[43] = tctxt.m.b8[40];
+ ctx->m.b8[44] = tctxt.m.b8[47];
+ ctx->m.b8[45] = tctxt.m.b8[46];
+ ctx->m.b8[46] = tctxt.m.b8[45];
+ ctx->m.b8[47] = tctxt.m.b8[44];
+ ctx->m.b8[48] = tctxt.m.b8[51];
+ ctx->m.b8[49] = tctxt.m.b8[50];
+ ctx->m.b8[50] = tctxt.m.b8[49];
+ ctx->m.b8[51] = tctxt.m.b8[48];
+ ctx->m.b8[52] = tctxt.m.b8[55];
+ ctx->m.b8[53] = tctxt.m.b8[54];
+ ctx->m.b8[54] = tctxt.m.b8[53];
+ ctx->m.b8[55] = tctxt.m.b8[52];
+ ctx->m.b8[56] = tctxt.m.b8[59];
+ ctx->m.b8[57] = tctxt.m.b8[58];
+ ctx->m.b8[58] = tctxt.m.b8[57];
+ ctx->m.b8[59] = tctxt.m.b8[56];
+ ctx->m.b8[60] = tctxt.m.b8[63];
+ ctx->m.b8[61] = tctxt.m.b8[62];
+ ctx->m.b8[62] = tctxt.m.b8[61];
+ ctx->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(&ctx->m.b8[0], 0, 64);
}
-#define REVERSE64(w,x) { \
- uint64 tmp = (w); \
- tmp = (tmp >> 32) | (tmp << 32); \
- tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
- ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
- (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
- ((tmp & 0x0000ffff0000ffffULL) << 16); \
+
+static void
+pg_sha1_pad(pg_sha1_ctx *ctx)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctx->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ pg_sha1_step(ctx);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctx->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctx->c.b8[0]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[7]);
+#else
+ PUTPAD(ctx->c.b8[7]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[0]);
+#endif
}
-#endif /* not bigendian */
-/*
- * Macro for incrementally adding the unsigned 64-bit integer n to the
- * unsigned 128-bit integer (represented using a two-element array of
- * 64-bit words):
- */
-#define ADDINC128(w,n) { \
- (w)[0] += (uint64)(n); \
- if ((w)[0] < (n)) { \
- (w)[1]++; \
- } \
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(pg_sha1_ctx));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
}
-/*** THE SIX LOGICAL FUNCTIONS ****************************************/
-/*
- * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
- *
- * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
- * S is a ROTATION) because the SHA-256/384/512 description document
- * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
- * uses this same "backwards" definition.
- */
-/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
-#define R(b,x) ((x) >> (b))
-/* 32-bit Rotate-right (used in SHA-256): */
-#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
-/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
-#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
-/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
-#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
-#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+ input = (const uint8 *) input0;
+ off = 0;
-/* Four of six logical functions used in SHA-256: */
-#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
-#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
-#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
-#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctx->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctx->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ pg_sha1_step(ctx);
+ off += copysiz;
+ }
+}
-/* Four of six logical functions used in SHA-384 and SHA-512: */
-#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
-#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
-#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
-#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) dest;
+ pg_sha1_pad(ctx);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctx->h.b8[0], 20);
+#else
+ digest[0] = ctx->h.b8[3];
+ digest[1] = ctx->h.b8[2];
+ digest[2] = ctx->h.b8[1];
+ digest[3] = ctx->h.b8[0];
+ digest[4] = ctx->h.b8[7];
+ digest[5] = ctx->h.b8[6];
+ digest[6] = ctx->h.b8[5];
+ digest[7] = ctx->h.b8[4];
+ digest[8] = ctx->h.b8[11];
+ digest[9] = ctx->h.b8[10];
+ digest[10] = ctx->h.b8[9];
+ digest[11] = ctx->h.b8[8];
+ digest[12] = ctx->h.b8[15];
+ digest[13] = ctx->h.b8[14];
+ digest[14] = ctx->h.b8[13];
+ digest[15] = ctx->h.b8[12];
+ digest[16] = ctx->h.b8[19];
+ digest[17] = ctx->h.b8[18];
+ digest[18] = ctx->h.b8[17];
+ digest[19] = ctx->h.b8[16];
+#endif
+}
-/*** INTERNAL FUNCTION PROTOTYPES *************************************/
-/* NOTE: These should not be accessed directly from outside this
- * library -- they are intended for private internal visibility/use
- * only.
+/*
+ * UNROLLED TRANSFORM LOOP NOTE:
+ * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
+ * loop version for the hash transform rounds (defined using macros
+ * later in this file). Either define on the command line, for example:
+ *
+ * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
+ *
+ * or define below:
+ *
+ * #define SHA2_UNROLL_TRANSFORM
+ *
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
+/*** SHA-256/384/512 Various Length Definitions ***********************/
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -248,16 +492,124 @@ static const uint64 sha512_initial_hash_value[8] = {
0x5be0cd19137e2179ULL
};
+/*** ENDIAN REVERSAL MACROS *******************************************/
+#ifndef WORDS_BIGENDIAN
+#define REVERSE32(w,x) { \
+ uint32 tmp = (w); \
+ tmp = (tmp >> 16) | (tmp << 16); \
+ (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+}
+#define REVERSE64(w,x) { \
+ uint64 tmp = (w); \
+ tmp = (tmp >> 32) | (tmp << 32); \
+ tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
+ ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
+ (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
+ ((tmp & 0x0000ffff0000ffffULL) << 16); \
+}
+#endif /* not bigendian */
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+/*
+ * Macro for incrementally adding the unsigned 64-bit integer n to the
+ * unsigned 128-bit integer (represented using a two-element array of
+ * 64-bit words):
+ */
+#define ADDINC128(w,n) { \
+ (w)[0] += (uint64)(n); \
+ if ((w)[0] < (n)) { \
+ (w)[1]++; \
+ } \
+}
+
+/*** THE SIX LOGICAL FUNCTIONS ****************************************/
+/*
+ * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
+ *
+ * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
+ * S is a ROTATION) because the SHA-256/384/512 description document
+ * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
+ * uses this same "backwards" definition.
+ */
+/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
+#define R(b,x) ((x) >> (b))
+/* 32-bit Rotate-right (used in SHA-256): */
+#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
+/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
+#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+
+/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
+#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+/* Four of six logical functions used in SHA-256: */
+#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
+#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
+#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
+#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+
+/* Four of six logical functions used in SHA-384 and SHA-512: */
+#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
+#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
+#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
+#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+
+/*** INTERNAL FUNCTION PROTOTYPES *************************************/
+/* NOTE: These should not be accessed directly from outside this
+ * library -- they are intended for private internal visibility/use
+ * only.
+ */
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
+
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -287,7 +639,7 @@ SHA256_Init(SHA256_CTX *context)
} while(0)
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +655,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,14 +695,14 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -358,7 +710,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +727,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +785,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -616,7 +829,7 @@ SHA512_Init(SHA512_CTX *context)
} while(0)
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +842,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,14 +882,14 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
@@ -684,7 +897,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +911,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +972,82 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +1056,148 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +1207,42 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/* Interface routines for SHA-384 */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1252,41 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+/* Interface routines for SHA-224 */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1295,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha_openssl.c b/src/common/sha_openssl.c
new file mode 100644
index 0000000..c6aac90
--- /dev/null
+++ b/src/common/sha_openssl.c
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA
+ * functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha.h"
+
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ SHA1_Init((SHA_CTX *) ctx);
+}
+
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ SHA1_Update((SHA_CTX *) ctx, input0, len);
+}
+
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ SHA1_Final(dest, (SHA_CTX *) ctx);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha.h b/src/include/common/sha.h
new file mode 100644
index 0000000..5434e6a
--- /dev/null
+++ b/src/include/common/sha.h
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha.h
+ * Generic headers for SHA functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _PG_SHA_H_
+#define _PG_SHA_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA-1/224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA1_BLOCK_LENGTH 64
+#define PG_SHA1_DIGEST_LENGTH 20
+#define PG_SHA1_DIGEST_STRING_LENGTH (PG_SHA1_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA_CTX pg_sha1_ctx;
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha1_ctx
+{
+ 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;
+} pg_sha1_ctx;
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA-1/224/256/384/512 */
+extern void pg_sha1_init(pg_sha1_ctx *ctx);
+extern void pg_sha1_update(pg_sha1_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest);
+
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index b3ed1f5..79db3df 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
restricted_token.c));
@@ -440,13 +449,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.9.3
0002-Move-encoding-routines-to-src-common.patchapplication/x-patch; name=0002-Move-encoding-routines-to-src-common.patchDownload
From 365b96004ea81fb223b3565c75c12820651a4caf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 13:35:26 +0900
Subject: [PATCH 2/8] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA-256, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +----------------------------
src/backend/utils/adt/varlena.c | 1 +
src/common/Makefile | 6 +-
src/{backend/utils/adt => common}/encode.c | 356 ++++++++++---------------
src/include/common/encode.h | 30 +++
src/include/utils/builtins.h | 2 -
src/tools/msvc/Mkvcbuild.pm | 2 +-
7 files changed, 168 insertions(+), 637 deletions(-)
copy src/{backend/utils/adt => common}/encode.c (72%)
create mode 100644 src/include/common/encode.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..76747bf 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 582d3e4..8e65621 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode.h"
#include "common/md5.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index f1cce0f..47c42a9 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o exec.o encode.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode.c
index d833efc..4a07089 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
* encode.c
- * Various data encoding/decoding things.
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the \
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode.h b/src/include/common/encode.h
new file mode 100644
index 0000000..8166376
--- /dev/null
+++ b/src/include/common/encode.h
@@ -0,0 +1,30 @@
+/*
+ * encode.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode.h
+ */
+#ifndef COMMON_ENCODE_H
+#define COMMON_ENCODE_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* COMMON_ENCODE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e36d28e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -161,8 +161,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 79db3df..06f1a46 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ config_info.c controldata_utils.c encode.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.9.3
0003-Switch-password_encryption-to-a-enum.patchapplication/x-patch; name=0003-Switch-password_encryption-to-a-enum.patchDownload
From eee988fa8d858d538f1edb4f2c76db57f07fc4f7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 13:59:43 +0900
Subject: [PATCH 3/8] Switch password_encryption to a enum
This makes this parameter more extensible in order to add support for
future password-based authentication protocols.
---
doc/src/sgml/config.sgml | 16 +++++++++--
src/backend/commands/user.c | 22 +++++++--------
src/backend/utils/misc/guc.c | 40 ++++++++++++++++++---------
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/include/commands/user.h | 11 ++++++--
5 files changed, 60 insertions(+), 31 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7c483c6..2c3566a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,7 +1163,7 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>enum</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
@@ -1175,8 +1175,18 @@ include_dir 'conf.d'
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
<literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ password is to be encrypted.
+ </para>
+
+ <para>
+ A value set to <literal>on</> or <literal>md5</> corresponds to a
+ MD5-encrypted password, <literal>off</> or <literal>plain</>
+ corresponds to an unencrypted password.
+ </para>
+
+ <para>
+ The default is <literal>on</> (encrypt the password with MD5
+ encryption).
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 821dce3..da4acdf 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -44,7 +44,7 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
-extern bool Password_encryption;
+int Password_encryption = PASSWORD_TYPE_MD5;
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -80,7 +80,7 @@ CreateRole(CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
@@ -139,9 +139,9 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -357,7 +357,7 @@ CreateRole(CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -380,7 +380,7 @@ CreateRole(CreateRoleStmt *stmt)
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
@@ -492,7 +492,7 @@ AlterRole(AlterRoleStmt *stmt)
ListCell *option;
char *rolename = NULL;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
@@ -537,9 +537,9 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -732,7 +732,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -791,7 +791,7 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c5178f7..7274eca 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -34,6 +34,7 @@
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "commands/trigger.h"
@@ -393,6 +394,20 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
{NULL, 0, false}
};
+static const struct config_enum_entry password_encryption_options[] = {
+ {"off", PASSWORD_TYPE_PLAINTEXT, false},
+ {"on", PASSWORD_TYPE_MD5, false},
+ {"md5", PASSWORD_TYPE_MD5, false},
+ {"plain", PASSWORD_TYPE_PLAINTEXT, false},
+ {"true", PASSWORD_TYPE_MD5, true},
+ {"false", PASSWORD_TYPE_PLAINTEXT, true},
+ {"yes", PASSWORD_TYPE_MD5, true},
+ {"no", PASSWORD_TYPE_PLAINTEXT, true},
+ {"1", PASSWORD_TYPE_MD5, true},
+ {"0", PASSWORD_TYPE_PLAINTEXT, true},
+ {NULL, 0, false}
+};
+
/*
* Options for enum values stored in other modules
*/
@@ -423,8 +438,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -1314,17 +1327,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3810,6 +3812,18 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("Encrypt passwords."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines whether the password is to be encrypted.")
+ },
+ &Password_encryption,
+ PASSWORD_TYPE_MD5, password_encryption_options,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 6d0666c..c61ed8d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = on # on, off, md5 or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d35cb0c..3acbcbd 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -15,9 +15,14 @@
#include "nodes/parsenodes.h"
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
+/* Types of password */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern int Password_encryption;
typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
--
2.9.3
0004-Refactor-decision-making-of-password-encryption-into.patchapplication/x-patch; name=0004-Refactor-decision-making-of-password-encryption-into.patchDownload
From 8dc49ba7062c00742876a88aabeb9ad264a5e0aa Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:13:59 +0900
Subject: [PATCH 4/8] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 81 +++++++++++++++++++++++++++++++--------------
1 file changed, 57 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index da4acdf..feb8614 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,47 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = password;
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = password;
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +124,7 @@ CreateRole(CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -380,17 +423,12 @@ CreateRole(CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -493,7 +531,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -791,17 +829,12 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
--
2.9.3
0005-Create-generic-routine-to-fetch-password-and-valid-u.patchapplication/x-patch; name=0005-Create-generic-routine-to-fetch-password-and-valid-u.patchDownload
From 65bf1db7dae12f3e3095de73c1449b11437399dc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:40:15 +0900
Subject: [PATCH 5/8] Create generic routine to fetch password and valid until
values for a role
This is used now for the MD5-encrypted case and the plain text, and this
is going to be used as well for SCRAM-SHA256. That's as well useful for
any new password-based protocols.
---
src/backend/libpq/crypt.c | 59 +++++++++++++++++++++++++++++++++++------------
src/include/libpq/crypt.h | 2 ++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c41c57 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,49 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
--
2.9.3
0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-patch; name=0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From 9a52518e0cdb741d59353df9e68505e8d2007fb5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Sat, 3 Sep 2016 21:27:45 +0900
Subject: [PATCH 6/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 4 +
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 9 +-
doc/src/sgml/protocol.sgml | 147 +++++-
doc/src/sgml/ref/create_role.sgml | 14 +-
src/backend/commands/user.c | 16 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 712 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 132 +++++
src/backend/libpq/crypt.c | 1 +
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/parser/gram.y | 8 +-
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 51 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 26 +
src/interfaces/libpq/.gitignore | 4 +
src/interfaces/libpq/Makefile | 11 +-
src/interfaces/libpq/fe-auth-scram.c | 418 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
33 files changed, 1946 insertions(+), 46 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..9a9f2bf 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -128,6 +128,10 @@ check_password(const char *username,
#endif
break;
+ case PASSWORD_TYPE_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
+
default:
elog(ERROR, "unrecognized password type: %d", password_type);
break;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 322d8d6..227afa5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 2c3566a..3dde970 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1181,7 +1181,8 @@ include_dir 'conf.d'
<para>
A value set to <literal>on</> or <literal>md5</> corresponds to a
MD5-encrypted password, <literal>off</> or <literal>plain</>
- corresponds to an unencrypted password.
+ corresponds to an unencrypted password. Setting this parameter to
+ <literal>scram</> will encrypt the password with SCRAM-SHA-256.
</para>
<para>
@@ -1260,8 +1261,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 68b0941..8af888d 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>
@@ -2578,6 +2607,114 @@ 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>
@@ -4340,7 +4477,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index feb8614..a079c32 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,8 +70,8 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -80,11 +81,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = password;
else
{
@@ -100,6 +101,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..fadebfc
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,712 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode.h"
+#include "common/scram-common.h"
+#include "common/sha.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+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 */
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d907e6b..55deae0 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,9 +27,11 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -201,6 +203,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -262,6 +270,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;
@@ -542,6 +551,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, "");
@@ -716,6 +729,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * 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);
+
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1c41c57..3c6701b 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -84,6 +84,7 @@ get_role_details(const char *role,
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
+
}
return STATUS_OK;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cb5cfc4..c89ebe3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -628,10 +628,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
- SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
- SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
- STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
+ SAVEPOINT SCHEMA SCRAM SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+ SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE
+ SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+ START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index eaf3f61..9d85e3f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2341,6 +2341,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7274eca..bf1e8f9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -398,6 +398,7 @@ static const struct config_enum_entry password_encryption_options[] = {
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"true", PASSWORD_TYPE_MD5, true},
{"false", PASSWORD_TYPE_PLAINTEXT, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c61ed8d..4a5211c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on # on, off, md5 or plain
+#password_encryption = on # on, off, md5, plain or scram
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 47c42a9..d300805 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o encode.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 3acbcbd..d72ecc7 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -19,7 +19,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..f3beea4
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index ecdfbc6..54e9c2d 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..c2354f0
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..8558f58 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,9 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha.c
+/sha_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..eb1fe1a 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
@@ -42,10 +42,12 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += ip.o md5.o encode.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha_openssl.o
+else
+OBJS += sha.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode.c scram-common.c sha.c sha_openssl.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..529b833
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,418 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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");
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+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;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+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 != SCRAM_SALT_LEN)
+ 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;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 9668b52..b55ea2c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 a94ead0..191552d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 06f1a46..448b6ea 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c encode.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha_openssl.c if building without OpenSSL, and remove sha.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha.c');
}
my $libpqwalreceiver =
--
2.9.3
0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-patch; name=0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 29aa58464b8f3f9c50bd0ae952ea084a4c947812 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:00:32 +0900
Subject: [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
doc/src/sgml/ref/alter_role.sgml | 2 ++
doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
src/backend/commands/user.c | 72 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 6 ++++
4 files changed, 94 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..b25d2c0 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the wanted protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+ an unencrypted password. If the presented password string is already
+ in MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+ as-is.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index a079c32..0f71f36 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -178,7 +178,8 @@ CreateRole(CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -186,9 +187,41 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -296,8 +329,6 @@ CreateRole(CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -571,6 +602,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -579,9 +611,41 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -669,8 +733,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c89ebe3..bdfe383 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,12 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)));
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.9.3
0008-Add-regression-tests-for-passwords.patchapplication/x-patch; name=0008-Add-regression-tests-for-passwords.patchDownload
From 217a63e8d21791ad88d4019b27ad1b288af9a3a2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:55:49 +0900
Subject: [PATCH 8/8] Add regression tests for passwords
---
src/test/regress/expected/password.out | 101 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..a90f323
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: off, on, md5, scram, plain.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------
+ regress_passwd1 |
+ regress_passwd2 |
+ regress_passwd3 |
+ regress_passwd4 |
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1cb5dfc..c7b6003 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8958d8c..bd29296 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.9.3
On 9/3/16 8:36 AM, Michael Paquier wrote:
Attached is a new series:
* [PATCH 1/8] Refactor SHA functions and move them to src/common/
I'd like to see more code comments in sha.c (though I realize this was
copied directly from pgcrypto.)
I tested by building with and without --with-openssl and running make
check for the project as a whole and the pgcrypto extension.
I notice that the copyright from pgcrypto/sha1.c was carried over but
not the copyright from pgcrypto/sha2.c. I'm no expert on how this
works, but I believe the copyright from sha2.c must be copied over.
Also, are there any plans to expose these functions directly to the user
without loading pgcrypto? Now that the functionality is in core it
seems that would be useful. In addition, it would make this patch stand
on its own rather than just being a building block
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?
* [PATCH 3/8] Switch password_encryption to a enum
Does not apply on HEAD (98c2d3332):
error: patch failed: src/backend/commands/user.c:139
error: src/backend/commands/user.c: patch does not apply
error: patch failed: src/include/commands/user.h:15
error: src/include/commands/user.h: patch does not apply
For here on I used 39b691f251 for review and testing.
I seems you are keeping on/off for backwards compatibility, shouldn't
the default now be "md5"?
-#password_encryption = on
+#password_encryption = on # on, off, md5 or plain
* [PATCH 4/8] Refactor decision-making of password encryption into a
single routine
+++ b/src/backend/commands/user.c
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
pfree(encrypted_passwd) here or let it get freed with the context?
* [PATCH 5/8] Create generic routine to fetch password and valid until
values for a role
Couldn't md5_crypt_verify() be made more general and take the hash type?
For instance, password_crypt_verify() with the last param as the new
password type enum.
* [PATCH 6/8] Support for SCRAM-SHA-256 authentication
+++ b/contrib/passwordcheck/passwordcheck.c
+ case PASSWORD_TYPE_SCRAM:
+ /* unfortunately not much can be done here */
+ break;
Why can't we at least do the same check as md5 to make sure the username
was not used as the password?
+++ b/src/backend/libpq/auth.c
+ * without relying on the length word, but we hardly care about protocol
+ * version or older anymore.)
Do you mean protocol version 2 or older?
+++ b/src/backend/libpq/crypt.c
return STATUS_ERROR; /* empty password */
+
Looks like a stray LF.
+++ b/src/backend/parser/gram.y
+ SAVEPOINT SCHEMA SCRAM SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
Doesn't this belong in patch 7? Even in patch 7 it doesn't appear that
SCRAM is a keyword since the protocol specified after USING is quoted.
I tested this patch using both md5 and scram and was able to get both of
them to working separately.
However, it doesn't look like they can be used in conjunction since the
pg_hba.conf entry must specify either m5 or scram (though the database
can easily contain a mixture). This would probably make a migration
very unpleasant.
Is there any chance of a mixed mode that will allow new passwords to be
set as scram while still honoring the old md5 passwords? Or does that
cause too many complications with the protocol?
* [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE
+++ b/doc/src/sgml/ref/create_role.sgml
+ Sets the role's password using the wanted protocol.
How about "Sets the role's password using the requested procotol."
+ an unencrypted password. If the presented password string is
already
+ in MD5-encrypted or SCRAM-encrypted format, then it is stored
encrypted
+ as-is.
How about, "If the password string is..."
* [PATCH 8/8] Add regression tests for passwords
OK.
On the whole I find this patch set easier to digest than what was
submitted for 9.6. It is more targeted but still provides very valuable
functionality.
I'm a bit concerned that a mixture of md5/scram could cause confusion
and think this may warrant discussion somewhere in the documentation
since the idea is for users to migrate from md5 to scram.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net> wrote:
On 9/3/16 8:36 AM, Michael Paquier wrote:
Attached is a new series:
Thanks for the review and the comments!
* [PATCH 1/8] Refactor SHA functions and move them to src/common/
I'd like to see more code comments in sha.c (though I realize this was
copied directly from pgcrypto.)
OK... I have added some comments for the user-facing routines, as well
as the private routines that are doing step-by-step random
calculations.
I notice that the copyright from pgcrypto/sha1.c was carried over but
not the copyright from pgcrypto/sha2.c. I'm no expert on how this
works, but I believe the copyright from sha2.c must be copied over.
Right, those copyright bits are missing:
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
[...]
- * Copyright (c) 2000-2001, Aaron D. Gifford
The license block being the same, it seems to me that there is no need
to copy it over. The copyright should be enough.
Also, are there any plans to expose these functions directly to the user
without loading pgcrypto? Now that the functionality is in core it
seems that would be useful. In addition, it would make this patch stand
on its own rather than just being a building block.
There have been discussions about avoiding enabling those functions by
default in the distribution. We'd rather not do that...
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?
Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.
* [PATCH 3/8] Switch password_encryption to a enum
Does not apply on HEAD (98c2d3332):
Interesting, it works for me on da6c4f6.
For here on I used 39b691f251 for review and testing.
I seems you are keeping on/off for backwards compatibility, shouldn't
the default now be "md5"?-#password_encryption = on +#password_encryption = on # on, off, md5 or plain
That sounds like a good idea, so switched this way.
* [PATCH 4/8] Refactor decision-making of password encryption into a
single routine+++ b/src/backend/commands/user.c + new_record[Anum_pg_authid_rolpassword - 1] = + CStringGetTextDatum(encrypted_passwd);pfree(encrypted_passwd) here or let it get freed with the context?
Calling encrypt_password did not ensure that the password needs to be
free'd.. So I guess that at the moment I coded that I just relied on
the context. But well reading now let's do this cleanly and have
encrypt_password return a palloc'ed string. That's more consistent.
* [PATCH 5/8] Create generic routine to fetch password and valid until
values for a roleCouldn't md5_crypt_verify() be made more general and take the hash type?
For instance, password_crypt_verify() with the last param as the new
password type enum.
This would mean incorporating the whole SASL message exchange into
this routine because the password string is part of the scram
initialization context, and it seems to me that it is better to just
do once a lookup at the entry in pg_authid. So we'd finish with a more
confusing code I am afraid. At least that's the conclusion I came up
with when doing that.. md5_crypt_verify does only the work on a
received password.
* [PATCH 6/8] Support for SCRAM-SHA-256 authentication
+++ b/contrib/passwordcheck/passwordcheck.c + case PASSWORD_TYPE_SCRAM: + /* unfortunately not much can be done here */ + break;Why can't we at least do the same check as md5 to make sure the username
was not used as the password?
You are right. We could at least check that, so changed the way you suggest.
+++ b/src/backend/libpq/auth.c + * without relying on the length word, but we hardly care about protocol + * version or older anymore.)Do you mean protocol version 2 or older?
+++ b/src/backend/libpq/crypt.c return STATUS_ERROR; /* empty password */ +Looks like a stray LF.
Fixed.
+++ b/src/backend/parser/gram.y + SAVEPOINT SCHEMA SCRAM SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCEDoesn't this belong in patch 7? Even in patch 7 it doesn't appear that
SCRAM is a keyword since the protocol specified after USING is quoted.
This is some garbage from a past version. Fixed.
However, it doesn't look like they can be used in conjunction since the
pg_hba.conf entry must specify either m5 or scram (though the database
can easily contain a mixture). This would probably make a migration
very unpleasant.
Yep, it uses a given auth-method once user and database match. This is
partially related to the problem to support multiple password
verifiers per users, which was submitted last CF but got rejected
because of a lack of interest, and removed to simplify this patch. You
need as well to think about other things like password and protocol
aging. But well, it is a problem that we don't have to tackle with
this patch...
Is there any chance of a mixed mode that will allow new passwords to be
set as scram while still honoring the old md5 passwords? Or does that
cause too many complications with the protocol?
Hm. That looks complicated to me. This sounds to me like a retry logic
if for multiple authentication methods, and a different feature. What
you'd be looking for here is a connection parameter to specify a list
of protocols and try them all, no?
And that:
+ * multiple messags sent in both directions. First message is always from
* [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE
+++ b/doc/src/sgml/ref/create_role.sgml + Sets the role's password using the wanted protocol.How about "Sets the role's password using the requested procotol."
Done.
+ an unencrypted password. If the presented password string is already + in MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted + as-is.How about, "If the password string is..."
OK.
On the whole I find this patch set easier to digest than what was
submitted for 9.6. It is more targeted but still provides very valuable
functionality.
Thanks.
I'm a bit concerned that a mixture of md5/scram could cause confusion
and think this may warrant discussion somewhere in the documentation
since the idea is for users to migrate from md5 to scram.
We could finish with a red warning in the docs to say that users are
recommended to use SCRAM instead of MD5. Just an idea, perhaps that's
not mandatory for the first shot though.
--
Michael
Attachments:
0001-Refactor-SHA-functions-and-move-them-to-src-common.patchapplication/x-download; name=0001-Refactor-SHA-functions-and-move-them-to-src-common.patchDownload
From a40827182a8536e62aa0e90c8543f0dcd98f8cc4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:07:57 +0900
Subject: [PATCH 1/8] Refactor SHA functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA functions,
allowing to link to either OpenSSL or the in-core stuff taken from OpenBSD
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 10 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 +-
contrib/pgcrypto/internal.c | 32 +-
contrib/pgcrypto/sha1.c | 341 --------
contrib/pgcrypto/sha1.h | 75 --
contrib/pgcrypto/sha2.h | 100 ---
src/common/Makefile | 6 +
contrib/pgcrypto/sha2.c => src/common/sha.c | 1184 ++++++++++++++++++---------
src/common/sha_openssl.c | 120 +++
src/include/common/sha.h | 107 +++
src/tools/msvc/Mkvcbuild.pm | 11 +-
13 files changed, 1110 insertions(+), 974 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
delete mode 100644 contrib/pgcrypto/sha2.h
rename contrib/pgcrypto/sha2.c => src/common/sha.c (51%)
create mode 100644 src/common/sha_openssl.c
create mode 100644 src/include/common/sha.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..582110e 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha.c
+/sha_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..195fc73 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,10 +1,11 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
- fortuna.c random.c pgp-mpi-internal.c imath.c
+INT_SRCS = md5.c internal.c internal-sha2.c blf.c rijndael.c \
+ fortuna.c random.c pgp-mpi-internal.c imath.c \
+ sha.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +60,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha.c sha_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..6bc6faf 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..3868fd2 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index 02ff976..4033ad9 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -33,9 +33,10 @@
#include <time.h>
+#include "common/sha.h"
+
#include "px.h"
#include "md5.h"
-#include "sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
@@ -63,15 +64,6 @@
#define MD5_DIGEST_LENGTH 16
#endif
-#ifndef SHA1_DIGEST_LENGTH
-#ifdef SHA1_RESULTLEN
-#define SHA1_DIGEST_LENGTH SHA1_RESULTLEN
-#else
-#define SHA1_DIGEST_LENGTH 20
-#endif
-#endif
-
-#define SHA1_BLOCK_SIZE 64
#define MD5_BLOCK_SIZE 64
static void init_md5(PX_MD *h);
@@ -152,43 +144,43 @@ int_md5_free(PX_MD *h)
static unsigned
int_sha1_len(PX_MD *h)
{
- return SHA1_DIGEST_LENGTH;
+ return PG_SHA1_DIGEST_LENGTH;
}
static unsigned
int_sha1_block_len(PX_MD *h)
{
- return SHA1_BLOCK_SIZE;
+ return PG_SHA1_BLOCK_LENGTH;
}
static void
int_sha1_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Update(ctx, data, dlen);
+ pg_sha1_update(ctx, data, dlen);
}
static void
int_sha1_reset(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Init(ctx);
+ pg_sha1_init(ctx);
}
static void
int_sha1_finish(PX_MD *h, uint8 *dst)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Final(dst, ctx);
+ pg_sha1_final(ctx, dst);
}
static void
int_sha1_free(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -220,7 +212,7 @@ init_md5(PX_MD *md)
static void
init_sha1(PX_MD *md)
{
- SHA1_CTX *ctx;
+ pg_sha1_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
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 2f61e45..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 *);
-
-/* compatibility 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/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index a5fa649..f1cce0f 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha_openssl.o
+else
+OBJS_COMMON += sha.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha.c
similarity index 51%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha.c
index 231f9df..920fa64 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha.c
@@ -1,10 +1,23 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha.c
+ * Set of SHA functions for SHA-1, SHA-224, SHA-256, SHA-384 and
+ * SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
- * FILE: sha2.c
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2000-2001, Aaron D. Gifford
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -15,14 +28,14 @@
* 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 copyright holder nor the names of contributors
+ * 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 AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ * 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 AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ * 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)
@@ -31,109 +44,364 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
+ * 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>
+ *
+ * SHA-224, SHA-256, SHA-384, SHA-512 implemented by
+ * Aaron D. Gifford <me@aarongifford.com>
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha.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) (ctx->h.b32[(n)])
+#define COUNT (ctx->count)
+#define BCOUNT (ctx->c.b64[0] / 8)
+#define W(n) (ctx->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctx->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
/*
- * UNROLLED TRANSFORM LOOP NOTE:
- * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
- * loop version for the hash transform rounds (defined using macros
- * later in this file). Either define on the command line, for example:
- *
- * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
- *
- * or define below:
- *
- * #define SHA2_UNROLL_TRANSFORM
- *
+ * Internal routines for SHA-1 generation. Those should remain private.
*/
+static void pg_sha1_step(pg_sha1_ctx *ctx);
+static void pg_sha1_pad(pg_sha1_ctx *ctx);
-/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+/*
+ * Perform a step calculation, consisting in shuffling the bytes of
+ * data around to add more randomness in the result.
+ */
+static void
+pg_sha1_step(pg_sha1_ctx *ctx)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
-/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
-#define REVERSE32(w,x) { \
- uint32 tmp = (w); \
- tmp = (tmp >> 16) | (tmp << 16); \
- (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+ pg_sha1_ctx tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctx->m.b8[0], 64);
+ ctx->m.b8[0] = tctxt.m.b8[3];
+ ctx->m.b8[1] = tctxt.m.b8[2];
+ ctx->m.b8[2] = tctxt.m.b8[1];
+ ctx->m.b8[3] = tctxt.m.b8[0];
+ ctx->m.b8[4] = tctxt.m.b8[7];
+ ctx->m.b8[5] = tctxt.m.b8[6];
+ ctx->m.b8[6] = tctxt.m.b8[5];
+ ctx->m.b8[7] = tctxt.m.b8[4];
+ ctx->m.b8[8] = tctxt.m.b8[11];
+ ctx->m.b8[9] = tctxt.m.b8[10];
+ ctx->m.b8[10] = tctxt.m.b8[9];
+ ctx->m.b8[11] = tctxt.m.b8[8];
+ ctx->m.b8[12] = tctxt.m.b8[15];
+ ctx->m.b8[13] = tctxt.m.b8[14];
+ ctx->m.b8[14] = tctxt.m.b8[13];
+ ctx->m.b8[15] = tctxt.m.b8[12];
+ ctx->m.b8[16] = tctxt.m.b8[19];
+ ctx->m.b8[17] = tctxt.m.b8[18];
+ ctx->m.b8[18] = tctxt.m.b8[17];
+ ctx->m.b8[19] = tctxt.m.b8[16];
+ ctx->m.b8[20] = tctxt.m.b8[23];
+ ctx->m.b8[21] = tctxt.m.b8[22];
+ ctx->m.b8[22] = tctxt.m.b8[21];
+ ctx->m.b8[23] = tctxt.m.b8[20];
+ ctx->m.b8[24] = tctxt.m.b8[27];
+ ctx->m.b8[25] = tctxt.m.b8[26];
+ ctx->m.b8[26] = tctxt.m.b8[25];
+ ctx->m.b8[27] = tctxt.m.b8[24];
+ ctx->m.b8[28] = tctxt.m.b8[31];
+ ctx->m.b8[29] = tctxt.m.b8[30];
+ ctx->m.b8[30] = tctxt.m.b8[29];
+ ctx->m.b8[31] = tctxt.m.b8[28];
+ ctx->m.b8[32] = tctxt.m.b8[35];
+ ctx->m.b8[33] = tctxt.m.b8[34];
+ ctx->m.b8[34] = tctxt.m.b8[33];
+ ctx->m.b8[35] = tctxt.m.b8[32];
+ ctx->m.b8[36] = tctxt.m.b8[39];
+ ctx->m.b8[37] = tctxt.m.b8[38];
+ ctx->m.b8[38] = tctxt.m.b8[37];
+ ctx->m.b8[39] = tctxt.m.b8[36];
+ ctx->m.b8[40] = tctxt.m.b8[43];
+ ctx->m.b8[41] = tctxt.m.b8[42];
+ ctx->m.b8[42] = tctxt.m.b8[41];
+ ctx->m.b8[43] = tctxt.m.b8[40];
+ ctx->m.b8[44] = tctxt.m.b8[47];
+ ctx->m.b8[45] = tctxt.m.b8[46];
+ ctx->m.b8[46] = tctxt.m.b8[45];
+ ctx->m.b8[47] = tctxt.m.b8[44];
+ ctx->m.b8[48] = tctxt.m.b8[51];
+ ctx->m.b8[49] = tctxt.m.b8[50];
+ ctx->m.b8[50] = tctxt.m.b8[49];
+ ctx->m.b8[51] = tctxt.m.b8[48];
+ ctx->m.b8[52] = tctxt.m.b8[55];
+ ctx->m.b8[53] = tctxt.m.b8[54];
+ ctx->m.b8[54] = tctxt.m.b8[53];
+ ctx->m.b8[55] = tctxt.m.b8[52];
+ ctx->m.b8[56] = tctxt.m.b8[59];
+ ctx->m.b8[57] = tctxt.m.b8[58];
+ ctx->m.b8[58] = tctxt.m.b8[57];
+ ctx->m.b8[59] = tctxt.m.b8[56];
+ ctx->m.b8[60] = tctxt.m.b8[63];
+ ctx->m.b8[61] = tctxt.m.b8[62];
+ ctx->m.b8[62] = tctxt.m.b8[61];
+ ctx->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(&ctx->m.b8[0], 0, 64);
}
-#define REVERSE64(w,x) { \
- uint64 tmp = (w); \
- tmp = (tmp >> 32) | (tmp << 32); \
- tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
- ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
- (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
- ((tmp & 0x0000ffff0000ffffULL) << 16); \
+
+/*
+ * Add some padding to a SHA-1.
+ */
+static void
+pg_sha1_pad(pg_sha1_ctx *ctx)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctx->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ pg_sha1_step(ctx);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctx->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctx->c.b8[0]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[7]);
+#else
+ PUTPAD(ctx->c.b8[7]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[0]);
+#endif
}
-#endif /* not bigendian */
/*
- * Macro for incrementally adding the unsigned 64-bit integer n to the
- * unsigned 128-bit integer (represented using a two-element array of
- * 64-bit words):
+ * pg_sha1_init
+ * Initialize calculation of SHA-1.
*/
-#define ADDINC128(w,n) { \
- (w)[0] += (uint64)(n); \
- if ((w)[0] < (n)) { \
- (w)[1]++; \
- } \
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(pg_sha1_ctx));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
}
-/*** THE SIX LOGICAL FUNCTIONS ****************************************/
/*
- * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
- *
- * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
- * S is a ROTATION) because the SHA-256/384/512 description document
- * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
- * uses this same "backwards" definition.
+ * pg_sha1_update
+ * Update SHA-1 using given input data.
*/
-/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
-#define R(b,x) ((x) >> (b))
-/* 32-bit Rotate-right (used in SHA-256): */
-#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
-/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
-#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
-/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
-#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
-#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+ input = (const uint8 *) input0;
+ off = 0;
-/* Four of six logical functions used in SHA-256: */
-#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
-#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
-#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
-#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctx->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctx->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ pg_sha1_step(ctx);
+ off += copysiz;
+ }
+}
-/* Four of six logical functions used in SHA-384 and SHA-512: */
-#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
-#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
-#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
-#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+/*
+ * pg_sha1_final
+ * Finalize calculation of SHA-1 and save result to be reused by caller.
+ */
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) dest;
+ pg_sha1_pad(ctx);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctx->h.b8[0], 20);
+#else
+ digest[0] = ctx->h.b8[3];
+ digest[1] = ctx->h.b8[2];
+ digest[2] = ctx->h.b8[1];
+ digest[3] = ctx->h.b8[0];
+ digest[4] = ctx->h.b8[7];
+ digest[5] = ctx->h.b8[6];
+ digest[6] = ctx->h.b8[5];
+ digest[7] = ctx->h.b8[4];
+ digest[8] = ctx->h.b8[11];
+ digest[9] = ctx->h.b8[10];
+ digest[10] = ctx->h.b8[9];
+ digest[11] = ctx->h.b8[8];
+ digest[12] = ctx->h.b8[15];
+ digest[13] = ctx->h.b8[14];
+ digest[14] = ctx->h.b8[13];
+ digest[15] = ctx->h.b8[12];
+ digest[16] = ctx->h.b8[19];
+ digest[17] = ctx->h.b8[18];
+ digest[18] = ctx->h.b8[17];
+ digest[19] = ctx->h.b8[16];
+#endif
+}
-/*** INTERNAL FUNCTION PROTOTYPES *************************************/
-/* NOTE: These should not be accessed directly from outside this
- * library -- they are intended for private internal visibility/use
- * only.
+/*
+ * UNROLLED TRANSFORM LOOP NOTE:
+ * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
+ * loop version for the hash transform rounds (defined using macros
+ * later in this file). Either define on the command line, for example:
+ *
+ * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
+ *
+ * or define below:
+ *
+ * #define SHA2_UNROLL_TRANSFORM
+ *
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
+/*** SHA-256/384/512 Various Length Definitions ***********************/
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -248,16 +516,124 @@ static const uint64 sha512_initial_hash_value[8] = {
0x5be0cd19137e2179ULL
};
+/*** ENDIAN REVERSAL MACROS *******************************************/
+#ifndef WORDS_BIGENDIAN
+#define REVERSE32(w,x) { \
+ uint32 tmp = (w); \
+ tmp = (tmp >> 16) | (tmp << 16); \
+ (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+}
+#define REVERSE64(w,x) { \
+ uint64 tmp = (w); \
+ tmp = (tmp >> 32) | (tmp << 32); \
+ tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
+ ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
+ (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
+ ((tmp & 0x0000ffff0000ffffULL) << 16); \
+}
+#endif /* not bigendian */
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+/*
+ * Macro for incrementally adding the unsigned 64-bit integer n to the
+ * unsigned 128-bit integer (represented using a two-element array of
+ * 64-bit words):
+ */
+#define ADDINC128(w,n) { \
+ (w)[0] += (uint64)(n); \
+ if ((w)[0] < (n)) { \
+ (w)[1]++; \
+ } \
+}
+
+/*** THE SIX LOGICAL FUNCTIONS ****************************************/
+/*
+ * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
+ *
+ * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
+ * S is a ROTATION) because the SHA-256/384/512 description document
+ * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
+ * uses this same "backwards" definition.
+ */
+/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
+#define R(b,x) ((x) >> (b))
+/* 32-bit Rotate-right (used in SHA-256): */
+#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
+/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
+#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+
+/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
+#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+/* Four of six logical functions used in SHA-256: */
+#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
+#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
+#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
+#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+
+/* Four of six logical functions used in SHA-384 and SHA-512: */
+#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
+#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
+#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
+#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+
+/*** INTERNAL FUNCTION PROTOTYPES *************************************/
+/* NOTE: These should not be accessed directly from outside this
+ * library -- they are intended for private internal visibility/use
+ * only.
+ */
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
+
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +662,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +684,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +724,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +761,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +819,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +862,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +881,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +921,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +955,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +1016,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +1107,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +1275,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1333,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1390,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha_openssl.c b/src/common/sha_openssl.c
new file mode 100644
index 0000000..c6aac90
--- /dev/null
+++ b/src/common/sha_openssl.c
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA
+ * functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha.h"
+
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ SHA1_Init((SHA_CTX *) ctx);
+}
+
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ SHA1_Update((SHA_CTX *) ctx, input0, len);
+}
+
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ SHA1_Final(dest, (SHA_CTX *) ctx);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha.h b/src/include/common/sha.h
new file mode 100644
index 0000000..5434e6a
--- /dev/null
+++ b/src/include/common/sha.h
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha.h
+ * Generic headers for SHA functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _PG_SHA_H_
+#define _PG_SHA_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA-1/224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA1_BLOCK_LENGTH 64
+#define PG_SHA1_DIGEST_LENGTH 20
+#define PG_SHA1_DIGEST_STRING_LENGTH (PG_SHA1_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA_CTX pg_sha1_ctx;
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha1_ctx
+{
+ 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;
+} pg_sha1_ctx;
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA-1/224/256/384/512 */
+extern void pg_sha1_init(pg_sha1_ctx *ctx);
+extern void pg_sha1_update(pg_sha1_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest);
+
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 07c91d1..4ec014d 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
restricted_token.c));
@@ -422,13 +431,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.0
0002-Move-encoding-routines-to-src-common.patchapplication/x-download; name=0002-Move-encoding-routines-to-src-common.patchDownload
From ed20a73db10a8c03877bef9cf9582ba85561d915 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:21:01 +0900
Subject: [PATCH 2/8] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 is planned to be used by SCRAM-SHA-256, moving the others makes sense
for consistency.
---
src/backend/utils/adt/encode.c | 408 +--------------------
src/backend/utils/adt/varlena.c | 1 +
src/common/Makefile | 6 +-
.../utils/adt/encode.c => common/encode_utils.c} | 358 +++++++-----------
src/include/common/encode_utils.h | 30 ++
src/include/utils/builtins.h | 2 -
src/tools/msvc/Mkvcbuild.pm | 2 +-
7 files changed, 169 insertions(+), 638 deletions(-)
copy src/{backend/utils/adt/encode.c => common/encode_utils.c} (72%)
create mode 100644 src/include/common/encode_utils.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..306dbdd 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode_utils.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 260a5aa..ac13067 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode_utils.h"
#include "common/md5.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index f1cce0f..3b36c0c 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o exec.o encode_utils.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/backend/utils/adt/encode.c b/src/common/encode_utils.c
similarity index 72%
copy from src/backend/utils/adt/encode.c
copy to src/common/encode_utils.c
index d833efc..490392a 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/common/encode_utils.c
@@ -1,200 +1,27 @@
/*-------------------------------------------------------------------------
*
- * encode.c
- * Various data encoding/decoding things.
+ * encode_utils.c
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the
+ * error handling, something needed by libpq.
*
* Copyright (c) 2001-2016, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
- * src/backend/utils/adt/encode.c
+ * src/common/encode_utils.c
*
*-------------------------------------------------------------------------
*/
-#include "postgres.h"
-
-#include <ctype.h>
-
-#include "utils/builtins.h"
-
-
-struct pg_encoding
-{
- unsigned (*encode_len) (const char *data, unsigned dlen);
- unsigned (*decode_len) (const char *data, unsigned dlen);
- unsigned (*encode) (const char *data, unsigned dlen, char *res);
- unsigned (*decode) (const char *data, unsigned dlen, char *res);
-};
-
-static const struct pg_encoding *pg_find_encoding(const char *name);
-
-/*
- * SQL functions.
- */
-
-Datum
-binary_encode(PG_FUNCTION_ARGS)
-{
- bytea *data = PG_GETARG_BYTEA_P(0);
- Datum name = PG_GETARG_DATUM(1);
- text *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->encode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->encode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - encode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_TEXT_P(result);
-}
-
-Datum
-binary_decode(PG_FUNCTION_ARGS)
-{
- text *data = PG_GETARG_TEXT_P(0);
- Datum name = PG_GETARG_DATUM(1);
- bytea *result;
- char *namebuf;
- int datalen,
- resultlen,
- res;
- const struct pg_encoding *enc;
-
- datalen = VARSIZE(data) - VARHDRSZ;
-
- namebuf = TextDatumGetCString(name);
-
- enc = pg_find_encoding(namebuf);
- if (enc == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized encoding: \"%s\"", namebuf)));
-
- resultlen = enc->decode_len(VARDATA(data), datalen);
- result = palloc(VARHDRSZ + resultlen);
-
- res = enc->decode(VARDATA(data), datalen, VARDATA(result));
-
- /* Make this FATAL 'cause we've trodden on memory ... */
- if (res > resultlen)
- elog(FATAL, "overflow - decode estimate too small");
-
- SET_VARSIZE(result, VARHDRSZ + res);
-
- PG_RETURN_BYTEA_P(result);
-}
-
-
-/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
+#include "common/encode_utils.h"
/*
* BASE64
@@ -214,7 +41,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 +88,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,
@@ -290,9 +117,15 @@ b64_decode(const char *src, unsigned len, char *dst)
else if (pos == 3)
end = 2;
else
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
}
b = 0;
}
@@ -302,9 +135,16 @@ b64_decode(const char *src, unsigned len, char *dst)
if (c > 0 && c < 127)
b = b64lookup[(unsigned char) c];
if (b < 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid symbol \"%c\" while decoding base64 sequence", (int) c)));
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
}
/* add it to buffer */
buf = (buf << 6) + b;
@@ -322,23 +162,29 @@ b64_decode(const char *src, unsigned len, char *dst)
}
if (pos != 0)
+ {
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid base64 end sequence"),
errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
return p - 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;
@@ -361,7 +207,7 @@ b64_dec_len(const char *src, unsigned srclen)
#define VAL(CH) ((CH) - '0')
#define DIG(VAL) ((VAL) + '0')
-static unsigned
+unsigned
esc_encode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -400,7 +246,7 @@ esc_encode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_decode(const char *src, unsigned srclen, char *dst)
{
const char *end = src + srclen;
@@ -437,9 +283,13 @@ esc_decode(const char *src, unsigned srclen, char *dst)
* One backslash, not followed by ### valid octal. Should never
* get here, since esc_dec_len does same check.
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -448,7 +298,7 @@ esc_decode(const char *src, unsigned srclen, char *dst)
return len;
}
-static unsigned
+unsigned
esc_enc_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -469,7 +319,7 @@ esc_enc_len(const char *src, unsigned srclen)
return len;
}
-static unsigned
+unsigned
esc_dec_len(const char *src, unsigned srclen)
{
const char *end = src + srclen;
@@ -502,9 +352,13 @@ esc_dec_len(const char *src, unsigned srclen)
/*
* one backslash, not followed by ### valid octal
*/
+#ifndef FRONTEND
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
}
len++;
@@ -513,50 +367,104 @@ esc_dec_len(const char *src, unsigned srclen)
}
/*
- * Common
+ * HEX
*/
-static const struct
-{
- const char *name;
- struct pg_encoding enc;
-} enclist[] =
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
{
+ const char *end = src + len;
+
+ while (src < end)
{
- "hex",
- {
- hex_enc_len, hex_dec_len, hex_encode, hex_decode
- }
- },
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
{
- "base64",
- {
- b64_enc_len, b64_dec_len, b64_encode, b64_decode
- }
- },
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
{
- "escape",
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
{
- esc_enc_len, esc_dec_len, esc_encode, esc_decode
+ s++;
+ continue;
}
- },
- {
- NULL,
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
{
- NULL, NULL, NULL, NULL
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
}
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
}
-};
-static const struct pg_encoding *
-pg_find_encoding(const char *name)
-{
- int i;
+ return p - dst;
+}
- for (i = 0; enclist[i].name; i++)
- if (pg_strcasecmp(enclist[i].name, name) == 0)
- return &enclist[i].enc;
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
- return NULL;
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
}
diff --git a/src/include/common/encode_utils.h b/src/include/common/encode_utils.h
new file mode 100644
index 0000000..92d5b2f
--- /dev/null
+++ b/src/include/common/encode_utils.h
@@ -0,0 +1,30 @@
+/*
+ * encode_utils.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode_utils.h
+ */
+#ifndef ENCODE_UTILS_H
+#define ENCODE_UTILS_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* ENCODE_UTILS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e36d28e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -161,8 +161,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 4ec014d..50b0f4b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ config_info.c controldata_utils.c encode_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.10.0
0003-Switch-password_encryption-to-a-enum.patchapplication/x-download; name=0003-Switch-password_encryption-to-a-enum.patchDownload
From 17d8ca94ccabffb1881349a8eb26ba9c15934467 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:43:30 +0900
Subject: [PATCH 3/8] Switch password_encryption to a enum
This makes this parameter more extensible in order to add support for
future password-based authentication protocols.
---
doc/src/sgml/config.sgml | 15 ++++++++--
src/backend/commands/user.c | 22 +++++++--------
src/backend/utils/misc/guc.c | 40 ++++++++++++++++++---------
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/include/commands/user.h | 11 ++++++--
5 files changed, 59 insertions(+), 31 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a848a7e..02e9bb4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,7 +1163,7 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>enum</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
@@ -1175,8 +1175,17 @@ include_dir 'conf.d'
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
<literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ password is to be encrypted.
+ </para>
+
+ <para>
+ A value set to <literal>on</> or <literal>md5</> corresponds to a
+ MD5-encrypted password, <literal>off</> or <literal>plain</>
+ corresponds to an unencrypted password.
+ </para>
+
+ <para>
+ The default is <literal>md5</>.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4027c89..fa3e984 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -44,7 +44,7 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
-extern bool Password_encryption;
+int Password_encryption = PASSWORD_TYPE_MD5;
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -80,7 +80,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
@@ -140,9 +140,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -370,7 +370,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -393,7 +393,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
@@ -505,7 +505,7 @@ AlterRole(AlterRoleStmt *stmt)
ListCell *option;
char *rolename = NULL;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
@@ -550,9 +550,9 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -745,7 +745,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -804,7 +804,7 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ce4eef9..40600ab 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -34,6 +34,7 @@
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "commands/trigger.h"
@@ -393,6 +394,20 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
{NULL, 0, false}
};
+static const struct config_enum_entry password_encryption_options[] = {
+ {"off", PASSWORD_TYPE_PLAINTEXT, false},
+ {"on", PASSWORD_TYPE_MD5, false},
+ {"md5", PASSWORD_TYPE_MD5, false},
+ {"plain", PASSWORD_TYPE_PLAINTEXT, false},
+ {"true", PASSWORD_TYPE_MD5, true},
+ {"false", PASSWORD_TYPE_PLAINTEXT, true},
+ {"yes", PASSWORD_TYPE_MD5, true},
+ {"no", PASSWORD_TYPE_PLAINTEXT, true},
+ {"1", PASSWORD_TYPE_MD5, true},
+ {"0", PASSWORD_TYPE_PLAINTEXT, true},
+ {NULL, 0, false}
+};
+
/*
* Options for enum values stored in other modules
*/
@@ -423,8 +438,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -1314,17 +1327,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3810,6 +3812,18 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("Encrypt passwords."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines whether the password is to be encrypted.")
+ },
+ &Password_encryption,
+ PASSWORD_TYPE_MD5, password_encryption_options,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b1c3aea..1fdbc06 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = md5 # on, off, md5 or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 1f0cfcc..7a63841 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -16,9 +16,14 @@
#include "parser/parse_node.h"
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
+/* Types of password */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern int Password_encryption;
typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
--
2.10.0
0004-Refactor-decision-making-of-password-encryption-into.patchapplication/x-download; name=0004-Refactor-decision-making-of-password-encryption-into.patchDownload
From 9a6115b693d31871d8bfa3e618fa323f17fc4661 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:51:15 +0900
Subject: [PATCH 4/8] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 84 ++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index fa3e984..2e89f1f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,48 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = pstrdup(password);
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = pstrdup(password);
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +125,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +437,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
+ pfree(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,18 +844,14 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ pfree(encrypted_passwd);
}
/* unset password */
--
2.10.0
0005-Create-generic-routine-to-fetch-password-and-valid-u.patchapplication/x-download; name=0005-Create-generic-routine-to-fetch-password-and-valid-u.patchDownload
From bc04047a070fec73089567a20287492b62cef525 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:40:15 +0900
Subject: [PATCH 5/8] Create generic routine to fetch password and valid until
values for a role
This is used now for the MD5-encrypted case and the plain text, and this
is going to be used as well for SCRAM-SHA256. That's as well useful for
any new password-based protocols.
---
src/backend/libpq/crypt.c | 59 +++++++++++++++++++++++++++++++++++------------
src/include/libpq/crypt.h | 2 ++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c41c57 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,49 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
--
2.10.0
0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-download; name=0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From cc17fba706f8aa412e0553e0e7e442ef5108b2d7 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:51:39 +0900
Subject: [PATCH 6/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 9 +-
doc/src/sgml/protocol.sgml | 147 +++++-
doc/src/sgml/ref/create_role.sgml | 14 +-
src/backend/commands/user.c | 18 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 713 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 132 +++++
src/backend/libpq/crypt.c | 1 +
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 51 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 +
src/interfaces/libpq/.gitignore | 4 +
src/interfaces/libpq/Makefile | 11 +-
src/interfaces/libpq/fe-auth-scram.c | 418 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
32 files changed, 1957 insertions(+), 46 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 02e9bb4..c9f7a80 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1181,7 +1181,8 @@ include_dir 'conf.d'
<para>
A value set to <literal>on</> or <literal>md5</> corresponds to a
MD5-encrypted password, <literal>off</> or <literal>plain</>
- corresponds to an unencrypted password.
+ corresponds to an unencrypted password. Setting this parameter to
+ <literal>scram</> will encrypt the password with SCRAM-SHA-256.
</para>
<para>
@@ -1259,8 +1260,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 68b0941..8af888d 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>
@@ -2578,6 +2607,114 @@ 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>
@@ -4340,7 +4477,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2e89f1f..c76d273 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = pstrdup(password);
else
{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..4b1b41f
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,713 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode_utils.h"
+#include "common/scram-common.h"
+#include "common/sha.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+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 */
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d907e6b..d0bfadb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,9 +27,11 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -201,6 +203,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -262,6 +270,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;
@@ -542,6 +551,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, "");
@@ -716,6 +729,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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);
+
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1c41c57..3c6701b 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -84,6 +84,7 @@ get_role_details(const char *role,
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
+
}
return STATUS_OK;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 0c0a609..6222126 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2351,6 +2351,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 40600ab..abb14f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -398,6 +398,7 @@ static const struct config_enum_entry password_encryption_options[] = {
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"true", PASSWORD_TYPE_MD5, true},
{"false", PASSWORD_TYPE_PLAINTEXT, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1fdbc06..ae8e849 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # on, off, md5 or plain
+#password_encryption = md5 # on, off, md5, plain or scram
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 3b36c0c..1dbbbdd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o encode_utils.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a63841..a5ff5f3 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -20,7 +20,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..f3beea4
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..f08750f
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..8bbc75f 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode_utils.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,9 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha.c
+/sha_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..3289823 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
@@ -42,10 +42,12 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += ip.o md5.o encode_utils.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha_openssl.o
+else
+OBJS += sha.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode_utils.c scram-common.c sha.c sha_openssl.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..a40972a
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,418 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode_utils.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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");
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+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;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+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';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+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 != SCRAM_SALT_LEN)
+ 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;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 50b0f4b..8e749e6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c encode_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha_openssl.c if building without OpenSSL, and remove sha.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha.c');
}
my $libpqwalreceiver =
--
2.10.0
0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-download; name=0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 4469698da86e1d63dfb5a62db7951ec445ea37ad Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:57:07 +0900
Subject: [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
doc/src/sgml/ref/alter_role.sgml | 2 ++
doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
src/backend/commands/user.c | 72 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 ++++
4 files changed, 95 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..fa74466 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+ as-is.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c76d273..e7a4b8f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -179,7 +179,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -188,9 +189,41 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -310,8 +343,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -586,6 +617,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -594,9 +626,41 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -684,8 +748,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..bed09f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.10.0
0008-Add-regression-tests-for-passwords.patchapplication/x-download; name=0008-Add-regression-tests-for-passwords.patchDownload
From 50794671390dd18b4034aa403345ecc3ef333504 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:55:49 +0900
Subject: [PATCH 8/8] Add regression tests for passwords
---
src/test/regress/expected/password.out | 101 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..a90f323
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: off, on, md5, scram, plain.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------
+ regress_passwd1 |
+ regress_passwd2 |
+ regress_passwd3 |
+ regress_passwd4 |
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.10.0
On 09/26/2016 09:02 AM, Michael Paquier wrote:
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net> wrote:
However, it doesn't look like they can be used in conjunction since the
pg_hba.conf entry must specify either m5 or scram (though the database
can easily contain a mixture). This would probably make a migration
very unpleasant.Yep, it uses a given auth-method once user and database match. This is
partially related to the problem to support multiple password
verifiers per users, which was submitted last CF but got rejected
because of a lack of interest, and removed to simplify this patch. You
need as well to think about other things like password and protocol
aging. But well, it is a problem that we don't have to tackle with
this patch...Is there any chance of a mixed mode that will allow new passwords to be
set as scram while still honoring the old md5 passwords? Or does that
cause too many complications with the protocol?Hm. That looks complicated to me. This sounds to me like a retry logic
if for multiple authentication methods, and a different feature. What
you'd be looking for here is a connection parameter to specify a list
of protocols and try them all, no?
It would be possible to have a "md5-or-scram" authentication method in
pg_hba.conf, such that the server would look up the pg_authid row of the
user when it receives startup message, and send an MD5 or SCRAM
challenge depending on which one the user's password is encrypted with.
It has one drawback though: it allows an unauthenticated user to probe
if there is a role with a given name in the system, because if a user
doesn't exist, we'd have to still send an MD5 or SCRAM challenge, or a
"user does not exist" error without a challenge. If we send a SCRAM
challenge for a non-existent user, and the attacker knows that most
users still have a MD5 password, that reveals that the username doesn't
most likely doesn't exist.
Hmm. The server could send a SCRAM challenge first, and if the client
gives an incorrect response, or the username doesn't exist, or the
user's password is actually MD5-encrypted, the server could then send an
MD5 challenge. It would add one round-trip to the authentication of MD5
passwords, but that seems acceptable.
We can do this as a follow-up patch though. Let's try to keep this patch
series small.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/26/16 4:54 AM, Heikki Linnakangas wrote:
On 09/26/2016 09:02 AM, Michael Paquier wrote:
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net>
wrote:However, it doesn't look like they can be used in conjunction since the
pg_hba.conf entry must specify either m5 or scram (though the database
can easily contain a mixture). This would probably make a migration
very unpleasant.Yep, it uses a given auth-method once user and database match. This is
partially related to the problem to support multiple password
verifiers per users, which was submitted last CF but got rejected
because of a lack of interest, and removed to simplify this patch. You
need as well to think about other things like password and protocol
aging. But well, it is a problem that we don't have to tackle with
this patch...Is there any chance of a mixed mode that will allow new passwords to be
set as scram while still honoring the old md5 passwords? Or does that
cause too many complications with the protocol?Hm. That looks complicated to me. This sounds to me like a retry logic
if for multiple authentication methods, and a different feature. What
you'd be looking for here is a connection parameter to specify a list
of protocols and try them all, no?It would be possible to have a "md5-or-scram" authentication method in
pg_hba.conf, such that the server would look up the pg_authid row of the
user when it receives startup message, and send an MD5 or SCRAM
challenge depending on which one the user's password is encrypted with.
It has one drawback though: it allows an unauthenticated user to probe
if there is a role with a given name in the system, because if a user
doesn't exist, we'd have to still send an MD5 or SCRAM challenge, or a
"user does not exist" error without a challenge. If we send a SCRAM
challenge for a non-existent user, and the attacker knows that most
users still have a MD5 password, that reveals that the username doesn't
most likely doesn't exist.Hmm. The server could send a SCRAM challenge first, and if the client
gives an incorrect response, or the username doesn't exist, or the
user's password is actually MD5-encrypted, the server could then send an
MD5 challenge. It would add one round-trip to the authentication of MD5
passwords, but that seems acceptable.We can do this as a follow-up patch though. Let's try to keep this patch
series small.
Fair enough. I'm not even 100% sure we should do it, but wanted to
raise it as a possible issue.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Sep 26, 2016 at 9:22 PM, David Steele <david@pgmasters.net> wrote:
On 9/26/16 4:54 AM, Heikki Linnakangas wrote:
Hmm. The server could send a SCRAM challenge first, and if the client
gives an incorrect response, or the username doesn't exist, or the
user's password is actually MD5-encrypted, the server could then send an
MD5 challenge. It would add one round-trip to the authentication of MD5
passwords, but that seems acceptable.
I don't think that this applies just to md5 or scram. Could we for
example use a connection parameter, like expected_auth_methods to do
that? We include that in the startup packet if the caller has defined
it, then the backend checks for matching entries in pg_hba.conf using
the username, database and the expected auth method if specified.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/26/2016 09:02 AM, Michael Paquier wrote:
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net> wrote:
On 9/3/16 8:36 AM, Michael Paquier wrote:
Attached is a new series:
Thanks for the review and the comments!
I read-through this again, and did a bunch of little fixes:
* Added error-handling for OOM and other errors in liybpq
* In libpq, added check that the server sent back the same client-nonce
* Turned ERRORs into COMMERRORs and removed DEBUG4 lines (they could
reveal useful information to an attacker)
* Improved comments
Some things that need to be resolved (I also added FIXME comments for
some of this):
* A source of random values. This currently uses PostmasterRandom()
similarly to how the MD5 salt is generated, in the server, but plain old
random() in the client. If built with OpenSSL, we should probably use
RAND_bytes(). But what if the client is built without OpenSSL? I believe
the protocol doesn't require cryptographically strong randomness for the
nonces, i.e. it's OK if they're predictable, but they should be
different for each session.
* Nonce and salt lengths. The patch currently uses 10 bytes for both,
but I think I just pulled number that out of thin air. The spec doesn't
say anything about nonce and salt lengths AFAICS. What do other
implementations use? Is 10 bytes enough?
* The spec defines a final "server-error" message that the server sends
on authentication failure, or e.g. if a required extension is not
supported. The patch just uses FATAL for those. Should we try to send a
server-error message instead, or before, the elog(FATAL) ?
I'll continue hacking this later, but need a little break for now.
I'm a bit concerned that a mixture of md5/scram could cause confusion
and think this may warrant discussion somewhere in the documentation
since the idea is for users to migrate from md5 to scram.We could finish with a red warning in the docs to say that users are
recommended to use SCRAM instead of MD5. Just an idea, perhaps that's
not mandatory for the first shot though.
Some sort of Migration Guide would certainly be in order. There isn't
any easy migration path with this patch series alone, so perhaps that
should be part of the follow-up patches that add the "MD5 or SCRAM"
authentication method to pg_hba.conf, or support for having both
verifiers for the same user in pg_authid.
- Heikki
Attachments:
0001-Refactor-SHA-functions-and-move-them-to-src-common.patchtext/x-diff; name=0001-Refactor-SHA-functions-and-move-them-to-src-common.patchDownload
From 3f035da4cbb08f45428a02054f36d614d84641b4 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 26 Sep 2016 14:59:35 +0300
Subject: [PATCH 1/8] Refactor SHA functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA functions,
allowing to link to either OpenSSL or the in-core stuff taken from OpenBSD
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 10 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 +-
contrib/pgcrypto/internal.c | 32 +-
contrib/pgcrypto/sha1.c | 341 --------
contrib/pgcrypto/sha1.h | 75 --
contrib/pgcrypto/sha2.h | 100 ---
src/common/Makefile | 6 +
contrib/pgcrypto/sha2.c => src/common/sha.c | 1184 ++++++++++++++++++---------
src/common/sha_openssl.c | 120 +++
src/include/common/sha.h | 107 +++
src/tools/msvc/Mkvcbuild.pm | 21 +-
13 files changed, 1118 insertions(+), 976 deletions(-)
delete mode 100644 contrib/pgcrypto/sha1.c
delete mode 100644 contrib/pgcrypto/sha1.h
delete mode 100644 contrib/pgcrypto/sha2.h
rename contrib/pgcrypto/sha2.c => src/common/sha.c (51%)
create mode 100644 src/common/sha_openssl.c
create mode 100644 src/include/common/sha.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..582110e 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha.c
+/sha_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..195fc73 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,10 +1,11 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
- fortuna.c random.c pgp-mpi-internal.c imath.c
+INT_SRCS = md5.c internal.c internal-sha2.c blf.c rijndael.c \
+ fortuna.c random.c pgp-mpi-internal.c imath.c \
+ sha.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +60,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha.c sha_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..6bc6faf 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..3868fd2 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index 02ff976..4033ad9 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -33,9 +33,10 @@
#include <time.h>
+#include "common/sha.h"
+
#include "px.h"
#include "md5.h"
-#include "sha1.h"
#include "blf.h"
#include "rijndael.h"
#include "fortuna.h"
@@ -63,15 +64,6 @@
#define MD5_DIGEST_LENGTH 16
#endif
-#ifndef SHA1_DIGEST_LENGTH
-#ifdef SHA1_RESULTLEN
-#define SHA1_DIGEST_LENGTH SHA1_RESULTLEN
-#else
-#define SHA1_DIGEST_LENGTH 20
-#endif
-#endif
-
-#define SHA1_BLOCK_SIZE 64
#define MD5_BLOCK_SIZE 64
static void init_md5(PX_MD *h);
@@ -152,43 +144,43 @@ int_md5_free(PX_MD *h)
static unsigned
int_sha1_len(PX_MD *h)
{
- return SHA1_DIGEST_LENGTH;
+ return PG_SHA1_DIGEST_LENGTH;
}
static unsigned
int_sha1_block_len(PX_MD *h)
{
- return SHA1_BLOCK_SIZE;
+ return PG_SHA1_BLOCK_LENGTH;
}
static void
int_sha1_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Update(ctx, data, dlen);
+ pg_sha1_update(ctx, data, dlen);
}
static void
int_sha1_reset(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Init(ctx);
+ pg_sha1_init(ctx);
}
static void
int_sha1_finish(PX_MD *h, uint8 *dst)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
- SHA1Final(dst, ctx);
+ pg_sha1_final(ctx, dst);
}
static void
int_sha1_free(PX_MD *h)
{
- SHA1_CTX *ctx = (SHA1_CTX *) h->p.ptr;
+ pg_sha1_ctx *ctx = (pg_sha1_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -220,7 +212,7 @@ init_md5(PX_MD *md)
static void
init_sha1(PX_MD *md)
{
- SHA1_CTX *ctx;
+ pg_sha1_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
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 2f61e45..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 *);
-
-/* compatibility 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/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index a5fa649..f1cce0f 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha_openssl.o
+else
+OBJS_COMMON += sha.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha.c
similarity index 51%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha.c
index 231f9df..920fa64 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha.c
@@ -1,10 +1,23 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha.c
+ * Set of SHA functions for SHA-1, SHA-224, SHA-256, SHA-384 and
+ * SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
- * FILE: sha2.c
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2000-2001, Aaron D. Gifford
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -15,14 +28,14 @@
* 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 copyright holder nor the names of contributors
+ * 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 AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ * 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 AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ * 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)
@@ -31,109 +44,364 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
+ * 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>
+ *
+ * SHA-224, SHA-256, SHA-384, SHA-512 implemented by
+ * Aaron D. Gifford <me@aarongifford.com>
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha.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) (ctx->h.b32[(n)])
+#define COUNT (ctx->count)
+#define BCOUNT (ctx->c.b64[0] / 8)
+#define W(n) (ctx->m.b32[(n)])
+
+#define PUTBYTE(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ ctx->c.b64[0] += 8; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
+
+#define PUTPAD(x) \
+do { \
+ ctx->m.b8[(COUNT % 64)] = (x); \
+ COUNT++; \
+ COUNT %= 64; \
+ if (COUNT % 64 == 0) \
+ pg_sha1_step(ctx); \
+} while (0)
/*
- * UNROLLED TRANSFORM LOOP NOTE:
- * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
- * loop version for the hash transform rounds (defined using macros
- * later in this file). Either define on the command line, for example:
- *
- * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
- *
- * or define below:
- *
- * #define SHA2_UNROLL_TRANSFORM
- *
+ * Internal routines for SHA-1 generation. Those should remain private.
*/
+static void pg_sha1_step(pg_sha1_ctx *ctx);
+static void pg_sha1_pad(pg_sha1_ctx *ctx);
-/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+/*
+ * Perform a step calculation, consisting in shuffling the bytes of
+ * data around to add more randomness in the result.
+ */
+static void
+pg_sha1_step(pg_sha1_ctx *ctx)
+{
+ uint32 a,
+ b,
+ c,
+ d,
+ e;
+ size_t t,
+ s;
+ uint32 tmp;
-/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
-#define REVERSE32(w,x) { \
- uint32 tmp = (w); \
- tmp = (tmp >> 16) | (tmp << 16); \
- (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+ pg_sha1_ctx tctxt;
+
+ memmove(&tctxt.m.b8[0], &ctx->m.b8[0], 64);
+ ctx->m.b8[0] = tctxt.m.b8[3];
+ ctx->m.b8[1] = tctxt.m.b8[2];
+ ctx->m.b8[2] = tctxt.m.b8[1];
+ ctx->m.b8[3] = tctxt.m.b8[0];
+ ctx->m.b8[4] = tctxt.m.b8[7];
+ ctx->m.b8[5] = tctxt.m.b8[6];
+ ctx->m.b8[6] = tctxt.m.b8[5];
+ ctx->m.b8[7] = tctxt.m.b8[4];
+ ctx->m.b8[8] = tctxt.m.b8[11];
+ ctx->m.b8[9] = tctxt.m.b8[10];
+ ctx->m.b8[10] = tctxt.m.b8[9];
+ ctx->m.b8[11] = tctxt.m.b8[8];
+ ctx->m.b8[12] = tctxt.m.b8[15];
+ ctx->m.b8[13] = tctxt.m.b8[14];
+ ctx->m.b8[14] = tctxt.m.b8[13];
+ ctx->m.b8[15] = tctxt.m.b8[12];
+ ctx->m.b8[16] = tctxt.m.b8[19];
+ ctx->m.b8[17] = tctxt.m.b8[18];
+ ctx->m.b8[18] = tctxt.m.b8[17];
+ ctx->m.b8[19] = tctxt.m.b8[16];
+ ctx->m.b8[20] = tctxt.m.b8[23];
+ ctx->m.b8[21] = tctxt.m.b8[22];
+ ctx->m.b8[22] = tctxt.m.b8[21];
+ ctx->m.b8[23] = tctxt.m.b8[20];
+ ctx->m.b8[24] = tctxt.m.b8[27];
+ ctx->m.b8[25] = tctxt.m.b8[26];
+ ctx->m.b8[26] = tctxt.m.b8[25];
+ ctx->m.b8[27] = tctxt.m.b8[24];
+ ctx->m.b8[28] = tctxt.m.b8[31];
+ ctx->m.b8[29] = tctxt.m.b8[30];
+ ctx->m.b8[30] = tctxt.m.b8[29];
+ ctx->m.b8[31] = tctxt.m.b8[28];
+ ctx->m.b8[32] = tctxt.m.b8[35];
+ ctx->m.b8[33] = tctxt.m.b8[34];
+ ctx->m.b8[34] = tctxt.m.b8[33];
+ ctx->m.b8[35] = tctxt.m.b8[32];
+ ctx->m.b8[36] = tctxt.m.b8[39];
+ ctx->m.b8[37] = tctxt.m.b8[38];
+ ctx->m.b8[38] = tctxt.m.b8[37];
+ ctx->m.b8[39] = tctxt.m.b8[36];
+ ctx->m.b8[40] = tctxt.m.b8[43];
+ ctx->m.b8[41] = tctxt.m.b8[42];
+ ctx->m.b8[42] = tctxt.m.b8[41];
+ ctx->m.b8[43] = tctxt.m.b8[40];
+ ctx->m.b8[44] = tctxt.m.b8[47];
+ ctx->m.b8[45] = tctxt.m.b8[46];
+ ctx->m.b8[46] = tctxt.m.b8[45];
+ ctx->m.b8[47] = tctxt.m.b8[44];
+ ctx->m.b8[48] = tctxt.m.b8[51];
+ ctx->m.b8[49] = tctxt.m.b8[50];
+ ctx->m.b8[50] = tctxt.m.b8[49];
+ ctx->m.b8[51] = tctxt.m.b8[48];
+ ctx->m.b8[52] = tctxt.m.b8[55];
+ ctx->m.b8[53] = tctxt.m.b8[54];
+ ctx->m.b8[54] = tctxt.m.b8[53];
+ ctx->m.b8[55] = tctxt.m.b8[52];
+ ctx->m.b8[56] = tctxt.m.b8[59];
+ ctx->m.b8[57] = tctxt.m.b8[58];
+ ctx->m.b8[58] = tctxt.m.b8[57];
+ ctx->m.b8[59] = tctxt.m.b8[56];
+ ctx->m.b8[60] = tctxt.m.b8[63];
+ ctx->m.b8[61] = tctxt.m.b8[62];
+ ctx->m.b8[62] = tctxt.m.b8[61];
+ ctx->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(&ctx->m.b8[0], 0, 64);
}
-#define REVERSE64(w,x) { \
- uint64 tmp = (w); \
- tmp = (tmp >> 32) | (tmp << 32); \
- tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
- ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
- (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
- ((tmp & 0x0000ffff0000ffffULL) << 16); \
+
+/*
+ * Add some padding to a SHA-1.
+ */
+static void
+pg_sha1_pad(pg_sha1_ctx *ctx)
+{
+ size_t padlen; /* pad length in bytes */
+ size_t padstart;
+
+ PUTPAD(0x80);
+
+ padstart = COUNT % 64;
+ padlen = 64 - padstart;
+ if (padlen < 8)
+ {
+ memset(&ctx->m.b8[padstart], 0, padlen);
+ COUNT += padlen;
+ COUNT %= 64;
+ pg_sha1_step(ctx);
+ padstart = COUNT % 64; /* should be 0 */
+ padlen = 64 - padstart; /* should be 64 */
+ }
+ memset(&ctx->m.b8[padstart], 0, padlen - 8);
+ COUNT += (padlen - 8);
+ COUNT %= 64;
+#ifdef WORDS_BIGENDIAN
+ PUTPAD(ctx->c.b8[0]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[7]);
+#else
+ PUTPAD(ctx->c.b8[7]);
+ PUTPAD(ctx->c.b8[6]);
+ PUTPAD(ctx->c.b8[5]);
+ PUTPAD(ctx->c.b8[4]);
+ PUTPAD(ctx->c.b8[3]);
+ PUTPAD(ctx->c.b8[2]);
+ PUTPAD(ctx->c.b8[1]);
+ PUTPAD(ctx->c.b8[0]);
+#endif
}
-#endif /* not bigendian */
/*
- * Macro for incrementally adding the unsigned 64-bit integer n to the
- * unsigned 128-bit integer (represented using a two-element array of
- * 64-bit words):
+ * pg_sha1_init
+ * Initialize calculation of SHA-1.
*/
-#define ADDINC128(w,n) { \
- (w)[0] += (uint64)(n); \
- if ((w)[0] < (n)) { \
- (w)[1]++; \
- } \
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ memset(ctx, 0, sizeof(pg_sha1_ctx));
+ H(0) = 0x67452301;
+ H(1) = 0xefcdab89;
+ H(2) = 0x98badcfe;
+ H(3) = 0x10325476;
+ H(4) = 0xc3d2e1f0;
}
-/*** THE SIX LOGICAL FUNCTIONS ****************************************/
/*
- * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
- *
- * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
- * S is a ROTATION) because the SHA-256/384/512 description document
- * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
- * uses this same "backwards" definition.
+ * pg_sha1_update
+ * Update SHA-1 using given input data.
*/
-/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
-#define R(b,x) ((x) >> (b))
-/* 32-bit Rotate-right (used in SHA-256): */
-#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
-/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
-#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ const uint8 *input;
+ size_t gaplen;
+ size_t gapstart;
+ size_t off;
+ size_t copysiz;
-/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
-#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
-#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+ input = (const uint8 *) input0;
+ off = 0;
-/* Four of six logical functions used in SHA-256: */
-#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
-#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
-#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
-#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+ while (off < len)
+ {
+ gapstart = COUNT % 64;
+ gaplen = 64 - gapstart;
+
+ copysiz = (gaplen < len - off) ? gaplen : len - off;
+ memmove(&ctx->m.b8[gapstart], &input[off], copysiz);
+ COUNT += copysiz;
+ COUNT %= 64;
+ ctx->c.b64[0] += copysiz * 8;
+ if (COUNT % 64 == 0)
+ pg_sha1_step(ctx);
+ off += copysiz;
+ }
+}
-/* Four of six logical functions used in SHA-384 and SHA-512: */
-#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
-#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
-#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
-#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+/*
+ * pg_sha1_final
+ * Finalize calculation of SHA-1 and save result to be reused by caller.
+ */
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ uint8 *digest;
+
+ digest = (uint8 *) dest;
+ pg_sha1_pad(ctx);
+#ifdef WORDS_BIGENDIAN
+ memmove(digest, &ctx->h.b8[0], 20);
+#else
+ digest[0] = ctx->h.b8[3];
+ digest[1] = ctx->h.b8[2];
+ digest[2] = ctx->h.b8[1];
+ digest[3] = ctx->h.b8[0];
+ digest[4] = ctx->h.b8[7];
+ digest[5] = ctx->h.b8[6];
+ digest[6] = ctx->h.b8[5];
+ digest[7] = ctx->h.b8[4];
+ digest[8] = ctx->h.b8[11];
+ digest[9] = ctx->h.b8[10];
+ digest[10] = ctx->h.b8[9];
+ digest[11] = ctx->h.b8[8];
+ digest[12] = ctx->h.b8[15];
+ digest[13] = ctx->h.b8[14];
+ digest[14] = ctx->h.b8[13];
+ digest[15] = ctx->h.b8[12];
+ digest[16] = ctx->h.b8[19];
+ digest[17] = ctx->h.b8[18];
+ digest[18] = ctx->h.b8[17];
+ digest[19] = ctx->h.b8[16];
+#endif
+}
-/*** INTERNAL FUNCTION PROTOTYPES *************************************/
-/* NOTE: These should not be accessed directly from outside this
- * library -- they are intended for private internal visibility/use
- * only.
+/*
+ * UNROLLED TRANSFORM LOOP NOTE:
+ * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform
+ * loop version for the hash transform rounds (defined using macros
+ * later in this file). Either define on the command line, for example:
+ *
+ * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c
+ *
+ * or define below:
+ *
+ * #define SHA2_UNROLL_TRANSFORM
+ *
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
+/*** SHA-256/384/512 Various Length Definitions ***********************/
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -248,16 +516,124 @@ static const uint64 sha512_initial_hash_value[8] = {
0x5be0cd19137e2179ULL
};
+/*** ENDIAN REVERSAL MACROS *******************************************/
+#ifndef WORDS_BIGENDIAN
+#define REVERSE32(w,x) { \
+ uint32 tmp = (w); \
+ tmp = (tmp >> 16) | (tmp << 16); \
+ (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \
+}
+#define REVERSE64(w,x) { \
+ uint64 tmp = (w); \
+ tmp = (tmp >> 32) | (tmp << 32); \
+ tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \
+ ((tmp & 0x00ff00ff00ff00ffULL) << 8); \
+ (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \
+ ((tmp & 0x0000ffff0000ffffULL) << 16); \
+}
+#endif /* not bigendian */
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+/*
+ * Macro for incrementally adding the unsigned 64-bit integer n to the
+ * unsigned 128-bit integer (represented using a two-element array of
+ * 64-bit words):
+ */
+#define ADDINC128(w,n) { \
+ (w)[0] += (uint64)(n); \
+ if ((w)[0] < (n)) { \
+ (w)[1]++; \
+ } \
+}
+
+/*** THE SIX LOGICAL FUNCTIONS ****************************************/
+/*
+ * Bit shifting and rotation (used by the six SHA-XYZ logical functions:
+ *
+ * NOTE: The naming of R and S appears backwards here (R is a SHIFT and
+ * S is a ROTATION) because the SHA-256/384/512 description document
+ * (see http://www.iwar.org.uk/comsec/resources/cipher/sha256-384-512.pdf)
+ * uses this same "backwards" definition.
+ */
+/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */
+#define R(b,x) ((x) >> (b))
+/* 32-bit Rotate-right (used in SHA-256): */
+#define S32(b,x) (((x) >> (b)) | ((x) << (32 - (b))))
+/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */
+#define S64(b,x) (((x) >> (b)) | ((x) << (64 - (b))))
+
+/* Two of six logical functions used in SHA-256, SHA-384, and SHA-512: */
+#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
+#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+
+/* Four of six logical functions used in SHA-256: */
+#define Sigma0_256(x) (S32(2, (x)) ^ S32(13, (x)) ^ S32(22, (x)))
+#define Sigma1_256(x) (S32(6, (x)) ^ S32(11, (x)) ^ S32(25, (x)))
+#define sigma0_256(x) (S32(7, (x)) ^ S32(18, (x)) ^ R(3 , (x)))
+#define sigma1_256(x) (S32(17, (x)) ^ S32(19, (x)) ^ R(10, (x)))
+
+/* Four of six logical functions used in SHA-384 and SHA-512: */
+#define Sigma0_512(x) (S64(28, (x)) ^ S64(34, (x)) ^ S64(39, (x)))
+#define Sigma1_512(x) (S64(14, (x)) ^ S64(18, (x)) ^ S64(41, (x)))
+#define sigma0_512(x) (S64( 1, (x)) ^ S64( 8, (x)) ^ R( 7, (x)))
+#define sigma1_512(x) (S64(19, (x)) ^ S64(61, (x)) ^ R( 6, (x)))
+
+/*** INTERNAL FUNCTION PROTOTYPES *************************************/
+/* NOTE: These should not be accessed directly from outside this
+ * library -- they are intended for private internal visibility/use
+ * only.
+ */
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
+
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +662,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +684,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +724,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +761,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +819,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +862,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +881,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +921,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +955,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +1016,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +1107,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +1275,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1333,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1390,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha_openssl.c b/src/common/sha_openssl.c
new file mode 100644
index 0000000..c6aac90
--- /dev/null
+++ b/src/common/sha_openssl.c
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA
+ * functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha.h"
+
+/* Interface routines for SHA-1 */
+void
+pg_sha1_init(pg_sha1_ctx *ctx)
+{
+ SHA1_Init((SHA_CTX *) ctx);
+}
+
+void
+pg_sha1_update(pg_sha1_ctx *ctx, const uint8 *input0, size_t len)
+{
+ SHA1_Update((SHA_CTX *) ctx, input0, len);
+}
+
+void
+pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest)
+{
+ SHA1_Final(dest, (SHA_CTX *) ctx);
+}
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha.h b/src/include/common/sha.h
new file mode 100644
index 0000000..5434e6a
--- /dev/null
+++ b/src/include/common/sha.h
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha.h
+ * Generic headers for SHA functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef _PG_SHA_H_
+#define _PG_SHA_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA-1/224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA1_BLOCK_LENGTH 64
+#define PG_SHA1_DIGEST_LENGTH 20
+#define PG_SHA1_DIGEST_STRING_LENGTH (PG_SHA1_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA_CTX pg_sha1_ctx;
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha1_ctx
+{
+ 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;
+} pg_sha1_ctx;
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA-1/224/256/384/512 */
+extern void pg_sha1_init(pg_sha1_ctx *ctx);
+extern void pg_sha1_update(pg_sha1_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha1_final(pg_sha1_ctx *ctx, uint8 *dest);
+
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 07c91d1..fc075f4 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,16 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
+
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha.c');
+ }
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c
@@ -224,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha_openssl.c if building without OpenSSL, and remove sha.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha.c');
}
my $libpqwalreceiver =
@@ -422,13 +437,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.9.3
0002-Move-encoding-routines-to-src-common.patchtext/x-diff; name=0002-Move-encoding-routines-to-src-common.patchDownload
From a2961406c48bc14ce7341c8d943e7546864e846a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 26 Sep 2016 15:30:24 +0300
Subject: [PATCH 2/8] Move encoding routines to src/common/
The following encoding routines are moved for decode and encode:
- escape
- base64
- hex
base64 and hex are to be used by the new SCRAM-SHA-256 mechanism, moving
'escape' too for consistency.
---
src/backend/utils/adt/encode.c | 408 +--------------------------------
src/backend/utils/adt/varlena.c | 1 +
src/common/Makefile | 6 +-
src/common/encode_utils.c | 470 ++++++++++++++++++++++++++++++++++++++
src/include/common/encode_utils.h | 30 +++
src/include/utils/builtins.h | 2 -
src/tools/msvc/Mkvcbuild.pm | 2 +-
7 files changed, 506 insertions(+), 413 deletions(-)
create mode 100644 src/common/encode_utils.c
create mode 100644 src/include/common/encode_utils.h
diff --git a/src/backend/utils/adt/encode.c b/src/backend/utils/adt/encode.c
index d833efc..306dbdd 100644
--- a/src/backend/utils/adt/encode.c
+++ b/src/backend/utils/adt/encode.c
@@ -15,6 +15,7 @@
#include <ctype.h>
+#include "common/encode_utils.h"
#include "utils/builtins.h"
@@ -106,413 +107,6 @@ binary_decode(PG_FUNCTION_ARGS)
/*
- * HEX
- */
-
-static const char hextbl[] = "0123456789abcdef";
-
-static const int8 hexlookup[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, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
-};
-
-unsigned
-hex_encode(const char *src, unsigned len, char *dst)
-{
- const char *end = src + len;
-
- while (src < end)
- {
- *dst++ = hextbl[(*src >> 4) & 0xF];
- *dst++ = hextbl[*src & 0xF];
- src++;
- }
- return len * 2;
-}
-
-static inline char
-get_hex(char c)
-{
- int res = -1;
-
- if (c > 0 && c < 127)
- res = hexlookup[(unsigned char) c];
-
- if (res < 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal digit: \"%c\"", c)));
-
- return (char) res;
-}
-
-unsigned
-hex_decode(const char *src, unsigned len, char *dst)
-{
- const char *s,
- *srcend;
- char v1,
- v2,
- *p;
-
- srcend = src + len;
- s = src;
- p = dst;
- while (s < srcend)
- {
- if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
- {
- s++;
- continue;
- }
- v1 = get_hex(*s++) << 4;
- if (s >= srcend)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid hexadecimal data: odd number of digits")));
-
- v2 = get_hex(*s++);
- *p++ = v1 | v2;
- }
-
- return p - dst;
-}
-
-static unsigned
-hex_enc_len(const char *src, unsigned srclen)
-{
- return srclen << 1;
-}
-
-static unsigned
-hex_dec_len(const char *src, unsigned srclen)
-{
- return srclen >> 1;
-}
-
-/*
- * 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
- 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)));
- }
- /* 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, is truncated, or is otherwise corrupted.")));
-
- 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;
-}
-
-/*
- * Escape
- * Minimally escape bytea to text.
- * De-escape text to bytea.
- *
- * We must escape zero bytes and high-bit-set bytes to avoid generating
- * text that might be invalid in the current encoding, or that might
- * change to something else if passed through an encoding conversion
- * (leading to failing to de-escape to the original bytea value).
- * Also of course backslash itself has to be escaped.
- *
- * De-escaping processes \\ and any \### octal
- */
-
-#define VAL(CH) ((CH) - '0')
-#define DIG(VAL) ((VAL) + '0')
-
-static unsigned
-esc_encode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- unsigned char c = (unsigned char) *src;
-
- if (c == '\0' || IS_HIGHBIT_SET(c))
- {
- rp[0] = '\\';
- rp[1] = DIG(c >> 6);
- rp[2] = DIG((c >> 3) & 7);
- rp[3] = DIG(c & 7);
- rp += 4;
- len += 4;
- }
- else if (c == '\\')
- {
- rp[0] = '\\';
- rp[1] = '\\';
- rp += 2;
- len += 2;
- }
- else
- {
- *rp++ = c;
- len++;
- }
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_decode(const char *src, unsigned srclen, char *dst)
-{
- const char *end = src + srclen;
- char *rp = dst;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- *rp++ = *src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- int val;
-
- val = VAL(src[1]);
- val <<= 3;
- val += VAL(src[2]);
- val <<= 3;
- *rp++ = val + VAL(src[3]);
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- *rp++ = '\\';
- src += 2;
- }
- else
- {
- /*
- * One backslash, not followed by ### valid octal. Should never
- * get here, since esc_dec_len does same check.
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
-
- return len;
-}
-
-static unsigned
-esc_enc_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (*src == '\0' || IS_HIGHBIT_SET(*src))
- len += 4;
- else if (*src == '\\')
- len += 2;
- else
- len++;
-
- src++;
- }
-
- return len;
-}
-
-static unsigned
-esc_dec_len(const char *src, unsigned srclen)
-{
- const char *end = src + srclen;
- int len = 0;
-
- while (src < end)
- {
- if (src[0] != '\\')
- src++;
- else if (src + 3 < end &&
- (src[1] >= '0' && src[1] <= '3') &&
- (src[2] >= '0' && src[2] <= '7') &&
- (src[3] >= '0' && src[3] <= '7'))
- {
- /*
- * backslash + valid octal
- */
- src += 4;
- }
- else if (src + 1 < end &&
- (src[1] == '\\'))
- {
- /*
- * two backslashes = backslash
- */
- src += 2;
- }
- else
- {
- /*
- * one backslash, not followed by ### valid octal
- */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type bytea")));
- }
-
- len++;
- }
- return len;
-}
-
-/*
* Common
*/
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 260a5aa..ac13067 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -21,6 +21,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/encode_utils.h"
#include "common/md5.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index f1cce0f..3b36c0c 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = config_info.o controldata_utils.o exec.o encode_utils.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/common/encode_utils.c b/src/common/encode_utils.c
new file mode 100644
index 0000000..490392a
--- /dev/null
+++ b/src/common/encode_utils.c
@@ -0,0 +1,470 @@
+/*-------------------------------------------------------------------------
+ *
+ * encode_utils.c
+ * Various data encoding/decoding things for base64, hexadecimal and
+ * escape. In case of failure, those routines return elog(ERROR) in
+ * the backend, and 0 in the frontend to let the caller handle the
+ * error handling, something needed by libpq.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/encode_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/encode_utils.h"
+
+/*
+ * 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,
+};
+
+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;
+}
+
+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
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
+ }
+ /* 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)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid base64 end sequence"),
+ errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
+
+ return p - dst;
+}
+
+
+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);
+}
+
+unsigned
+b64_dec_len(const char *src, unsigned srclen)
+{
+ return (srclen * 3) >> 2;
+}
+
+/*
+ * Escape
+ * Minimally escape bytea to text.
+ * De-escape text to bytea.
+ *
+ * We must escape zero bytes and high-bit-set bytes to avoid generating
+ * text that might be invalid in the current encoding, or that might
+ * change to something else if passed through an encoding conversion
+ * (leading to failing to de-escape to the original bytea value).
+ * Also of course backslash itself has to be escaped.
+ *
+ * De-escaping processes \\ and any \### octal
+ */
+
+#define VAL(CH) ((CH) - '0')
+#define DIG(VAL) ((VAL) + '0')
+
+unsigned
+esc_encode(const char *src, unsigned srclen, char *dst)
+{
+ const char *end = src + srclen;
+ char *rp = dst;
+ int len = 0;
+
+ while (src < end)
+ {
+ unsigned char c = (unsigned char) *src;
+
+ if (c == '\0' || IS_HIGHBIT_SET(c))
+ {
+ rp[0] = '\\';
+ rp[1] = DIG(c >> 6);
+ rp[2] = DIG((c >> 3) & 7);
+ rp[3] = DIG(c & 7);
+ rp += 4;
+ len += 4;
+ }
+ else if (c == '\\')
+ {
+ rp[0] = '\\';
+ rp[1] = '\\';
+ rp += 2;
+ len += 2;
+ }
+ else
+ {
+ *rp++ = c;
+ len++;
+ }
+
+ src++;
+ }
+
+ return len;
+}
+
+unsigned
+esc_decode(const char *src, unsigned srclen, char *dst)
+{
+ const char *end = src + srclen;
+ char *rp = dst;
+ int len = 0;
+
+ while (src < end)
+ {
+ if (src[0] != '\\')
+ *rp++ = *src++;
+ else if (src + 3 < end &&
+ (src[1] >= '0' && src[1] <= '3') &&
+ (src[2] >= '0' && src[2] <= '7') &&
+ (src[3] >= '0' && src[3] <= '7'))
+ {
+ int val;
+
+ val = VAL(src[1]);
+ val <<= 3;
+ val += VAL(src[2]);
+ val <<= 3;
+ *rp++ = val + VAL(src[3]);
+ src += 4;
+ }
+ else if (src + 1 < end &&
+ (src[1] == '\\'))
+ {
+ *rp++ = '\\';
+ src += 2;
+ }
+ else
+ {
+ /*
+ * One backslash, not followed by ### valid octal. Should never
+ * get here, since esc_dec_len does same check.
+ */
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
+ }
+
+ len++;
+ }
+
+ return len;
+}
+
+unsigned
+esc_enc_len(const char *src, unsigned srclen)
+{
+ const char *end = src + srclen;
+ int len = 0;
+
+ while (src < end)
+ {
+ if (*src == '\0' || IS_HIGHBIT_SET(*src))
+ len += 4;
+ else if (*src == '\\')
+ len += 2;
+ else
+ len++;
+
+ src++;
+ }
+
+ return len;
+}
+
+unsigned
+esc_dec_len(const char *src, unsigned srclen)
+{
+ const char *end = src + srclen;
+ int len = 0;
+
+ while (src < end)
+ {
+ if (src[0] != '\\')
+ src++;
+ else if (src + 3 < end &&
+ (src[1] >= '0' && src[1] <= '3') &&
+ (src[2] >= '0' && src[2] <= '7') &&
+ (src[3] >= '0' && src[3] <= '7'))
+ {
+ /*
+ * backslash + valid octal
+ */
+ src += 4;
+ }
+ else if (src + 1 < end &&
+ (src[1] == '\\'))
+ {
+ /*
+ * two backslashes = backslash
+ */
+ src += 2;
+ }
+ else
+ {
+ /*
+ * one backslash, not followed by ### valid octal
+ */
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type bytea")));
+#else
+ return 0;
+#endif
+ }
+
+ len++;
+ }
+ return len;
+}
+
+/*
+ * HEX
+ */
+
+static const char hextbl[] = "0123456789abcdef";
+
+static const int8 hexlookup[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, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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,
+};
+
+unsigned
+hex_encode(const char *src, unsigned len, char *dst)
+{
+ const char *end = src + len;
+
+ while (src < end)
+ {
+ *dst++ = hextbl[(*src >> 4) & 0xF];
+ *dst++ = hextbl[*src & 0xF];
+ src++;
+ }
+ return len * 2;
+}
+
+static inline char
+get_hex(char c)
+{
+ int res = -1;
+
+ if (c > 0 && c < 127)
+ res = hexlookup[(unsigned char) c];
+
+ if (res < 0)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal digit: \"%c\"", c)));
+#else
+ return 0;
+#endif
+ }
+
+ return (char) res;
+}
+
+unsigned
+hex_decode(const char *src, unsigned len, char *dst)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ while (s < srcend)
+ {
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
+ {
+ s++;
+ continue;
+ }
+ v1 = get_hex(*s++) << 4;
+ if (s >= srcend)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid hexadecimal data: odd number of digits")));
+#else
+ return 0;
+#endif
+ }
+
+ v2 = get_hex(*s++);
+ *p++ = v1 | v2;
+ }
+
+ return p - dst;
+}
+
+unsigned
+hex_enc_len(const char *src, unsigned srclen)
+{
+ return srclen << 1;
+}
+
+unsigned
+hex_dec_len(const char *src, unsigned srclen)
+{
+ return srclen >> 1;
+}
diff --git a/src/include/common/encode_utils.h b/src/include/common/encode_utils.h
new file mode 100644
index 0000000..92d5b2f
--- /dev/null
+++ b/src/include/common/encode_utils.h
@@ -0,0 +1,30 @@
+/*
+ * encode_utils.h
+ * Encoding and decoding routines for base64, hexadecimal and escape.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/encode_utils.h
+ */
+#ifndef ENCODE_UTILS_H
+#define ENCODE_UTILS_H
+
+/* base 64 */
+unsigned b64_encode(const char *src, unsigned len, char *dst);
+unsigned b64_decode(const char *src, unsigned len, char *dst);
+unsigned b64_enc_len(const char *src, unsigned srclen);
+unsigned b64_dec_len(const char *src, unsigned srclen);
+
+/* hex */
+unsigned hex_encode(const char *src, unsigned len, char *dst);
+unsigned hex_decode(const char *src, unsigned len, char *dst);
+unsigned hex_enc_len(const char *src, unsigned srclen);
+unsigned hex_dec_len(const char *src, unsigned srclen);
+
+/* escape */
+unsigned esc_encode(const char *src, unsigned srclen, char *dst);
+unsigned esc_decode(const char *src, unsigned srclen, char *dst);
+unsigned esc_enc_len(const char *src, unsigned srclen);
+unsigned esc_dec_len(const char *src, unsigned srclen);
+
+#endif /* ENCODE_UTILS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e36d28e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -161,8 +161,6 @@ extern int errdomainconstraint(Oid datatypeOid, const char *conname);
/* encode.c */
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);
/* enum.c */
extern Datum enum_in(PG_FUNCTION_ARGS);
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index fc075f4..8e749e6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ config_info.c controldata_utils.c encode_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
scram-common.c string.c username.c wait_error.c);
--
2.9.3
0003-Switch-password_encryption-to-a-enum.patchtext/x-diff; name=0003-Switch-password_encryption-to-a-enum.patchDownload
From c5e1ba67f07137e5648556ede65d49bdf71dede9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:43:30 +0900
Subject: [PATCH 3/8] Switch password_encryption to a enum
This makes this parameter more extensible in order to add support for
future password-based authentication protocols.
---
doc/src/sgml/config.sgml | 15 ++++++++--
src/backend/commands/user.c | 22 +++++++--------
src/backend/utils/misc/guc.c | 40 ++++++++++++++++++---------
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/include/commands/user.h | 11 ++++++--
5 files changed, 59 insertions(+), 31 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index a848a7e..02e9bb4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1163,7 +1163,7 @@ include_dir 'conf.d'
</varlistentry>
<varlistentry id="guc-password-encryption" xreflabel="password_encryption">
- <term><varname>password_encryption</varname> (<type>boolean</type>)
+ <term><varname>password_encryption</varname> (<type>enum</type>)
<indexterm>
<primary><varname>password_encryption</> configuration parameter</primary>
</indexterm>
@@ -1175,8 +1175,17 @@ include_dir 'conf.d'
<xref linkend="sql-alterrole">
without writing either <literal>ENCRYPTED</> or
<literal>UNENCRYPTED</>, this parameter determines whether the
- password is to be encrypted. The default is <literal>on</>
- (encrypt the password).
+ password is to be encrypted.
+ </para>
+
+ <para>
+ A value set to <literal>on</> or <literal>md5</> corresponds to a
+ MD5-encrypted password, <literal>off</> or <literal>plain</>
+ corresponds to an unencrypted password.
+ </para>
+
+ <para>
+ The default is <literal>md5</>.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4027c89..fa3e984 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -44,7 +44,7 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
-extern bool Password_encryption;
+int Password_encryption = PASSWORD_TYPE_MD5;
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -80,7 +80,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
@@ -140,9 +140,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -370,7 +370,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -393,7 +393,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
@@ -505,7 +505,7 @@ AlterRole(AlterRoleStmt *stmt)
ListCell *option;
char *rolename = NULL;
char *password = NULL; /* user password */
- bool encrypt_password = Password_encryption; /* encrypt password? */
+ int password_type = Password_encryption;
char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
@@ -550,9 +550,9 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
- encrypt_password = true;
+ password_type = PASSWORD_TYPE_MD5;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
- encrypt_password = false;
+ password_type = PASSWORD_TYPE_PLAINTEXT;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -745,7 +745,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ password_type,
validUntil_datum,
validUntil_null);
@@ -804,7 +804,7 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (!encrypt_password || isMD5(password))
+ if (password_type == PASSWORD_TYPE_PLAINTEXT || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ce4eef9..40600ab 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -34,6 +34,7 @@
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/prepare.h"
+#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/variable.h"
#include "commands/trigger.h"
@@ -393,6 +394,20 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
{NULL, 0, false}
};
+static const struct config_enum_entry password_encryption_options[] = {
+ {"off", PASSWORD_TYPE_PLAINTEXT, false},
+ {"on", PASSWORD_TYPE_MD5, false},
+ {"md5", PASSWORD_TYPE_MD5, false},
+ {"plain", PASSWORD_TYPE_PLAINTEXT, false},
+ {"true", PASSWORD_TYPE_MD5, true},
+ {"false", PASSWORD_TYPE_PLAINTEXT, true},
+ {"yes", PASSWORD_TYPE_MD5, true},
+ {"no", PASSWORD_TYPE_PLAINTEXT, true},
+ {"1", PASSWORD_TYPE_MD5, true},
+ {"0", PASSWORD_TYPE_PLAINTEXT, true},
+ {NULL, 0, false}
+};
+
/*
* Options for enum values stored in other modules
*/
@@ -423,8 +438,6 @@ bool check_function_bodies = true;
bool default_with_oids = false;
bool SQL_inheritance = true;
-bool Password_encryption = true;
-
int log_min_error_statement = ERROR;
int log_min_messages = WARNING;
int client_min_messages = NOTICE;
@@ -1314,17 +1327,6 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
- {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
- gettext_noop("Encrypt passwords."),
- gettext_noop("When a password is specified in CREATE USER or "
- "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
- "this parameter determines whether the password is to be encrypted.")
- },
- &Password_encryption,
- true,
- NULL, NULL, NULL
- },
- {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
@@ -3810,6 +3812,18 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY,
+ gettext_noop("Encrypt passwords."),
+ gettext_noop("When a password is specified in CREATE USER or "
+ "ALTER USER without writing either ENCRYPTED or UNENCRYPTED, "
+ "this parameter determines whether the password is to be encrypted.")
+ },
+ &Password_encryption,
+ PASSWORD_TYPE_MD5, password_encryption_options,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b1c3aea..1fdbc06 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = on
+#password_encryption = md5 # on, off, md5 or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 1f0cfcc..7a63841 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -16,9 +16,14 @@
#include "parser/parse_node.h"
-/* Hook to check passwords in CreateRole() and AlterRole() */
-#define PASSWORD_TYPE_PLAINTEXT 0
-#define PASSWORD_TYPE_MD5 1
+/* Types of password */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern int Password_encryption;
typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
--
2.9.3
0004-Refactor-decision-making-of-password-encryption-into.patchtext/x-diff; name=0004-Refactor-decision-making-of-password-encryption-into.patchDownload
From 35e22d1dead598e63be2ff2e6444ff2e282ea872 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:51:15 +0900
Subject: [PATCH 4/8] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 84 ++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index fa3e984..2e89f1f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,48 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = pstrdup(password);
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = pstrdup(password);
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +125,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +437,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
+ pfree(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,18 +844,14 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ pfree(encrypted_passwd);
}
/* unset password */
--
2.9.3
0005-Create-generic-routine-to-fetch-password-and-valid-u.patchtext/x-diff; name=0005-Create-generic-routine-to-fetch-password-and-valid-u.patchDownload
From c55b72d51db8c6ec91b99bab8b4f6bacdaeefbab Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:40:15 +0900
Subject: [PATCH 5/8] Create generic routine to fetch password and valid until
values for a role
This is used now for the MD5-encrypted case and the plain text, and this
is going to be used as well for SCRAM-SHA256. That's as well useful for
any new password-based protocols.
---
src/backend/libpq/crypt.c | 59 +++++++++++++++++++++++++++++++++++------------
src/include/libpq/crypt.h | 2 ++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c41c57 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,49 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
--
2.9.3
0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchtext/x-diff; name=0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From f6687d48f45aff65b7fa3062b69ee71b5e3f1558 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:51:39 +0900
Subject: [PATCH 6/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 9 +-
doc/src/sgml/protocol.sgml | 147 ++++-
doc/src/sgml/ref/create_role.sgml | 14 +-
src/backend/commands/user.c | 18 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 764 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 132 +++++
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 196 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 55 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 3 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 +
src/interfaces/libpq/.gitignore | 4 +
src/interfaces/libpq/Makefile | 11 +-
src/interfaces/libpq/fe-auth-scram.c | 605 ++++++++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
30 files changed, 2190 insertions(+), 44 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 02e9bb4..c9f7a80 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1181,7 +1181,8 @@ include_dir 'conf.d'
<para>
A value set to <literal>on</> or <literal>md5</> corresponds to a
MD5-encrypted password, <literal>off</> or <literal>plain</>
- corresponds to an unencrypted password.
+ corresponds to an unencrypted password. Setting this parameter to
+ <literal>scram</> will encrypt the password with SCRAM-SHA-256.
</para>
<para>
@@ -1259,8 +1260,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 68b0941..8af888d 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>
@@ -2578,6 +2607,114 @@ 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>
@@ -4340,7 +4477,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2e89f1f..c76d273 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = pstrdup(password);
else
{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..1513b21
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,764 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode_utils.h"
+#include "common/scram-common.h"
+#include "common/sha.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+typedef enum
+{
+ INIT, /* receive client-first-message */
+ SALT_SENT, /* receive client-final-message */
+ FINISHED /* all done */
+} scram_exchange_state;
+
+typedef struct
+{
+ scram_exchange_state state;
+
+ /* Username from startup packet */
+ const char *username;
+
+ /* Fields from the password verifier, stored in pg_authid. */
+ char *salt; /* base64-encoded */
+ int iterations;
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+
+ /* Fields from the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+/*
+ * 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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ elog(ERROR, "invalid SCRAM verifier");
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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;
+
+ 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;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+static char *
+read_attr_value(char **input, char attr)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin != attr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (attr %c expected)", attr)));
+ begin++;
+
+ if (*begin != '=')
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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')))
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (invalid attribute char)")));
+ if (attr_p)
+ *attr_p = attr;
+ begin++;
+
+ if (*begin != '=')
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+ input = pstrdup(input);
+
+ /*----------
+ * According to RFC 5820, the syntax for the client's first
+ * message is:
+ *
+ * saslname = 1*(value-safe-char / "=2C" / "=3D")
+ * ;; Conforms to <value>.
+ *
+ * authzid = "a=" saslname
+ * ;; Protocol specific.
+ *
+ * username = "n=" saslname
+ * ;; Usernames are prepared using SASLprep.
+ *
+ * reserved-mext = "m=" 1*(value-char)
+ * ;; Reserved for signaling mandatory extensions.
+ * ;; The exact syntax will be defined in
+ * ;; the future.
+ *
+ * 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
+ *
+ * Note: Contrary to the spec, the username in the SASL exchange
+ * is always sent as an empty string, because we get the username
+ * from the startup packet.
+ *
+ * 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(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("channel binding not supported")));
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM request")));
+ }
+ if (*input != ',')
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("malformed SCRAM request (',' expected after gs2-cbind-flag)")));
+ input++;
+
+ /* read optional authzid (authorization identity) */
+ if (*input != ',')
+ {
+ state->client_authzid = read_attr_value(&input, 'a');
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SCRAM authorization identity not supported")));
+ }
+ else
+ input++;
+
+ /*
+ * We're now at the beginning of 'client_first_message_bare'. Save it,
+ * because it's needed later in the verification of client-proof.
+ */
+ state->client_first_message_bare = pstrdup(input);
+
+ /*
+ * Any mandatory extensions would go here. We don't support any, so
+ * throw an error if we see one.
+ */
+ if (*input == 'm')
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SCRAM mandatory extension not supported")));
+
+ /* read username and nonce */
+ /* FIXME: Should we check that the username is empty? */
+ state->client_username = read_attr_value(&input, 'n');
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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);
+
+ /* 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);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN];
+ int encoded_len;
+
+ /*----------
+ * According to RFC 5820, the syntax of the server-first-message is:
+ *
+ * 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
+ *----------
+ */
+
+ /* Use the nonce that was pre-generated before forking */
+ /* FIXME: use RAND_bytes() here if built with OpenSSL? */
+ memcpy(nonce, MyProcPort->scramNonce, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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);
+
+ /*----------
+ * According to RFC 5820, the syntax of the client-final-message is:
+ *
+ * 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
+ *----------
+ */
+
+ /*
+ * Since we don't support channel binding, nor authorization identity,
+ * cbind-input is always "n,,". That's "biws" when base64-encoded.
+ */
+ channel_binding = read_attr_value(&p, 'c');
+ if (strcmp(channel_binding, "biws") != 0)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid channel binding input in SCRAM message")));
+ 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)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (invalid ClientProof)")));
+ memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ pfree(client_proof);
+
+ if (*p != '\0')
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("malformed SCRAM message (unexpected attribute %c at end of message)", 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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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';
+
+ /*----------
+ * According to RFC 5820, the syntax for the server-final message is:
+ *
+ * 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);
+}
+
+/*
+ * Functions for serializing/deserializing SCRAM password verifiers in
+ * pg_authid.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ */
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+ int i;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ /* FIXME: use RAND_bytes() here if built with OpenSSL? */
+ for (i = 0; i < SCRAM_SALT_LEN; i++)
+ salt[i] = random() % 255 + 1;
+
+ encoded_salt = palloc(b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d907e6b..d0bfadb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,9 +27,11 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -201,6 +203,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -262,6 +270,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;
@@ -542,6 +551,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, "");
@@ -716,6 +729,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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);
+
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 0c0a609..5e8812f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2351,6 +2351,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ RandomSalt(port->scramNonce, sizeof(port->scramNonce));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 40600ab..abb14f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -398,6 +398,7 @@ static const struct config_enum_entry password_encryption_options[] = {
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"true", PASSWORD_TYPE_MD5, true},
{"false", PASSWORD_TYPE_PLAINTEXT, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1fdbc06..ae8e849 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # on, off, md5 or plain
+#password_encryption = md5 # on, off, md5, plain or scram
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 3b36c0c..1dbbbdd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = config_info.o controldata_utils.o exec.o encode_utils.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..6fb4719
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,196 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256), pass
+ * it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well in
+ * the frontend in order to be able to encode properly this string, and
+ * then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a63841..a5ff5f3 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -20,7 +20,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..60460e8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/scram-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+/*
+ * FIXME: Where does '10' come from? I don't see a length specified in
+ * RFC 5802. What length do other implementations use?
+ */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..210111f 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,8 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char scramNonce[10]; /* SCRAM server-nonce, size of SCRAM_NONCE_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..f08750f
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..8bbc75f 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
/exports.list
/chklocale.c
/crypt.c
+/encode_utils.c
/getaddrinfo.c
/getpeereid.c
/inet_aton.c
@@ -9,6 +10,9 @@
/open.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha.c
+/sha_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..3289823 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
@@ -42,10 +42,12 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += ip.o md5.o encode_utils.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha_openssl.o
+else
+OBJS += sha.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+encode_utils.c scram-common.c sha.c sha_openssl.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..2fd3a5c
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,605 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * Portions Copyright (c) 1996-2016, 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/encode_utils.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef enum
+{
+ INIT, /* Send client-first-message */
+ NONCE_SENT, /* Receive server-first-message, respond with
+ * client-final-message */
+ PROOF_SENT, /* Receive server-final-message, verify proof */
+ FINISHED
+} fe_scram_exchange_state;
+
+typedef struct
+{
+ fe_scram_exchange_state state;
+
+ /* Supplied by caller */
+ const char *username;
+ const char *password;
+
+ char client_nonce[SCRAM_NONCE_LEN + 1];
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* Fields from the server-first message */
+ char *server_first_message;
+ char *salt;
+ int saltlen;
+ int iterations;
+ char *nonce; /* client+server nonces */
+
+ /* Fields from the server-final message */
+ char *server_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+/*
+ * Initialize a SCRAM authentication exchange.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange state.
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->nonce)
+ free(state->nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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_first_message */
+ *output = build_client_first_message(state);
+ if (*output == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("out of memory"));
+ *done = true;
+ state->state = FINISHED;
+ return;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = NONCE_SENT;
+ break;
+
+ case NONCE_SENT:
+ /* receive salt and server nonce, send response */
+ if (!read_server_first_message(state, input, errorMessage))
+ {
+ *done = true;
+ state->state = FINISHED;
+ return;
+ }
+ *output = build_client_final_message(state);
+ if (*output == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("out of memory"));
+ *done = true;
+ state->state = FINISHED;
+ return;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = PROOF_SENT;
+ break;
+
+ case PROOF_SENT:
+ /* receive server proof, and verify it */
+ if (!read_server_final_message(state, input, errorMessage))
+ {
+ *done = true;
+ state->state = FINISHED;
+ return;
+ }
+ *success = verify_server_proof(state);
+ *done = true;
+ state->state = FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ *done = true;
+ *success = false;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid SCRAM exchange state"));
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin != attr)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (%c expected)"), attr);
+ return NULL;
+ }
+ begin++;
+
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected '=' in attr %c)"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ *
+ * Returns NULL on out-of-memory.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char *buf;
+ char msglen;
+
+ generate_nonce(state->client_nonce, SCRAM_NONCE_LEN);
+
+ /* Construct message */
+ msglen = 5 + strlen(state->username) + 3 + strlen(state->client_nonce);
+ buf = malloc(msglen + 1);
+ if (!buf)
+ return NULL;
+ snprintf(buf, msglen + 1, "n,,n=%s,r=%s",
+ state->username, state->client_nonce);
+
+ /* Save the client_first_message_bare part for later */
+ state->client_first_message_bare = strdup(buf + 3);
+ if (!state->client_first_message_bare)
+ {
+ free(buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * Build the final exchange message sent by the client.
+ *
+ * Returns NULL on out-of-memory.
+ */
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+ int len;
+ char *buf;
+ uint8 client_proof[SCRAM_KEY_LEN];
+ char client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+ int client_proof_len;
+
+ /* Build client_final_message_without_proof */
+ len = 9 + strlen(state->nonce);
+ buf = malloc(len + 1);
+ if (!buf)
+ return NULL;
+ snprintf(buf, len + 1,
+ "c=biws,r=%s", state->nonce);
+ state->client_final_message_without_proof = buf;
+
+ /* Calculate client-proof */
+ calculate_client_proof(state,
+ 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';
+
+ /* Build client_final_message (with the proof) */
+ len = strlen(state->client_final_message_without_proof) + 3 + client_proof_len;
+ buf = malloc(len + 1);
+ if (!buf)
+ return NULL;
+ snprintf(buf, len + 1, "%s,p=%s",
+ state->client_final_message_without_proof,
+ client_proof_base64);
+
+ return buf;
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ *
+ * Returns true on success. On failure, returns false,
+ * and adds details to 'errormessage'.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errorMessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+ char *s;
+
+ state->server_first_message = strdup(input);
+ if (!state->server_first_message)
+ return false;
+
+ /*----------
+ * Parse the message.
+ *
+ * According to RFC 5820, the syntax of the server-first-message is:
+ *
+ * server-first-message =
+ * [reserved-mext ","] nonce "," salt ","
+ * iteration-count ["," extensions]
+ *
+ * reserved-mext = "m=" 1*(value-char)
+ * ;; Reserved for signaling mandatory extensions.
+ * ;; The exact syntax will be defined in
+ * ;; the future.
+ *
+ * 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
+ *----------
+ */
+
+ /*
+ * Check for possible mandatory extensions first. We don't support any, so
+ * throw an error, if there are any.
+ */
+ if (*input == 'm')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("server requires a SCRAM extension that is not supported"));
+ return false;
+ }
+
+ /*
+ * Read nonce. It consists of the client-nonce that we sent earlier, and
+ * the server-generated part. Check that the client-nonce matches what we
+ * sent.
+ */
+ s = read_attr_value(&input, 'r', errorMessage);
+ if (!s)
+ return false;
+ state->nonce = strdup(s);
+ if (state->nonce == NULL)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("out of memory"));
+ return false;
+ }
+
+ if (strncmp(state->nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid client-nonce in SCRAM message"));
+ return false;
+ }
+
+ /* Read the salt */
+ 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)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("out of memory"));
+ return false;
+ }
+ state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+
+ /* FIXME: shouldn't we allow any salt length? */
+ if (state->saltlen != SCRAM_SALT_LEN)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (invalid salt length)"));
+ return false;
+ }
+
+ /* Read iteration count */
+ iterations_str = read_attr_value(&input, 'i', errorMessage);
+ if (iterations_str == NULL)
+ return false;
+ state->iterations = strtol(iterations_str, &endptr, 10);
+ if (*endptr != '\0' || state->iterations < 1)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (invalid # of iterations)"));
+ return false;
+ }
+
+ if (*input != '\0')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (garbage after end of message)"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errorMessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+ char buf[SCRAM_KEY_LEN * 2];
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /*
+ * Parse the message. It consists of a single "v=..." attribute,
+ * containing the base64-encoded server-proof.
+ */
+
+ /*
+ * FIXME: also parse a possible server-error attribute. The server
+ * currently never sends that, but still.
+ */
+ encoded_server_proof = read_attr_value(&input, 'v', errorMessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ if (b64_dec_len(encoded_server_proof,
+ strlen(encoded_server_proof)) > sizeof(buf))
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (invalid ServerProof length)"));
+ return false;
+ }
+
+ server_proof_len = b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ buf);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (invalid ServerProof length)"));
+ return false;
+ }
+ memcpy(state->ServerProof, buf, SCRAM_KEY_LEN);
+
+ if (*input != '\0')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (garbage after end of message)"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ *
+ * It's important to verify the server-proof, even if the server subsequently
+ * accepts the login, to detect that the server is genuine! (XXX: An impersonating
+ * server could choose to present us an MD5 challenge instead, or no challenge
+ * at all, but that's a different problem.)
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ int i;
+
+ /* FIXME: use RAND_bytes() if available? */
+ for (i = 0; i < len; i++)
+ buf[i] = random() % 255 + 1;
+
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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.9.3
0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchtext/x-diff; name=0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From b20aaee8fb883894a288372f167076aeb4fb637f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:57:07 +0900
Subject: [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
doc/src/sgml/ref/alter_role.sgml | 2 ++
doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
src/backend/commands/user.c | 72 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 ++++
4 files changed, 95 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..fa74466 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+ as-is.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c76d273..e7a4b8f 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -179,7 +179,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -188,9 +189,41 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -310,8 +343,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -586,6 +617,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -594,9 +626,41 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -684,8 +748,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1526c73..bed09f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.9.3
0008-Add-regression-tests-for-passwords.patchtext/x-diff; name=0008-Add-regression-tests-for-passwords.patchDownload
From f6f56fa8a254de957e031ef938d8128a27f060f5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:55:49 +0900
Subject: [PATCH 8/8] Add regression tests for passwords
---
src/test/regress/expected/password.out | 101 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..a90f323
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: off, on, md5, scram, plain.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------
+ regress_passwd1 |
+ regress_passwd2 |
+ regress_passwd3 |
+ regress_passwd4 |
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.9.3
On Tue, Sep 27, 2016 at 9:01 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
* Added error-handling for OOM and other errors in liybpq
* In libpq, added check that the server sent back the same client-nonce
* Turned ERRORs into COMMERRORs and removed DEBUG4 lines (they could reveal
useful information to an attacker)
* Improved comments
Thanks!
* A source of random values. This currently uses PostmasterRandom()
similarly to how the MD5 salt is generated, in the server, but plain old
random() in the client. If built with OpenSSL, we should probably use
RAND_bytes(). But what if the client is built without OpenSSL? I believe the
protocol doesn't require cryptographically strong randomness for the nonces,
i.e. it's OK if they're predictable, but they should be different for each
session.
And what if we just replace PostmasterRandom()? pgcrypto is a useful
source of inspiration here. If the server is built with OpenSSL we use
RAND_bytes all the time. If not, let's use /dev/urandom. If urandom is
not there, we fallback to /dev/random. For WIN32, there is
CryptGenRandom(). This could just be done as an independent patch with
a routine in src/common/ for example to allow both frontend and
backend to use it. Do you think that this is a requirement for this
patch? I think not really for the first shot.
* Nonce and salt lengths. The patch currently uses 10 bytes for both, but I
think I just pulled number that out of thin air. The spec doesn't say
anything about nonce and salt lengths AFAICS. What do other implementations
use? Is 10 bytes enough?
Good question, but that seems rather short to me now that you mention
it. Mongo has implemented already SCRAM-SHA-1 and they are using 3
uint64 so that's 24 bytes (sasl_scramsha1_client_conversation.cpp for
example). For the salt I am seeing a reference to a string "salt"
only, which is too short.
* The spec defines a final "server-error" message that the server sends on
authentication failure, or e.g. if a required extension is not supported.
The patch just uses FATAL for those. Should we try to send a server-error
message instead, or before, the elog(FATAL) ?
It seems to me that sending back the error while the context is still
alive, aka before the FATAL would be the way to go. That could be
nicely done with an error callback while the exchange is happening. I
missed that while going through the spec.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/27/2016 04:19 PM, Michael Paquier wrote:
On Tue, Sep 27, 2016 at 9:01 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
* A source of random values. This currently uses PostmasterRandom()
similarly to how the MD5 salt is generated, in the server, but plain old
random() in the client. If built with OpenSSL, we should probably use
RAND_bytes(). But what if the client is built without OpenSSL? I believe the
protocol doesn't require cryptographically strong randomness for the nonces,
i.e. it's OK if they're predictable, but they should be different for each
session.And what if we just replace PostmasterRandom()? pgcrypto is a useful
source of inspiration here. If the server is built with OpenSSL we use
RAND_bytes all the time. If not, let's use /dev/urandom. If urandom is
not there, we fallback to /dev/random. For WIN32, there is
CryptGenRandom(). This could just be done as an independent patch with
a routine in src/common/ for example to allow both frontend and
backend to use it.
Yeah, if built with OpenSSL, we probably should just always use
RAND_bytes(). Without OpenSSL, we have to think a bit harder.
The server-side code in the patch is probably good enough. After all, we
use the same mechanism for the MD5 salt today.
The libpq-side is not. Just calling random() won't do. We haven't needed
for random numbers in libpq before, but now we do. Is the pgcrypto
solution portable enough that we can use it in libpq?
Do you think that this is a requirement for this
patch? I think not really for the first shot.
We need something for libpq. We can't just call random(), as that's not
random unless you also do srandom(), and we don't want to do that
because the application might have a different idea of what the seed
should be.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Sep 27, 2016 at 10:42 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
The libpq-side is not. Just calling random() won't do. We haven't needed for
random numbers in libpq before, but now we do. Is the pgcrypto solution
portable enough that we can use it in libpq?
Do you think that urandom would be enough then? The last time I took a
look at that, I saw urandom on all modern platforms even those ones:
OpenBSD, NetBSD, Solaris, SunOS. For Windows the CryptGen stuff would
be nice enough I guess..
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/26/16 2:02 AM, Michael Paquier wrote:
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net> wrote:
Thanks for the review and the comments!
I notice that the copyright from pgcrypto/sha1.c was carried over but
not the copyright from pgcrypto/sha2.c. I'm no expert on how this
works, but I believe the copyright from sha2.c must be copied over.Right, those copyright bits are missing:
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
[...]
- * Copyright (c) 2000-2001, Aaron D. Gifford
The license block being the same, it seems to me that there is no need
to copy it over. The copyright should be enough.
Looks fine to me.
Also, are there any plans to expose these functions directly to the user
without loading pgcrypto? Now that the functionality is in core it
seems that would be useful. In addition, it would make this patch stand
on its own rather than just being a building block.There have been discussions about avoiding enabling those functions by
default in the distribution. We'd rather not do that...
OK.
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.
I like that better.
Couldn't md5_crypt_verify() be made more general and take the hash type?
For instance, password_crypt_verify() with the last param as the new
password type enum.This would mean incorporating the whole SASL message exchange into
this routine because the password string is part of the scram
initialization context, and it seems to me that it is better to just
do once a lookup at the entry in pg_authid. So we'd finish with a more
confusing code I am afraid. At least that's the conclusion I came up
with when doing that.. md5_crypt_verify does only the work on a
received password.
Ah, yes, I see now. I missed that when I reviewed patch 6.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/26/2016 09:02 AM, Michael Paquier wrote:
On Mon, Sep 26, 2016 at 2:15 AM, David Steele <david@pgmasters.net> wrote:
* [PATCH 3/8] Switch password_encryption to a enum
Does not apply on HEAD (98c2d3332):
Interesting, it works for me on da6c4f6.
For here on I used 39b691f251 for review and testing.
I seems you are keeping on/off for backwards compatibility, shouldn't
the default now be "md5"?-#password_encryption = on +#password_encryption = on # on, off, md5 or plainThat sounds like a good idea, so switched this way.
Committed this patch in the series, to turn password_encryption GUC into
an enum.
There was one bug in the patch: if a plaintext password was given with
CREATE/ALTER USER foo PASSWORD 'bar', but password_encryption was 'md5',
it would incorrectly pass PASSWORD_TYPE_MD5 to the check-password hook.
That would limit the amount of checking that the hook can do. Fixed
that. Also edited the docs and comments a little bit, hopefully for the
better.
Once we get the main SCRAM patch in, we may want to remove the "on"
alias altogether. We don't promise backwards-compatibility of config
files or GUC values, and not many people set password_encryption=on
explicitly anyway, since it's the default. But I kept it now, as there's
no ambiguity on what "on" means, yet.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/26/2016 09:02 AM, Michael Paquier wrote:
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.
Looking at these encoding functions, the SCRAM protocol actually uses
base64 for everything. The hex encoding is only used in the server, to
encode the StoredKey and ServerKey in pg_authid. So we don't need that
in the client. It would actually make sense to use base64 for the fields
in pg_authid, too. Takes less space, and seems more natural for SCRAM
anyway.
libpq actually has its own implementation of hex encoding and decoding
already, in fe-exec.c. So if we wanted to use hex-encoding for
something, we could use that, or if we moved the routines from
src/backend/utils/encode.c, then we should try to reuse them for the
purposes of fe-exec.c, too. And libpq already has an implementation of
the 'escape' encoding, too, in fe-exec.c. But as I said above, I don't
think we need to touch any of that.
In summary, I think we only need to move the base64 routines to
src/common. I'd prefer to be quite surgical in what we put in
src/common, and avoid moving stuff that's not strictly required by both
the server and the client.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/28/2016 12:53 PM, Heikki Linnakangas wrote:
On 09/26/2016 09:02 AM, Michael Paquier wrote:
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.Looking at these encoding functions, the SCRAM protocol actually uses
base64 for everything.
Oh, one more thing. The SCRAM spec says:
The use of base64 in SCRAM is restricted to the canonical form with
no whitespace.
Our b64_encode routine does use whitespace, so we can't use it as is for
SCRAM. As the patch stands, we might never output anything long enough
to create linefeeds, but let's be tidy. The base64 implementation is
about 100 lines of code, so perhaps we should just leave
src/backend/utils/encode.c alone, and make a new copy of the base64
routines in src/common.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 09/28/2016 12:53 PM, Heikki Linnakangas wrote:
On 09/26/2016 09:02 AM, Michael Paquier wrote:
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.Looking at these encoding functions, the SCRAM protocol actually uses
base64 for everything.
Oh, one more thing. The SCRAM spec says:
The use of base64 in SCRAM is restricted to the canonical form with
no whitespace.
Our b64_encode routine does use whitespace, so we can't use it as is for
SCRAM. As the patch stands, we might never output anything long enough
to create linefeeds, but let's be tidy. The base64 implementation is
about 100 lines of code, so perhaps we should just leave
src/backend/utils/encode.c alone, and make a new copy of the base64
routines in src/common.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Sep 28, 2016 at 7:03 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 09/28/2016 12:53 PM, Heikki Linnakangas wrote:
On 09/26/2016 09:02 AM, Michael Paquier wrote:
* [PATCH 2/8] Move encoding routines to src/common/
I wonder if it is confusing to have two of encode.h/encode.c. Perhaps
they should be renamed to make them distinct?Yes it may be a good idea to rename that, like encode_utils.[c|h] for
the new files.Looking at these encoding functions, the SCRAM protocol actually uses
base64 for everything.
OK, I thought that moving everything made more sense for consistency
but let's keep src/common/ as small as possible.
Oh, one more thing. The SCRAM spec says:
The use of base64 in SCRAM is restricted to the canonical form with
no whitespace.Our b64_encode routine does use whitespace, so we can't use it as is for
SCRAM. As the patch stands, we might never output anything long enough to
create linefeeds, but let's be tidy. The base64 implementation is about 100
lines of code, so perhaps we should just leave src/backend/utils/encode.c
alone, and make a new copy of the base64 routines in src/common.
OK, I'll refresh that tomorrow with the rest. Thanks for the commit to
extend password_encryption.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 9/28/16 5:25 AM, Heikki Linnakangas wrote:
Once we get the main SCRAM patch in, we may want to remove the "on"
alias altogether. We don't promise backwards-compatibility of config
files or GUC values, and not many people set password_encryption=on
explicitly anyway, since it's the default.
+1.
--
-David
david@pgmasters.net
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki, Michael, Magnus,
* Michael Paquier (michael.paquier@gmail.com) wrote:
On Tue, Sep 27, 2016 at 10:42 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
The libpq-side is not. Just calling random() won't do. We haven't needed for
random numbers in libpq before, but now we do. Is the pgcrypto solution
portable enough that we can use it in libpq?Do you think that urandom would be enough then? The last time I took a
look at that, I saw urandom on all modern platforms even those ones:
OpenBSD, NetBSD, Solaris, SunOS. For Windows the CryptGen stuff would
be nice enough I guess..
Magnus had been working on a patch that, as I recall, he thought was
portable and I believe could be used on both sides.
Magnus, would what you were working on be helpful here...?
Thanks!
Stephen
On Wed, Sep 28, 2016 at 8:55 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Our b64_encode routine does use whitespace, so we can't use it as is for
SCRAM. As the patch stands, we might never output anything long enough to
create linefeeds, but let's be tidy. The base64 implementation is about 100
lines of code, so perhaps we should just leave src/backend/utils/encode.c
alone, and make a new copy of the base64 routines in src/common.OK, I'll refresh that tomorrow with the rest. Thanks for the commit to
extend password_encryption.
OK, so after more chatting with Heikki, here is a list of TODO items
and a summary of the state of things:
- base64 encoding routines should drop whitespace (' ', \r, \t), and
it would be better to just copy those from the backend's encode.c to
src/common/. No need to move escape and binary things, nor touch
backend's base64 routines.
- No need to move sha1.c to src/common/. Better to just get sha2.c
into src/common/ as we aim at SCRAM-SHA-256.
- random() called in the client is no good. We need something better here.
- The error handling needs to be reworked and should follow the
protocol presented by RFC5802, by sending back e= messages. This needs
a bit of work, not much I think though as the infra is in place in the
core patch.
- Let's discard the md5-or-scram optional thing in pg_hba.conf. This
complicates the error handling protocol.
I am marking this patch as returned with feedback for current CF and
will post a new set soon, moving it to the next CF once I have the new
set of patches ready for posting.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Sep 29, 2016 at 12:48 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Wed, Sep 28, 2016 at 8:55 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:Our b64_encode routine does use whitespace, so we can't use it as is for
SCRAM. As the patch stands, we might never output anything long enough to
create linefeeds, but let's be tidy. The base64 implementation is about 100
lines of code, so perhaps we should just leave src/backend/utils/encode.c
alone, and make a new copy of the base64 routines in src/common.OK, I'll refresh that tomorrow with the rest. Thanks for the commit to
extend password_encryption.OK, so after more chatting with Heikki, here is a list of TODO items
and a summary of the state of things:
- base64 encoding routines should drop whitespace (' ', \r, \t), and
it would be better to just copy those from the backend's encode.c to
src/common/. No need to move escape and binary things, nor touch
backend's base64 routines.
- No need to move sha1.c to src/common/. Better to just get sha2.c
into src/common/ as we aim at SCRAM-SHA-256.
- random() called in the client is no good. We need something better here.
- The error handling needs to be reworked and should follow the
protocol presented by RFC5802, by sending back e= messages. This needs
a bit of work, not much I think though as the infra is in place in the
core patch.
- Let's discard the md5-or-scram optional thing in pg_hba.conf. This
complicates the error handling protocol.I am marking this patch as returned with feedback for current CF and
will post a new set soon, moving it to the next CF once I have the new
set of patches ready for posting.
And so we are back on that, with a new set:
- 0001, introducing pg_strong_random() in src/port/ to have the
backend portion of SCRAM use it instead of random(). This patch is
from Magnus who has kindly sent is to me, so the authorship goes to
him. This patch replaces at the same time PostmasterRandom() with it,
this way once SCRAM gets integrated both the frontend and the backend
finish using the same facility. I think that's good for consistency.
Compared to the version Magnus has sent me, I have changed two things:
-- Reading from /dev/urandom and /dev/random is not influenced by
EINTR. read() handling is also made better in case of partial reads
from a given source.
-- Win32 Crypto routines use MS_DEF_PROV instead of NULL. I think
that's a better idea to not let the user the choice of the encryption
source here.
- 0002, moving all the SHA2 functions to src/common/. As mentioned
upthread, this keeps the amount of code moved to src/common/ to a
minimum. I have been careful to get the header files and copyright
mentions into a correct shape at the same time. I have moved a couple
of code blocks in a shape that make a bit more sense, not sure how you
feel about that, Heikki.
- 0003, creating a set of base64 routines without whitespace handling.
That's more or less a copy of what is in encode.c, simplified for
SCRAM. At the same time I have prefixed the routines with pg_ to make
a difference with what is in encode.c.
- 0004 does some refactoring regarding encrypted passwords in user.c
- 0005 creates a generic routine to fetch password and valid until
values for a role
- 0006 adds support for SCRAM-SHA-256. I have not yet addressed the
concerns regarding the handling of e= messages yet. I have fixed the
nonce generation with random() though.
- 0007 adds the extension for CREATE ROLE .. PASSWORD foo USING protocol
- 0008 is a basic set of regression tests to test passwords.
To be honest, I have now put some love into 0001~0004, but less in the
rest. The first refactoring patches are going to be subject to enough
comments I guess :) I'll put more love into 0005~ in the next couple
of days though while reworking the message interface.
Thanks,
--
Michael
Attachments:
0001-Introduce-pg_strong_random.patchtext/plain; charset=US-ASCII; name=0001-Introduce-pg_strong_random.patchDownload
From d9a41e87fa10f6a54760d43d3987134370632442 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 15:10:44 +0900
Subject: [PATCH 1/8] Introduce pg_strong_random()
This new routine, available for both frontend and backend, is aimed at
producing strong random numbers based on a hierarchy:
- OpenSSL if support is built.
- On Windows, the native cryptographic functions are used.
- /dev/urandom.
- /dev/random.
PostmasterRandom is replaced by that, making the initial random number
generation less predictible.
---
src/backend/postmaster/postmaster.c | 89 ++---------------------
src/include/port.h | 3 +
src/port/Makefile | 2 +-
src/port/pgsrandom.c | 140 ++++++++++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
5 files changed, 150 insertions(+), 86 deletions(-)
create mode 100644 src/port/pgsrandom.c
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2d43506..8bf69ea 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -358,14 +358,6 @@ static volatile bool avlauncher_needs_signal = false;
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;
-/*
- * State for assigning random salts and cancel keys.
- * Also, the global MyCancelKey passes the cancel key assigned to a given
- * backend from the postmaster to that backend (via fork).
- */
-static unsigned int random_seed = 0;
-static struct timeval random_start_time;
-
#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif
@@ -403,8 +395,6 @@ static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
-static long PostmasterRandom(void);
-static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -581,7 +571,7 @@ PostmasterMain(int argc, char *argv[])
* Note: the seed is pretty predictable from externally-visible facts such
* as postmaster start time, so avoid using random() for security-critical
* random values during postmaster startup. At the time of first
- * connection, PostmasterRandom will select a hopefully-more-random seed.
+ * connection, pg_strong_random will select a hopefully-more-random seed.
*/
srandom((unsigned int) (MyProcPid ^ MyStartTime));
@@ -1292,8 +1282,6 @@ PostmasterMain(int argc, char *argv[])
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
- /* PostmasterRandom wants its own copy */
- gettimeofday(&random_start_time, NULL);
/*
* We're ready to rock and roll...
@@ -2350,7 +2338,7 @@ ConnCreate(int serverFd)
* after. Else the postmaster's random sequence won't get advanced, and
* all backends would end up using the same salt...
*/
- RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+ pg_strong_random(port->md5Salt, sizeof(port->md5Salt));
/*
* Allocate GSSAPI specific state struct
@@ -3904,7 +3892,7 @@ BackendStartup(Port *port)
* backend will have its own copy in the forked-off process' value of
* MyCancelKey, so that it can transmit the key to the frontend.
*/
- MyCancelKey = PostmasterRandom();
+ pg_strong_random(&MyCancelKey, sizeof(MyCancelKey));
bn->cancel_key = MyCancelKey;
/* Pass down canAcceptConnections state */
@@ -4212,13 +4200,6 @@ BackendRun(Port *port)
int usecs;
int i;
- /*
- * Don't want backend to be able to see the postmaster random number
- * generator state. We have to clobber the static random_seed *and* start
- * a new random sequence in the random() library function.
- */
- random_seed = 0;
- random_start_time.tv_usec = 0;
/* slightly hacky way to convert timestamptz into integers */
TimestampDifference(0, port->SessionStartTime, &secs, &usecs);
srandom((unsigned int) (MyProcPid ^ (usecs << 12) ^ secs));
@@ -5067,66 +5048,6 @@ StartupPacketTimeoutHandler(void)
/*
- * RandomSalt
- */
-static void
-RandomSalt(char *salt, int len)
-{
- long rand;
- int i;
-
- /*
- * We use % 255, sacrificing one possible byte value, so as to ensure that
- * all bits of the random() value participate in the result. While at it,
- * add one to avoid generating any null bytes.
- */
- for (i = 0; i < len; i++)
- {
- rand = PostmasterRandom();
- salt[i] = (rand % 255) + 1;
- }
-}
-
-/*
- * PostmasterRandom
- *
- * Caution: use this only for values needed during connection-request
- * processing. Otherwise, the intended property of having an unpredictable
- * delay between random_start_time and random_stop_time will be broken.
- */
-static long
-PostmasterRandom(void)
-{
- /*
- * Select a random seed at the time of first receiving a request.
- */
- if (random_seed == 0)
- {
- do
- {
- struct timeval random_stop_time;
-
- gettimeofday(&random_stop_time, NULL);
-
- /*
- * We are not sure how much precision is in tv_usec, so we swap
- * the high and low 16 bits of 'random_stop_time' and XOR them
- * with 'random_start_time'. On the off chance that the result is
- * 0, we loop until it isn't.
- */
- random_seed = random_start_time.tv_usec ^
- ((random_stop_time.tv_usec << 16) |
- ((random_stop_time.tv_usec >> 16) & 0xffff));
- }
- while (random_seed == 0);
-
- srandom(random_seed);
- }
-
- return random();
-}
-
-/*
* Count up number of child processes of specified types (dead_end chidren
* are always excluded).
*/
@@ -5303,7 +5224,7 @@ StartAutovacuumWorker(void)
* we'd better have something random in the field to prevent
* unfriendly people from sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
+ pg_strong_random(&MyCancelKey, sizeof(MyCancelKey));
bn->cancel_key = MyCancelKey;
/* Autovac workers are not dead_end and need a child slot */
@@ -5615,7 +5536,7 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
* have something random in the field to prevent unfriendly people from
* sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
+ pg_strong_random(&MyCancelKey, sizeof(MyCancelKey));
bn->cancel_key = MyCancelKey;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
diff --git a/src/include/port.h b/src/include/port.h
index b81fa4a..957dfe4 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -460,6 +460,9 @@ extern int pg_check_dir(const char *dir);
/* port/pgmkdirp.c */
extern int pg_mkdir_p(char *path, int omode);
+/* port/pgsrandom.c */
+extern void pg_strong_random(void *buf, size_t len);
+
/* port/pqsignal.c */
typedef void (*pqsigfunc) (int signo);
extern pqsigfunc pqsignal(int signo, pqsigfunc func);
diff --git a/src/port/Makefile b/src/port/Makefile
index bc9b63a..7150043 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -32,7 +32,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS = $(LIBOBJS) $(PG_CRC32C_OBJS) chklocale.o erand48.o inet_net_ntop.o \
noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o \
- pgstrcasecmp.o pqsignal.o \
+ pgsrandom.o pgstrcasecmp.o pqsignal.o \
qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o
# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
diff --git a/src/port/pgsrandom.c b/src/port/pgsrandom.c
new file mode 100644
index 0000000..9aee85e
--- /dev/null
+++ b/src/port/pgsrandom.c
@@ -0,0 +1,140 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgsrandom.c
+ * pg_strong_random() function to return a strong random number
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/port/pgsrandom.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef USE_SSL
+#include <openssl/rand.h>
+#endif
+
+static bool
+random_from_file(char *filename, void *buf, size_t len);
+
+#ifdef WIN32
+/*
+ * Cache a global crypto provider that gets freed when the process
+ * exits only, in case we're going to end up getting random numbers
+ * more than once.
+ */
+static HCRYPTPROV hProvider = 0;
+#endif
+
+/*
+ * Read a random number from a file. This is wanted as non-interruptible
+ * to give the user the insurance that a random number is returned.
+ */
+static bool
+random_from_file(char *filename, void *buf, size_t len)
+{
+ int f;
+ char *p = buf;
+ int res;
+
+ f = open(filename, O_RDONLY, 0);
+ if (f == -1)
+ return false;
+
+ while (len)
+ {
+ res = read(f, p, len);
+ if (res <= 0)
+ {
+ if (errno == EINTR)
+ continue;
+ close(f);
+ return false;
+ }
+
+ p += res;
+ len -= res;
+ }
+
+ close(f);
+ return true;
+}
+
+/*
+ * pg_strong_random
+ *
+ * Generate a strong random number based on a hierarchy of what is available,
+ * falling back to the next method if the previous one failed:
+ * - First try OpenSSL's RAND_bytes() if support is provided.
+ * - On Windows, use the in-core cryptographic functions.
+ * - Use /dev/urandom.
+ * - Use /dev/random.
+ */
+void
+pg_strong_random(void *buf, size_t len)
+{
+#ifdef USE_SSL
+ /*
+ * When built with OpenSSL, first try the random generation
+ * function from there.
+ */
+ if (RAND_bytes(buf, len) == 1)
+ return;
+#endif
+
+#ifdef WIN32
+ /*
+ * Windows has CryptoAPI for strong cryptographic numbers.
+ */
+ if (hProvider == 0)
+ {
+ if (!CryptAcquireContext(&hProvider,
+ NULL,
+ MS_DEF_PROV,
+ PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+ {
+ /*
+ * On failure, set back to 0 in case the value was for
+ * some reason modified.
+ */
+ hProvider = 0;
+ }
+ }
+
+ /* Re-check in case we just retrieved the provider */
+ if (hProvider != 0)
+ {
+ if (CryptGenRandom(hProvider, len, buf))
+ return;
+ }
+#endif
+
+ /*
+ * If there is no OpenSSL and no CryptoAPI (or they didn't work),
+ * then fall back on reading /dev/urandom or even /dev/random.
+ */
+ if (random_from_file("/dev/urandom", buf, len))
+ return;
+ if (random_from_file("/dev/random", buf, len))
+ return;
+
+ /* This should really be impossible */
+#ifndef FRONTEND
+ elog(FATAL, "could not generate random data.");
+#else
+ fprintf(stderr, _("could not generate random data."));
+ exit(1);
+#endif
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..eae8630 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -92,7 +92,7 @@ sub mkvcbuild
srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c
- mkdtemp.c qsort.c qsort_arg.c quotes.c system.c
+ mkdtemp.c pgsrandom.c qsort.c qsort_arg.c quotes.c system.c
sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c
win32env.c win32error.c win32security.c win32setlocale.c);
--
2.10.1
0002-Refactor-SHA2-functions-and-move-them-to-src-common.patchtext/plain; charset=US-ASCII; name=0002-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From 7b17ea10d821cb2d630287fda5397dddf51e2200 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 16:04:42 +0900
Subject: [PATCH 2/8] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 5 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 ++--
contrib/pgcrypto/sha2.h | 100 -----
src/common/Makefile | 6 +
{contrib/pgcrypto => src/common}/sha2.c | 722 ++++++++++++++++++--------------
src/common/sha2_openssl.c | 102 +++++
src/include/common/sha2.h | 115 +++++
src/tools/msvc/Mkvcbuild.pm | 20 +-
10 files changed, 698 insertions(+), 470 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (65%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..30619bf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..4085abb 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.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
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..ba74db6 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..e06f554 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..5ddfff8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 65%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9df..ea33fbc 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,4 +1,18 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
* FILE: sha2.c
@@ -33,15 +47,19 @@
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +76,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +146,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -249,15 +264,54 @@ static const uint64 sha512_initial_hash_value[8] = {
};
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +340,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +362,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +402,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +439,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +497,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +540,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +559,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +599,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +633,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +694,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +785,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +953,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1011,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1068,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000..91d0c39
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000..015a905
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index eae8630..7e762f8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -422,13 +431,14 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'fortuna.c', 'random.c',
- 'pgp-mpi-internal.c', 'imath.c');
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'random.c', 'pgp-mpi-internal.c',
+ 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.1
0003-Add-support-for-base64-encoding-decoding-without-whi.patchtext/plain; charset=US-ASCII; name=0003-Add-support-for-base64-encoding-decoding-without-whi.patchDownload
From 1cf835172c0f6c66a2d1c7d99c531a51a218043b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 17:06:27 +0900
Subject: [PATCH 3/8] Add support for base64 encoding/decoding without
whitespace to src/common/
Those routines are taken from the backend's encode.c, and adapted to remove
support for SCRAM-SHA-256, where base64 should not support whitespaces.
---
src/common/Makefile | 6 +-
src/common/base64.c | 194 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/base64.h | 19 +++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
4 files changed, 217 insertions(+), 4 deletions(-)
create mode 100644 src/common/base64.c
create mode 100644 src/include/common/base64.h
diff --git a/src/common/Makefile b/src/common/Makefile
index 5ddfff8..49e41cf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/base64.c b/src/common/base64.c
new file mode 100644
index 0000000..c37a21f
--- /dev/null
+++ b/src/common/base64.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * base64.c
+ * Set of encoding and decoding routines for base64 without support
+ * for whitespace. In case of failure, those routines return elog(ERROR)
+ * in the backend, and 0 in the frontend to let the caller handle any
+ * error.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/base64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/base64.h"
+
+/*
+ * 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,
+};
+
+unsigned
+pg_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++;
+
+ if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')
+ continue;
+
+ /* 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;
+}
+
+unsigned
+pg_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
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unexpected \"=\" while decoding base64 sequence")));
+#else
+ return 0;
+#endif
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid symbol \"%c\" while decoding base64 sequence",
+ (int) c)));
+#else
+ return 0;
+#endif
+ }
+ }
+ /* 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)
+ {
+#ifndef FRONTEND
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid base64 end sequence"),
+ errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
+#else
+ return 0;
+#endif
+ }
+
+ return p - dst;
+}
+
+
+unsigned
+pg_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);
+}
+
+unsigned
+pg_b64_dec_len(const char *src, unsigned srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/include/common/base64.h b/src/include/common/base64.h
new file mode 100644
index 0000000..4be3e24
--- /dev/null
+++ b/src/include/common/base64.h
@@ -0,0 +1,19 @@
+/*
+ * base64.h
+ * Encoding and decoding routines for base64 without whitespace
+ * support.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/base64.h
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+/* base 64 */
+unsigned pg_b64_encode(const char *src, unsigned len, char *dst);
+unsigned pg_b64_decode(const char *src, unsigned len, char *dst);
+unsigned pg_b64_enc_len(const char *src, unsigned srclen);
+unsigned pg_b64_dec_len(const char *src, unsigned srclen);
+
+#endif /* BASE64_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 7e762f8..6748186 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.10.1
0004-Refactor-decision-making-of-password-encryption-into.patchtext/plain; charset=US-ASCII; name=0004-Refactor-decision-making-of-password-encryption-into.patchDownload
From cca87dac91b0f635cc49fa895f9f688682e5f1c2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:51:15 +0900
Subject: [PATCH 4/8] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 84 ++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99..7f396c9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,48 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = pstrdup(password);
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = pstrdup(password);
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +125,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +437,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
+ pfree(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,18 +844,14 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ pfree(encrypted_passwd);
}
/* unset password */
--
2.10.1
0005-Create-generic-routine-to-fetch-password-and-valid-u.patchtext/plain; charset=US-ASCII; name=0005-Create-generic-routine-to-fetch-password-and-valid-u.patchDownload
From c6ba7cbe81253d848d0c38d1b8bf5c7a34dc1e43 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 14:40:15 +0900
Subject: [PATCH 5/8] Create generic routine to fetch password and valid until
values for a role
This is used now for the MD5-encrypted case and the plain text, and this
is going to be used as well for SCRAM-SHA256. That's as well useful for
any new password-based protocols.
---
src/backend/libpq/crypt.c | 59 +++++++++++++++++++++++++++++++++++------------
src/include/libpq/crypt.h | 2 ++
2 files changed, 46 insertions(+), 15 deletions(-)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c41c57 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,49 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char **logdetail);
--
2.10.1
0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchtext/plain; charset=US-ASCII; name=0006-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From 1a5d7e39ec69f18ead4a220d1b52b8c808b0e0ee Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:51:39 +0900
Subject: [PATCH 6/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 13 +-
doc/src/sgml/protocol.sgml | 147 +++++-
doc/src/sgml/ref/create_role.sgml | 14 +-
src/backend/commands/user.c | 18 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 713 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 132 +++++
src/backend/libpq/crypt.c | 1 +
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/postmaster/postmaster.c | 1 +
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 51 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 +
src/interfaces/libpq/.gitignore | 5 +
src/interfaces/libpq/Makefile | 17 +-
src/interfaces/libpq/fe-auth-scram.c | 414 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
32 files changed, 1959 insertions(+), 51 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e826c19..bccd98e 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1176,9 +1176,10 @@ include_dir 'conf.d'
password is to be encrypted. The default value is <literal>md5</>, which
stores the password as an MD5 hash. Setting this to <literal>plain</> stores
it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
- aliases for <literal>md5</> and <literal>plain</>, respectively.
- </para>
-
+ aliases for <literal>md5</> and <literal>plain</>, respectively. Setting
+ this parameter to <literal>scram</> will encrypt the password with
+ SCRAM-SHA-256.
+ </para>
</listitem>
</varlistentry>
@@ -1251,8 +1252,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3384e73..45d92ab 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>
@@ -2585,6 +2614,114 @@ 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>
@@ -4347,7 +4484,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 7f396c9..8b430ad 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = pstrdup(password);
else
{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..6f2967f
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,713 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(pg_b64_enc_len(salt, SCRAM_SALT_LEN) + 1);
+ encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+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 */
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(pg_b64_enc_len(nonce, SCRAM_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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(pg_b64_dec_len(value, strlen(value)));
+ if (pg_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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(pg_b64_enc_len((const char *) ServerSignature,
+ SCRAM_KEY_LEN) + 1);
+ siglen = pg_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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 0ba8530..47d398d 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,9 +30,11 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -204,6 +206,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -265,6 +273,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;
@@ -545,6 +554,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, "");
@@ -719,6 +732,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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);
+
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1c41c57..3c6701b 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -84,6 +84,7 @@ get_role_details(const char *role,
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
+
}
return STATUS_OK;
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 8bf69ea..f0eeaed 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2339,6 +2339,7 @@ ConnCreate(int serverFd)
* all backends would end up using the same salt...
*/
pg_strong_random(port->md5Salt, sizeof(port->md5Salt));
+ pg_strong_random(port->SASLSalt, sizeof(port->SASLSalt));
/*
* Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 622279b..39f2d1b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
static const struct config_enum_entry password_encryption_options[] = {
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 05b1373..393f0ac 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # md5 or plain
+#password_encryption = md5 # md5, scram or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf..971ddd5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5..1ff441a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption; /* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..3cc71e6
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha2.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN 10
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index c6bbfc2..7db809b 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..f08750f
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..c572e11 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pgsrandom.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..0f3c87b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
- thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pgsrandom.o pgstrcasecmp.o \
+ pqsignal.o thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgsrandom.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.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..5076589
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,414 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ *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");
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+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;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /* Generate message */
+ 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;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+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 (pg_b64_enc_len((char *) client_proof, SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ return NULL;
+
+ client_proof_len = pg_b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+ client_proof_base64[client_proof_len] = '\0';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+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(pg_b64_dec_len(encoded_salt, strlen(encoded_salt)));
+ if (state->salt == NULL)
+ return false;
+ state->saltlen = pg_b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+ if (state->saltlen != SCRAM_SALT_LEN)
+ 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;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ */
+static void
+generate_nonce(char *buf, int len)
+{
+ pg_strong_random(buf, len);
+ buf[len] = '\0';
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 6748186..39c9b3e 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha2_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha2.c');
}
my $libpqwalreceiver =
--
2.10.1
0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchtext/plain; charset=US-ASCII; name=0007-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 6f67d095e42ebae2c56d4afee23069a4f795fa5f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:57:07 +0900
Subject: [PATCH 7/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is.
---
doc/src/sgml/ref/alter_role.sgml | 2 ++
doc/src/sgml/ref/create_role.sgml | 19 +++++++++++
src/backend/commands/user.c | 72 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 ++++
4 files changed, 95 insertions(+), 5 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 93f0763..fa74466 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,24 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA256, or <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-encrypted format, then it is stored encrypted
+ as-is.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 8b430ad..88f48c4 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -179,7 +179,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -188,9 +189,41 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
parser_errposition(pstate, defel->location)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -310,8 +343,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -586,6 +617,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -594,9 +626,41 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -684,8 +748,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..233081b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.10.1
0008-Add-regression-tests-for-passwords.patchtext/plain; charset=US-ASCII; name=0008-Add-regression-tests-for-passwords.patchDownload
From e6886e5f18b89d4a2c94e6acd7e267bf555a5eb6 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Mon, 25 Jul 2016 16:55:49 +0900
Subject: [PATCH 8/8] Add regression tests for passwords
---
src/test/regress/expected/password.out | 101 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..a90f323
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,101 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: off, on, md5, scram, plain.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------
+ regress_passwd1 |
+ regress_passwd2 |
+ regress_passwd3 |
+ regress_passwd4 |
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.10.1
On 10/12/2016 11:11 AM, Michael Paquier wrote:
And so we are back on that, with a new set:
Great! I'm looking at this first one for now:
- 0001, introducing pg_strong_random() in src/port/ to have the
backend portion of SCRAM use it instead of random(). This patch is
from Magnus who has kindly sent is to me, so the authorship goes to
him. This patch replaces at the same time PostmasterRandom() with it,
this way once SCRAM gets integrated both the frontend and the backend
finish using the same facility. I think that's good for consistency.
Compared to the version Magnus has sent me, I have changed two things:
-- Reading from /dev/urandom and /dev/random is not influenced by
EINTR. read() handling is also made better in case of partial reads
from a given source.
-- Win32 Crypto routines use MS_DEF_PROV instead of NULL. I think
that's a better idea to not let the user the choice of the encryption
source here.
I spent some time whacking that around:
* Renamed the file to src/port/pg_strong_random.c "pgsrandom" makes me
think of srandom(), which this isn't.
* Changed pg_strong_random() to return false on error, and let the
callers handle errors. That's more error-prone than throwing an error in
the function itself, as it's an easy mistake to forget to check for the
return value, but we can't just "exit(1)" if called in the frontend. If
it gets called from libpq during authentication, as it will with SCRAM,
we want to close the connection and report an error, not exit the whole
user application. Likewise, in postmaster, if we fail to generate a
query cancel key when forking a backend, we don't want to FATAL and shut
down the whole postmaster.
* There used to be this:
/*
- * Precompute password salt values to use for this connection. It's
- * slightly annoying to do this long in advance of knowing whether we'll
- * need 'em or not, but we must do the random() calls before we fork, not
- * after. Else the postmaster's random sequence won't get advanced, and
- * all backends would end up using the same salt...
- */
- RandomSalt(port->md5Salt, sizeof(port->md5Salt));
But that whole business of advancing postmaster's random sequence is
moot now. So I moved the generation of md5 salt from postmaster to where
MD5 authentication is performed.
* This comment in postmaster.c was wrong:
@@ -581,7 +571,7 @@ PostmasterMain(int argc, char *argv[]) * Note: the seed is pretty predictable from externally-visible facts such * as postmaster start time, so avoid using random() for security-critical * random values during postmaster startup. At the time of first - * connection, PostmasterRandom will select a hopefully-more-random seed. + * connection, pg_strong_random will select a hopefully-more-random seed. */ srandom((unsigned int) (MyProcPid ^ MyStartTime));
We don't use pg_strong_random() for that, the same PID+timestamp method
is still used as before. Adjusted the comment to reflect reality.
* Added "#include <Wincrypt.h>", for the CryptAcquireContext and
CryptGenRandom functions? It compiled OK without that, so I guess it got
pulled in via some other header file, but seems more clear and
future-proof to #include it directly.
* random comment kibitzing (no pun intended).
This is pretty much ready for commit now, IMO, but please do review one
more time. And I do have some small questions still:
* We now open and close /dev/(u)random on every pg_strong_random() call.
Should we be worried about performance of that?
* Now that we don't call random() in postmaster anymore, is there any
point in calling srandom() there (i.e. where the above incorrect comment
was)? Should we remove it? random() might be used by pre-loaded
extensions, though. (Hopefully not for cryptographic purposes.)
* Should we backport this? Sorry if we discussed that already, but I
don't remember.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/14/2016 03:08 PM, Heikki Linnakangas wrote:
I spent some time whacking that around:
Sigh, forgot attachment. Here you go.
- Heikki
Attachments:
0001-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchtext/x-diff; name=0001-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchDownload
From 4b3000df3dc71ad41018a6606c92bc4a0adeb8f5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 14 Oct 2016 14:58:44 +0300
Subject: [PATCH 1/1] Replace PostmasterRandom() with a stronger way of
generating randomness.
This adds a new routine, pg_strong_random() for generating random bytes,
for use in both frontend and backend. At the moment, it's only used in
the backend, but the upcoming SCRAM authentication patches need strong
random numbers in libpq as well.
pg_strong_random() can acquire strong random numbers from a number of
sources, depending on what's available:
- OpenSSL RAND_bytes(), if built with OpenSSL
- On Windows, the native cryptographic functions are used
- /dev/urandom
- /dev/random
Original patch by Magnus Hagander, with further tweaks by Michael Paquier
and me.
Discussion: <CAB7nPqRy3krN8quR9XujMVVHYtXJ0_60nqgVc6oUk8ygyVkZsA@mail.gmail.com>
---
src/backend/libpq/auth.c | 21 ++++-
src/backend/postmaster/postmaster.c | 153 ++++++++++--------------------------
src/include/port.h | 3 +
src/port/Makefile | 2 +-
src/port/pg_strong_random.c | 148 ++++++++++++++++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
6 files changed, 212 insertions(+), 117 deletions(-)
create mode 100644 src/port/pg_strong_random.c
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 0ba8530..e617ac6 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -45,6 +45,12 @@ 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);
+/*----------------------------------------------------------------
+ * MD5 authentication
+ *----------------------------------------------------------------
+ */
+static int CheckMD5Auth(Port *port, char **logdetail);
+
/*----------------------------------------------------------------
* Ident authentication
@@ -535,9 +541,7 @@ ClientAuthentication(Port *port)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
- /* include the salt to use for computing the response */
- sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
- status = recv_and_check_password_packet(port, &logdetail);
+ status = CheckMD5Auth(port, &logdetail);
break;
case uaPassword:
@@ -696,6 +700,17 @@ recv_password_packet(Port *port)
*----------------------------------------------------------------
*/
+static int
+CheckMD5Auth(Port *port, char **logdetail)
+{
+ /* include the salt to use for computing the response */
+ pg_strong_random(port->md5Salt, sizeof(port->md5Salt));
+
+ sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
+ return recv_and_check_password_packet(port, logdetail);
+}
+
+
/*
* Called when we have sent an authorization request for a password.
* Get the response and check it.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2d43506..d646692 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -358,14 +358,6 @@ static volatile bool avlauncher_needs_signal = false;
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;
-/*
- * State for assigning random salts and cancel keys.
- * Also, the global MyCancelKey passes the cancel key assigned to a given
- * backend from the postmaster to that backend (via fork).
- */
-static unsigned int random_seed = 0;
-static struct timeval random_start_time;
-
#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif
@@ -403,8 +395,6 @@ static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
-static long PostmasterRandom(void);
-static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -579,9 +569,11 @@ PostmasterMain(int argc, char *argv[])
* Initialize random(3) so we don't get the same values in every run.
*
* Note: the seed is pretty predictable from externally-visible facts such
- * as postmaster start time, so avoid using random() for security-critical
- * random values during postmaster startup. At the time of first
- * connection, PostmasterRandom will select a hopefully-more-random seed.
+ * as postmaster start time, so don't use random() for security-critical
+ * random values (use pg_strong_random() instead). At the time of first
+ * connection, BackendRun() will select a somewhat-more-random seed, based
+ * on the PID and session start timestamp, but that is still not suitable
+ * for security-critical values.
*/
srandom((unsigned int) (MyProcPid ^ MyStartTime));
@@ -1292,8 +1284,6 @@ PostmasterMain(int argc, char *argv[])
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
- /* PostmasterRandom wants its own copy */
- gettimeofday(&random_start_time, NULL);
/*
* We're ready to rock and roll...
@@ -2344,15 +2334,6 @@ ConnCreate(int serverFd)
}
/*
- * Precompute password salt values to use for this connection. It's
- * slightly annoying to do this long in advance of knowing whether we'll
- * need 'em or not, but we must do the random() calls before we fork, not
- * after. Else the postmaster's random sequence won't get advanced, and
- * all backends would end up using the same salt...
- */
- RandomSalt(port->md5Salt, sizeof(port->md5Salt));
-
- /*
* Allocate GSSAPI specific state struct
*/
#ifndef EXEC_BACKEND
@@ -3904,7 +3885,12 @@ BackendStartup(Port *port)
* backend will have its own copy in the forked-off process' value of
* MyCancelKey, so that it can transmit the key to the frontend.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
+ return STATUS_ERROR;
+ }
bn->cancel_key = MyCancelKey;
/* Pass down canAcceptConnections state */
@@ -4212,13 +4198,6 @@ BackendRun(Port *port)
int usecs;
int i;
- /*
- * Don't want backend to be able to see the postmaster random number
- * generator state. We have to clobber the static random_seed *and* start
- * a new random sequence in the random() library function.
- */
- random_seed = 0;
- random_start_time.tv_usec = 0;
/* slightly hacky way to convert timestamptz into integers */
TimestampDifference(0, port->SessionStartTime, &secs, &usecs);
srandom((unsigned int) (MyProcPid ^ (usecs << 12) ^ secs));
@@ -5067,66 +5046,6 @@ StartupPacketTimeoutHandler(void)
/*
- * RandomSalt
- */
-static void
-RandomSalt(char *salt, int len)
-{
- long rand;
- int i;
-
- /*
- * We use % 255, sacrificing one possible byte value, so as to ensure that
- * all bits of the random() value participate in the result. While at it,
- * add one to avoid generating any null bytes.
- */
- for (i = 0; i < len; i++)
- {
- rand = PostmasterRandom();
- salt[i] = (rand % 255) + 1;
- }
-}
-
-/*
- * PostmasterRandom
- *
- * Caution: use this only for values needed during connection-request
- * processing. Otherwise, the intended property of having an unpredictable
- * delay between random_start_time and random_stop_time will be broken.
- */
-static long
-PostmasterRandom(void)
-{
- /*
- * Select a random seed at the time of first receiving a request.
- */
- if (random_seed == 0)
- {
- do
- {
- struct timeval random_stop_time;
-
- gettimeofday(&random_stop_time, NULL);
-
- /*
- * We are not sure how much precision is in tv_usec, so we swap
- * the high and low 16 bits of 'random_stop_time' and XOR them
- * with 'random_start_time'. On the off chance that the result is
- * 0, we loop until it isn't.
- */
- random_seed = random_start_time.tv_usec ^
- ((random_stop_time.tv_usec << 16) |
- ((random_stop_time.tv_usec >> 16) & 0xffff));
- }
- while (random_seed == 0);
-
- srandom(random_seed);
- }
-
- return random();
-}
-
-/*
* Count up number of child processes of specified types (dead_end chidren
* are always excluded).
*/
@@ -5303,31 +5222,37 @@ StartAutovacuumWorker(void)
* we'd better have something random in the field to prevent
* unfriendly people from sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
- bn->cancel_key = MyCancelKey;
+ if (pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ bn->cancel_key = MyCancelKey;
- /* Autovac workers are not dead_end and need a child slot */
- bn->dead_end = false;
- bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
- bn->bgworker_notify = false;
+ /* Autovac workers are not dead_end and need a child slot */
+ bn->dead_end = false;
+ bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
+ bn->bgworker_notify = false;
- bn->pid = StartAutoVacWorker();
- if (bn->pid > 0)
- {
- bn->bkend_type = BACKEND_TYPE_AUTOVAC;
- dlist_push_head(&BackendList, &bn->elem);
+ bn->pid = StartAutoVacWorker();
+ if (bn->pid > 0)
+ {
+ bn->bkend_type = BACKEND_TYPE_AUTOVAC;
+ dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
- ShmemBackendArrayAdd(bn);
+ ShmemBackendArrayAdd(bn);
#endif
- /* all OK */
- return;
+ /* all OK */
+ return;
+ }
+
+ /*
+ * fork failed, fall through to report -- actual error message was
+ * logged by StartAutoVacWorker
+ */
+ (void) ReleasePostmasterChildSlot(bn->child_slot);
}
+ else
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
- /*
- * fork failed, fall through to report -- actual error message was
- * logged by StartAutoVacWorker
- */
- (void) ReleasePostmasterChildSlot(bn->child_slot);
free(bn);
}
else
@@ -5615,7 +5540,11 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
* have something random in the field to prevent unfriendly people from
* sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ rw->rw_crashed_at = GetCurrentTimestamp();
+ return false;
+ }
bn->cancel_key = MyCancelKey;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
diff --git a/src/include/port.h b/src/include/port.h
index b81fa4a..4bb9fee 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -454,6 +454,9 @@ extern int pg_codepage_to_encoding(UINT cp);
extern char *inet_net_ntop(int af, const void *src, int bits,
char *dst, size_t size);
+/* port/pg_strong_random.c */
+extern bool pg_strong_random(void *buf, size_t len);
+
/* port/pgcheckdir.c */
extern int pg_check_dir(const char *dir);
diff --git a/src/port/Makefile b/src/port/Makefile
index bc9b63a..d34f409 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -32,7 +32,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS = $(LIBOBJS) $(PG_CRC32C_OBJS) chklocale.o erand48.o inet_net_ntop.o \
noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o \
- pgstrcasecmp.o pqsignal.o \
+ pg_strong_random.o pgstrcasecmp.o pqsignal.o \
qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o
# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
new file mode 100644
index 0000000..a404111
--- /dev/null
+++ b/src/port/pg_strong_random.c
@@ -0,0 +1,148 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_strong_random.c
+ * pg_strong_random() function to return a strong random number
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/port/pg_strong_random.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef USE_SSL
+#include <openssl/rand.h>
+#endif
+#ifdef WIN32
+#include <Wincrypt.h>
+#endif
+
+static bool random_from_file(char *filename, void *buf, size_t len);
+
+#ifdef WIN32
+/*
+ * Cache a global crypto provider that only gets freed when the process
+ * exits, in case we need random numbers more than once.
+ */
+static HCRYPTPROV hProvider = 0;
+#endif
+
+/*
+ * Read (random) bytes from a file.
+ */
+static bool
+random_from_file(char *filename, void *buf, size_t len)
+{
+ int f;
+ char *p = buf;
+ ssize_t res;
+
+ f = open(filename, O_RDONLY, 0);
+ if (f == -1)
+ return false;
+
+ while (len)
+ {
+ res = read(f, p, len);
+ if (res <= 0)
+ {
+ if (errno == EINTR)
+ continue; /* interrupted by signal, just retry */
+
+ close(f);
+ return false;
+ }
+
+ p += res;
+ len -= res;
+ }
+
+ close(f);
+ return true;
+}
+
+/*
+ * pg_strong_random
+ *
+ * Generate requested number of random bytes. The bytes are
+ * cryptographically strong random, suitable for use e.g. in key
+ * generation.
+ *
+ * The bytes can be acquired from a number of sources, depending
+ * on what's available. We try the following, in this order:
+ *
+ * 1. OpenSSL's RAND_bytes()
+ * 2. Windows' CryptGenRandom() function
+ * 3. /dev/urandom
+ * 4. /dev/random
+ *
+ * Returns true on success, and false if none of the sources
+ * were available. NB: It is important to check the return value!
+ * Proceeding with key generation when no random data was available
+ * would lead to predictable keys and security issues.
+ */
+bool
+pg_strong_random(void *buf, size_t len)
+{
+#ifdef USE_SSL
+
+ /*
+ * When built with OpenSSL, first try the random generation function from
+ * there.
+ */
+ if (RAND_bytes(buf, len) == 1)
+ return true;
+#endif
+
+#ifdef WIN32
+
+ /*
+ * Windows has CryptoAPI for strong cryptographic numbers.
+ */
+ if (hProvider == 0)
+ {
+ if (!CryptAcquireContext(&hProvider,
+ NULL,
+ MS_DEF_PROV,
+ PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+ {
+ /*
+ * On failure, set back to 0 in case the value was for some reason
+ * modified.
+ */
+ hProvider = 0;
+ }
+ }
+
+ /* Re-check in case we just retrieved the provider */
+ if (hProvider != 0)
+ {
+ if (CryptGenRandom(hProvider, len, buf))
+ return true;
+ }
+#endif
+
+ /*
+ * If there is no OpenSSL and no CryptoAPI (or they didn't work), then
+ * fall back on reading /dev/urandom or even /dev/random.
+ */
+ if (random_from_file("/dev/urandom", buf, len))
+ return true;
+ if (random_from_file("/dev/random", buf, len))
+ return true;
+
+ /* None of the sources were available. */
+ return false;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..4d4821a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -92,7 +92,7 @@ sub mkvcbuild
srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c
- mkdtemp.c qsort.c qsort_arg.c quotes.c system.c
+ mkdtemp.c pg_strong_random.c qsort.c qsort_arg.c quotes.c system.c
sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c
win32env.c win32error.c win32security.c win32setlocale.c);
--
2.9.3
On Fri, Oct 14, 2016 at 9:08 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 10/12/2016 11:11 AM, Michael Paquier wrote:
* Changed pg_strong_random() to return false on error, and let the callers
handle errors. That's more error-prone than throwing an error in the
function itself, as it's an easy mistake to forget to check for the return
value, but we can't just "exit(1)" if called in the frontend. If it gets
called from libpq during authentication, as it will with SCRAM, we want to
close the connection and report an error, not exit the whole user
application. Likewise, in postmaster, if we fail to generate a query cancel
key when forking a backend, we don't want to FATAL and shut down the whole
postmaster.
Okay for this one. Indeed that's a cleaner interface.
This is pretty much ready for commit now, IMO, but please do review one more
time.
OK, I had an extra lookup and the patch looks in pretty good shape
seen from here.
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ rw->rw_crashed_at = GetCurrentTimestamp();
+ return false;
+ }
It would be nice to LOG an entry here for bgworkers.
+ /*
+ * fork failed, fall through to report -- actual error
message was
+ * logged by StartAutoVacWorker
+ */
Since you created a new block, the first line gets longer than 80 characters.
* We now open and close /dev/(u)random on every pg_strong_random() call.
Should we be worried about performance of that?
Actually I have hacked up a small program that can be used to compare
using /dev/urandom with random() calls (this emulates RandomSalt), and
opening/closing /dev/urandom causes a performance hit, but the
difference becomes noticeable with loop calls higher than 10k on my
Linux laptop. I recall that /dev/urandom is quite slow on Linux
compared to other platforms still... So for a single call per
connection attempt we won't actually notice it much. I am just
attaching that if you want to play with it, and you can use it as
follows:
./calc [dev|random] nbytes loops
That's really a quick hack but it does the job if you worry about the
performance.
* Now that we don't call random() in postmaster anymore, is there any point
in calling srandom() there (i.e. where the above incorrect comment was)?
Should we remove it? random() might be used by pre-loaded extensions,
though. (Hopefully not for cryptographic purposes.)
That's the business of the maintainers such modules, so my heart is
telling me to rip it off, but my mind tells me that there is no point
in making them unhappy either if they rely on it. I'd trust my mind on
this one, other opinions are welcome.
* Should we backport this? Sorry if we discussed that already, but I don't
remember.
I think that we discussed quickly the point at last PGCon during the
SCRAM-committee-unofficial meeting, and that we talked about doing
that only for HEAD.
--
Michael
Attachments:
On 10/15/2016 04:26 PM, Michael Paquier wrote:
* Now that we don't call random() in postmaster anymore, is there any point
in calling srandom() there (i.e. where the above incorrect comment was)?
Should we remove it? random() might be used by pre-loaded extensions,
though. (Hopefully not for cryptographic purposes.)That's the business of the maintainers such modules, so my heart is
telling me to rip it off, but my mind tells me that there is no point
in making them unhappy either if they rely on it. I'd trust my mind on
this one, other opinions are welcome.
I kept it for now. Doesn't do any harm either, even if it's unnecessary.
* Should we backport this? Sorry if we discussed that already, but I don't
remember.I think that we discussed quickly the point at last PGCon during the
SCRAM-committee-unofficial meeting, and that we talked about doing
that only for HEAD.
Ok, committed to HEAD.
Thanks!
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 17, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 10/15/2016 04:26 PM, Michael Paquier wrote:
* Now that we don't call random() in postmaster anymore, is there any
point
in calling srandom() there (i.e. where the above incorrect comment was)?
Should we remove it? random() might be used by pre-loaded extensions,
though. (Hopefully not for cryptographic purposes.)That's the business of the maintainers such modules, so my heart is
telling me to rip it off, but my mind tells me that there is no point
in making them unhappy either if they rely on it. I'd trust my mind on
this one, other opinions are welcome.I kept it for now. Doesn't do any harm either, even if it's unnecessary.
* Should we backport this? Sorry if we discussed that already, but I
don't
remember.I think that we discussed quickly the point at last PGCon during the
SCRAM-committee-unofficial meeting, and that we talked about doing
that only for HEAD.Ok, committed to HEAD.
You removed the part of pgcrypto in charge of randomness, nice move. I
was wondering about how to do with the perfc and the unix_std at some
point, and ripping them off as you did is fine for me.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/17/2016 12:18 PM, Michael Paquier wrote:
You removed the part of pgcrypto in charge of randomness, nice move. I
was wondering about how to do with the perfc and the unix_std at some
point, and ripping them off as you did is fine for me.
Yeah. I didn't understand the need for the perfc stuff. Are there
Windows systems that don't have the Crypto APIs? I doubt it, but the
buildfarm will tell us in a moment if there are.
And if we don't have a good source of randomness like /dev/random, I
think it's better to fail, than try to collect entropy ourselves (which
is what unix_std did). If there's a platform where that doesn't work,
someone will hopefully send us a patch, rather than silently fall back
to an iffy implementation.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/17/2016 12:27 PM, Heikki Linnakangas wrote:
On 10/17/2016 12:18 PM, Michael Paquier wrote:
You removed the part of pgcrypto in charge of randomness, nice move. I
was wondering about how to do with the perfc and the unix_std at some
point, and ripping them off as you did is fine for me.Yeah. I didn't understand the need for the perfc stuff. Are there
Windows systems that don't have the Crypto APIs? I doubt it, but the
buildfarm will tell us in a moment if there are.And if we don't have a good source of randomness like /dev/random, I
think it's better to fail, than try to collect entropy ourselves (which
is what unix_std did). If there's a platform where that doesn't work,
someone will hopefully send us a patch, rather than silently fall back
to an iffy implementation.
Looks like Tom's old HP-UX box, pademelon, is not happy about this. Does
(that version of) HP-UX not have /dev/urandom?
I think we're going to need a bit more logging if no randomness source
is available. What we have now is just "could not generate random query
cancel key", which isn't very informative. Perhaps we should also call
pg_strong_random() once at postmaster startup, to check that it works,
instead of starting up but not accepting any connections.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Oct 17, 2016 at 6:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Oct 17, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Ok, committed to HEAD.
Attached is a rebased patch set for SCRAM, with the following things:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.
- 0002, creating a set of base64 routines without whitespace handling.
Previous version sent had a bug: I missed the point that the backend
version of base64 was adding a newline every 76 characters. So this is
removed to make the encoding not using any whitespace. Also the
routines are reworked so as they return -1 in the event of an error
instead of generating an elog by themselves. That will be useful for
SCRAM that needs to do its own error handling with the e= messages
from the server. I think that's cleaner this way. Encoding does not
have any error code paths, but decoding has, so one possible
improvement would be to add in arguments a string to store an error
message to make things easier for callers to debug.
- 0003 does some refactoring regarding encrypted passwords in user.c.
I am pretty happy with this one as well.
- 0004 adds the extension for CREATE ROLE .. PASSWORD foo USING
protocol. I found a bug in this one when using CREATE|ALTER ROLE ..
PASSWORD missing to update the given password correctly using
password_encryption. This one I am happy with it. Even if it depends
on 0005 in this patch set it is possible to make it independent of it
to introduce the grammar just for 'plain' and 'md5' first. In previous
sets it was located after SCRAM, but it looks cleaner to get that
first. I don't think I am going to change that much more now.
- 0005 adds support for SCRAM-SHA-256. There is still some work to do
here, particularly the error handling that requires to be extended
with the e= messages sent back to the client before moving to a
PG-like error code path. Those need to be set in the context of the
SASL message exchange. I noticed as well that this is missing a hell
lot of error checks when building the exchange messages, and when
doing encoding and decoding of base64 strings. I'll address that in
the next couple of days.
- 0006 is the basic set of regression tests for passwords. Nothing new
here, they are useful as basic tests when checking the patch. I don't
think that they are worth having committed at the end.
--
Michael
Attachments:
0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchapplication/x-download; name=0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From c3807c86533a6017c9eb83682a7e5eb92f9867f9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 16:04:42 +0900
Subject: [PATCH 1/6] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 5 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 ++--
contrib/pgcrypto/sha2.h | 100 -----
src/common/Makefile | 6 +
{contrib/pgcrypto => src/common}/sha2.c | 722 ++++++++++++++++++--------------
src/common/sha2_openssl.c | 102 +++++
src/include/common/sha2.h | 115 +++++
src/tools/msvc/Mkvcbuild.pm | 19 +-
10 files changed, 697 insertions(+), 470 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (65%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..30619bf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 76c2f1a..17a528e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..ba74db6 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..e06f554 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..5ddfff8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 65%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9df..ea33fbc 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,4 +1,18 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
* FILE: sha2.c
@@ -33,15 +47,19 @@
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +76,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +146,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -249,15 +264,54 @@ static const uint64 sha512_initial_hash_value[8] = {
};
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +340,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +362,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +402,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +439,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +497,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +540,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +559,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +599,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +633,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +694,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +785,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +953,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1011,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1068,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000..91d0c39
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000..015a905
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index e6c4aef..1b2c8e4 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -422,13 +431,13 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'fortuna.c', 'pgp-mpi-internal.c',
- 'imath.c');
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.1
0002-Add-encoding-routines-for-base64-without-whitespace-.patchapplication/x-download; name=0002-Add-encoding-routines-for-base64-without-whitespace-.patchDownload
From 030fadbdf1e1917a6c304485a9cdea6f195dc7b1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 15:28:45 +0900
Subject: [PATCH 2/6] Add encoding routines for base64 without whitespace in
src/common/
Those routines are taken from the backend's encode.c, and adapted for
SCRAM-SHA-256, where base64 should not support whitespaces as per RFC5802.
---
src/common/Makefile | 6 +-
src/common/base64.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/base64.h | 19 +++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
4 files changed, 224 insertions(+), 4 deletions(-)
create mode 100644 src/common/base64.c
create mode 100644 src/include/common/base64.h
diff --git a/src/common/Makefile b/src/common/Makefile
index 5ddfff8..49e41cf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/base64.c b/src/common/base64.c
new file mode 100644
index 0000000..0c9eba4
--- /dev/null
+++ b/src/common/base64.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * base64.c
+ * Set of encoding and decoding routines for base64 without support
+ * for whitespace. In case of failure, those routines return -1 in case
+ * of error to let the caller do any error handling.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/base64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/base64.h"
+
+/*
+ * 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,
+};
+
+/*
+ * pg_b64_encode
+ *
+ * Encode into base64 the given string. Returns the length of the encoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_encode(const char *src, int len, char *dst)
+{
+ char *p;
+ 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 (pos != 2)
+ {
+ *p++ = _base64[(buf >> 18) & 0x3f];
+ *p++ = _base64[(buf >> 12) & 0x3f];
+ *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+ *p++ = '=';
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_decode
+ *
+ * Decode the given base64 string. Returns the length of the decoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_decode(const char *src, int 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++;
+
+ /* Leave if a whitespace is found */
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ return -1;
+
+ if (c == '=')
+ {
+ /* end sequence */
+ if (!end)
+ {
+ if (pos == 2)
+ end = 1;
+ else if (pos == 3)
+ end = 2;
+ else
+ {
+ /*
+ * Unexpected "=" character found while decoding base64
+ * sequence.
+ */
+ return -1;
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+ /* invalid symbol found */
+ return -1;
+ }
+ }
+ /* 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)
+ {
+ /*
+ * base64 end sequence is invalid. Input data is missing padding,
+ * is truncated or is otherwise corrupted.
+ */
+ return -1;
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_enc_len
+ *
+ * Returns to caller the length of the string if it were encoded with
+ * base64 based on the length provided by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual encoding.
+ */
+int
+pg_b64_enc_len(int srclen)
+{
+ /* 3 bytes will be converted to 4 */
+ return (srclen + 2) * 4 / 3;
+}
+
+/*
+ * pg_b64_dec_len
+ *
+ * Returns to caller the length of the string if it were to be decoded
+ * with base64, based on the length given by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual decoding.
+ */
+int
+pg_b64_dec_len(int srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/include/common/base64.h b/src/include/common/base64.h
new file mode 100644
index 0000000..8ad8eb6
--- /dev/null
+++ b/src/include/common/base64.h
@@ -0,0 +1,19 @@
+/*
+ * base64.h
+ * Encoding and decoding routines for base64 without whitespace
+ * support.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/base64.h
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+/* base 64 */
+int pg_b64_encode(const char *src, int len, char *dst);
+int pg_b64_decode(const char *src, int len, char *dst);
+int pg_b64_enc_len(int srclen);
+int pg_b64_dec_len(int srclen);
+
+#endif /* BASE64_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 1b2c8e4..c5b737a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.10.1
0003-Refactor-decision-making-of-password-encryption-into.patchapplication/x-download; name=0003-Refactor-decision-making-of-password-encryption-into.patchDownload
From 6bbcd2224b774bd4b81f5c3800e5512ba656731d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:51:15 +0900
Subject: [PATCH 3/6] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 84 ++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99..7f396c9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,48 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = pstrdup(password);
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = pstrdup(password);
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +125,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +437,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
+ pfree(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,18 +844,14 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ pfree(encrypted_passwd);
}
/* unset password */
--
2.10.1
0004-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-download; name=0004-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 5afe85f0acf98ee5620bab88b4ca57dff8f77b89 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 16:03:13 +0900
Subject: [PATCH 4/6] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is. This extension is useful to support future
protocols, particularly SCRAM-SHA-256.
---
doc/src/sgml/ref/alter_role.sgml | 2 +
doc/src/sgml/ref/create_role.sgml | 18 ++++++++
src/backend/commands/user.c | 90 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 +++
4 files changed, 110 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..d172ff1 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,23 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, and <literal>plain</> to use an
+ unencrypted password. If the password string is already in
+ MD5-encrypted format, then it is stored encrypted even if
+ <literal>plain</> is specified.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 7f396c9..25f9431 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -175,7 +175,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -183,10 +184,49 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -306,8 +346,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -582,6 +620,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -589,10 +628,49 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -680,8 +758,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..233081b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.10.1
0005-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-download; name=0005-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From fa813cb65425253cccf62275e066ce760259566c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 16:25:09 +0900
Subject: [PATCH 5/6] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 13 +-
doc/src/sgml/protocol.sgml | 147 +++++-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/commands/user.c | 49 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 712 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 148 ++++++
src/backend/libpq/crypt.c | 60 ++-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 +++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 51 ++
src/include/libpq/auth.h | 5 +
src/include/libpq/crypt.h | 2 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 27 +
src/interfaces/libpq/.gitignore | 5 +
src/interfaces/libpq/Makefile | 17 +-
src/interfaces/libpq/fe-auth-scram.c | 424 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 106 ++++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
32 files changed, 2063 insertions(+), 72 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 99ff9f5..5ed9ef5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1176,9 +1176,10 @@ include_dir 'conf.d'
password is to be encrypted. The default value is <literal>md5</>, which
stores the password as an MD5 hash. Setting this to <literal>plain</> stores
it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
- aliases for <literal>md5</> and <literal>plain</>, respectively.
- </para>
-
+ aliases for <literal>md5</> and <literal>plain</>, respectively. Setting
+ this parameter to <literal>scram</> will encrypt the password with
+ SCRAM-SHA-256.
+ </para>
</listitem>
</varlistentry>
@@ -1251,8 +1252,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3384e73..45d92ab 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>
@@ -2585,6 +2614,114 @@ 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>
@@ -4347,7 +4484,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index d172ff1..e19bc8c 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -229,16 +229,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
@@ -254,10 +254,11 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
attribute, but you can nonetheless define one for roles without it.)
If you do not plan to use password authentication you can omit this
option. The protocols supported are <literal>md5</> to enforce
- a password to be MD5-encrypted, and <literal>plain</> to use an
- unencrypted password. If the password string is already in
- MD5-encrypted format, then it is stored encrypted even if
- <literal>plain</> is specified.
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA-256, and <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-SHA-256 format, then it is stored encrypted
+ even if another protocol is specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 25f9431..7e03ac4 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = pstrdup(password);
else
{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
@@ -221,6 +225,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(protocol, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -450,11 +456,22 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
/*
* Build a tuple to insert
@@ -663,6 +680,8 @@ AlterRole(AlterRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(protocol, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -859,11 +878,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
+
/*
* Build an updated tuple, perusing the information just obtained
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..582e88e
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,712 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ 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 bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+
+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 *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+
+ if (!parse_scram_verifier(verifier, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ elog(ERROR, "invalid SCRAM verifier");
+ return NULL;
+ }
+
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ return state;
+}
+
+/*
+ * 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(DEBUG4, "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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
+ encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+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 */
+ 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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(nonce, SCRAM_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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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(pg_b64_dec_len(strlen(value)));
+ if (pg_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';
+
+ /* XXX: check channel_binding field if support is added */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ siglen = pg_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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 44b2212..28a6a0b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,9 +30,11 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/timestamp.h"
/*----------------------------------------------------------------
@@ -210,6 +212,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -271,6 +279,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;
@@ -549,6 +558,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, "");
@@ -738,6 +751,141 @@ recv_and_check_password_packet(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ TimestampTz vuntil = 0;
+ char *output = NULL;
+ int outputlen = 0;
+ int result;
+ char *passwd;
+ bool vuntil_null;
+
+ /*
+ * 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /* compute the salt to use for computing responses */
+ if (!pg_strong_random(port->SASLSalt, sizeof(port->SASLSalt)))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return STATUS_ERROR;
+ }
+
+ /* fetch details about role needed for password checks */
+ /*
+ * FIXME: error handling needs to happen under the SASL exchange here.
+ * SASL has its own error messages for incorrect users, missing
+ * passwords, etc.
+ */
+ if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
+ if (!is_scram_verifier(passwd))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ scram_opaq = scram_init(port->user_name, passwd);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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);
+
+ /*
+ * FIXME: Issue e= message in case of error before returning error status
+ * back to the client.
+ */
+
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ *logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+ port->user_name);
+ return STATUS_ERROR;
+ }
+
+ /* exchange is completed, check if this is past validuntil */
+ if (vuntil_null)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ port->user_name);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..3c6701b 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and return STATUS_OK or STATUS_ERROR. In the case of an error,
+ * optionally store a palloc'd string at *logdetail that will be sent
+ * to the postmaster log (but not the client).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -65,22 +67,50 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
return STATUS_ERROR; /* empty password */
+
}
+ return STATUS_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != STATUS_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +182,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 65660c1..2cc4747 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
static const struct config_enum_entry password_encryption_options[] = {
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 159ada3..e4f0bd3 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # md5 or plain
+#password_encryption = md5 # md5, scram or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf..971ddd5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5..1ff441a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption; /* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..674eeb5
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha2.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN SCRAM_NONCE_LEN
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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 3cd06b7..5a02534 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 5725bb4..856c451 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,8 @@
#include "libpq/libpq-be.h"
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, 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 dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 9648422..bbcb2f7 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..f08750f
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..2224ada 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pg_strong_random.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..460b0a1 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
- thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
+ pgstrcasecmp.o pqsignal.o thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.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..b4a2885
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,424 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+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 bool generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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 */
+ /* FIXME: Need to handle failure case where this returns NULL */
+ *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 */
+ /* FIXME: Need to handle failure case */
+ 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 */
+ /* FIXME: Need to handle failure case */
+ read_server_final_message(state, input, errorMessage);
+ *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");
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+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;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+ return NULL;
+
+ /* Generate message */
+ 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;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+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);
+ /* FIXME: Missing error handling on pg_b64_enc_len here */
+ if (pg_b64_enc_len(SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ return NULL;
+
+ client_proof_len = pg_b64_encode((char *) client_proof, SCRAM_KEY_LEN, client_proof_base64);
+ client_proof_base64[client_proof_len] = '\0';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+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(pg_b64_dec_len(strlen(encoded_salt)));
+ if (state->salt == NULL)
+ return false;
+ state->saltlen = pg_b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+ if (state->saltlen != SCRAM_SALT_LEN)
+ 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;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ return false;
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL)
+ return false;
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage, "invalid ServerProof");
+ return false;
+ }
+
+ if (*input != '\0')
+ return false;
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ * Returns true of nonce has been succesfully generated, and false
+ * otherwise.
+ */
+static bool
+generate_nonce(char *buf, int len)
+{
+ if (!pg_strong_random(buf, len))
+ return false;
+
+ buf[len] = '\0';
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c5b737a..0f19d1c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha2_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha2.c');
}
my $libpqwalreceiver =
--
2.10.1
0006-Add-regression-tests-for-passwords.patchapplication/x-download; name=0006-Add-regression-tests-for-passwords.patchDownload
From 424f042448e46e463257212effdc9190b7d5b677 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 14:13:30 +0900
Subject: [PATCH 6/6] Add regression tests for passwords
---
src/test/regress/expected/password.out | 102 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..0f2f28d
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,102 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: plain, md5, scram, off, on.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | role_pwd1
+ regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
+ regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
+ regress_passwd4 | AAAAAAAAAAAAAA==:4096:c32d0b9681e3d827fe5b5287c0ba9c9e276fe69e611dcc93cddd41f122b82e5b:51c60a9394db319302dc2727e2b8cb6c463a507312dbbf53a09adbc01ec276d3
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.10.1
On Tue, Oct 18, 2016 at 4:35 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Oct 17, 2016 at 6:18 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Mon, Oct 17, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Ok, committed to HEAD.
Attached is a rebased patch set for SCRAM, with the following things:
[...]
And as the PostmasterRandom() patch has been reverted, here is once
again a new set:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.
- 0002, replacing PostmasterRandom by pg_strong_random(), with a fix
for the cancel key problem.
- 0003, adding for pg_strong_random() a fallback for any nix platform
not having /dev/random. This should be grouped with 0002, but I split
it for clarity.
- 0004, Add encoding routines for base64 without whitespace in
src/common/. I improved the error handling here by making them return
-1 in case of error and let the caller handle the error.
- 0005, Refactor decision-making of password encryption into a single routine.
- 0006, Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE.
- 0007, the SCRAM implementation. I have reworked the error handling
on both the frontend and the backend. In the frontend, there were many
code paths that did not bother much about many sanity checks like
OOMs, so I addressed that as a whole thing. For the backend, in the
event of an error, the backend sends back to the client a e= message
with an error string corresponding to what happened per RFC5802.
Sanity checks of the user data on the server (get the SCRAM verifier,
its validuntil, empty password and the user name itself), are made
part of the message exchange as in case of errors we need to return
errors like e=unknown-user, e=other-errors and stuff similar to that.
This makes the code in auth.c slightly cleaner btw.
- 0008 is a set of regression tests.
The PostmasterRandom() patch sent in this set contains the fix for
cancel keys that were previously broken. I have also implemented a
fallback method in 0003 inspired by pgcrypto's try_unix_std. It simply
uses gettimeofday() (should be put in the upper loop actually now that
I think about it!), getpid() and random() to generate some randomness,
and then processes the whole through a SHA-256 hash, generating chunks
of random data worth of SHA256_DIGEST_LENGTH bytes. I have not added a
./configure switch for it, but there were voices in favor of that. And
this is not available on Windows (no need to care anyway as there are
crypto APIs). A requirement of this patch is to have the SHA-256
routines in src/common/ first, and this will allow any platform
without /dev/random to generate random numbers like pademelon.
The fallback method for the pg_strong_random() is clearly not ready
for commit, one reason is that libpgport should stand at a level lower
than libpgcommon as far as I understand. But this patch makes
pg_strong_random() in src/port depend on the SHA2 routines in
src/common so it would make more sense if pg_strong_random() is moved
as well to src/common instead of src/port. Honestly I think that we'd
get away better with something like that than trying for example to
reimplement a dependency with PRNG knowing that OpenSSL does it
already, and perhaps better than we could do it.
Thoughts welcome. A lot of bits are independent of that part in the
patch set anyway.
--
Michael
Attachments:
0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchapplication/x-download; name=0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From 5d44444de6136e2c81a484b0cad8e15b5621cafd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 16:04:42 +0900
Subject: [PATCH 1/8] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 5 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 ++--
contrib/pgcrypto/sha2.h | 100 -----
src/common/Makefile | 6 +
{contrib/pgcrypto => src/common}/sha2.c | 722 ++++++++++++++++++--------------
src/common/sha2_openssl.c | 102 +++++
src/include/common/sha2.h | 115 +++++
src/tools/msvc/Mkvcbuild.pm | 22 +-
10 files changed, 699 insertions(+), 471 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (65%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..30619bf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..4085abb 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.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
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..ba74db6 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..e06f554 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..5ddfff8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 65%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9df..ea33fbc 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,4 +1,18 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
* FILE: sha2.c
@@ -33,15 +47,19 @@
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +76,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +146,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -249,15 +264,54 @@ static const uint64 sha512_initial_hash_value[8] = {
};
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +340,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +362,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +402,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +439,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +497,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +540,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +559,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +599,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +633,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +694,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +785,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +953,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1011,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1068,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000..91d0c39
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000..015a905
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..21c4600 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -421,14 +430,15 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'fortuna.c', 'random.c',
- 'pgp-mpi-internal.c', 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'random.c', 'pgp-mpi-internal.c',
+ 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.1
0002-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchapplication/x-download; name=0002-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchDownload
From ab95df3b846751472ae809483e7ffe619df2a483 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 17 Oct 2016 11:52:50 +0300
Subject: [PATCH 2/8] Replace PostmasterRandom() with a stronger way of
generating randomness.
This adds a new routine, pg_strong_random() for generating random bytes,
for use in both frontend and backend. At the moment, it's only used in
the backend, but the upcoming SCRAM authentication patches need strong
random numbers in libpq as well.
pg_strong_random() is based on, and replaces, the existing implementation
in pgcrypto. It can acquire strong random numbers from a number of sources,
depending on what's available:
- OpenSSL RAND_bytes(), if built with OpenSSL
- On Windows, the native cryptographic functions are used
- /dev/urandom
- /dev/random
Original patch by Magnus Hagander, with further work by Michael Paquier
and me.
Discussion: <CAB7nPqRy3krN8quR9XujMVVHYtXJ0_60nqgVc6oUk8ygyVkZsA@mail.gmail.com>
---
contrib/pgcrypto/Makefile | 2 +-
contrib/pgcrypto/internal.c | 40 +++---
contrib/pgcrypto/random.c | 247 ------------------------------------
src/backend/libpq/auth.c | 27 +++-
src/backend/postmaster/postmaster.c | 153 ++++++----------------
src/backend/utils/init/globals.c | 2 +-
src/include/miscadmin.h | 2 +-
src/include/port.h | 3 +
src/port/Makefile | 2 +-
src/port/pg_strong_random.c | 148 +++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 13 +-
11 files changed, 249 insertions(+), 390 deletions(-)
delete mode 100644 contrib/pgcrypto/random.c
create mode 100644 src/port/pg_strong_random.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 4085abb..17a528e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,7 +1,7 @@
# contrib/pgcrypto/Makefile
INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
- fortuna.c random.c pgp-mpi-internal.c imath.c
+ fortuna.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index 02ff976..ad942f7 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -626,8 +626,6 @@ static time_t check_time = 0;
static void
system_reseed(void)
{
- uint8 buf[1024];
- int n;
time_t t;
int skip = 1;
@@ -642,24 +640,34 @@ system_reseed(void)
else if (check_time == 0 ||
(t - check_time) > SYSTEM_RESEED_CHECK_TIME)
{
+ uint8 buf;
+
check_time = t;
/* roll dice */
- px_get_random_bytes(buf, 1);
- skip = buf[0] >= SYSTEM_RESEED_CHANCE;
- }
- /* clear 1 byte */
- px_memset(buf, 0, sizeof(buf));
-
- if (skip)
- return;
-
- n = px_acquire_system_randomness(buf);
- if (n > 0)
- fortuna_add_entropy(buf, n);
+ px_get_random_bytes(&buf, 1);
+ skip = (buf >= SYSTEM_RESEED_CHANCE);
- seed_time = t;
- px_memset(buf, 0, sizeof(buf));
+ /* clear 1 byte */
+ px_memset(&buf, 0, sizeof(buf));
+ }
+ if (!skip)
+ {
+ /*
+ * fortuna_add_entropy passes the input to SHA-256, so there's no
+ * point in giving it more than 256 bits of input to begin with.
+ */
+ uint8 buf[32];
+
+ if (!pg_strong_random(buf, sizeof(buf)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not acquire random data")));
+ fortuna_add_entropy(buf, sizeof(buf));
+
+ seed_time = t;
+ px_memset(buf, 0, sizeof(buf));
+ }
}
int
diff --git a/contrib/pgcrypto/random.c b/contrib/pgcrypto/random.c
deleted file mode 100644
index d72679e..0000000
--- a/contrib/pgcrypto/random.c
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * random.c
- * Acquire randomness from system. For seeding RNG.
- *
- * Copyright (c) 2001 Marko Kreen
- * 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.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/random.c
- */
-
-#include "postgres.h"
-
-#include "px.h"
-#include "utils/memdebug.h"
-
-/* how many bytes to ask from system random provider */
-#define RND_BYTES 32
-
-/*
- * Try to read from /dev/urandom or /dev/random on these OS'es.
- *
- * The list can be pretty liberal, as the device not existing
- * is expected event.
- */
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) \
- || defined(__NetBSD__) || defined(__DragonFly__) \
- || defined(__darwin__) || defined(__SOLARIS__) \
- || defined(__hpux) || defined(__HPUX__) \
- || defined(__CYGWIN__) || defined(_AIX)
-
-#define TRY_DEV_RANDOM
-
-#include <fcntl.h>
-#include <unistd.h>
-
-static int
-safe_read(int fd, void *buf, size_t count)
-{
- int done = 0;
- char *p = buf;
- int res;
-
- while (count)
- {
- res = read(fd, p, count);
- if (res <= 0)
- {
- if (errno == EINTR)
- continue;
- return PXE_DEV_READ_ERROR;
- }
- p += res;
- done += res;
- count -= res;
- }
- return done;
-}
-
-static uint8 *
-try_dev_random(uint8 *dst)
-{
- int fd;
- int res;
-
- fd = open("/dev/urandom", O_RDONLY, 0);
- if (fd == -1)
- {
- fd = open("/dev/random", O_RDONLY, 0);
- if (fd == -1)
- return dst;
- }
- res = safe_read(fd, dst, RND_BYTES);
- close(fd);
- if (res > 0)
- dst += res;
- return dst;
-}
-#endif
-
-/*
- * Try to find randomness on Windows
- */
-#ifdef WIN32
-
-#define TRY_WIN32_GENRAND
-#define TRY_WIN32_PERFC
-
-#include <windows.h>
-#include <wincrypt.h>
-
-/*
- * this function is from libtomcrypt
- *
- * try to use Microsoft crypto API
- */
-static uint8 *
-try_win32_genrand(uint8 *dst)
-{
- int res;
- HCRYPTPROV h = 0;
-
- res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
- (CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET));
- if (!res)
- res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
- CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET);
- if (!res)
- return dst;
-
- res = CryptGenRandom(h, RND_BYTES, dst);
- if (res == TRUE)
- dst += RND_BYTES;
-
- CryptReleaseContext(h, 0);
- return dst;
-}
-
-static uint8 *
-try_win32_perfc(uint8 *dst)
-{
- int res;
- LARGE_INTEGER time;
-
- res = QueryPerformanceCounter(&time);
- if (!res)
- return dst;
-
- memcpy(dst, &time, sizeof(time));
- return dst + sizeof(time);
-}
-#endif /* WIN32 */
-
-
-/*
- * If we are not on Windows, then hopefully we are
- * on a unix-like system. Use the usual suspects
- * for randomness.
- */
-#ifndef WIN32
-
-#define TRY_UNIXSTD
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
-
-/*
- * Everything here is predictible, only needs some patience.
- *
- * But there is a chance that the system-specific functions
- * did not work. So keep faith and try to slow the attacker down.
- */
-static uint8 *
-try_unix_std(uint8 *dst)
-{
- pid_t pid;
- int x;
- PX_MD *md;
- struct timeval tv;
- int res;
-
- /* process id */
- pid = getpid();
- memcpy(dst, (uint8 *) &pid, sizeof(pid));
- dst += sizeof(pid);
-
- /* time */
- gettimeofday(&tv, NULL);
- memcpy(dst, (uint8 *) &tv, sizeof(tv));
- dst += sizeof(tv);
-
- /* pointless, but should not hurt */
- x = random();
- memcpy(dst, (uint8 *) &x, sizeof(x));
- dst += sizeof(x);
-
- /* hash of uninitialized stack and heap allocations */
- res = px_find_digest("sha1", &md);
- if (res >= 0)
- {
- uint8 *ptr;
- uint8 stack[8192];
- int alloc = 32 * 1024;
-
- VALGRIND_MAKE_MEM_DEFINED(stack, sizeof(stack));
- px_md_update(md, stack, sizeof(stack));
- ptr = px_alloc(alloc);
- VALGRIND_MAKE_MEM_DEFINED(ptr, alloc);
- px_md_update(md, ptr, alloc);
- px_free(ptr);
-
- px_md_finish(md, dst);
- px_md_free(md);
-
- dst += 20;
- }
-
- return dst;
-}
-#endif
-
-/*
- * try to extract some randomness for initial seeding
- *
- * dst should have room for 1024 bytes.
- */
-unsigned
-px_acquire_system_randomness(uint8 *dst)
-{
- uint8 *p = dst;
-
-#ifdef TRY_DEV_RANDOM
- p = try_dev_random(p);
-#endif
-#ifdef TRY_WIN32_GENRAND
- p = try_win32_genrand(p);
-#endif
-#ifdef TRY_WIN32_PERFC
- p = try_win32_perfc(p);
-#endif
-#ifdef TRY_UNIXSTD
- p = try_unix_std(p);
-#endif
- return p - dst;
-}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 0ba8530..44b2212 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -45,6 +45,12 @@ 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);
+/*----------------------------------------------------------------
+ * MD5 authentication
+ *----------------------------------------------------------------
+ */
+static int CheckMD5Auth(Port *port, char **logdetail);
+
/*----------------------------------------------------------------
* Ident authentication
@@ -535,9 +541,7 @@ ClientAuthentication(Port *port)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
- /* include the salt to use for computing the response */
- sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
- status = recv_and_check_password_packet(port, &logdetail);
+ status = CheckMD5Auth(port, &logdetail);
break;
case uaPassword:
@@ -692,10 +696,25 @@ recv_password_packet(Port *port)
/*----------------------------------------------------------------
- * MD5 authentication
+ * MD5 and password authentication
*----------------------------------------------------------------
*/
+static int
+CheckMD5Auth(Port *port, char **logdetail)
+{
+ /* include the salt to use for computing the response */
+ if (!pg_strong_random(port->md5Salt, sizeof(port->md5Salt)))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
+ return recv_and_check_password_packet(port, logdetail);
+}
+
+
/*
* Called when we have sent an authorization request for a password.
* Get the response and check it.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2d43506..4420138 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -358,14 +358,6 @@ static volatile bool avlauncher_needs_signal = false;
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;
-/*
- * State for assigning random salts and cancel keys.
- * Also, the global MyCancelKey passes the cancel key assigned to a given
- * backend from the postmaster to that backend (via fork).
- */
-static unsigned int random_seed = 0;
-static struct timeval random_start_time;
-
#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif
@@ -403,8 +395,6 @@ static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
-static long PostmasterRandom(void);
-static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -579,9 +569,11 @@ PostmasterMain(int argc, char *argv[])
* Initialize random(3) so we don't get the same values in every run.
*
* Note: the seed is pretty predictable from externally-visible facts such
- * as postmaster start time, so avoid using random() for security-critical
- * random values during postmaster startup. At the time of first
- * connection, PostmasterRandom will select a hopefully-more-random seed.
+ * as postmaster start time, so don't use random() for security-critical
+ * random values (use pg_strong_random() instead). Backends select a
+ * somewhat more random seed after forking, in BackendRun(), based on the
+ * PID and session start timestamp, but that is still not suitable for
+ * security-critical values.
*/
srandom((unsigned int) (MyProcPid ^ MyStartTime));
@@ -1292,8 +1284,6 @@ PostmasterMain(int argc, char *argv[])
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
- /* PostmasterRandom wants its own copy */
- gettimeofday(&random_start_time, NULL);
/*
* We're ready to rock and roll...
@@ -2344,15 +2334,6 @@ ConnCreate(int serverFd)
}
/*
- * Precompute password salt values to use for this connection. It's
- * slightly annoying to do this long in advance of knowing whether we'll
- * need 'em or not, but we must do the random() calls before we fork, not
- * after. Else the postmaster's random sequence won't get advanced, and
- * all backends would end up using the same salt...
- */
- RandomSalt(port->md5Salt, sizeof(port->md5Salt));
-
- /*
* Allocate GSSAPI specific state struct
*/
#ifndef EXEC_BACKEND
@@ -3904,7 +3885,12 @@ BackendStartup(Port *port)
* backend will have its own copy in the forked-off process' value of
* MyCancelKey, so that it can transmit the key to the frontend.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
+ return STATUS_ERROR;
+ }
bn->cancel_key = MyCancelKey;
/* Pass down canAcceptConnections state */
@@ -4212,13 +4198,6 @@ BackendRun(Port *port)
int usecs;
int i;
- /*
- * Don't want backend to be able to see the postmaster random number
- * generator state. We have to clobber the static random_seed *and* start
- * a new random sequence in the random() library function.
- */
- random_seed = 0;
- random_start_time.tv_usec = 0;
/* slightly hacky way to convert timestamptz into integers */
TimestampDifference(0, port->SessionStartTime, &secs, &usecs);
srandom((unsigned int) (MyProcPid ^ (usecs << 12) ^ secs));
@@ -5067,66 +5046,6 @@ StartupPacketTimeoutHandler(void)
/*
- * RandomSalt
- */
-static void
-RandomSalt(char *salt, int len)
-{
- long rand;
- int i;
-
- /*
- * We use % 255, sacrificing one possible byte value, so as to ensure that
- * all bits of the random() value participate in the result. While at it,
- * add one to avoid generating any null bytes.
- */
- for (i = 0; i < len; i++)
- {
- rand = PostmasterRandom();
- salt[i] = (rand % 255) + 1;
- }
-}
-
-/*
- * PostmasterRandom
- *
- * Caution: use this only for values needed during connection-request
- * processing. Otherwise, the intended property of having an unpredictable
- * delay between random_start_time and random_stop_time will be broken.
- */
-static long
-PostmasterRandom(void)
-{
- /*
- * Select a random seed at the time of first receiving a request.
- */
- if (random_seed == 0)
- {
- do
- {
- struct timeval random_stop_time;
-
- gettimeofday(&random_stop_time, NULL);
-
- /*
- * We are not sure how much precision is in tv_usec, so we swap
- * the high and low 16 bits of 'random_stop_time' and XOR them
- * with 'random_start_time'. On the off chance that the result is
- * 0, we loop until it isn't.
- */
- random_seed = random_start_time.tv_usec ^
- ((random_stop_time.tv_usec << 16) |
- ((random_stop_time.tv_usec >> 16) & 0xffff));
- }
- while (random_seed == 0);
-
- srandom(random_seed);
- }
-
- return random();
-}
-
-/*
* Count up number of child processes of specified types (dead_end chidren
* are always excluded).
*/
@@ -5303,31 +5222,37 @@ StartAutovacuumWorker(void)
* we'd better have something random in the field to prevent
* unfriendly people from sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
- bn->cancel_key = MyCancelKey;
+ if (pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ bn->cancel_key = MyCancelKey;
- /* Autovac workers are not dead_end and need a child slot */
- bn->dead_end = false;
- bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
- bn->bgworker_notify = false;
+ /* Autovac workers are not dead_end and need a child slot */
+ bn->dead_end = false;
+ bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
+ bn->bgworker_notify = false;
- bn->pid = StartAutoVacWorker();
- if (bn->pid > 0)
- {
- bn->bkend_type = BACKEND_TYPE_AUTOVAC;
- dlist_push_head(&BackendList, &bn->elem);
+ bn->pid = StartAutoVacWorker();
+ if (bn->pid > 0)
+ {
+ bn->bkend_type = BACKEND_TYPE_AUTOVAC;
+ dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
- ShmemBackendArrayAdd(bn);
+ ShmemBackendArrayAdd(bn);
#endif
- /* all OK */
- return;
+ /* all OK */
+ return;
+ }
+
+ /*
+ * fork failed, fall through to report -- actual error message
+ * was logged by StartAutoVacWorker
+ */
+ (void) ReleasePostmasterChildSlot(bn->child_slot);
}
+ else
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
- /*
- * fork failed, fall through to report -- actual error message was
- * logged by StartAutoVacWorker
- */
- (void) ReleasePostmasterChildSlot(bn->child_slot);
free(bn);
}
else
@@ -5615,7 +5540,11 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
* have something random in the field to prevent unfriendly people from
* sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ rw->rw_crashed_at = GetCurrentTimestamp();
+ return false;
+ }
bn->cancel_key = MyCancelKey;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index f232083..634578d 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -38,7 +38,7 @@ volatile uint32 CritSectionCount = 0;
int MyProcPid;
pg_time_t MyStartTime;
struct Port *MyProcPort;
-long MyCancelKey;
+uint32 MyCancelKey;
int MyPMChildSlot;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 78545da..5e623a1 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -162,7 +162,7 @@ extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT struct Port *MyProcPort;
extern PGDLLIMPORT struct Latch *MyLatch;
-extern long MyCancelKey;
+extern uint32 MyCancelKey;
extern int MyPMChildSlot;
extern char OutputFileName[];
diff --git a/src/include/port.h b/src/include/port.h
index b81fa4a..4bb9fee 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -454,6 +454,9 @@ extern int pg_codepage_to_encoding(UINT cp);
extern char *inet_net_ntop(int af, const void *src, int bits,
char *dst, size_t size);
+/* port/pg_strong_random.c */
+extern bool pg_strong_random(void *buf, size_t len);
+
/* port/pgcheckdir.c */
extern int pg_check_dir(const char *dir);
diff --git a/src/port/Makefile b/src/port/Makefile
index bc9b63a..d34f409 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -32,7 +32,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS = $(LIBOBJS) $(PG_CRC32C_OBJS) chklocale.o erand48.o inet_net_ntop.o \
noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o \
- pgstrcasecmp.o pqsignal.o \
+ pg_strong_random.o pgstrcasecmp.o pqsignal.o \
qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o
# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
new file mode 100644
index 0000000..a404111
--- /dev/null
+++ b/src/port/pg_strong_random.c
@@ -0,0 +1,148 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_strong_random.c
+ * pg_strong_random() function to return a strong random number
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/port/pg_strong_random.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef USE_SSL
+#include <openssl/rand.h>
+#endif
+#ifdef WIN32
+#include <Wincrypt.h>
+#endif
+
+static bool random_from_file(char *filename, void *buf, size_t len);
+
+#ifdef WIN32
+/*
+ * Cache a global crypto provider that only gets freed when the process
+ * exits, in case we need random numbers more than once.
+ */
+static HCRYPTPROV hProvider = 0;
+#endif
+
+/*
+ * Read (random) bytes from a file.
+ */
+static bool
+random_from_file(char *filename, void *buf, size_t len)
+{
+ int f;
+ char *p = buf;
+ ssize_t res;
+
+ f = open(filename, O_RDONLY, 0);
+ if (f == -1)
+ return false;
+
+ while (len)
+ {
+ res = read(f, p, len);
+ if (res <= 0)
+ {
+ if (errno == EINTR)
+ continue; /* interrupted by signal, just retry */
+
+ close(f);
+ return false;
+ }
+
+ p += res;
+ len -= res;
+ }
+
+ close(f);
+ return true;
+}
+
+/*
+ * pg_strong_random
+ *
+ * Generate requested number of random bytes. The bytes are
+ * cryptographically strong random, suitable for use e.g. in key
+ * generation.
+ *
+ * The bytes can be acquired from a number of sources, depending
+ * on what's available. We try the following, in this order:
+ *
+ * 1. OpenSSL's RAND_bytes()
+ * 2. Windows' CryptGenRandom() function
+ * 3. /dev/urandom
+ * 4. /dev/random
+ *
+ * Returns true on success, and false if none of the sources
+ * were available. NB: It is important to check the return value!
+ * Proceeding with key generation when no random data was available
+ * would lead to predictable keys and security issues.
+ */
+bool
+pg_strong_random(void *buf, size_t len)
+{
+#ifdef USE_SSL
+
+ /*
+ * When built with OpenSSL, first try the random generation function from
+ * there.
+ */
+ if (RAND_bytes(buf, len) == 1)
+ return true;
+#endif
+
+#ifdef WIN32
+
+ /*
+ * Windows has CryptoAPI for strong cryptographic numbers.
+ */
+ if (hProvider == 0)
+ {
+ if (!CryptAcquireContext(&hProvider,
+ NULL,
+ MS_DEF_PROV,
+ PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+ {
+ /*
+ * On failure, set back to 0 in case the value was for some reason
+ * modified.
+ */
+ hProvider = 0;
+ }
+ }
+
+ /* Re-check in case we just retrieved the provider */
+ if (hProvider != 0)
+ {
+ if (CryptGenRandom(hProvider, len, buf))
+ return true;
+ }
+#endif
+
+ /*
+ * If there is no OpenSSL and no CryptoAPI (or they didn't work), then
+ * fall back on reading /dev/urandom or even /dev/random.
+ */
+ if (random_from_file("/dev/urandom", buf, len))
+ return true;
+ if (random_from_file("/dev/random", buf, len))
+ return true;
+
+ /* None of the sources were available. */
+ return false;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 21c4600..1b2c8e4 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -92,7 +92,7 @@ sub mkvcbuild
srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c
- mkdtemp.c qsort.c qsort_arg.c quotes.c system.c
+ mkdtemp.c pg_strong_random.c qsort.c qsort_arg.c quotes.c system.c
sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c
win32env.c win32error.c win32security.c win32setlocale.c);
@@ -430,12 +430,11 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'internal.c',
- 'internal-sha2.c', 'blf.c',
- 'rijndael.c', 'fortuna.c',
- 'random.c', 'pgp-mpi-internal.c',
- 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
$pgcrypto->AddReference($libpgcommon);
--
2.10.1
0003-Implement-last-resort-method-in-pg_strong_random.patchapplication/x-download; name=0003-Implement-last-resort-method-in-pg_strong_random.patchDownload
From 7a22503bcb92230d9afa88d16f8e426208df832f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 19 Oct 2016 11:56:47 +0900
Subject: [PATCH 3/8] Implement last-resort method in pg_strong_random
This method will be used on nix platforms in the case where any of the
previous methods failed. It uses pid(), gettimeofday() and random() to
grab some entropy, even if this is predictible, and processes the
obtained value through a SHA-256 hash.
---
src/port/pg_strong_random.c | 76 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index a404111..17c217a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -18,6 +18,8 @@
#include "postgres_fe.h"
#endif
+#include "common/sha2.h"
+
#include <fcntl.h>
#include <unistd.h>
@@ -29,6 +31,9 @@
#endif
static bool random_from_file(char *filename, void *buf, size_t len);
+#ifndef WIN32
+static bool random_from_std(void *dst, size_t len);
+#endif
#ifdef WIN32
/*
@@ -72,6 +77,67 @@ random_from_file(char *filename, void *buf, size_t len)
return true;
}
+#ifndef WIN32
+/*
+ * This is not available on Windows, and the cryptography standards available
+ * there are enough anyway to generate randomness.
+ */
+#include <sys/time.h>
+#include <time.h>
+
+/*
+ * Generate some random data using environment data as entropy
+ * through SHA-256. All the other methods failed, this can be used
+ * as a last resort method even if it is predictible, and rather
+ * slow compared to the rest.
+ */
+static bool
+random_from_std(void *dst, size_t len)
+{
+ pid_t pid;
+ size_t remaining = len;
+
+ /* process ID */
+ pid = getpid();
+
+ /*
+ * Compute a random value by generating successive chunks worth
+ * of PG_SHA256_DIGEST_LENGTH bytes.
+ */
+ while (remaining != 0)
+ {
+ int x;
+ struct timeval tv;
+ pg_sha256_ctx ctx;
+ uint8 sha_buf[PG_SHA256_DIGEST_LENGTH];
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, (uint8 *) &pid, sizeof(pid));
+
+ /* time */
+ gettimeofday(&tv, NULL);
+ pg_sha256_update(&ctx, (uint8 *) &tv, sizeof(tv));
+
+ /* pointless, but should not hurt */
+ x = random();
+ pg_sha256_update(&ctx, (uint8 *) &x, sizeof(x));
+ pg_sha256_final(&ctx, sha_buf);
+
+ /* copy the chunk generated in the result */
+ memcpy(dst, sha_buf, Min(remaining, PG_SHA256_DIGEST_LENGTH));
+
+ /* and prepare to loop further */
+ if (remaining >= PG_SHA256_DIGEST_LENGTH)
+ remaining -= PG_SHA256_DIGEST_LENGTH;
+ else
+ remaining = 0;
+ dst = (uint8 *) dst + PG_SHA256_DIGEST_LENGTH;
+ }
+
+ return true;
+}
+#endif
+
/*
* pg_strong_random
*
@@ -86,6 +152,8 @@ random_from_file(char *filename, void *buf, size_t len)
* 2. Windows' CryptGenRandom() function
* 3. /dev/urandom
* 4. /dev/random
+ * 5. Use the internal, predictible method computing a value through SHA-256
+ * with system-related entropy.
*
* Returns true on success, and false if none of the sources
* were available. NB: It is important to check the return value!
@@ -143,6 +211,14 @@ pg_strong_random(void *buf, size_t len)
if (random_from_file("/dev/random", buf, len))
return true;
+#ifndef WIN32
+ /*
+ * As final method, generate some randomness using the in-house method.
+ */
+ if (random_from_std(buf, len))
+ return true;
+#endif
+
/* None of the sources were available. */
return false;
}
--
2.10.1
0004-Add-encoding-routines-for-base64-without-whitespace-.patchapplication/x-download; name=0004-Add-encoding-routines-for-base64-without-whitespace-.patchDownload
From f1911ecbb3f4a23157019aa314e27298664fdb9c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 15:28:45 +0900
Subject: [PATCH 4/8] Add encoding routines for base64 without whitespace in
src/common/
Those routines are taken from the backend's encode.c, and adapted for
SCRAM-SHA-256, where base64 should not support whitespaces as per RFC5802.
---
src/common/Makefile | 6 +-
src/common/base64.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/base64.h | 19 +++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
4 files changed, 224 insertions(+), 4 deletions(-)
create mode 100644 src/common/base64.c
create mode 100644 src/include/common/base64.h
diff --git a/src/common/Makefile b/src/common/Makefile
index 5ddfff8..49e41cf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/base64.c b/src/common/base64.c
new file mode 100644
index 0000000..0c9eba4
--- /dev/null
+++ b/src/common/base64.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * base64.c
+ * Set of encoding and decoding routines for base64 without support
+ * for whitespace. In case of failure, those routines return -1 in case
+ * of error to let the caller do any error handling.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/base64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/base64.h"
+
+/*
+ * 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,
+};
+
+/*
+ * pg_b64_encode
+ *
+ * Encode into base64 the given string. Returns the length of the encoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_encode(const char *src, int len, char *dst)
+{
+ char *p;
+ 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 (pos != 2)
+ {
+ *p++ = _base64[(buf >> 18) & 0x3f];
+ *p++ = _base64[(buf >> 12) & 0x3f];
+ *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+ *p++ = '=';
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_decode
+ *
+ * Decode the given base64 string. Returns the length of the decoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_decode(const char *src, int 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++;
+
+ /* Leave if a whitespace is found */
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ return -1;
+
+ if (c == '=')
+ {
+ /* end sequence */
+ if (!end)
+ {
+ if (pos == 2)
+ end = 1;
+ else if (pos == 3)
+ end = 2;
+ else
+ {
+ /*
+ * Unexpected "=" character found while decoding base64
+ * sequence.
+ */
+ return -1;
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+ /* invalid symbol found */
+ return -1;
+ }
+ }
+ /* 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)
+ {
+ /*
+ * base64 end sequence is invalid. Input data is missing padding,
+ * is truncated or is otherwise corrupted.
+ */
+ return -1;
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_enc_len
+ *
+ * Returns to caller the length of the string if it were encoded with
+ * base64 based on the length provided by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual encoding.
+ */
+int
+pg_b64_enc_len(int srclen)
+{
+ /* 3 bytes will be converted to 4 */
+ return (srclen + 2) * 4 / 3;
+}
+
+/*
+ * pg_b64_dec_len
+ *
+ * Returns to caller the length of the string if it were to be decoded
+ * with base64, based on the length given by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual decoding.
+ */
+int
+pg_b64_dec_len(int srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/include/common/base64.h b/src/include/common/base64.h
new file mode 100644
index 0000000..8ad8eb6
--- /dev/null
+++ b/src/include/common/base64.h
@@ -0,0 +1,19 @@
+/*
+ * base64.h
+ * Encoding and decoding routines for base64 without whitespace
+ * support.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/base64.h
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+/* base 64 */
+int pg_b64_encode(const char *src, int len, char *dst);
+int pg_b64_decode(const char *src, int len, char *dst);
+int pg_b64_enc_len(int srclen);
+int pg_b64_dec_len(int srclen);
+
+#endif /* BASE64_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 1b2c8e4..c5b737a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.10.1
0005-Refactor-decision-making-of-password-encryption-into.patchapplication/x-download; name=0005-Refactor-decision-making-of-password-encryption-into.patchDownload
From f0f075c0032c348a459e3adc504d2a26c0631b49 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 13:51:15 +0900
Subject: [PATCH 5/8] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 84 ++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99..7f396c9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,48 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = pstrdup(password);
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = pstrdup(password);
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ Assert(0); /* should not come here */
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +125,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +437,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
+ pfree(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,18 +844,14 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ pfree(encrypted_passwd);
}
/* unset password */
--
2.10.1
0006-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-download; name=0006-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 1a3e743e28d38fb88a989183330ad851ff4afd4c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 16:03:13 +0900
Subject: [PATCH 6/8] Add clause PASSWORD val USING protocol to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is. This extension is useful to support future
protocols, particularly SCRAM-SHA-256.
---
doc/src/sgml/ref/alter_role.sgml | 2 +
doc/src/sgml/ref/create_role.sgml | 18 ++++++++
src/backend/commands/user.c | 90 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 +++
4 files changed, 110 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..3cae101 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..d172ff1 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">protocol</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,23 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable> USING <replaceable class="parameter">protocol</replaceable></term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested protocol. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The protocols supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, and <literal>plain</> to use an
+ unencrypted password. If the password string is already in
+ MD5-encrypted format, then it is stored encrypted even if
+ <literal>plain</> is specified.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 7f396c9..25f9431 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -175,7 +175,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -183,10 +184,49 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -306,8 +346,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -582,6 +620,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "protocolPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -589,10 +628,49 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "protocolPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the protocol wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *protocol = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(protocol, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(protocol, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password protocol %s", protocol)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -680,8 +758,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5547fc8..233081b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -934,6 +934,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD Sconst USING Sconst
+ {
+ $$ = makeDefElem("protocolPassword",
+ (Node *)list_make2(makeString($2),
+ makeString($4)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.10.1
0007-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-download; name=0007-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From e106c68e34ac74d21020441822b7d6bdc20c4beb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 20 Oct 2016 14:03:23 +0900
Subject: [PATCH 7/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used as hashing function. This commit introduces the basic
SASL communication protocol plugged in on top of the existing
infrastructure.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/config.sgml | 13 +-
doc/src/sgml/protocol.sgml | 147 +++-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/commands/user.c | 49 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 956 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 111 +++
src/backend/libpq/crypt.c | 69 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 ++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 56 ++
src/include/libpq/crypt.h | 8 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 35 +
src/interfaces/libpq/.gitignore | 5 +
src/interfaces/libpq/Makefile | 17 +-
src/interfaces/libpq/fe-auth-scram.c | 544 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 108 +++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
31 files changed, 2410 insertions(+), 77 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 99ff9f5..5ed9ef5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1176,9 +1176,10 @@ include_dir 'conf.d'
password is to be encrypted. The default value is <literal>md5</>, which
stores the password as an MD5 hash. Setting this to <literal>plain</> stores
it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
- aliases for <literal>md5</> and <literal>plain</>, respectively.
- </para>
-
+ aliases for <literal>md5</> and <literal>plain</>, respectively. Setting
+ this parameter to <literal>scram</> will encrypt the password with
+ SCRAM-SHA-256.
+ </para>
</listitem>
</varlistentry>
@@ -1251,8 +1252,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 3384e73..45d92ab 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>
@@ -2585,6 +2614,114 @@ 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>
@@ -4347,7 +4484,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index d172ff1..e19bc8c 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -229,16 +229,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
@@ -254,10 +254,11 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
attribute, but you can nonetheless define one for roles without it.)
If you do not plan to use password authentication you can omit this
option. The protocols supported are <literal>md5</> to enforce
- a password to be MD5-encrypted, and <literal>plain</> to use an
- unencrypted password. If the password string is already in
- MD5-encrypted format, then it is stored encrypted even if
- <literal>plain</> is specified.
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA-256, and <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-SHA-256 format, then it is stored encrypted
+ even if another protocol is specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 25f9431..7e03ac4 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * A password already identified as a SCRAM-encrypted or MD5-encrypted
+ * those are used as such. If the password given is not encrypted,
+ * adapt it depending on the type wanted by the caller of this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = pstrdup(password);
else
{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
Assert(0); /* should not come here */
}
@@ -221,6 +225,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(protocol, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -450,11 +456,22 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
/*
* Build a tuple to insert
@@ -663,6 +680,8 @@ AlterRole(AlterRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(protocol, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(protocol, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -859,11 +878,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
+
/*
* Build an updated tuple, perusing the information just obtained
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..b5ddfac
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,956 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.h"
+
+/*
+ * Status data for SCRAM. This should be kept internal to this file.
+ */
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ char *server_first_message;
+ char *server_nonce; /* base64-encoded */
+ char *server_signature;
+} scram_state;
+
+/*
+ * Internal error codes for exchange functions.
+ * Callers of the exchange routines do not need to be aware of any of
+ * that and should just send messages generated here.
+ */
+typedef enum
+{
+ SASL_NO_ERROR = 0,
+ /* error codes */
+ SASL_INVALID_ENCODING,
+ SASL_EXTENSIONS_NOT_SUPPORTED,
+ SASL_CHANNEL_BINDING_UNMATCH,
+ SASL_CHANNEL_BINDING_NO_SUPPORT,
+ SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED,
+ SASL_UNKNOWN_USER,
+ SASL_INVALID_PROOF,
+ SASL_INVALID_USERNAME_ENCODING,
+ SASL_NO_RESOURCES,
+ /*
+ * Unrecognized errors should be treated as "other-error". In order to
+ * prevent information disclosure, the server may substitute the real
+ * reason with "other-error".
+ */
+ SASL_OTHER_ERROR
+} SASLStatus;
+
+
+static SASLStatus read_client_first_message(scram_state *state,
+ char *input, char **logdetail);
+static SASLStatus read_client_final_message(scram_state *state,
+ char *input, char **logdetail);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static char *build_error_message(SASLStatus status);
+static SASLStatus check_client_data(void *opaque, char **logdetail);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+static bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+static void generate_nonce(char *out, int len);
+
+/*
+ * build_error_message
+ *
+ * Build an error message for a problem that happened during the SASL
+ * message exchange. Those messages are formatted with e= as prefix
+ * and need to be sent back to the client.
+ */
+static char *
+build_error_message(SASLStatus status)
+{
+ char *res = NULL;
+
+ /*
+ * The following error format is respected here:
+ *
+ * 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.
+ */
+
+ switch (status)
+ {
+ case SASL_INVALID_ENCODING:
+ res = psprintf("e=invalid-encoding");
+ break;
+ case SASL_EXTENSIONS_NOT_SUPPORTED:
+ res = psprintf("e=extensions-not-supported");
+ break;
+ case SASL_INVALID_PROOF:
+ res = psprintf("e=invalid-proof");
+ break;
+ case SASL_CHANNEL_BINDING_UNMATCH:
+ res = psprintf("e=channel-bindings-dont-match");
+ break;
+ case SASL_CHANNEL_BINDING_NO_SUPPORT:
+ res = psprintf("e=server-does-support-channel-binding");
+ break;
+ case SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED:
+ res = psprintf("e=unsupported-channel-binding-type");
+ break;
+ case SASL_UNKNOWN_USER:
+ res = psprintf("e=unknown-user");
+ break;
+ case SASL_INVALID_USERNAME_ENCODING:
+ res = psprintf("e=invalid-username-encoding");
+ break;
+ case SASL_NO_RESOURCES:
+ res = psprintf("e=no-resources");
+ break;
+ case SASL_OTHER_ERROR:
+ res = psprintf("e=other-error");
+ break;
+ case SASL_NO_ERROR:
+ default:
+ Assert(0); /* should not happen */
+ }
+
+ Assert(res != NULL);
+ return res;
+}
+
+/*
+ * pg_be_scram_init
+ *
+ * Initialize a new SCRAM authentication exchange status tracker. This
+ * needs to be called before doing any exchange. It will be filled later
+ * after the beginning of the exchange with verifier data.
+ */
+void *
+pg_be_scram_init(char *username)
+{
+ scram_state *state;
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+ state->salt = NULL;
+
+ return state;
+}
+
+/*
+ * check_client_data
+ *
+ * Fill into the SASL exchange status all the information related to user and
+ * perform sanity checks.
+ */
+static SASLStatus
+check_client_data(void *opaque, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaque;
+ char *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+ int res;
+ char *passwd;
+ TimestampTz vuntil = 0;
+ bool vuntil_null;
+
+ /* compute the salt to use for computing responses */
+ if (!pg_strong_random(MyProcPort->SASLSalt, sizeof(MyProcPort->SASLSalt)))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return SASL_OTHER_ERROR;
+ }
+
+ /*
+ * Fetch details about role needed for password checks.
+ */
+ res = get_role_details(state->username, &passwd, &vuntil,
+ &vuntil_null,
+ logdetail);
+
+ /*
+ * Check if an error has happened. "logdetail" is already filled,
+ * here we just need to find what is the error mapping with the SASL
+ * exchange and let the client know what happened.
+ */
+ if (res == PG_ROLE_NOT_DEFINED)
+ return SASL_UNKNOWN_USER;
+ else if (res == PG_ROLE_NO_PASSWORD ||
+ res == PG_ROLE_EMPTY_PASSWORD)
+ return SASL_OTHER_ERROR;
+
+ /*
+ * Check password validity, there is nothing to do if the password
+ * validity field is null.
+ */
+ if (!vuntil_null)
+ {
+ if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+ }
+
+ /* The SCRAM verifier needs to be in correct shape as well. */
+ if (!parse_scram_verifier(passwd, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+
+ /* OK to fill in everything */
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ pfree(passwd);
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ *
+ * The next message to send to client is saved in "output", for a length
+ * of "outputlen". In the case of an error, optionally store a palloc'd
+ * string at *logdetail that will be sent to the postmaster log (but not
+ * the client).
+ */
+int
+pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaq;
+ SASLStatus status;
+ int result;
+
+ *output = NULL;
+ *outputlen = 0;
+
+ if (inputlen > 0)
+ elog(DEBUG4, "got SCRAM message: %s", input);
+
+ switch (state->state)
+ {
+ case INIT:
+ /*
+ * Initialization phase. Things happen in the following order:
+ * 1) Receive the first message from client and be sure that it
+ * is parsed correctly.
+ * 2) Validate the user information. A couple of things are done
+ * here, mainly validity checks on the password and the user.
+ * 3) Send the challenge to the client.
+ */
+ status = read_client_first_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* check validity of user data */
+ status = check_client_data(state, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* prepare message to send challenge */
+ *output = build_server_first_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+ *outputlen = strlen(*output);
+ state->state = SALT_SENT;
+ result = SASL_EXCHANGE_CONTINUE;
+ break;
+
+ case SALT_SENT:
+ /*
+ * Final phase for the server. First receive the response to
+ * the challenge previously sent and then let the client know
+ * that everything went well.
+ */
+ status = read_client_final_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Now check the final nonce and the client proof */
+ if (!verify_final_nonce(state) ||
+ !verify_client_proof(state))
+ {
+ *output = build_error_message(SASL_INVALID_PROOF);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Build final message for client */
+ *output = build_server_final_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Success! */
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_SUCCESS;
+ 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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
+ encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ *
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ *
+ * Returns true if the SCRAM verifier has been parsed, and false otherwise.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = strtol(p, &p, SCRAM_ITERATION_LEN);
+ 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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')))
+ return NULL;
+ if (attr_p)
+ *attr_p = attr;
+ begin++;
+
+ if (*begin != '=')
+ return NULL;
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message. In the event of an error, returns
+ * to caller a e= message to be used for the rest of the exchange, or
+ * NULL in case of success.
+ */
+static SASLStatus
+read_client_first_message(scram_state *state, char *input, char **logdetail)
+{
+ 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 */
+ *logdetail = psprintf(_("channel binding not supported."));
+ return SASL_CHANNEL_BINDING_NO_SUPPORT;
+ }
+
+ /* any mandatory extensions would go here. */
+ if (*input != ',')
+ {
+ *logdetail = psprintf(_("mandatory extension %c not supported."), *input);
+ return SASL_OTHER_ERROR;
+ }
+ 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 */
+ 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! */
+ return SASL_EXCHANGE_CONTINUE;
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+ if (encoded_len < 0)
+ return NULL;
+
+ 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;
+}
+
+
+/*
+ * Read and parse the final message received from client.
+ */
+static SASLStatus
+read_client_final_message(scram_state *state, char *input, char **logdetail)
+{
+ 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)
+ {
+ *logdetail = psprintf(_("invalid channel binding input."));
+ return SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED;
+ }
+ 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(pg_b64_dec_len(strlen(value)));
+ if (pg_b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+ {
+ *logdetail = psprintf(_("invalid client proof."));
+ return SASL_INVALID_PROOF;
+ }
+ memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ pfree(client_proof);
+
+ if (*p != '\0')
+ {
+ *logdetail = psprintf(_("malformed SCRAM message (garbage at end of message %c)."),
+ attr);
+ return SASL_OTHER_ERROR;
+ }
+
+ 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';
+
+ /* XXX: check channel_binding field if support is added */
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ siglen = pg_b64_encode((const char *) ServerSignature,
+ SCRAM_KEY_LEN, server_signature_base64);
+ if (siglen < 0)
+ return NULL;
+ server_signature_base64[siglen] = '\0';
+
+ /*
+ * The following error is generated:
+ *
+ * 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 44b2212..6f774d9 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,6 +30,7 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -210,6 +211,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -271,6 +278,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;
@@ -549,6 +557,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, "");
@@ -738,6 +750,105 @@ 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 *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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /*
+ * Send first the authentication request to user. As for MD5, we want
+ * the user to send its password first even if nothing has been done
+ * yet. This avoids consistency issues where a user would be able to
+ * guess that a server is expecting SASL or MD5 depending on the answer
+ * given by the backend without the user providing a password first.
+ */
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ /* Initialize the status tracker for message exchanges */
+ scram_opaq = pg_be_scram_init(port->user_name);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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 = pg_be_scram_exchange(scram_opaq, buf.data, buf.len,
+ &output, &outputlen, logdetail);
+
+ /* 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);
+
+ /* Oops, Something bad happened */
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ /* an error should have been set during the exchange checks */
+ Assert(*logdetail != NULL);
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c9db9a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,30 +30,31 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and returns status code describing the error. In the case of an error,
+ * store a palloc'd string at *logdetail that will be sent to the postmaster
+ * log (but not the client!).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
{
- *logdetail = psprintf(_("Role \"%s\" does not exist."),
- role);
- return STATUS_ERROR; /* no such user */
+ *logdetail = psprintf(_("Role \"%s\" does not exist."), role);
+ return PG_ROLE_NOT_DEFINED; /* no such user */
}
datum = SysCacheGetAttr(AUTHNAME, roleTup,
@@ -63,24 +64,52 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return STATUS_ERROR; /* user has no password */
+ return PG_ROLE_NO_PASSWORD; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
- return STATUS_ERROR; /* empty password */
+ pfree(*password);
+ return PG_ROLE_EMPTY_PASSWORD; /* empty password */
}
+ return PG_ROLE_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != PG_ROLE_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 65660c1..2cc4747 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
static const struct config_enum_entry password_encryption_options[] = {
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 159ada3..e4f0bd3 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # md5 or plain
+#password_encryption = md5 # md5, scram or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf..971ddd5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5..1ff441a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption; /* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..e9028fb
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha2.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN SCRAM_NONCE_LEN
+
+/* number of bytes used when sending iteration number during exchange */
+#define SCRAM_ITERATION_LEN 10
+
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..522cfa2 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,14 @@
#include "libpq/libpq-be.h"
+/* Detailed error codes for get_role_details() */
+#define PG_ROLE_OK 0
+#define PG_ROLE_NOT_DEFINED 1
+#define PG_ROLE_NO_PASSWORD 2
+#define PG_ROLE_EMPTY_PASSWORD 3
+
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, 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 dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 9648422..bbcb2f7 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..58638eb
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS 1
+#define SASL_EXCHANGE_FAILURE 2
+
+/* Routines dedicated to authentication */
+extern void *pg_be_scram_init(char *username);
+extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Routines to handle and check SCRAM-SHA-256 verifier */
+extern char *scram_build_verifier(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..2224ada 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pg_strong_random.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..460b0a1 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
- thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
+ pgstrcasecmp.o pqsignal.o thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.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..5bd2fbf
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,544 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static char *build_client_final_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static bool verify_server_proof(fe_scram_state *state);
+static bool generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = NONCE_SENT;
+ break;
+
+ case NONCE_SENT:
+ /* receive salt and server nonce, send response */
+ if (!read_server_first_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *output = build_client_final_message(state, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = PROOF_SENT;
+ break;
+
+ case PROOF_SENT:
+ /* receive server proof, and verify it */
+ if (!read_server_final_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *success = verify_server_proof(state);
+ *done = true;
+ state->state = FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ *done = true;
+ *success = false;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid SCRAM exchange state\n"));
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ *
+ * This routine is able to handle error messages e= sent by the server
+ * during the exchange of SASL messages. Returns NULL in case of error,
+ * setting errorMessage as well.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin == 'e')
+ {
+ /* Received an error */
+ begin++;
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr e)\n"));
+ return NULL;
+ }
+
+ begin++;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("error received from server in SASL exchange: %s\n"),
+ begin);
+ return NULL;
+ }
+
+ if (*begin != attr)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (%c expected)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr %c)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
+ return NULL;
+ }
+
+ /* Generate message */
+ msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+ buf = malloc(msglen + 1);
+ if (buf == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+ 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)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+static char *
+build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ 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 (pg_b64_enc_len(SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed client proof (%d found)\n"),
+ pg_b64_enc_len(SCRAM_KEY_LEN));
+ return NULL;
+ }
+
+ client_proof_len = pg_b64_encode((char *) client_proof,
+ SCRAM_KEY_LEN,
+ client_proof_base64);
+ if (client_proof_len < 0)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("failure when encoding client proof\n"));
+ return NULL;
+ }
+ client_proof_base64[client_proof_len] = '\0';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ if (state->client_final_message_without_proof == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+ char *server_nonce;
+
+ state->server_first_message = strdup(input);
+ if (!state->server_first_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ server_nonce = read_attr_value(&input, 'r', errormessage);
+ if (server_nonce == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+
+ state->server_nonce = strdup(server_nonce);
+ if (state->server_nonce == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ encoded_salt = read_attr_value(&input, 's', errormessage);
+ if (encoded_salt == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt)));
+ if (state->salt == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+ state->saltlen = pg_b64_decode(encoded_salt,
+ strlen(encoded_salt),
+ state->salt);
+ if (state->saltlen != SCRAM_SALT_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid salt length: found %d, expected %d\n"),
+ state->saltlen, SCRAM_SALT_LEN);
+ return false;
+ }
+
+ iterations_str = read_attr_value(&input, 'i', errormessage);
+ if (iterations_str == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN);
+ if (*endptr != '\0')
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message for number of iterations\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error message */
+ return false;
+ }
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid server proof\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ * Returns true of nonce has been succesfully generated, and false
+ * otherwise.
+ */
+static bool
+generate_nonce(char *buf, int len)
+{
+ if (!pg_strong_random(buf, len))
+ return false;
+
+ buf[len] = '\0';
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 404bc93..809f0d7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,35 @@ 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)
+ {
+ /* Use error message already if any set */
+ if (conn->errorMessage.len == 0)
+ 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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c5b737a..0f19d1c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha2_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha2.c');
}
my $libpqwalreceiver =
--
2.10.1
0008-Add-regression-tests-for-passwords.patchapplication/x-download; name=0008-Add-regression-tests-for-passwords.patchDownload
From 7feb2bba18f8c9dcc3573f762312a538fa436411 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 14:13:30 +0900
Subject: [PATCH 8/8] Add regression tests for passwords
---
src/test/regress/expected/password.out | 102 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..0f2f28d
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,102 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: plain, md5, scram, off, on.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | role_pwd1
+ regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
+ regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
+ regress_passwd4 | AAAAAAAAAAAAAA==:4096:c32d0b9681e3d827fe5b5287c0ba9c9e276fe69e611dcc93cddd41f122b82e5b:51c60a9394db319302dc2727e2b8cb6c463a507312dbbf53a09adbc01ec276d3
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ERROR: unsupported password protocol non_existent
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..4d789b0
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD 'foo' USING 'non_existent';
+ALTER ROLE regress_passwd1 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'; -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD 'foo' USING 'plain'; -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'; -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD 'kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'; -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.10.1
The organization of these patches makes sense to me.
On 10/20/16 1:14 AM, Michael Paquier wrote:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.
That's probably alright, although the patch contains a lot more changes
than I would imagine for a simple file move. I'll still have to review
that in detail.
- 0002, replacing PostmasterRandom by pg_strong_random(), with a fix
for the cancel key problem.
- 0003, adding for pg_strong_random() a fallback for any nix platform
not having /dev/random. This should be grouped with 0002, but I split
it for clarity.
Also makes sense, but will need more detailed review. I did not follow
the previous PostmasterRandom issues closely.
- 0004, Add encoding routines for base64 without whitespace in
src/common/. I improved the error handling here by making them return
-1 in case of error and let the caller handle the error.
I don't think we want to have two different copies of base64 routines.
Surely we can make the existing routines do what we want with a
parameter or two about whitespace and line length.
- 0005, Refactor decision-making of password encryption into a single routine.
It makes sense to factor this out. We probably don't need the pstrdup
if we just keep the string as is. (You could make an argument for it if
the input values were const char *.) We probably also don't need the
pfree. The Assert(0) can probably be done better. We usually use
elog() in such cases.
- 0006, Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE.
"protocol" is a weird choice here. Maybe something like "method" is
better. The way the USING clause is placed can be confusing. It's not
clear that it belongs to PASSWORD. If someone wants to augment another
clause in CREATE ROLE with a secondary argument, then it could get
really confusing. I'd suggest something to group things together, like
PASSWORD (val USING method). The method could be an identifier instead
of a string.
Please add an example to the documentation and explain better how this
interacts with the existing ENCRYPTED PASSWORD clause.
- 0007, the SCRAM implementation.
No documentation about pg_hba.conf changes, so I don't know how to use
this. ;-)
This implements SASL and SCRAM and SHA256. We need to be clear about
which term we advertise to users. An explanation in the missing
documentation would probably be a good start.
I would also like to see a test suite that covers the authentication
specifically.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Nov 5, 2016 at 12:58 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
The organization of these patches makes sense to me.
On 10/20/16 1:14 AM, Michael Paquier wrote:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.That's probably alright, although the patch contains a lot more changes
than I would imagine for a simple file move. I'll still have to review
that in detail.
The main point is to know if people are happy of having an interface
of the type pg_sha256_[init|update|finish] to tackle the fact that
core code contains a set of routines that map with some of the OpenSSL
APIs...
- 0002, replacing PostmasterRandom by pg_strong_random(), with a fix
for the cancel key problem.
- 0003, adding for pg_strong_random() a fallback for any nix platform
not having /dev/random. This should be grouped with 0002, but I split
it for clarity.Also makes sense, but will need more detailed review. I did not follow
the previous PostmasterRandom issues closely.
pademelon does not have /dev/random and /dev/urandom, so the issue is
related to having a fallback method... But Heikki feels that having a
method producing potentially weak keys should not be in
pg_strong_random(). I'd suggest to control that with a ./configure
switch and call it a day. Platforms without any of the four randomness
methods pg_strong_random includes play a dangerous game but...
- 0004, Add encoding routines for base64 without whitespace in
src/common/. I improved the error handling here by making them return
-1 in case of error and let the caller handle the error.I don't think we want to have two different copies of base64 routines.
Surely we can make the existing routines do what we want with a
parameter or two about whitespace and line length.
We could. Though after hacking on that I find cleaner copying the code
from encoding.c after removing the whitespace handling, as Heikki has
suggested.
- 0005, Refactor decision-making of password encryption into a single routine.
It makes sense to factor this out. We probably don't need the pstrdup
if we just keep the string as is. (You could make an argument for it if
the input values were const char *.) We probably also don't need the
pfree. The Assert(0) can probably be done better. We usually use
elog() in such cases.
Hm, OK. Agreed with that.
- 0006, Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE.
"protocol" is a weird choice here. Maybe something like "method" is
better. The way the USING clause is placed can be confusing. It's not
clear that it belongs to PASSWORD. If someone wants to augment another
clause in CREATE ROLE with a secondary argument, then it could get
really confusing. I'd suggest something to group things together, like
PASSWORD (val USING method). The method could be an identifier instead
of a string.
Why not.
Please add an example to the documentation and explain better how this
interacts with the existing ENCRYPTED PASSWORD clause.
Sure.
- 0007, the SCRAM implementation.
No documentation about pg_hba.conf changes, so I don't know how to use
this. ;-)
Oops. I have focused on the code a lot during last rewrite of the
patch and forgot that. I'll think about something.
This implements SASL and SCRAM and SHA256. We need to be clear about
which term we advertise to users. An explanation in the missing
documentation would probably be a good start.
pg_hba.conf uses "scram" as keyword, but scram refers to a family of
authentication methods. There is as well SCRAM-SHA-1, SCRAM-SHA-256
(what this patch does). Hence wouldn't it make sense to use
scram_sha256 in pg_hba.conf instead? If for example in the future
there is a SHA-512 version of SCRAM we could switch easily to that and
define scram_sha512.
There is also the channel binding to think about... So we could have a
list of keywords perhaps associated with SASL? Imagine for example:
sasl $algo,$channel_binding
Giving potentially:
sasl scram_sha256
sasl scram_sha256,channel
sasl scram_sha512
sasl scram_sha512,channel
In the case of the patch of this thread just the first entry would
make sense, once channel binding support is added a second
keyword/option could be added. And there are of course other methods
that could replace SCRAM..
I would also like to see a test suite that covers the authentication
specifically.
What you have in mind is a TAP test with a couple of roles and
pg_hba.conf getting rewritten then reloaded? Adding it in
src/test/recovery/ is the first place that comes in mind but that's
not really something related to recovery... Any ideas?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, 18 Oct 2016 16:35:27 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:
Hi
Attached is a rebased patch set for SCRAM, with the following things:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.
It seems, that client nonce generation in this patch is not
RFC-compliant.
RFC 5802 states that SCRAM nonce should be
a sequence of random printable ASCII
characters excluding ','
while this patch uses sequence of random bytes from pg_strong_random
function with zero byte appended.
It could cause following problems
1. If zero byte happens inside random sequence, nonce would be shorter
than expected, or even empty.
2. If one of bytes happens to be ASCII Code of comma, than server
to the client-first message, which includes copy of client nonce,
appended by server nonce,
as one of unquoted comman-separated field, would be parsed incorrectly.
Regards, Victor
--
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 9, 2016 at 3:13 PM, Victor Wagner <vitus@wagner.pp.ru> wrote:
On Tue, 18 Oct 2016 16:35:27 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:Hi
Attached is a rebased patch set for SCRAM, with the following things:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.It seems, that client nonce generation in this patch is not
RFC-compliant.RFC 5802 states that SCRAM nonce should be
a sequence of random printable ASCII
characters excluding ','while this patch uses sequence of random bytes from pg_strong_random
function with zero byte appended.
(This is about patch 0007, not 0001)
Thanks, you are right. That's not good as-is. So this basically means
that the characters here should be from 32 to 127 included.
generate_nonce needs just to be made smarter in the way it selects the
character bytes.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, 9 Nov 2016 15:23:11 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:
(This is about patch 0007, not 0001)
Thanks, you are right. That's not good as-is. So this basically means
that the characters here should be from 32 to 127 included.
Really, most important is to exclude comma from the list of allowed
characters. And this prevents us from using a range.
I'd do something like:
char prinables="0123456789ABCDE...xyz!@#*&+";
unsigned int r;
for (i=0;i<SCRAM_NONCE_SIZE;i++) {
pg_strong_random(&r,sizeof(unsigned int))
nonce[i]=printables[r%(sizeof(prinables)-1)]
/* -1 is here to exclude terminating zero byte*/
}
generate_nonce needs just to be made smarter in the way it selects the
character bytes.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Nov 5, 2016 at 9:36 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Sat, Nov 5, 2016 at 12:58 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:The organization of these patches makes sense to me.
On 10/20/16 1:14 AM, Michael Paquier wrote:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.That's probably alright, although the patch contains a lot more changes
than I would imagine for a simple file move. I'll still have to review
that in detail.The main point is to know if people are happy of having an interface
of the type pg_sha256_[init|update|finish] to tackle the fact that
core code contains a set of routines that map with some of the OpenSSL
APIs...
Or in short that:
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
- 0005, Refactor decision-making of password encryption into a single routine.
It makes sense to factor this out. We probably don't need the pstrdup
if we just keep the string as is. (You could make an argument for it if
the input values were const char *.) We probably also don't need the
pfree. The Assert(0) can probably be done better. We usually use
elog() in such cases.Hm, OK. Agreed with that.
I have replaced the Assert(0) with an elog(ERROR). OK for the
additional palloc and pfree calls. I just made that for consistency in
the routine for all the password types, but changed your way.
- 0006, Add clause PASSWORD val USING protocol to CREATE/ALTER ROLE.
"protocol" is a weird choice here. Maybe something like "method" is
better. The way the USING clause is placed can be confusing. It's not
clear that it belongs to PASSWORD. If someone wants to augment another
clause in CREATE ROLE with a secondary argument, then it could get
really confusing. I'd suggest something to group things together, like
PASSWORD (val USING method). The method could be an identifier instead
of a string.Why not.
Done.
Please add an example to the documentation and explain better how this
interacts with the existing ENCRYPTED PASSWORD clause.Sure.
Done.
- 0007, the SCRAM implementation.
No documentation about pg_hba.conf changes, so I don't know how to use
this. ;-)Oops. I have focused on the code a lot during last rewrite of the
patch and forgot that. I'll think about something.This implements SASL and SCRAM and SHA256. We need to be clear about
which term we advertise to users. An explanation in the missing
documentation would probably be a good start.pg_hba.conf uses "scram" as keyword, but scram refers to a family of
authentication methods. There is as well SCRAM-SHA-1, SCRAM-SHA-256
(what this patch does). Hence wouldn't it make sense to use
scram_sha256 in pg_hba.conf instead? If for example in the future
there is a SHA-512 version of SCRAM we could switch easily to that and
define scram_sha512.
OK, I have added more docs regarding the use of scram in pg_hba.conf,
particularly in client-auth.sgml to describe what scram is better than
md5 in terms of protection, and also completed the data of pg_hba.conf
about the new keyword used in it.
I would also like to see a test suite that covers the authentication
specifically.What you have in mind is a TAP test with a couple of roles and
pg_hba.conf getting rewritten then reloaded? Adding it in
src/test/recovery/ is the first place that comes in mind but that's
not really something related to recovery... Any ideas?
OK, hearing no complaints I have done exactly that and added a test in
src/test/recovery/ with patch 0009. This place may not be the best fit
though, but it looks like an overkill to add a new module in
src/test/modules just for that and that's a pretty compact test.
On Wed, Nov 9, 2016 at 3:13 PM, Victor Wagner <vitus@wagner.pp.ru> wrote:
On Tue, 18 Oct 2016 16:35:27 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:Attached is a rebased patch set for SCRAM, with the following things:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.It seems, that client nonce generation in this patch is not
RFC-compliant.RFC 5802 states that SCRAM nonce should be
a sequence of random printable ASCII
characters excluding ','while this patch uses sequence of random bytes from pg_strong_random
function with zero byte appended.
Right, I have fixed that in 0007 with a solution less exotic than what
you suggested upthread by scanning the ASCII characters between '!'
and '~', ignoring comma if selected.
--
Michael
Attachments:
0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchtext/x-diff; charset=US-ASCII; name=0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From 920623508bdc9dad446365b912db35527de1d067 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 12 Oct 2016 16:04:42 +0900
Subject: [PATCH 1/9] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 5 +-
contrib/pgcrypto/fortuna.c | 12 +-
contrib/pgcrypto/internal-sha2.c | 82 ++--
contrib/pgcrypto/sha2.h | 100 -----
src/common/Makefile | 6 +
{contrib/pgcrypto => src/common}/sha2.c | 722 ++++++++++++++++++--------------
src/common/sha2_openssl.c | 102 +++++
src/include/common/sha2.h | 115 +++++
src/tools/msvc/Mkvcbuild.pm | 22 +-
10 files changed, 699 insertions(+), 471 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (65%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..30619bf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..4085abb 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.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
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..ba74db6 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..e06f554 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..5ddfff8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 65%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9df..ea33fbc 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,4 +1,18 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
/*
* FILE: sha2.c
@@ -33,15 +47,19 @@
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
*
- * contrib/pgcrypto/sha2.c
+ * src/common/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +76,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +146,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void pg_sha512_last(pg_sha512_ctx *ctx);
+static void pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data);
+static void pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -249,15 +264,54 @@ static const uint64 sha512_initial_hash_value[8] = {
};
-/*** SHA-256: *********************************************************/
-void
-SHA256_Init(SHA256_CTX *context)
+static void
+pg_sha512_last(pg_sha512_ctx *ctx)
{
- if (context == NULL)
- return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount[0], ctx->bitcount[0]);
+ REVERSE64(ctx->bitcount[1], ctx->bitcount[1]);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
+ }
+ }
+ else
+ {
+ /* Prepare for final transform: */
+ memset(ctx->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Store the length of input data (in bits): */
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = ctx->bitcount[1];
+ *(uint64 *) &ctx->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = ctx->bitcount[0];
+
+ /* Final transform: */
+ pg_sha512_transform(ctx, ctx->buffer);
}
#ifdef SHA2_UNROLL_TRANSFORM
@@ -286,8 +340,13 @@ SHA256_Init(SHA256_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -303,17 +362,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -343,22 +402,27 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-256 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-256 generation.
+ */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+pg_sha256_transform(pg_sha256_ctx *ctx, const uint8 *data)
{
uint32 a,
b,
@@ -375,17 +439,17 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
*W256;
int j;
- W256 = (uint32 *) context->buffer;
+ W256 = (uint32 *) ctx->buffer;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -433,159 +497,20 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
} while (j < 64);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
-void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
-{
- size_t freespace,
- usedspace;
-
- /* Calling with no data is valid (we do nothing) */
- if (len == 0)
- return;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
- if (usedspace > 0)
- {
- /* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
-
- if (len >= freespace)
- {
- /* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- context->bitcount += freespace << 3;
- len -= freespace;
- data += freespace;
- SHA256_Transform(context, context->buffer);
- }
- else
- {
- /* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- context->bitcount += len << 3;
- /* Clean up: */
- usedspace = freespace = 0;
- return;
- }
- }
- while (len >= SHA256_BLOCK_LENGTH)
- {
- /* Process as many complete blocks as we can */
- SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
- }
- if (len > 0)
- {
- /* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- context->bitcount += len << 3;
- }
- /* Clean up: */
- usedspace = freespace = 0;
-}
-
-static void
-SHA256_Last(SHA256_CTX *context)
-{
- unsigned int usedspace;
-
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
-#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount, context->bitcount);
-#endif
- if (usedspace > 0)
- {
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
-
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
- {
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
- }
- else
- {
- if (usedspace < SHA256_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA256_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
- }
- }
- else
- {
- /* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
- }
- /* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
-
- /* Final transform: */
- SHA256_Transform(context, context->buffer);
-}
-
-void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
-{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
- {
- SHA256_Last(context);
-
-#ifndef WORDS_BIGENDIAN
- {
- /* Convert TO host byte order */
- int j;
-
- for (j = 0; j < 8; j++)
- {
- REVERSE32(context->state[j], context->state[j]);
- }
- }
-#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
- }
-
- /* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
-}
-
-
-/*** SHA-512: *********************************************************/
-void
-SHA512_Init(SHA512_CTX *context)
-{
- if (context == NULL)
- return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
-}
-
#ifdef SHA2_UNROLL_TRANSFORM
/* Unrolled SHA-512 round macros: */
@@ -615,8 +540,13 @@ SHA512_Init(SHA512_CTX *context)
j++; \
} while(0)
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -629,18 +559,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s0,
s1;
uint64 T1,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -669,22 +599,27 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = 0;
}
#else /* SHA2_UNROLL_TRANSFORM */
+/*
+ * Perform a round of transformation on a SHA-512 by using the given input
+ * data. This basically shuffles data around and uses the input data to
+ * add some extra randomness in the SHA-512 generation.
+ */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+pg_sha512_transform(pg_sha512_ctx *ctx, const uint8 *data)
{
uint64 a,
b,
@@ -698,18 +633,18 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
s1;
uint64 T1,
T2,
- *W512 = (uint64 *) context->buffer;
+ *W512 = (uint64 *) ctx->buffer;
int j;
/* Initialize registers with the prev. intermediate value */
- a = context->state[0];
- b = context->state[1];
- c = context->state[2];
- d = context->state[3];
- e = context->state[4];
- f = context->state[5];
- g = context->state[6];
- h = context->state[7];
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
j = 0;
do
@@ -759,22 +694,89 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
} while (j < 80);
/* Compute the current intermediate hash value */
- context->state[0] += a;
- context->state[1] += b;
- context->state[2] += c;
- context->state[3] += d;
- context->state[4] += e;
- context->state[5] += f;
- context->state[6] += g;
- context->state[7] += h;
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
/* Clean up */
a = b = c = d = e = f = g = h = T1 = T2 = 0;
}
#endif /* SHA2_UNROLL_TRANSFORM */
+static void
+pg_sha256_last(pg_sha256_ctx *ctx)
+{
+ unsigned int usedspace;
+
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
+#ifndef WORDS_BIGENDIAN
+ /* Convert FROM host byte order */
+ REVERSE64(ctx->bitcount, ctx->bitcount);
+#endif
+ if (usedspace > 0)
+ {
+ /* Begin padding with a 1 bit: */
+ ctx->buffer[usedspace++] = 0x80;
+
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
+ {
+ /* Set-up for the last transform: */
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ }
+ else
+ {
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
+ {
+ memset(&ctx->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
+ }
+ /* Do second-to-last transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+
+ /* And set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+ }
+ }
+ else
+ {
+ /* Set-up for the last transform: */
+ memset(ctx->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
+
+ /* Begin padding with a 1 bit: */
+ *ctx->buffer = 0x80;
+ }
+ /* Set the bit count: */
+ *(uint64 *) &ctx->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = ctx->bitcount;
+
+ /* Final transform: */
+ pg_sha256_transform(ctx, ctx->buffer);
+}
+
+/*
+ * pg_sha256_init
+ * Initialize calculation of SHA-256.
+ */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
+}
+
+
+/*
+ * pg_sha256_update
+ * Update SHA-256 using given input data.
+ */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,106 +785,165 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (ctx->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
/* Fill the buffer completely and process it */
- memcpy(&context->buffer[usedspace], data, freespace);
- ADDINC128(context->bitcount, freespace << 3);
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ctx->bitcount += freespace << 3;
len -= freespace;
data += freespace;
- SHA512_Transform(context, context->buffer);
+ pg_sha256_transform(ctx, ctx->buffer);
}
else
{
/* The buffer is not yet full */
- memcpy(&context->buffer[usedspace], data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ctx->bitcount += len << 3;
/* Clean up: */
usedspace = freespace = 0;
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
- SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ pg_sha256_transform(ctx, data);
+ ctx->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
/* There's left-overs, so save 'em */
- memcpy(context->buffer, data, len);
- ADDINC128(context->bitcount, len << 3);
+ memcpy(ctx->buffer, data, len);
+ ctx->bitcount += len << 3;
}
/* Clean up: */
usedspace = freespace = 0;
}
-static void
-SHA512_Last(SHA512_CTX *context)
+
+/*
+ * pg_sha256_final
+ * Finalize calculation of SHA-256 and save result to be reused by caller.
+ */
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
{
- unsigned int usedspace;
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
+ {
+ pg_sha256_last(ctx);
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
- /* Convert FROM host byte order */
- REVERSE64(context->bitcount[0], context->bitcount[0]);
- REVERSE64(context->bitcount[1], context->bitcount[1]);
+ {
+ /* Convert TO host byte order */
+ int j;
+
+ for (j = 0; j < 8; j++)
+ {
+ REVERSE32(ctx->state[j], ctx->state[j]);
+ }
+ }
#endif
+ memcpy(dest, ctx->state, PG_SHA256_DIGEST_LENGTH);
+ }
+
+ /* Clean up state data: */
+ memset(ctx, 0, sizeof(pg_sha256_ctx));
+}
+
+
+/*
+ * pg_sha512_init
+ * Initialize calculation of SHA-512.
+ */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+ memcpy(ctx->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA512_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
+}
+
+
+/*
+ * pg_sha512_update
+ * Update SHA-512 using given input data.
+ */
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ size_t freespace,
+ usedspace;
+
+ /* Calling with no data is valid (we do nothing) */
+ if (len == 0)
+ return;
+
+ usedspace = (ctx->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
- /* Begin padding with a 1 bit: */
- context->buffer[usedspace++] = 0x80;
+ /* Calculate how much free space is available in the buffer */
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (len >= freespace)
{
- /* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ /* Fill the buffer completely and process it */
+ memcpy(&ctx->buffer[usedspace], data, freespace);
+ ADDINC128(ctx->bitcount, freespace << 3);
+ len -= freespace;
+ data += freespace;
+ pg_sha512_transform(ctx, ctx->buffer);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
- {
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
- }
- /* Do second-to-last transform: */
- SHA512_Transform(context, context->buffer);
-
- /* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ /* The buffer is not yet full */
+ memcpy(&ctx->buffer[usedspace], data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ /* Clean up: */
+ usedspace = freespace = 0;
+ return;
}
}
- else
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
- /* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
-
- /* Begin padding with a 1 bit: */
- *context->buffer = 0x80;
+ /* Process as many complete blocks as we can */
+ pg_sha512_transform(ctx, data);
+ ADDINC128(ctx->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
- /* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
-
- /* Final transform: */
- SHA512_Transform(context, context->buffer);
+ if (len > 0)
+ {
+ /* There's left-overs, so save 'em */
+ memcpy(ctx->buffer, data, len);
+ ADDINC128(ctx->bitcount, len << 3);
+ }
+ /* Clean up: */
+ usedspace = freespace = 0;
}
+
+/*
+ * pg_sha512_final
+ * Finalize calculation of SHA-512 and save result to be reused by caller.
+ */
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last(context);
+ pg_sha512_last(ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -892,42 +953,55 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha512_ctx));
}
-/*** SHA-384: *********************************************************/
+/*
+ * pg_sha384_init
+ * Initialize calculation of SHA-384.
+ */
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
- context->bitcount[0] = context->bitcount[1] = 0;
+ memcpy(ctx->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA384_BLOCK_LENGTH);
+ ctx->bitcount[0] = ctx->bitcount[1] = 0;
}
+
+/*
+ * pg_sha384_update
+ * Update SHA-384 using given input data.
+ */
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha384_final
+ * Finalize calculation of SHA-384 and save result to be reused by caller.
+ */
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ pg_sha512_last((pg_sha512_ctx *) ctx);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -937,41 +1011,55 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
for (j = 0; j < 6; j++)
{
- REVERSE64(context->state[j], context->state[j]);
+ REVERSE64(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha384_ctx));
}
-/*** SHA-224: *********************************************************/
+
+/*
+ * pg_sha224_init
+ * Initialize calculation of SHA-224.
+ */
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *ctx)
{
- if (context == NULL)
+ if (ctx == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
- context->bitcount = 0;
+ memcpy(ctx->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(ctx->buffer, 0, PG_SHA256_BLOCK_LENGTH);
+ ctx->bitcount = 0;
}
+
+/*
+ * pg_sha224_update
+ * Update SHA-224 using given input data.
+ */
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) ctx, data, len);
}
+
+/*
+ * pg_sha224_final
+ * Finalize calculation of SHA-224 and save result to be reused by caller.
+ */
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
{
- /* If no digest buffer is passed, we don't bother doing this: */
- if (digest != NULL)
+ /* If no destination buffer is passed, we don't bother doing this: */
+ if (dest != NULL)
{
- SHA256_Last(context);
+ pg_sha256_last(ctx);
#ifndef WORDS_BIGENDIAN
{
@@ -980,13 +1068,13 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
for (j = 0; j < 8; j++)
{
- REVERSE32(context->state[j], context->state[j]);
+ REVERSE32(ctx->state[j], ctx->state[j]);
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(dest, ctx->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(ctx, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000..91d0c39
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000..015a905
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..21c4600 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -421,14 +430,15 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'fortuna.c', 'random.c',
- 'pgp-mpi-internal.c', 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'random.c', 'pgp-mpi-internal.c',
+ 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.2
0002-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchtext/x-diff; charset=US-ASCII; name=0002-Replace-PostmasterRandom-with-a-stronger-way-of-gene.patchDownload
From da2509670559aadf879cadee030060c1f57e3220 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 17 Oct 2016 11:52:50 +0300
Subject: [PATCH 2/9] Replace PostmasterRandom() with a stronger way of
generating randomness.
This adds a new routine, pg_strong_random() for generating random bytes,
for use in both frontend and backend. At the moment, it's only used in
the backend, but the upcoming SCRAM authentication patches need strong
random numbers in libpq as well.
pg_strong_random() is based on, and replaces, the existing implementation
in pgcrypto. It can acquire strong random numbers from a number of sources,
depending on what's available:
- OpenSSL RAND_bytes(), if built with OpenSSL
- On Windows, the native cryptographic functions are used
- /dev/urandom
- /dev/random
Original patch by Magnus Hagander, with further work by Michael Paquier
and me.
Discussion: <CAB7nPqRy3krN8quR9XujMVVHYtXJ0_60nqgVc6oUk8ygyVkZsA@mail.gmail.com>
---
contrib/pgcrypto/Makefile | 2 +-
contrib/pgcrypto/internal.c | 40 +++---
contrib/pgcrypto/random.c | 247 ------------------------------------
src/backend/libpq/auth.c | 27 +++-
src/backend/postmaster/postmaster.c | 153 ++++++----------------
src/backend/utils/init/globals.c | 2 +-
src/include/miscadmin.h | 2 +-
src/include/port.h | 3 +
src/port/Makefile | 2 +-
src/port/pg_strong_random.c | 148 +++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 13 +-
11 files changed, 249 insertions(+), 390 deletions(-)
delete mode 100644 contrib/pgcrypto/random.c
create mode 100644 src/port/pg_strong_random.c
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 4085abb..17a528e 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,7 +1,7 @@
# contrib/pgcrypto/Makefile
INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
- fortuna.c random.c pgp-mpi-internal.c imath.c
+ fortuna.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c
index 02ff976..ad942f7 100644
--- a/contrib/pgcrypto/internal.c
+++ b/contrib/pgcrypto/internal.c
@@ -626,8 +626,6 @@ static time_t check_time = 0;
static void
system_reseed(void)
{
- uint8 buf[1024];
- int n;
time_t t;
int skip = 1;
@@ -642,24 +640,34 @@ system_reseed(void)
else if (check_time == 0 ||
(t - check_time) > SYSTEM_RESEED_CHECK_TIME)
{
+ uint8 buf;
+
check_time = t;
/* roll dice */
- px_get_random_bytes(buf, 1);
- skip = buf[0] >= SYSTEM_RESEED_CHANCE;
- }
- /* clear 1 byte */
- px_memset(buf, 0, sizeof(buf));
-
- if (skip)
- return;
-
- n = px_acquire_system_randomness(buf);
- if (n > 0)
- fortuna_add_entropy(buf, n);
+ px_get_random_bytes(&buf, 1);
+ skip = (buf >= SYSTEM_RESEED_CHANCE);
- seed_time = t;
- px_memset(buf, 0, sizeof(buf));
+ /* clear 1 byte */
+ px_memset(&buf, 0, sizeof(buf));
+ }
+ if (!skip)
+ {
+ /*
+ * fortuna_add_entropy passes the input to SHA-256, so there's no
+ * point in giving it more than 256 bits of input to begin with.
+ */
+ uint8 buf[32];
+
+ if (!pg_strong_random(buf, sizeof(buf)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not acquire random data")));
+ fortuna_add_entropy(buf, sizeof(buf));
+
+ seed_time = t;
+ px_memset(buf, 0, sizeof(buf));
+ }
}
int
diff --git a/contrib/pgcrypto/random.c b/contrib/pgcrypto/random.c
deleted file mode 100644
index d72679e..0000000
--- a/contrib/pgcrypto/random.c
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * random.c
- * Acquire randomness from system. For seeding RNG.
- *
- * Copyright (c) 2001 Marko Kreen
- * 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.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/random.c
- */
-
-#include "postgres.h"
-
-#include "px.h"
-#include "utils/memdebug.h"
-
-/* how many bytes to ask from system random provider */
-#define RND_BYTES 32
-
-/*
- * Try to read from /dev/urandom or /dev/random on these OS'es.
- *
- * The list can be pretty liberal, as the device not existing
- * is expected event.
- */
-#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) \
- || defined(__NetBSD__) || defined(__DragonFly__) \
- || defined(__darwin__) || defined(__SOLARIS__) \
- || defined(__hpux) || defined(__HPUX__) \
- || defined(__CYGWIN__) || defined(_AIX)
-
-#define TRY_DEV_RANDOM
-
-#include <fcntl.h>
-#include <unistd.h>
-
-static int
-safe_read(int fd, void *buf, size_t count)
-{
- int done = 0;
- char *p = buf;
- int res;
-
- while (count)
- {
- res = read(fd, p, count);
- if (res <= 0)
- {
- if (errno == EINTR)
- continue;
- return PXE_DEV_READ_ERROR;
- }
- p += res;
- done += res;
- count -= res;
- }
- return done;
-}
-
-static uint8 *
-try_dev_random(uint8 *dst)
-{
- int fd;
- int res;
-
- fd = open("/dev/urandom", O_RDONLY, 0);
- if (fd == -1)
- {
- fd = open("/dev/random", O_RDONLY, 0);
- if (fd == -1)
- return dst;
- }
- res = safe_read(fd, dst, RND_BYTES);
- close(fd);
- if (res > 0)
- dst += res;
- return dst;
-}
-#endif
-
-/*
- * Try to find randomness on Windows
- */
-#ifdef WIN32
-
-#define TRY_WIN32_GENRAND
-#define TRY_WIN32_PERFC
-
-#include <windows.h>
-#include <wincrypt.h>
-
-/*
- * this function is from libtomcrypt
- *
- * try to use Microsoft crypto API
- */
-static uint8 *
-try_win32_genrand(uint8 *dst)
-{
- int res;
- HCRYPTPROV h = 0;
-
- res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
- (CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET));
- if (!res)
- res = CryptAcquireContext(&h, NULL, MS_DEF_PROV, PROV_RSA_FULL,
- CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET | CRYPT_NEWKEYSET);
- if (!res)
- return dst;
-
- res = CryptGenRandom(h, RND_BYTES, dst);
- if (res == TRUE)
- dst += RND_BYTES;
-
- CryptReleaseContext(h, 0);
- return dst;
-}
-
-static uint8 *
-try_win32_perfc(uint8 *dst)
-{
- int res;
- LARGE_INTEGER time;
-
- res = QueryPerformanceCounter(&time);
- if (!res)
- return dst;
-
- memcpy(dst, &time, sizeof(time));
- return dst + sizeof(time);
-}
-#endif /* WIN32 */
-
-
-/*
- * If we are not on Windows, then hopefully we are
- * on a unix-like system. Use the usual suspects
- * for randomness.
- */
-#ifndef WIN32
-
-#define TRY_UNIXSTD
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <time.h>
-#include <unistd.h>
-
-/*
- * Everything here is predictible, only needs some patience.
- *
- * But there is a chance that the system-specific functions
- * did not work. So keep faith and try to slow the attacker down.
- */
-static uint8 *
-try_unix_std(uint8 *dst)
-{
- pid_t pid;
- int x;
- PX_MD *md;
- struct timeval tv;
- int res;
-
- /* process id */
- pid = getpid();
- memcpy(dst, (uint8 *) &pid, sizeof(pid));
- dst += sizeof(pid);
-
- /* time */
- gettimeofday(&tv, NULL);
- memcpy(dst, (uint8 *) &tv, sizeof(tv));
- dst += sizeof(tv);
-
- /* pointless, but should not hurt */
- x = random();
- memcpy(dst, (uint8 *) &x, sizeof(x));
- dst += sizeof(x);
-
- /* hash of uninitialized stack and heap allocations */
- res = px_find_digest("sha1", &md);
- if (res >= 0)
- {
- uint8 *ptr;
- uint8 stack[8192];
- int alloc = 32 * 1024;
-
- VALGRIND_MAKE_MEM_DEFINED(stack, sizeof(stack));
- px_md_update(md, stack, sizeof(stack));
- ptr = px_alloc(alloc);
- VALGRIND_MAKE_MEM_DEFINED(ptr, alloc);
- px_md_update(md, ptr, alloc);
- px_free(ptr);
-
- px_md_finish(md, dst);
- px_md_free(md);
-
- dst += 20;
- }
-
- return dst;
-}
-#endif
-
-/*
- * try to extract some randomness for initial seeding
- *
- * dst should have room for 1024 bytes.
- */
-unsigned
-px_acquire_system_randomness(uint8 *dst)
-{
- uint8 *p = dst;
-
-#ifdef TRY_DEV_RANDOM
- p = try_dev_random(p);
-#endif
-#ifdef TRY_WIN32_GENRAND
- p = try_win32_genrand(p);
-#endif
-#ifdef TRY_WIN32_PERFC
- p = try_win32_perfc(p);
-#endif
-#ifdef TRY_UNIXSTD
- p = try_unix_std(p);
-#endif
- return p - dst;
-}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 0ba8530..44b2212 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -45,6 +45,12 @@ 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);
+/*----------------------------------------------------------------
+ * MD5 authentication
+ *----------------------------------------------------------------
+ */
+static int CheckMD5Auth(Port *port, char **logdetail);
+
/*----------------------------------------------------------------
* Ident authentication
@@ -535,9 +541,7 @@ ClientAuthentication(Port *port)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
- /* include the salt to use for computing the response */
- sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
- status = recv_and_check_password_packet(port, &logdetail);
+ status = CheckMD5Auth(port, &logdetail);
break;
case uaPassword:
@@ -692,10 +696,25 @@ recv_password_packet(Port *port)
/*----------------------------------------------------------------
- * MD5 authentication
+ * MD5 and password authentication
*----------------------------------------------------------------
*/
+static int
+CheckMD5Auth(Port *port, char **logdetail)
+{
+ /* include the salt to use for computing the response */
+ if (!pg_strong_random(port->md5Salt, sizeof(port->md5Salt)))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return STATUS_ERROR;
+ }
+
+ sendAuthRequest(port, AUTH_REQ_MD5, port->md5Salt, 4);
+ return recv_and_check_password_packet(port, logdetail);
+}
+
+
/*
* Called when we have sent an authorization request for a password.
* Get the response and check it.
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 24add74..2baa087 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -358,14 +358,6 @@ static volatile bool avlauncher_needs_signal = false;
static volatile bool StartWorkerNeeded = true;
static volatile bool HaveCrashedWorker = false;
-/*
- * State for assigning random salts and cancel keys.
- * Also, the global MyCancelKey passes the cancel key assigned to a given
- * backend from the postmaster to that backend (via fork).
- */
-static unsigned int random_seed = 0;
-static struct timeval random_start_time;
-
#ifdef USE_BONJOUR
static DNSServiceRef bonjour_sdref = NULL;
#endif
@@ -403,8 +395,6 @@ static void processCancelRequest(Port *port, void *pkt);
static int initMasks(fd_set *rmask);
static void report_fork_failure_to_client(Port *port, int errnum);
static CAC_state canAcceptConnections(void);
-static long PostmasterRandom(void);
-static void RandomSalt(char *salt, int len);
static void signal_child(pid_t pid, int signal);
static bool SignalSomeChildren(int signal, int targets);
static void TerminateChildren(int signal);
@@ -579,9 +569,11 @@ PostmasterMain(int argc, char *argv[])
* Initialize random(3) so we don't get the same values in every run.
*
* Note: the seed is pretty predictable from externally-visible facts such
- * as postmaster start time, so avoid using random() for security-critical
- * random values during postmaster startup. At the time of first
- * connection, PostmasterRandom will select a hopefully-more-random seed.
+ * as postmaster start time, so don't use random() for security-critical
+ * random values (use pg_strong_random() instead). Backends select a
+ * somewhat more random seed after forking, in BackendRun(), based on the
+ * PID and session start timestamp, but that is still not suitable for
+ * security-critical values.
*/
srandom((unsigned int) (MyProcPid ^ MyStartTime));
@@ -1292,8 +1284,6 @@ PostmasterMain(int argc, char *argv[])
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
- /* PostmasterRandom wants its own copy */
- gettimeofday(&random_start_time, NULL);
/*
* We're ready to rock and roll...
@@ -2345,15 +2335,6 @@ ConnCreate(int serverFd)
}
/*
- * Precompute password salt values to use for this connection. It's
- * slightly annoying to do this long in advance of knowing whether we'll
- * need 'em or not, but we must do the random() calls before we fork, not
- * after. Else the postmaster's random sequence won't get advanced, and
- * all backends would end up using the same salt...
- */
- RandomSalt(port->md5Salt, sizeof(port->md5Salt));
-
- /*
* Allocate GSSAPI specific state struct
*/
#ifndef EXEC_BACKEND
@@ -3905,7 +3886,12 @@ BackendStartup(Port *port)
* backend will have its own copy in the forked-off process' value of
* MyCancelKey, so that it can transmit the key to the frontend.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
+ return STATUS_ERROR;
+ }
bn->cancel_key = MyCancelKey;
/* Pass down canAcceptConnections state */
@@ -4213,13 +4199,6 @@ BackendRun(Port *port)
int usecs;
int i;
- /*
- * Don't want backend to be able to see the postmaster random number
- * generator state. We have to clobber the static random_seed *and* start
- * a new random sequence in the random() library function.
- */
- random_seed = 0;
- random_start_time.tv_usec = 0;
/* slightly hacky way to convert timestamptz into integers */
TimestampDifference(0, port->SessionStartTime, &secs, &usecs);
srandom((unsigned int) (MyProcPid ^ (usecs << 12) ^ secs));
@@ -5068,66 +5047,6 @@ StartupPacketTimeoutHandler(void)
/*
- * RandomSalt
- */
-static void
-RandomSalt(char *salt, int len)
-{
- long rand;
- int i;
-
- /*
- * We use % 255, sacrificing one possible byte value, so as to ensure that
- * all bits of the random() value participate in the result. While at it,
- * add one to avoid generating any null bytes.
- */
- for (i = 0; i < len; i++)
- {
- rand = PostmasterRandom();
- salt[i] = (rand % 255) + 1;
- }
-}
-
-/*
- * PostmasterRandom
- *
- * Caution: use this only for values needed during connection-request
- * processing. Otherwise, the intended property of having an unpredictable
- * delay between random_start_time and random_stop_time will be broken.
- */
-static long
-PostmasterRandom(void)
-{
- /*
- * Select a random seed at the time of first receiving a request.
- */
- if (random_seed == 0)
- {
- do
- {
- struct timeval random_stop_time;
-
- gettimeofday(&random_stop_time, NULL);
-
- /*
- * We are not sure how much precision is in tv_usec, so we swap
- * the high and low 16 bits of 'random_stop_time' and XOR them
- * with 'random_start_time'. On the off chance that the result is
- * 0, we loop until it isn't.
- */
- random_seed = random_start_time.tv_usec ^
- ((random_stop_time.tv_usec << 16) |
- ((random_stop_time.tv_usec >> 16) & 0xffff));
- }
- while (random_seed == 0);
-
- srandom(random_seed);
- }
-
- return random();
-}
-
-/*
* Count up number of child processes of specified types (dead_end chidren
* are always excluded).
*/
@@ -5304,31 +5223,37 @@ StartAutovacuumWorker(void)
* we'd better have something random in the field to prevent
* unfriendly people from sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
- bn->cancel_key = MyCancelKey;
+ if (pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ bn->cancel_key = MyCancelKey;
- /* Autovac workers are not dead_end and need a child slot */
- bn->dead_end = false;
- bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
- bn->bgworker_notify = false;
+ /* Autovac workers are not dead_end and need a child slot */
+ bn->dead_end = false;
+ bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
+ bn->bgworker_notify = false;
- bn->pid = StartAutoVacWorker();
- if (bn->pid > 0)
- {
- bn->bkend_type = BACKEND_TYPE_AUTOVAC;
- dlist_push_head(&BackendList, &bn->elem);
+ bn->pid = StartAutoVacWorker();
+ if (bn->pid > 0)
+ {
+ bn->bkend_type = BACKEND_TYPE_AUTOVAC;
+ dlist_push_head(&BackendList, &bn->elem);
#ifdef EXEC_BACKEND
- ShmemBackendArrayAdd(bn);
+ ShmemBackendArrayAdd(bn);
#endif
- /* all OK */
- return;
+ /* all OK */
+ return;
+ }
+
+ /*
+ * fork failed, fall through to report -- actual error message
+ * was logged by StartAutoVacWorker
+ */
+ (void) ReleasePostmasterChildSlot(bn->child_slot);
}
+ else
+ ereport(LOG,
+ (errmsg("could not generate random query cancel key")));
- /*
- * fork failed, fall through to report -- actual error message was
- * logged by StartAutoVacWorker
- */
- (void) ReleasePostmasterChildSlot(bn->child_slot);
free(bn);
}
else
@@ -5616,7 +5541,11 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
* have something random in the field to prevent unfriendly people from
* sending cancels to them.
*/
- MyCancelKey = PostmasterRandom();
+ if (!pg_strong_random(&MyCancelKey, sizeof(MyCancelKey)))
+ {
+ rw->rw_crashed_at = GetCurrentTimestamp();
+ return false;
+ }
bn->cancel_key = MyCancelKey;
bn->child_slot = MyPMChildSlot = AssignPostmasterChildSlot();
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index f232083..634578d 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -38,7 +38,7 @@ volatile uint32 CritSectionCount = 0;
int MyProcPid;
pg_time_t MyStartTime;
struct Port *MyProcPort;
-long MyCancelKey;
+uint32 MyCancelKey;
int MyPMChildSlot;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 78545da..5e623a1 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -162,7 +162,7 @@ extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT struct Port *MyProcPort;
extern PGDLLIMPORT struct Latch *MyLatch;
-extern long MyCancelKey;
+extern uint32 MyCancelKey;
extern int MyPMChildSlot;
extern char OutputFileName[];
diff --git a/src/include/port.h b/src/include/port.h
index 8a63958..9c8da65 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -454,6 +454,9 @@ extern int pg_codepage_to_encoding(UINT cp);
extern char *inet_net_ntop(int af, const void *src, int bits,
char *dst, size_t size);
+/* port/pg_strong_random.c */
+extern bool pg_strong_random(void *buf, size_t len);
+
/* port/pgcheckdir.c */
extern int pg_check_dir(const char *dir);
diff --git a/src/port/Makefile b/src/port/Makefile
index bc9b63a..d34f409 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -32,7 +32,7 @@ LIBS += $(PTHREAD_LIBS)
OBJS = $(LIBOBJS) $(PG_CRC32C_OBJS) chklocale.o erand48.o inet_net_ntop.o \
noblock.o path.o pgcheckdir.o pgmkdirp.o pgsleep.o \
- pgstrcasecmp.o pqsignal.o \
+ pg_strong_random.o pgstrcasecmp.o pqsignal.o \
qsort.o qsort_arg.o quotes.o sprompt.o tar.o thread.o
# foo_srv.o and foo.o are both built from foo.c, but only foo.o has -DFRONTEND
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
new file mode 100644
index 0000000..a404111
--- /dev/null
+++ b/src/port/pg_strong_random.c
@@ -0,0 +1,148 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_strong_random.c
+ * pg_strong_random() function to return a strong random number
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/port/pg_strong_random.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef USE_SSL
+#include <openssl/rand.h>
+#endif
+#ifdef WIN32
+#include <Wincrypt.h>
+#endif
+
+static bool random_from_file(char *filename, void *buf, size_t len);
+
+#ifdef WIN32
+/*
+ * Cache a global crypto provider that only gets freed when the process
+ * exits, in case we need random numbers more than once.
+ */
+static HCRYPTPROV hProvider = 0;
+#endif
+
+/*
+ * Read (random) bytes from a file.
+ */
+static bool
+random_from_file(char *filename, void *buf, size_t len)
+{
+ int f;
+ char *p = buf;
+ ssize_t res;
+
+ f = open(filename, O_RDONLY, 0);
+ if (f == -1)
+ return false;
+
+ while (len)
+ {
+ res = read(f, p, len);
+ if (res <= 0)
+ {
+ if (errno == EINTR)
+ continue; /* interrupted by signal, just retry */
+
+ close(f);
+ return false;
+ }
+
+ p += res;
+ len -= res;
+ }
+
+ close(f);
+ return true;
+}
+
+/*
+ * pg_strong_random
+ *
+ * Generate requested number of random bytes. The bytes are
+ * cryptographically strong random, suitable for use e.g. in key
+ * generation.
+ *
+ * The bytes can be acquired from a number of sources, depending
+ * on what's available. We try the following, in this order:
+ *
+ * 1. OpenSSL's RAND_bytes()
+ * 2. Windows' CryptGenRandom() function
+ * 3. /dev/urandom
+ * 4. /dev/random
+ *
+ * Returns true on success, and false if none of the sources
+ * were available. NB: It is important to check the return value!
+ * Proceeding with key generation when no random data was available
+ * would lead to predictable keys and security issues.
+ */
+bool
+pg_strong_random(void *buf, size_t len)
+{
+#ifdef USE_SSL
+
+ /*
+ * When built with OpenSSL, first try the random generation function from
+ * there.
+ */
+ if (RAND_bytes(buf, len) == 1)
+ return true;
+#endif
+
+#ifdef WIN32
+
+ /*
+ * Windows has CryptoAPI for strong cryptographic numbers.
+ */
+ if (hProvider == 0)
+ {
+ if (!CryptAcquireContext(&hProvider,
+ NULL,
+ MS_DEF_PROV,
+ PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+ {
+ /*
+ * On failure, set back to 0 in case the value was for some reason
+ * modified.
+ */
+ hProvider = 0;
+ }
+ }
+
+ /* Re-check in case we just retrieved the provider */
+ if (hProvider != 0)
+ {
+ if (CryptGenRandom(hProvider, len, buf))
+ return true;
+ }
+#endif
+
+ /*
+ * If there is no OpenSSL and no CryptoAPI (or they didn't work), then
+ * fall back on reading /dev/urandom or even /dev/random.
+ */
+ if (random_from_file("/dev/urandom", buf, len))
+ return true;
+ if (random_from_file("/dev/random", buf, len))
+ return true;
+
+ /* None of the sources were available. */
+ return false;
+}
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 21c4600..1b2c8e4 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -92,7 +92,7 @@ sub mkvcbuild
srandom.c getaddrinfo.c gettimeofday.c inet_net_ntop.c kill.c open.c
erand48.c snprintf.c strlcat.c strlcpy.c dirmod.c noblock.c path.c
pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c pqsignal.c
- mkdtemp.c qsort.c qsort_arg.c quotes.c system.c
+ mkdtemp.c pg_strong_random.c qsort.c qsort_arg.c quotes.c system.c
sprompt.c tar.c thread.c getopt.c getopt_long.c dirent.c
win32env.c win32error.c win32security.c win32setlocale.c);
@@ -430,12 +430,11 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'internal.c',
- 'internal-sha2.c', 'blf.c',
- 'rijndael.c', 'fortuna.c',
- 'random.c', 'pgp-mpi-internal.c',
- 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'pgp-mpi-internal.c', 'imath.c');
}
$pgcrypto->AddReference($postgres);
$pgcrypto->AddReference($libpgcommon);
--
2.10.2
0003-Implement-last-resort-method-in-pg_strong_random.patchtext/x-diff; charset=US-ASCII; name=0003-Implement-last-resort-method-in-pg_strong_random.patchDownload
From 0f410fdf8391dce1ae2ce9e7794cc18de3732e2e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 19 Oct 2016 11:56:47 +0900
Subject: [PATCH 3/9] Implement last-resort method in pg_strong_random
This method will be used on nix platforms in the case where any of the
previous methods failed. It uses pid(), gettimeofday() and random() to
grab some entropy, even if this is predictible, and processes the
obtained value through a SHA-256 hash.
---
src/port/pg_strong_random.c | 76 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/src/port/pg_strong_random.c b/src/port/pg_strong_random.c
index a404111..17c217a 100644
--- a/src/port/pg_strong_random.c
+++ b/src/port/pg_strong_random.c
@@ -18,6 +18,8 @@
#include "postgres_fe.h"
#endif
+#include "common/sha2.h"
+
#include <fcntl.h>
#include <unistd.h>
@@ -29,6 +31,9 @@
#endif
static bool random_from_file(char *filename, void *buf, size_t len);
+#ifndef WIN32
+static bool random_from_std(void *dst, size_t len);
+#endif
#ifdef WIN32
/*
@@ -72,6 +77,67 @@ random_from_file(char *filename, void *buf, size_t len)
return true;
}
+#ifndef WIN32
+/*
+ * This is not available on Windows, and the cryptography standards available
+ * there are enough anyway to generate randomness.
+ */
+#include <sys/time.h>
+#include <time.h>
+
+/*
+ * Generate some random data using environment data as entropy
+ * through SHA-256. All the other methods failed, this can be used
+ * as a last resort method even if it is predictible, and rather
+ * slow compared to the rest.
+ */
+static bool
+random_from_std(void *dst, size_t len)
+{
+ pid_t pid;
+ size_t remaining = len;
+
+ /* process ID */
+ pid = getpid();
+
+ /*
+ * Compute a random value by generating successive chunks worth
+ * of PG_SHA256_DIGEST_LENGTH bytes.
+ */
+ while (remaining != 0)
+ {
+ int x;
+ struct timeval tv;
+ pg_sha256_ctx ctx;
+ uint8 sha_buf[PG_SHA256_DIGEST_LENGTH];
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, (uint8 *) &pid, sizeof(pid));
+
+ /* time */
+ gettimeofday(&tv, NULL);
+ pg_sha256_update(&ctx, (uint8 *) &tv, sizeof(tv));
+
+ /* pointless, but should not hurt */
+ x = random();
+ pg_sha256_update(&ctx, (uint8 *) &x, sizeof(x));
+ pg_sha256_final(&ctx, sha_buf);
+
+ /* copy the chunk generated in the result */
+ memcpy(dst, sha_buf, Min(remaining, PG_SHA256_DIGEST_LENGTH));
+
+ /* and prepare to loop further */
+ if (remaining >= PG_SHA256_DIGEST_LENGTH)
+ remaining -= PG_SHA256_DIGEST_LENGTH;
+ else
+ remaining = 0;
+ dst = (uint8 *) dst + PG_SHA256_DIGEST_LENGTH;
+ }
+
+ return true;
+}
+#endif
+
/*
* pg_strong_random
*
@@ -86,6 +152,8 @@ random_from_file(char *filename, void *buf, size_t len)
* 2. Windows' CryptGenRandom() function
* 3. /dev/urandom
* 4. /dev/random
+ * 5. Use the internal, predictible method computing a value through SHA-256
+ * with system-related entropy.
*
* Returns true on success, and false if none of the sources
* were available. NB: It is important to check the return value!
@@ -143,6 +211,14 @@ pg_strong_random(void *buf, size_t len)
if (random_from_file("/dev/random", buf, len))
return true;
+#ifndef WIN32
+ /*
+ * As final method, generate some randomness using the in-house method.
+ */
+ if (random_from_std(buf, len))
+ return true;
+#endif
+
/* None of the sources were available. */
return false;
}
--
2.10.2
0004-Add-encoding-routines-for-base64-without-whitespace-.patchtext/x-diff; charset=US-ASCII; name=0004-Add-encoding-routines-for-base64-without-whitespace-.patchDownload
From 286776a41752c8177b0219690cc68b68fcfdbf73 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 15:28:45 +0900
Subject: [PATCH 4/9] Add encoding routines for base64 without whitespace in
src/common/
Those routines are taken from the backend's encode.c, and adapted for
SCRAM-SHA-256, where base64 should not support whitespaces as per RFC5802.
---
src/common/Makefile | 6 +-
src/common/base64.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/base64.h | 19 +++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
4 files changed, 224 insertions(+), 4 deletions(-)
create mode 100644 src/common/base64.c
create mode 100644 src/include/common/base64.h
diff --git a/src/common/Makefile b/src/common/Makefile
index 5ddfff8..49e41cf 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/base64.c b/src/common/base64.c
new file mode 100644
index 0000000..0c9eba4
--- /dev/null
+++ b/src/common/base64.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * base64.c
+ * Set of encoding and decoding routines for base64 without support
+ * for whitespace. In case of failure, those routines return -1 in case
+ * of error to let the caller do any error handling.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/base64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/base64.h"
+
+/*
+ * 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,
+};
+
+/*
+ * pg_b64_encode
+ *
+ * Encode into base64 the given string. Returns the length of the encoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_encode(const char *src, int len, char *dst)
+{
+ char *p;
+ 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 (pos != 2)
+ {
+ *p++ = _base64[(buf >> 18) & 0x3f];
+ *p++ = _base64[(buf >> 12) & 0x3f];
+ *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+ *p++ = '=';
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_decode
+ *
+ * Decode the given base64 string. Returns the length of the decoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_decode(const char *src, int 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++;
+
+ /* Leave if a whitespace is found */
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ return -1;
+
+ if (c == '=')
+ {
+ /* end sequence */
+ if (!end)
+ {
+ if (pos == 2)
+ end = 1;
+ else if (pos == 3)
+ end = 2;
+ else
+ {
+ /*
+ * Unexpected "=" character found while decoding base64
+ * sequence.
+ */
+ return -1;
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+ /* invalid symbol found */
+ return -1;
+ }
+ }
+ /* 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)
+ {
+ /*
+ * base64 end sequence is invalid. Input data is missing padding,
+ * is truncated or is otherwise corrupted.
+ */
+ return -1;
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_enc_len
+ *
+ * Returns to caller the length of the string if it were encoded with
+ * base64 based on the length provided by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual encoding.
+ */
+int
+pg_b64_enc_len(int srclen)
+{
+ /* 3 bytes will be converted to 4 */
+ return (srclen + 2) * 4 / 3;
+}
+
+/*
+ * pg_b64_dec_len
+ *
+ * Returns to caller the length of the string if it were to be decoded
+ * with base64, based on the length given by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual decoding.
+ */
+int
+pg_b64_dec_len(int srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/include/common/base64.h b/src/include/common/base64.h
new file mode 100644
index 0000000..8ad8eb6
--- /dev/null
+++ b/src/include/common/base64.h
@@ -0,0 +1,19 @@
+/*
+ * base64.h
+ * Encoding and decoding routines for base64 without whitespace
+ * support.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/base64.h
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+/* base 64 */
+int pg_b64_encode(const char *src, int len, char *dst);
+int pg_b64_decode(const char *src, int len, char *dst);
+int pg_b64_enc_len(int srclen);
+int pg_b64_dec_len(int srclen);
+
+#endif /* BASE64_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 1b2c8e4..c5b737a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.10.2
0005-Refactor-decision-making-of-password-encryption-into.patchtext/x-diff; charset=US-ASCII; name=0005-Refactor-decision-making-of-password-encryption-into.patchDownload
From 7ef5de2d91741b0f2a416af1509a5c0027a3e7ed Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 18:08:40 +0900
Subject: [PATCH 5/9] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 83 ++++++++++++++++++++++++++++++++-------------
1 file changed, 59 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99..def8ea1 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,49 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password if any transformation on the input
+ * string has been done.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = password;
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = password;
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ elog(ERROR, "incorrect password type");
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +126,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +438,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,17 +844,12 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
--
2.10.2
0006-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchtext/x-diff; charset=US-ASCII; name=0006-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From 9278b2e18167060715f77a14c6a42058df79a69c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 18:24:10 +0900
Subject: [PATCH 6/9] Add clause PASSWORD (val USING protocol) to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is. This extension is useful to support future
protocols, particularly SCRAM-SHA-256.
---
doc/src/sgml/ref/alter_role.sgml | 10 +++++
doc/src/sgml/ref/create_role.sgml | 26 +++++++++++
src/backend/commands/user.c | 90 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 +++
4 files changed, 126 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9..43e45d50 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD ( '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">method</replaceable>' )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> ( '<replaceable class="parameter">password</replaceable>' USING '<replaceable class="parameter">method</replaceable>' )</term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
@@ -280,6 +282,14 @@ ALTER ROLE davide WITH PASSWORD 'hu8jmn3';
</para>
<para>
+ Change a role's password using MD5-encryption:
+
+<programlisting>
+ALTER ROLE lionel WITH PASSWORD ('hu8jmn3' USING 'md5');
+</programlisting>
+ </para>
+
+ <para>
Remove a role's password:
<programlisting>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..25911ec 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD ( '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">method</replaceable>' )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,23 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> ( '<replaceable class="parameter">password</replaceable>' USING '<replaceable class="parameter">method</replaceable>' )</term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested method. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The methods supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, and <literal>plain</> to use an
+ unencrypted password. If the password string is already in
+ MD5-encrypted format, then it is stored encrypted even if
+ <literal>plain</> is specified.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
@@ -426,6 +444,14 @@ CREATE USER davide WITH PASSWORD 'jw8s0F4';
</para>
<para>
+ Create a role with a MD5-encrypted password:
+
+<programlisting>
+CREATE USER lionel WITH PASSWORD ('asdh7as' USING 'md5');
+</programlisting>
+ </para>
+
+ <para>
Create a role with a password that is valid until the end of 2004.
After one second has ticked in 2005, the password is no longer
valid.
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index def8ea1..19eeb04 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -176,7 +176,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "methodPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -184,10 +185,49 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "methodPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the method wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *method = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(method, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(method, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password method %s", method)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -307,8 +347,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -582,6 +620,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "methodPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -589,10 +628,49 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "methodPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the method wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *method = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(method, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(method, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password method %s", method)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -680,8 +758,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0ec1cd3..fad4571 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -938,6 +938,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD '(' Sconst USING Sconst ')'
+ {
+ $$ = makeDefElem("methodPassword",
+ (Node *)list_make2(makeString($3),
+ makeString($5)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.10.2
0007-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchtext/x-diff; charset=US-ASCII; name=0007-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From 0c73872ebe781bee30598344339afc5b400bdda8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 19:39:32 +0900
Subject: [PATCH 7/9] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used as hashing function. This commit introduces the basic
SASL communication protocol plugged in on top of the existing
infrastructure.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 19 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/client-auth.sgml | 37 +-
doc/src/sgml/config.sgml | 13 +-
doc/src/sgml/protocol.sgml | 147 +++-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/commands/user.c | 52 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 972 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 111 +++
src/backend/libpq/crypt.c | 69 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 ++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 56 ++
src/include/libpq/crypt.h | 8 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 4 +-
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 35 +
src/interfaces/libpq/.gitignore | 5 +
src/interfaces/libpq/Makefile | 17 +-
src/interfaces/libpq/fe-auth-scram.c | 562 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 108 +++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
32 files changed, 2479 insertions(+), 82 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ Assert(0); /* should not happen */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bac169a..7cec087 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 960f5b5..817ed57 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -421,6 +421,17 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>scram</></term>
+ <listitem>
+ <para>
+ Require the client to supply a password encrypted with
+ SCRAM-SHA-256 for authentication.
+ See <xref linkend="auth-password"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>password</></term>
<listitem>
<para>
@@ -650,17 +661,24 @@ host all all localhost trust
host postgres all 192.168.93.0/24 ident
# Allow any user from host 192.168.12.10 to connect to database
-# "postgres" if the user's password is correctly supplied.
+# "postgres" if the user's password is correctly supplied and is
+# using the correct password method.
#
# TYPE DATABASE USER ADDRESS METHOD
host postgres all 192.168.12.10/32 md5
+host postgres all 192.168.12.10/32 scram
# Allow any user from hosts in the example.com domain to connect to
-# any database if the user's password is correctly supplied.
+# any database if the user's password is correctly supplied and is using
+# a MD5-encrypted password.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all .example.com md5
+# Same as previous entry, except that the supplied password must be
+# encrypted with SCRAM-SHA-256.
+host all all .example.com scram
+
# In the absence of preceding "host" lines, these two lines will
# reject all connections from 192.168.54.1 (since that entry will be
# matched first), but allow GSSAPI connections from anywhere else
@@ -888,9 +906,9 @@ omicron bryanh guest1
<para>
The password-based authentication methods are <literal>md5</>
- and <literal>password</>. These methods operate
+ <literal>scram</> and <literal>password</>. These methods operate
similarly except for the way that the password is sent across the
- connection, namely MD5-hashed and clear-text respectively.
+ connection, namely MD5-hashed, SCRAM-SHA-256 and clear-text respectively.
</para>
<para>
@@ -905,6 +923,17 @@ omicron bryanh guest1
</para>
<para>
+ <literal>scram</> has more advantages than <literal>md5</> as it
+ protects from cases where the hashed password is taken directly from
+ <structname>pg_authid</structname> in which case a connection using
+ only the stolen hash is possible without knowing the password behind
+ it. It protects as well from password interception and data sniffing
+ where the password data could be directly obtained from the network
+ as well as man-in-the-middle (MITM) attacks. So it is strongly
+ encouraged to use it over <literal>md5</> for password-based deployments.
+ </para>
+
+ <para>
<productname>PostgreSQL</productname> database passwords are
separate from operating system user passwords. The password for
each database user is stored in the <literal>pg_authid</> system
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index adab2f8..e1b7c5b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1176,9 +1176,10 @@ include_dir 'conf.d'
password is to be encrypted. The default value is <literal>md5</>, which
stores the password as an MD5 hash. Setting this to <literal>plain</> stores
it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
- aliases for <literal>md5</> and <literal>plain</>, respectively.
- </para>
-
+ aliases for <literal>md5</> and <literal>plain</>, respectively. Setting
+ this parameter to <literal>scram</> will encrypt the password with
+ SCRAM-SHA-256.
+ </para>
</listitem>
</varlistentry>
@@ -1251,8 +1252,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 50cf527..1c23c1d 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>
@@ -2585,6 +2614,114 @@ 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>
@@ -4347,7 +4484,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 25911ec..7750a88 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -229,16 +229,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
@@ -254,10 +254,11 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
attribute, but you can nonetheless define one for roles without it.)
If you do not plan to use password authentication you can omit this
option. The methods supported are <literal>md5</> to enforce
- a password to be MD5-encrypted, and <literal>plain</> to use an
- unencrypted password. If the password string is already in
- MD5-encrypted format, then it is stored encrypted even if
- <literal>plain</> is specified.
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA-256, and <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-SHA-256 format, then it is stored encrypted
+ even if another protocol is specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 19eeb04..21fe6c1 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,10 +70,10 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password if any transformation on the input
- * string has been done.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password if any transformation on
+ * the input string has been done.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -82,11 +83,12 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * If a password is already identified as MD5-encrypted or
+ * SCRAM-encrypted, it is used as such. If the password given is not
+ * encrypted, adapt it depending on the type wanted by the caller of
+ * this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = password;
else
{
@@ -102,6 +104,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
elog(ERROR, "incorrect password type");
}
@@ -222,6 +227,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(method, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(method, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -451,11 +458,22 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
/*
* Build a tuple to insert
@@ -663,6 +681,8 @@ AlterRole(AlterRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(method, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(method, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -859,11 +879,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
+
/*
* Build an updated tuple, perusing the information just obtained
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..7d41ce8
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,972 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.h"
+
+/*
+ * Status data for SCRAM. This should be kept internal to this file.
+ */
+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];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ char *server_first_message;
+ char *server_nonce; /* base64-encoded */
+ char *server_signature;
+} scram_state;
+
+/*
+ * Internal error codes for exchange functions.
+ * Callers of the exchange routines do not need to be aware of any of
+ * that and should just send messages generated here.
+ */
+typedef enum
+{
+ SASL_NO_ERROR = 0,
+ /* error codes */
+ SASL_INVALID_ENCODING,
+ SASL_EXTENSIONS_NOT_SUPPORTED,
+ SASL_CHANNEL_BINDING_UNMATCH,
+ SASL_CHANNEL_BINDING_NO_SUPPORT,
+ SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED,
+ SASL_UNKNOWN_USER,
+ SASL_INVALID_PROOF,
+ SASL_INVALID_USERNAME_ENCODING,
+ SASL_NO_RESOURCES,
+ /*
+ * Unrecognized errors should be treated as "other-error". In order to
+ * prevent information disclosure, the server may substitute the real
+ * reason with "other-error".
+ */
+ SASL_OTHER_ERROR
+} SASLStatus;
+
+
+static SASLStatus read_client_first_message(scram_state *state,
+ char *input, char **logdetail);
+static SASLStatus read_client_final_message(scram_state *state,
+ char *input, char **logdetail);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static char *build_error_message(SASLStatus status);
+static SASLStatus check_client_data(void *opaque, char **logdetail);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+static bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+static void generate_nonce(char *out, int len);
+
+/*
+ * build_error_message
+ *
+ * Build an error message for a problem that happened during the SASL
+ * message exchange. Those messages are formatted with e= as prefix
+ * and need to be sent back to the client.
+ */
+static char *
+build_error_message(SASLStatus status)
+{
+ char *res = NULL;
+
+ /*
+ * The following error format is respected here:
+ *
+ * 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.
+ */
+
+ switch (status)
+ {
+ case SASL_INVALID_ENCODING:
+ res = psprintf("e=invalid-encoding");
+ break;
+ case SASL_EXTENSIONS_NOT_SUPPORTED:
+ res = psprintf("e=extensions-not-supported");
+ break;
+ case SASL_INVALID_PROOF:
+ res = psprintf("e=invalid-proof");
+ break;
+ case SASL_CHANNEL_BINDING_UNMATCH:
+ res = psprintf("e=channel-bindings-dont-match");
+ break;
+ case SASL_CHANNEL_BINDING_NO_SUPPORT:
+ res = psprintf("e=server-does-support-channel-binding");
+ break;
+ case SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED:
+ res = psprintf("e=unsupported-channel-binding-type");
+ break;
+ case SASL_UNKNOWN_USER:
+ res = psprintf("e=unknown-user");
+ break;
+ case SASL_INVALID_USERNAME_ENCODING:
+ res = psprintf("e=invalid-username-encoding");
+ break;
+ case SASL_NO_RESOURCES:
+ res = psprintf("e=no-resources");
+ break;
+ case SASL_OTHER_ERROR:
+ res = psprintf("e=other-error");
+ break;
+ case SASL_NO_ERROR:
+ default:
+ Assert(0); /* should not happen */
+ }
+
+ Assert(res != NULL);
+ return res;
+}
+
+/*
+ * pg_be_scram_init
+ *
+ * Initialize a new SCRAM authentication exchange status tracker. This
+ * needs to be called before doing any exchange. It will be filled later
+ * after the beginning of the exchange with verifier data.
+ */
+void *
+pg_be_scram_init(char *username)
+{
+ scram_state *state;
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+ state->salt = NULL;
+
+ return state;
+}
+
+/*
+ * check_client_data
+ *
+ * Fill into the SASL exchange status all the information related to user and
+ * perform sanity checks.
+ */
+static SASLStatus
+check_client_data(void *opaque, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaque;
+ char *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+ int res;
+ char *passwd;
+ TimestampTz vuntil = 0;
+ bool vuntil_null;
+ int count = 0;
+
+ /* compute the salt to use for computing responses */
+ while (count < sizeof(MyProcPort->SASLSalt))
+ {
+ char byte;
+
+ if (!pg_strong_random(&byte, 1))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return SASL_OTHER_ERROR;
+ }
+
+ /*
+ * Only ASCII printable characters, except commas are accepted in
+ * the nonce.
+ */
+ if (byte < '!' || byte > '~' || byte == ',')
+ continue;
+
+ MyProcPort->SASLSalt[count] = byte;
+ count++;
+ }
+
+ /*
+ * Fetch details about role needed for password checks.
+ */
+ res = get_role_details(state->username, &passwd, &vuntil,
+ &vuntil_null,
+ logdetail);
+
+ /*
+ * Check if an error has happened. "logdetail" is already filled,
+ * here we just need to find what is the error mapping with the SASL
+ * exchange and let the client know what happened.
+ */
+ if (res == PG_ROLE_NOT_DEFINED)
+ return SASL_UNKNOWN_USER;
+ else if (res == PG_ROLE_NO_PASSWORD ||
+ res == PG_ROLE_EMPTY_PASSWORD)
+ return SASL_OTHER_ERROR;
+
+ /*
+ * Check password validity, there is nothing to do if the password
+ * validity field is null.
+ */
+ if (!vuntil_null)
+ {
+ if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+ }
+
+ /* The SCRAM verifier needs to be in correct shape as well. */
+ if (!parse_scram_verifier(passwd, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+
+ /* OK to fill in everything */
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ pfree(passwd);
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ *
+ * The next message to send to client is saved in "output", for a length
+ * of "outputlen". In the case of an error, optionally store a palloc'd
+ * string at *logdetail that will be sent to the postmaster log (but not
+ * the client).
+ */
+int
+pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaq;
+ SASLStatus status;
+ int result;
+
+ *output = NULL;
+ *outputlen = 0;
+
+ if (inputlen > 0)
+ elog(DEBUG4, "got SCRAM message: %s", input);
+
+ switch (state->state)
+ {
+ case INIT:
+ /*
+ * Initialization phase. Things happen in the following order:
+ * 1) Receive the first message from client and be sure that it
+ * is parsed correctly.
+ * 2) Validate the user information. A couple of things are done
+ * here, mainly validity checks on the password and the user.
+ * 3) Send the challenge to the client.
+ */
+ status = read_client_first_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* check validity of user data */
+ status = check_client_data(state, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* prepare message to send challenge */
+ *output = build_server_first_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+ *outputlen = strlen(*output);
+ state->state = SALT_SENT;
+ result = SASL_EXCHANGE_CONTINUE;
+ break;
+
+ case SALT_SENT:
+ /*
+ * Final phase for the server. First receive the response to
+ * the challenge previously sent and then let the client know
+ * that everything went well.
+ */
+ status = read_client_final_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Now check the final nonce and the client proof */
+ if (!verify_final_nonce(state) ||
+ !verify_client_proof(state))
+ {
+ *output = build_error_message(SASL_INVALID_PROOF);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Build final message for client */
+ *output = build_server_final_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Success! */
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_SUCCESS;
+ 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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
+ encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ *
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ *
+ * Returns true if the SCRAM verifier has been parsed, and false otherwise.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = strtol(p, &p, SCRAM_ITERATION_LEN);
+ 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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')))
+ return NULL;
+ if (attr_p)
+ *attr_p = attr;
+ begin++;
+
+ if (*begin != '=')
+ return NULL;
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message. In the event of an error, returns
+ * to caller a e= message to be used for the rest of the exchange, or
+ * NULL in case of success.
+ */
+static SASLStatus
+read_client_first_message(scram_state *state, char *input, char **logdetail)
+{
+ 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 */
+ *logdetail = psprintf(_("channel binding not supported."));
+ return SASL_CHANNEL_BINDING_NO_SUPPORT;
+ }
+
+ /* any mandatory extensions would go here. */
+ if (*input != ',')
+ {
+ *logdetail = psprintf(_("mandatory extension %c not supported."), *input);
+ return SASL_OTHER_ERROR;
+ }
+ 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 */
+ 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! */
+ return SASL_EXCHANGE_CONTINUE;
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+ if (encoded_len < 0)
+ return NULL;
+
+ 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;
+}
+
+
+/*
+ * Read and parse the final message received from client.
+ */
+static SASLStatus
+read_client_final_message(scram_state *state, char *input, char **logdetail)
+{
+ 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)
+ {
+ *logdetail = psprintf(_("invalid channel binding input."));
+ return SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED;
+ }
+ 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(pg_b64_dec_len(strlen(value)));
+ if (pg_b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+ {
+ *logdetail = psprintf(_("invalid client proof."));
+ return SASL_INVALID_PROOF;
+ }
+ memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ pfree(client_proof);
+
+ if (*p != '\0')
+ {
+ *logdetail = psprintf(_("malformed SCRAM message (garbage at end of message %c)."),
+ attr);
+ return SASL_OTHER_ERROR;
+ }
+
+ 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';
+
+ /* XXX: check channel_binding field if support is added */
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ siglen = pg_b64_encode((const char *) ServerSignature,
+ SCRAM_KEY_LEN, server_signature_base64);
+ if (siglen < 0)
+ return NULL;
+ server_signature_base64[siglen] = '\0';
+
+ /*
+ * The following error is generated:
+ *
+ * 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 44b2212..6f774d9 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,6 +30,7 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -210,6 +211,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -271,6 +278,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;
@@ -549,6 +557,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, "");
@@ -738,6 +750,105 @@ 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 *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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /*
+ * Send first the authentication request to user. As for MD5, we want
+ * the user to send its password first even if nothing has been done
+ * yet. This avoids consistency issues where a user would be able to
+ * guess that a server is expecting SASL or MD5 depending on the answer
+ * given by the backend without the user providing a password first.
+ */
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ /* Initialize the status tracker for message exchanges */
+ scram_opaq = pg_be_scram_init(port->user_name);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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 = pg_be_scram_exchange(scram_opaq, buf.data, buf.len,
+ &output, &outputlen, logdetail);
+
+ /* 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);
+
+ /* Oops, Something bad happened */
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ /* an error should have been set during the exchange checks */
+ Assert(*logdetail != NULL);
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c9db9a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,30 +30,31 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and returns status code describing the error. In the case of an error,
+ * store a palloc'd string at *logdetail that will be sent to the postmaster
+ * log (but not the client!).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
{
- *logdetail = psprintf(_("Role \"%s\" does not exist."),
- role);
- return STATUS_ERROR; /* no such user */
+ *logdetail = psprintf(_("Role \"%s\" does not exist."), role);
+ return PG_ROLE_NOT_DEFINED; /* no such user */
}
datum = SysCacheGetAttr(AUTHNAME, roleTup,
@@ -63,24 +64,52 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return STATUS_ERROR; /* user has no password */
+ return PG_ROLE_NO_PASSWORD; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
- return STATUS_ERROR; /* empty password */
+ pfree(*password);
+ return PG_ROLE_EMPTY_PASSWORD; /* empty password */
}
+ return PG_ROLE_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != PG_ROLE_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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 e0fbfcb..73f7973 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3c695c1..53d585e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
static const struct config_enum_entry password_encryption_options[] = {
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7c2daa5..4ce87aa 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # md5 or plain
+#password_encryption = md5 # md5, scram or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf..971ddd5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5..1ff441a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption; /* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..e9028fb
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha2.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN SCRAM_NONCE_LEN
+
+/* number of bytes used when sending iteration number during exchange */
+#define SCRAM_ITERATION_LEN 10
+
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..522cfa2 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,14 @@
#include "libpq/libpq-be.h"
+/* Detailed error codes for get_role_details() */
+#define PG_ROLE_OK 0
+#define PG_ROLE_NOT_DEFINED 1
+#define PG_ROLE_NO_PASSWORD 2
+#define PG_ROLE_EMPTY_PASSWORD 3
+
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, 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 dc7d257..9c93a6b 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/libpq-be.h b/src/include/libpq/libpq-be.h
index b91eca5..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char md5Salt[4]; /* Password salt */
+ char md5Salt[4]; /* MD5 password salt */
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 9648422..bbcb2f7 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..58638eb
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS 1
+#define SASL_EXCHANGE_FAILURE 2
+
+/* Routines dedicated to authentication */
+extern void *pg_be_scram_init(char *username);
+extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Routines to handle and check SCRAM-SHA-256 verifier */
+extern char *scram_build_verifier(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..2224ada 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pg_strong_random.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..460b0a1 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
- thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
+ pgstrcasecmp.o pqsignal.o thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.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..61b1020
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,562 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static char *build_client_final_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static bool verify_server_proof(fe_scram_state *state);
+static bool generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = NONCE_SENT;
+ break;
+
+ case NONCE_SENT:
+ /* receive salt and server nonce, send response */
+ if (!read_server_first_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *output = build_client_final_message(state, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = PROOF_SENT;
+ break;
+
+ case PROOF_SENT:
+ /* receive server proof, and verify it */
+ if (!read_server_final_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *success = verify_server_proof(state);
+ *done = true;
+ state->state = FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ *done = true;
+ *success = false;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid SCRAM exchange state\n"));
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ *
+ * This routine is able to handle error messages e= sent by the server
+ * during the exchange of SASL messages. Returns NULL in case of error,
+ * setting errorMessage as well.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin == 'e')
+ {
+ /* Received an error */
+ begin++;
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr e)\n"));
+ return NULL;
+ }
+
+ begin++;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("error received from server in SASL exchange: %s\n"),
+ begin);
+ return NULL;
+ }
+
+ if (*begin != attr)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (%c expected)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr %c)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
+ return NULL;
+ }
+
+ /* Generate message */
+ msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+ buf = malloc(msglen + 1);
+ if (buf == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+ 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)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+static char *
+build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ 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 (pg_b64_enc_len(SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed client proof (%d found)\n"),
+ pg_b64_enc_len(SCRAM_KEY_LEN));
+ return NULL;
+ }
+
+ client_proof_len = pg_b64_encode((char *) client_proof,
+ SCRAM_KEY_LEN,
+ client_proof_base64);
+ if (client_proof_len < 0)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("failure when encoding client proof\n"));
+ return NULL;
+ }
+ client_proof_base64[client_proof_len] = '\0';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ if (state->client_final_message_without_proof == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+ char *server_nonce;
+
+ state->server_first_message = strdup(input);
+ if (!state->server_first_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ server_nonce = read_attr_value(&input, 'r', errormessage);
+ if (server_nonce == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+
+ state->server_nonce = strdup(server_nonce);
+ if (state->server_nonce == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ encoded_salt = read_attr_value(&input, 's', errormessage);
+ if (encoded_salt == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt)));
+ if (state->salt == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+ state->saltlen = pg_b64_decode(encoded_salt,
+ strlen(encoded_salt),
+ state->salt);
+ if (state->saltlen != SCRAM_SALT_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid salt length: found %d, expected %d\n"),
+ state->saltlen, SCRAM_SALT_LEN);
+ return false;
+ }
+
+ iterations_str = read_attr_value(&input, 'i', errormessage);
+ if (iterations_str == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN);
+ if (*endptr != '\0')
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message for number of iterations\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error message */
+ return false;
+ }
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid server proof\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ * Returns true of nonce has been succesfully generated, and false
+ * otherwise.
+ */
+static bool
+generate_nonce(char *buf, int len)
+{
+ int count = 0;
+
+ /* compute the salt to use for computing responses */
+ while (count < len)
+ {
+ char byte;
+
+ if (!pg_strong_random(&byte, 1))
+ return false;
+
+ /*
+ * Only ASCII printable characters, except commas are accepted in
+ * the nonce.
+ */
+ if (byte < '!' || byte > '~' || byte == ',')
+ continue;
+
+ buf[count] = byte;
+ count++;
+ }
+
+ buf[len] = '\0';
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 19171fb..991e524 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -704,6 +783,35 @@ 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)
+ {
+ /* Use error message already if any set */
+ if (conn->errorMessage.len == 0)
+ 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 9d11654..f779fb2 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 b4f9ad7..523e9bf 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2638,6 +2638,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3240,6 +3283,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 854ec89..27cc74c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -450,7 +450,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c5b737a..0f19d1c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha2_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha2.c');
}
my $libpqwalreceiver =
--
2.10.2
0008-Add-regression-tests-for-passwords.patchtext/x-diff; charset=US-ASCII; name=0008-Add-regression-tests-for-passwords.patchDownload
From 626cbd562368c3c7718170bcbcdc6071657cc8c5 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 19:45:35 +0900
Subject: [PATCH 8/9] Add regression tests for passwords
---
src/test/regress/expected/password.out | 102 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000..42c25a1
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,102 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: plain, md5, scram, off, on.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | role_pwd1
+ regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
+ regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
+ regress_passwd4 | AAAAAAAAAAAAAA==:4096:c32d0b9681e3d827fe5b5287c0ba9c9e276fe69e611dcc93cddd41f122b82e5b:51c60a9394db319302dc2727e2b8cb6c463a507312dbbf53a09adbc01ec276d3
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD ('foo' USING 'non_existent');
+ERROR: unsupported password method non_existent
+ALTER ROLE regress_passwd1 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'); -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD ('foo' USING 'plain'); -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'); -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD ('kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'); -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..772e984 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..ce2f5a4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000..54b10df
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD ('foo' USING 'non_existent');
+ALTER ROLE regress_passwd1 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'); -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD ('foo' USING 'plain'); -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'); -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD ('kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'); -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.10.2
0009-Add-TAP-tests-for-authentication-methods.patchtext/x-diff; charset=US-ASCII; name=0009-Add-TAP-tests-for-authentication-methods.patchDownload
From 6194c8181924f6098d9dc391b95fc6c0a098433f Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 14:45:44 -0800
Subject: [PATCH 9/9] Add TAP tests for authentication methods
Those are useful to test what is expected from users having either plain,
MD5-encrypted or SCRAM passwords.
---
src/test/recovery/t/009_authentication.pl | 84 +++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 src/test/recovery/t/009_authentication.pl
diff --git a/src/test/recovery/t/009_authentication.pl b/src/test/recovery/t/009_authentication.pl
new file mode 100644
index 0000000..4713d0b
--- /dev/null
+++ b/src/test/recovery/t/009_authentication.pl
@@ -0,0 +1,84 @@
+# Set of tests for authentication and pg_hba.conf. The following password
+# methods are checked through this test:
+# - Plain
+# - MD5-encrypted
+# - SCRAM-encrypted
+# This test cannot run on Windows as Postgres cannot be set up with Unix
+# sockets and needs to go through SSPI.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 12;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ $node->append_conf('pg_hba.conf', "local all all $hba_method");
+ $node->reload;
+}
+
+# Test access for a single role, useful to wrap all tests into one.
+sub test_role
+{
+ my $node = shift;
+ my $role = shift;
+ my $method = shift;
+ my $expected_res = shift;
+ my $status_string = 'failed';
+
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $res = $node->psql('postgres', 'SELECT 1', extra_params => ['-U', $role]);
+ is($res, $expected_res,
+ "authentication $status_string for method $method, role $role");
+}
+
+SKIP:
+{
+ skip "authentication tests cannot run on Windows", 12 if ($windows_os);
+
+ # Initialize master node
+ my $node = get_new_node('master');
+ $node->init;
+ $node->start;
+
+ # Create 3 roles with different password methods for each one. The same
+ # password is used for all of them.
+ $node->safe_psql('postgres', "CREATE ROLE scram_role LOGIN PASSWORD ('pass' USING 'scram');");
+ $node->safe_psql('postgres', "CREATE ROLE md5_role LOGIN PASSWORD ('pass' USING 'md5');");
+ $node->safe_psql('postgres', "CREATE ROLE plain_role LOGIN PASSWORD ('pass' USING 'plain');");
+ $ENV{"PGPASSWORD"} = 'pass';
+
+ # For "trust" method, all users should be able to connect.
+ reset_pg_hba($node, 'trust');
+ test_role($node, 'scram_role', 'trust', 0);
+ test_role($node, 'md5_role', 'trust', 0);
+ test_role($node, 'plain_role', 'trust', 0);
+
+ # For "plain" method, users "plain_role" and "md5_role" should be able to
+ # connect.
+ reset_pg_hba($node, 'password');
+ test_role($node, 'scram_role', 'password', 2);
+ test_role($node, 'md5_role', 'password', 0);
+ test_role($node, 'plain_role', 'password', 0);
+
+ # For "scram" method, only user "scram_role" should be able to connect.
+ reset_pg_hba($node, 'scram');
+ test_role($node, 'scram_role', 'scram', 0);
+ test_role($node, 'md5_role', 'scram', 2);
+ test_role($node, 'plain_role', 'scram', 2);
+
+ # For "md5" method, users "plain_role" and "md5_role" should be able to
+ # connect.
+ reset_pg_hba($node, 'md5');
+ test_role($node, 'scram_role', 'md5', 2);
+ test_role($node, 'md5_role', 'md5', 0);
+ test_role($node, 'plain_role', 'md5', 0);
+}
--
2.10.2
On Fri, Nov 4, 2016 at 11:58 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
The organization of these patches makes sense to me.
On 10/20/16 1:14 AM, Michael Paquier wrote:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.That's probably alright, although the patch contains a lot more changes
than I would imagine for a simple file move. I'll still have to review
that in detail.
Even with git diff -M, reviewing 0001 is very difficult. It does
things that are considerably in excess of what is needed to move these
files from point A to point B, such as:
- Renaming static functions to have a "pg" prefix.
- Changing the order of the functions in the file.
- Renaming an argument called "context" to "cxt".
I think that is a bad plan. I think we should insist that 0001
content itself with a minimal move of the files changing no more than
is absolutely necessary. If refactoring is needed, those changes can
be submitted separately, which will be much easier to review. My
preliminary judgement is that most of this change is pointless and
should be reverted.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 15, 2016 at 10:40 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Nov 4, 2016 at 11:58 AM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:The organization of these patches makes sense to me.
On 10/20/16 1:14 AM, Michael Paquier wrote:
- 0001, moving all the SHA2 functions to src/common/ and introducing a
PG-like interface. No actual changes here.That's probably alright, although the patch contains a lot more changes
than I would imagine for a simple file move. I'll still have to review
that in detail.Even with git diff -M, reviewing 0001 is very difficult. It does
things that are considerably in excess of what is needed to move these
files from point A to point B, such as:- Renaming static functions to have a "pg" prefix.
- Changing the order of the functions in the file.
- Renaming an argument called "context" to "cxt".I think that is a bad plan. I think we should insist that 0001
content itself with a minimal move of the files changing no more than
is absolutely necessary. If refactoring is needed, those changes can
be submitted separately, which will be much easier to review. My
preliminary judgement is that most of this change is pointless and
should be reverted.
How do you plug in that with OpenSSL? Are you suggesting to use a set
of undef definitions in the new header in the same way as pgcrypto is
doing, which is rather ugly? Because that's what the deal is about in
this patch.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 15, 2016 at 2:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
How do you plug in that with OpenSSL? Are you suggesting to use a set
of undef definitions in the new header in the same way as pgcrypto is
doing, which is rather ugly? Because that's what the deal is about in
this patch.
Perhaps that justifies renaming them -- although I would think the
fact that they are static would prevent conflicts -- but why reorder
them and change variable names?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 15, 2016 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 15, 2016 at 2:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:How do you plug in that with OpenSSL? Are you suggesting to use a set
of undef definitions in the new header in the same way as pgcrypto is
doing, which is rather ugly? Because that's what the deal is about in
this patch.Perhaps that justifies renaming them -- although I would think the
fact that they are static would prevent conflicts -- but why reorder
them and change variable names?
Yeah... Perhaps I should not have done that, which was just for
consistency's sake, and even if the new reordering makes more sense
actually...
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 15, 2016 at 5:12 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Tue, Nov 15, 2016 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 15, 2016 at 2:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:How do you plug in that with OpenSSL? Are you suggesting to use a set
of undef definitions in the new header in the same way as pgcrypto is
doing, which is rather ugly? Because that's what the deal is about in
this patch.Perhaps that justifies renaming them -- although I would think the
fact that they are static would prevent conflicts -- but why reorder
them and change variable names?Yeah... Perhaps I should not have done that, which was just for
consistency's sake, and even if the new reordering makes more sense
actually...
Yeah, I don't see a point to that.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 4:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 15, 2016 at 5:12 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Tue, Nov 15, 2016 at 12:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Nov 15, 2016 at 2:24 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:How do you plug in that with OpenSSL? Are you suggesting to use a set
of undef definitions in the new header in the same way as pgcrypto is
doing, which is rather ugly? Because that's what the deal is about in
this patch.Perhaps that justifies renaming them -- although I would think the
fact that they are static would prevent conflicts -- but why reorder
them and change variable names?Yeah... Perhaps I should not have done that, which was just for
consistency's sake, and even if the new reordering makes more sense
actually...Yeah, I don't see a point to that.
OK, by doing so here is what I have. The patch generated by
format-patch, as well as diffs generated by git diff -M are reduced
and the patch gets half in size. They could be reduced more by adding
at the top of sha2.c a couple of defined to map the old SHAXXX_YYY
variables with their PG_ equivalents, but that does not seem worth it
to me, and diffs are listed line by line.
--
Michael
Attachments:
0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchapplication/x-patch; name=0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From 3171c40390703e9b12f97e25914f31accf480a52 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 16 Nov 2016 10:48:42 -0800
Subject: [PATCH] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 5 +-
contrib/pgcrypto/fortuna.c | 12 +--
contrib/pgcrypto/internal-sha2.c | 82 +++++++--------
contrib/pgcrypto/sha2.h | 100 ------------------
src/common/Makefile | 6 ++
{contrib/pgcrypto => src/common}/sha2.c | 174 +++++++++++++++++---------------
src/common/sha2_openssl.c | 102 +++++++++++++++++++
src/include/common/sha2.h | 115 +++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 22 ++--
10 files changed, 388 insertions(+), 234 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (82%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff..30619bf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..4085abb 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.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
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,9 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index 5028203..ba74db6 100644
--- a/contrib/pgcrypto/fortuna.c
+++ b/contrib/pgcrypto/fortuna.c
@@ -34,9 +34,9 @@
#include <sys/time.h>
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
#include "rijndael.h"
-#include "sha2.h"
#include "fortuna.h"
@@ -112,7 +112,7 @@
#define CIPH_BLOCK 16
/* for internal wrappers */
-#define MD_CTX SHA256_CTX
+#define MD_CTX pg_sha256_ctx
#define CIPH_CTX rijndael_ctx
struct fortuna_state
@@ -154,22 +154,22 @@ ciph_encrypt(CIPH_CTX * ctx, const uint8 *in, uint8 *out)
static void
md_init(MD_CTX * ctx)
{
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
md_update(MD_CTX * ctx, const uint8 *data, int len)
{
- SHA256_Update(ctx, data, len);
+ pg_sha256_update(ctx, data, len);
}
static void
md_result(MD_CTX * ctx, uint8 *dst)
{
- SHA256_CTX tmp;
+ pg_sha256_ctx tmp;
memcpy(&tmp, ctx, sizeof(*ctx));
- SHA256_Final(dst, &tmp);
+ pg_sha256_final(&tmp, dst);
px_memset(&tmp, 0, sizeof(tmp));
}
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e1..e06f554 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0..0000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..5ddfff8 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 82%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9df..4964675 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,5 +1,20 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
/*
* FILE: sha2.c
* AUTHOR: Aaron D. Gifford <me@aarongifford.com>
@@ -32,16 +47,18 @@
* SUCH DAMAGE.
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
- *
- * contrib/pgcrypto/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +75,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +145,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void SHA512_Last(pg_sha512_ctx *context);
+static void SHA256_Transform(pg_sha256_ctx *context, const uint8 *data);
+static void SHA512_Transform(pg_sha512_ctx *context, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -251,12 +265,12 @@ static const uint64 sha512_initial_hash_value[8] = {
/*** SHA-256: *********************************************************/
void
-SHA256_Init(SHA256_CTX *context)
+pg_sha256_init(pg_sha256_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
+ memcpy(context->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_BLOCK_LENGTH);
context->bitcount = 0;
}
@@ -287,7 +301,7 @@ SHA256_Init(SHA256_CTX *context)
} while(0)
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+SHA256_Transform(pg_sha256_ctx *context, const uint8 *data)
{
uint32 a,
b,
@@ -358,7 +372,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+SHA256_Transform(pg_sha256_ctx *context, const uint8 *data)
{
uint32 a,
b,
@@ -448,7 +462,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#endif /* SHA2_UNROLL_TRANSFORM */
void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *context, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -457,11 +471,11 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
+ usedspace = (context->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
@@ -482,13 +496,13 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
return;
}
}
- while (len >= SHA256_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
+ context->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
@@ -501,11 +515,11 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
}
static void
-SHA256_Last(SHA256_CTX *context)
+SHA256_Last(pg_sha256_ctx *context)
{
unsigned int usedspace;
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
+ usedspace = (context->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
/* Convert FROM host byte order */
REVERSE64(context->bitcount, context->bitcount);
@@ -515,41 +529,41 @@ SHA256_Last(SHA256_CTX *context)
/* Begin padding with a 1 bit: */
context->buffer[usedspace++] = 0x80;
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
{
/* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
}
else
{
- if (usedspace < SHA256_BLOCK_LENGTH)
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
{
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
}
/* Do second-to-last transform: */
SHA256_Transform(context, context->buffer);
/* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
}
}
else
{
/* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
/* Begin padding with a 1 bit: */
*context->buffer = 0x80;
}
/* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
+ *(uint64 *) &context->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
/* Final transform: */
SHA256_Transform(context, context->buffer);
}
void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
+pg_sha256_final(pg_sha256_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -567,22 +581,22 @@ SHA256_Final(uint8 digest[], SHA256_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA256_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha256_ctx));
}
/*** SHA-512: *********************************************************/
void
-SHA512_Init(SHA512_CTX *context)
+pg_sha512_init(pg_sha512_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
+ memcpy(context->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA512_BLOCK_LENGTH);
context->bitcount[0] = context->bitcount[1] = 0;
}
@@ -616,7 +630,7 @@ SHA512_Init(SHA512_CTX *context)
} while(0)
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+SHA512_Transform(pg_sha512_ctx *context, const uint8 *data)
{
uint64 a,
b,
@@ -684,7 +698,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+SHA512_Transform(pg_sha512_ctx *context, const uint8 *data)
{
uint64 a,
b,
@@ -774,7 +788,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#endif /* SHA2_UNROLL_TRANSFORM */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha512_update(pg_sha512_ctx *context, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,11 +797,11 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (context->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
@@ -808,13 +822,13 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ ADDINC128(context->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
if (len > 0)
{
@@ -827,11 +841,11 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
}
static void
-SHA512_Last(SHA512_CTX *context)
+SHA512_Last(pg_sha512_ctx *context)
{
unsigned int usedspace;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (context->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
/* Convert FROM host byte order */
REVERSE64(context->bitcount[0], context->bitcount[0]);
@@ -842,42 +856,42 @@ SHA512_Last(SHA512_CTX *context)
/* Begin padding with a 1 bit: */
context->buffer[usedspace++] = 0x80;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
{
/* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
{
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
}
/* Do second-to-last transform: */
SHA512_Transform(context, context->buffer);
/* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ memset(context->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
}
}
else
{
/* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
/* Begin padding with a 1 bit: */
*context->buffer = 0x80;
}
/* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
+ *(uint64 *) &context->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
+ *(uint64 *) &context->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
/* Final transform: */
SHA512_Transform(context, context->buffer);
}
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -896,38 +910,38 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha512_ctx));
}
/*** SHA-384: *********************************************************/
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
+ memcpy(context->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA384_BLOCK_LENGTH);
context->bitcount[0] = context->bitcount[1] = 0;
}
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *context, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) context, data, len);
}
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ SHA512_Last((pg_sha512_ctx *) context);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -941,32 +955,32 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha384_ctx));
}
/*** SHA-224: *********************************************************/
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
+ memcpy(context->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_BLOCK_LENGTH);
context->bitcount = 0;
}
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *context, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) context, data, len);
}
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -984,9 +998,9 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000..91d0c39
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000..015a905
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..21c4600 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -421,14 +430,15 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'fortuna.c', 'random.c',
- 'pgp-mpi-internal.c', 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'fortuna.c',
+ 'random.c', 'pgp-mpi-internal.c',
+ 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.10.2
On Wed, Nov 16, 2016 at 1:53 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Yeah, I don't see a point to that.
OK, by doing so here is what I have. The patch generated by
format-patch, as well as diffs generated by git diff -M are reduced
and the patch gets half in size. They could be reduced more by adding
at the top of sha2.c a couple of defined to map the old SHAXXX_YYY
variables with their PG_ equivalents, but that does not seem worth it
to me, and diffs are listed line by line.
All right, this version is much easier to review. I am a bit puzzled,
though. It looks like src/common will include sha2.o if built without
OpenSSL and sha2_openssl.o if built with OpenSSL. So far, so good.
One would think, then, that pgcrypto would not need to worry about
these functions any more because libpgcommon_srv.a is linked into the
server, so any references to those symbols would presumably just work.
However, that's not what you did. On Windows, you added a dependency
on libpgcommon which I think is unnecessary because that stuff is
already linked into the server. On non-Windows systems, however, you
have instead taught pgcrypto to copy the source file it needs from
src/common and recompile it. I don't understand why you need to do
any of that, or why it should be different on Windows vs. non-Windows.
So I think that the changes for the pgcrypto Makefile could just look
like this:
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 805db76..ddb0183 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 sha1.c internal.c internal-sha2.c blf.c rijndael.c \
fortuna.c random.c pgp-mpi-internal.c imath.c
INT_TESTS = sha2
And for Mkvcbuild.pm I think you could just do this:
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index de764dd..1993764 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -422,7 +431,7 @@ sub mkvcbuild
{
$pgcrypto->AddFiles(
'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
+ 'sha1.c',
'internal.c', 'internal-sha2.c',
'blf.c', 'rijndael.c',
'fortuna.c', 'random.c',
Is there some reason that won't work?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 11:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 805db76..ddb0183 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 sha1.c internal.c internal-sha2.c blf.c rijndael.c \ fortuna.c random.c pgp-mpi-internal.c imath.c INT_TESTS = sha2
I would like to do so. And while Linux is happy with that, macOS is
not, this results in linking resolution errors when compiling the
library.
And for Mkvcbuild.pm I think you could just do this:
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index de764dd..1993764 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -114,6 +114,15 @@ sub mkvcbuild md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c string.c username.c wait_error.c);+ if ($solution->{options}->{openssl}) + { + push(@pgcommonallfiles, 'sha2_openssl.c'); + } + else + { + push(@pgcommonallfiles, 'sha2.c'); + } + our @pgcommonfrontendfiles = ( @pgcommonallfiles, qw(fe_memutils.c file_utils.c restricted_token.c)); @@ -422,7 +431,7 @@ sub mkvcbuild { $pgcrypto->AddFiles( 'contrib/pgcrypto', 'md5.c', - 'sha1.c', 'sha2.c', + 'sha1.c', 'internal.c', 'internal-sha2.c', 'blf.c', 'rijndael.c', 'fortuna.c', 'random.c',Is there some reason that won't work?
Yes we could do that for consistency with the other nix platforms. But
is that really necessary as libpgcommon already has those objects?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 6:56 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Wed, Nov 16, 2016 at 11:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 805db76..ddb0183 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 sha1.c internal.c internal-sha2.c blf.c rijndael.c \ fortuna.c random.c pgp-mpi-internal.c imath.c INT_TESTS = sha2I would like to do so. And while Linux is happy with that, macOS is
not, this results in linking resolution errors when compiling the
library.
Well, I'm running macOS and it worked for me. TBH, I don't even quite
understand how it could NOT work. What makes the symbols provided by
libpgcommon any different from any other symbols that are part of the
binary? How could one set work and the other set fail? I can
understand how there might be some problem if the backend were
dynamically linked libpgcommon, but it's not. It's doing this:
gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -g -O2 -Wall -Werror -L../../src/port -L../../src/common
-Wl,-dead_strip_dylibs -Wall -Werror access/brin/brin.o [many more
.o files omitted for brevity] utils/fmgrtab.o
../../src/timezone/localtime.o ../../src/timezone/strftime.o
../../src/timezone/pgtz.o ../../src/port/libpgport_srv.a
../../src/common/libpgcommon_srv.a -lm -o postgres
As I understand it, listing the .a file on the linker command line
like that is exactly equivalent to listing out each individual .o file
that is part of that static library. There shouldn't be any
difference in how a symbol that's provided by one of the .o files
looks vs. how a symbol that's provided by one of the .a files looks.
Let's test it.
[rhaas pgsql]$ nm src/backend/postgres | grep -E 'GetUserIdAndContext|psprintf'
00000001003d71d0 T _GetUserIdAndContext
000000010040f160 T _psprintf
So... how would the dynamic loader know that it was supposed to find
the first one and fail to find the second one? More to the point,
it's clear that it DOES find the second one on every platform in the
buildfarm, because adminpack, dblink, pageinspect, and pgstattuple all
use psprintf without the push-ups you are proposing to undertake here.
pg_md5_encrypt is used by passwordcheck, and forkname_to_number is
used by pageinspect and pg_prewarm. It all just works. No special
magic required.
Yes we could do that for consistency with the other nix platforms. But
is that really necessary as libpgcommon already has those objects?
The point is that *postgres* already has those objects. You don't
need to include them twice.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
On 2016-11-16 19:29:41 -0500, Robert Haas wrote:
On Wed, Nov 16, 2016 at 6:56 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Wed, Nov 16, 2016 at 11:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 805db76..ddb0183 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 sha1.c internal.c internal-sha2.c blf.c rijndael.c \ fortuna.c random.c pgp-mpi-internal.c imath.c INT_TESTS = sha2I would like to do so. And while Linux is happy with that, macOS is
not, this results in linking resolution errors when compiling the
library.Well, I'm running macOS and it worked for me. TBH, I don't even quite
understand how it could NOT work. What makes the symbols provided by
libpgcommon any different from any other symbols that are part of the
binary? How could one set work and the other set fail? I can
understand how there might be some problem if the backend were
dynamically linked libpgcommon, but it's not. It's doing this:
With -Wl,--as-neeeded the linker will dismiss unused symbols found in a
static library. Maybe that's the difference?
Andres
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 7:36 PM, Andres Freund <andres@anarazel.de> wrote:
With -Wl,--as-neeeded the linker will dismiss unused symbols found in a
static library. Maybe that's the difference?
The man page --as-needed says that --as-needed modifies the behavior
of dynamic libraries, not static ones. If there is any such effect,
it is undocumented. Here is the text:
LD> This option affects ELF DT_NEEDED tags for dynamic libraries mentioned
LD> on the command line after the --as-needed option. Normally the linker will
LD> add a DT_NEEDED tag for each dynamic library mentioned on the
LD> command line, regardless of whether the library is actually needed or not.
LD> --as-needed causes a DT_NEEDED tag to only be emitted for a library
LD> that at that point in the link satisfies a non-weak undefined
symbol reference
LD> from a regular object file or, if the library is not found in the DT_NEEDED
LD> lists of other needed libraries, a non-weak undefined symbol reference
LD> from another needed dynamic library. Object files or libraries appearing
LD> on the command line after the library in question do not affect whether the
LD> library is seen as needed. This is similar to the rules for
extraction of object
LD> files from archives. --no-as-needed restores the default behaviour.
Some experimentation on my Mac reveals that my previous statement
about how this works was incorrect. See attached patch for what I
tried. What I find is:
1. If I create an additional source file in src/common containing a
completely unused symbol (wunk) it appears in the nm output for
libpgcommon_srv.a but not in the nm output for the postgres binary.
2. If I add an additional function to an existing source file in
src/common containing a completely unused symbol (quux) it appears in
the nm output for both libpgcommon_srv.a and also in the nm output for
the postgres binary.
3. If I create an additional source file in src/backend containing a
completely unused symbol (blarfle) it appears in the nm output for the
postgres binary.
So, it seems that the linker is willing to drop archive members if the
entire .o file is used, but not individual symbols. That explains why
Michael thinks we need to do something special here, because with his
0001 patch, nothing in the new sha2(_openssl).c file would immediately
be used in the backend. And indeed I see now that my earlier testing
was done incorrectly, and pgcrypto does in fact fail to build under my
proposal. Oops.
But I think that's a temporary thing. As soon as the backend is using
the sha2 routines for anything (which is the point, right?) the build
changes become unnecessary. For example, if I apply this patch:
--- a/src/backend/lib/binaryheap.c
+++ b/src/backend/lib/binaryheap.c
@@ -305,3 +305,7 @@ sift_down(binaryheap *heap, int node_off)
node_off = swap_off;
}
}
+
+#include "common/sha2.h"
+extern void ugh(void);
+void ugh(void) { pg_sha224_init(NULL); }
...then the backend ends up sucking in everything in sha2.c and the
pgcrypto build works again.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Attachments:
wunk.patchtext/x-patch; charset=US-ASCII; name=wunk.patchDownload
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa1..f84264a 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -46,7 +46,7 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
-OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
+OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o) wunk.o
all: libpgcommon.a libpgcommon_srv.a
diff --git a/src/common/ip.c b/src/common/ip.c
index 797d910..d517802 100644
--- a/src/common/ip.c
+++ b/src/common/ip.c
@@ -258,3 +258,11 @@ getnameinfo_unix(const struct sockaddr_un * sa, int salen,
return 0;
}
#endif /* HAVE_UNIX_SOCKETS */
+
+extern void quux(void);
+
+void
+quux(void)
+{
+ /* quux */
+}
diff --git a/src/common/wunk.c b/src/common/wunk.c
new file mode 100644
index 0000000..2db667c
--- /dev/null
+++ b/src/common/wunk.c
@@ -0,0 +1,7 @@
+extern void wunk(void);
+
+void
+wunk(void)
+{
+ /* wunk */
+}
On Wed, Nov 16, 2016 at 6:51 PM, Robert Haas <robertmhaas@gmail.com> wrote:
So, it seems that the linker is willing to drop archive members if the
entire .o file is used, but not individual symbols. That explains why
Michael thinks we need to do something special here, because with his
0001 patch, nothing in the new sha2(_openssl).c file would immediately
be used in the backend. And indeed I see now that my earlier testing
was done incorrectly, and pgcrypto does in fact fail to build under my
proposal. Oops.
Ah, thanks! I did not notice that before in configure.in:
if test "$PORTNAME" = "darwin"; then
PGAC_PROG_CC_LDFLAGS_OPT([-Wl,-dead_strip_dylibs], $link_test_func)
elif test "$PORTNAME" = "openbsd"; then
PGAC_PROG_CC_LDFLAGS_OPT([-Wl,-Bdynamic], $link_test_func)
else
PGAC_PROG_CC_LDFLAGS_OPT([-Wl,--as-needed], $link_test_func)
fi
In the current set of patches, the sha2 functions would not get used
until the main patch for SCRAM gets committed so that's a couple of
steps and many months ahead.. And --as-needed/--no-as-needed are not
supported in macos. So I would believe that the best route is just to
use this patch with the way it does things, and once SCRAM gets in we
could switch the build into more appropriate linking. At least that's
far less ugly than having fake objects in the backend code. Of course
a comment in pgcrypo's Makefile would be appropriate.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 8:04 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
In the current set of patches, the sha2 functions would not get used
until the main patch for SCRAM gets committed so that's a couple of
steps and many months ahead.. And --as-needed/--no-as-needed are not
supported in macos. So I would believe that the best route is just to
use this patch with the way it does things, and once SCRAM gets in we
could switch the build into more appropriate linking. At least that's
far less ugly than having fake objects in the backend code. Of course
a comment in pgcrypo's Makefile would be appropriate.
Or a comment with a "ifeq ($(PORTNAME), darwin)" containing the
additional objects to make clear that this is proper to only OSX.
Other ideas are welcome.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 16, 2016 at 11:28 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Wed, Nov 16, 2016 at 8:04 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:In the current set of patches, the sha2 functions would not get used
until the main patch for SCRAM gets committed so that's a couple of
steps and many months ahead.. And --as-needed/--no-as-needed are not
supported in macos. So I would believe that the best route is just to
use this patch with the way it does things, and once SCRAM gets in we
could switch the build into more appropriate linking. At least that's
far less ugly than having fake objects in the backend code. Of course
a comment in pgcrypo's Makefile would be appropriate.Or a comment with a "ifeq ($(PORTNAME), darwin)" containing the
additional objects to make clear that this is proper to only OSX.
Other ideas are welcome.
So, the problem isn't Darwin-specific. I experimented with this on
Linux and found Linux does the same thing with libpgcommon_srv.a that
macOS does: a file in the archive that is totally unused is omitted
from the postgres binary. In Linux, however, that doesn't prevent
pgcrypto from compiling anyway. It does, however, prevent it from
working. Instead of failing at compile time with a complaint about
missing symbols, it fails at load time. I think that's because macOS
has -bundle-loader and we use it; without that, I think we'd get the
same behavior on macOS that we get on Windows.
The fundamental problem here is that the archive-member-dropping
behavior that we're getting here is not really what we want, and I
think that's going to happen on most or all architectures. For GNU
ld, we could add -Wl,--whole-archive, and macOS has -all_load, but I
that this is just a nest of portability problems waiting to happen. I
think there are two things we can do here that are far simpler:
1. Rejigger things so that we don't build libpgcommon_srv.a in the
first place, and instead add $(top_builddir)/src/common to
src/backend/Makefile's value of SUBDIRS. With appropriate adjustments
to src/common/Makefile, this should allow us to include all of the
object files on the linker command line individually instead of
building an archive library that is then used only for the postgres
binary itself anyway. Then, things wouldn't get dropped.
2. Just postpone committing this patch until we're ready to use the
new code in the backend someplace (or add a dummy reference to it
someplace).
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Nov 17, 2016 at 8:12 AM, Robert Haas <robertmhaas@gmail.com> wrote:
So, the problem isn't Darwin-specific. I experimented with this on
Linux and found Linux does the same thing with libpgcommon_srv.a that
macOS does: a file in the archive that is totally unused is omitted
from the postgres binary. In Linux, however, that doesn't prevent
pgcrypto from compiling anyway. It does, however, prevent it from
working. Instead of failing at compile time with a complaint about
missing symbols, it fails at load time. I think that's because macOS
has -bundle-loader and we use it; without that, I think we'd get the
same behavior on macOS that we get on Windows.
Yes, right. I recall seeing the regression tests failing with pgcrypto
when doing that. Though I did not recall if this was specific to macos
or Linux when I looked again at this patch yesterday. When testing
again yesterday I was able to make the tests of pgcrypto to pass, but
perhaps my build was not in a clean state...
1. Rejigger things so that we don't build libpgcommon_srv.a in the
first place, and instead add $(top_builddir)/src/common to
src/backend/Makefile's value of SUBDIRS. With appropriate adjustments
to src/common/Makefile, this should allow us to include all of the
object files on the linker command line individually instead of
building an archive library that is then used only for the postgres
binary itself anyway. Then, things wouldn't get dropped.2. Just postpone committing this patch until we're ready to use the
new code in the backend someplace (or add a dummy reference to it
someplace).
At the end this refactoring makes sense because it will be used in the
backend with the SCRAM engine, so we could just wait for 2 instead of
having some workarounds. This is dropping the ball for later and there
will be already a lot of work for the SCRAM core part, though I don't
think that the SHA2 refactoring will change much going forward.
Option 3 would be to do things the patch does it, aka just compiling
pgcrypto using the source files directly and put a comment to revert
that once the APIs are used in the backend. I can guess that you don't
like that.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Nov 18, 2016 at 2:51 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Thu, Nov 17, 2016 at 8:12 AM, Robert Haas <robertmhaas@gmail.com> wrote:
So, the problem isn't Darwin-specific. I experimented with this on
Linux and found Linux does the same thing with libpgcommon_srv.a that
macOS does: a file in the archive that is totally unused is omitted
from the postgres binary. In Linux, however, that doesn't prevent
pgcrypto from compiling anyway. It does, however, prevent it from
working. Instead of failing at compile time with a complaint about
missing symbols, it fails at load time. I think that's because macOS
has -bundle-loader and we use it; without that, I think we'd get the
same behavior on macOS that we get on Windows.Yes, right. I recall seeing the regression tests failing with pgcrypto
when doing that. Though I did not recall if this was specific to macos
or Linux when I looked again at this patch yesterday. When testing
again yesterday I was able to make the tests of pgcrypto to pass, but
perhaps my build was not in a clean state...1. Rejigger things so that we don't build libpgcommon_srv.a in the
first place, and instead add $(top_builddir)/src/common to
src/backend/Makefile's value of SUBDIRS. With appropriate adjustments
to src/common/Makefile, this should allow us to include all of the
object files on the linker command line individually instead of
building an archive library that is then used only for the postgres
binary itself anyway. Then, things wouldn't get dropped.2. Just postpone committing this patch until we're ready to use the
new code in the backend someplace (or add a dummy reference to it
someplace).At the end this refactoring makes sense because it will be used in the
backend with the SCRAM engine, so we could just wait for 2 instead of
having some workarounds. This is dropping the ball for later and there
will be already a lot of work for the SCRAM core part, though I don't
think that the SHA2 refactoring will change much going forward.Option 3 would be to do things the patch does it, aka just compiling
pgcrypto using the source files directly and put a comment to revert
that once the APIs are used in the backend. I can guess that you don't
like that.
Nothing more will likely happen in this CF, so I have moved it to
2017-01 with the same status of "Needs Review".
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 29, 2016 at 1:36 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
Nothing more will likely happen in this CF, so I have moved it to
2017-01 with the same status of "Needs Review".
Attached is a new set of patches using the new routines
pg_backend_random() and pg_strong_random() to handle the randomness in
SCRAM:
- 0001 refactors the SHA2 routines. pgcrypto uses raw files from
src/common when compiling with this patch. That works on any platform,
and this is the simplified version of upthread.
- 0002 adds base64 routines to src/common.
- 0003 does some refactoring regarding the password encryption in
ALTER/CREATE USER queries.
- 0004 adds the clause PASSWORD (val USING method) in CREATE/ALTER USER.
- 0005 is the code patch for SCRAM. Note that this switches pgcrypto
to link to libpgcommon as SHA2 routines are used by the backend.
- 0006 adds some regression tests for passwords.
- 0007 adds some TAP tests for authentication.
This is added to the upcoming CF.
Thanks,
--
Michael
Attachments:
0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchapplication/x-download; name=0001-Refactor-SHA2-functions-and-move-them-to-src-common.patchDownload
From 3f5f3918101ae27b081c11a6b51bb53aa18908a0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 7 Dec 2016 14:11:10 +0900
Subject: [PATCH 1/7] Refactor SHA2 functions and move them to src/common/
This way both frontend and backends can refer to them if needed. Those
functions are taken from pgcrypto, which now fetches directly the source
files it needs from src/common/ when compiling its library.
A new interface, which is more PG-like is designed for those SHA2 functions,
allowing to link to either OpenSSL or the in-core stuff taken from KAME
as need be, which is the most flexible solution.
---
contrib/pgcrypto/.gitignore | 4 +
contrib/pgcrypto/Makefile | 9 +-
contrib/pgcrypto/internal-sha2.c | 82 +++++++--------
contrib/pgcrypto/sha2.h | 100 ------------------
src/common/Makefile | 6 ++
{contrib/pgcrypto => src/common}/sha2.c | 174 +++++++++++++++++---------------
src/common/sha2_openssl.c | 102 +++++++++++++++++++
src/include/common/sha2.h | 115 +++++++++++++++++++++
src/tools/msvc/Mkvcbuild.pm | 20 +++-
9 files changed, 385 insertions(+), 227 deletions(-)
delete mode 100644 contrib/pgcrypto/sha2.h
rename {contrib/pgcrypto => src/common}/sha2.c (82%)
create mode 100644 src/common/sha2_openssl.c
create mode 100644 src/include/common/sha2.h
diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore
index 5dcb3ff972..30619bfbbf 100644
--- a/contrib/pgcrypto/.gitignore
+++ b/contrib/pgcrypto/.gitignore
@@ -1,3 +1,7 @@
+# Source file copied from src/common
+/sha2.c
+/sha2_openssl.c
+
# Generated subdirectories
/log/
/results/
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index f65d84d1f3..14e74f899c 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -4,7 +4,7 @@ INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
pgp-mpi-internal.c imath.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,6 +59,13 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
+# Compiling pgcrypto with those two raw files is necessary as long
+# as none of their routines are used by the backend code. Note doing
+# so can either result in library loading failures or linking resolution
+# failures at compilation depending on the environment used.
+sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
+ rm -f $@ && $(LN_S) $< .
+
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/contrib/pgcrypto/internal-sha2.c b/contrib/pgcrypto/internal-sha2.c
index 55ec7e16bd..e06f55445e 100644
--- a/contrib/pgcrypto/internal-sha2.c
+++ b/contrib/pgcrypto/internal-sha2.c
@@ -33,8 +33,8 @@
#include <time.h>
+#include "common/sha2.h"
#include "px.h"
-#include "sha2.h"
void init_sha224(PX_MD *h);
void init_sha256(PX_MD *h);
@@ -46,43 +46,43 @@ void init_sha512(PX_MD *h);
static unsigned
int_sha224_len(PX_MD *h)
{
- return SHA224_DIGEST_LENGTH;
+ return PG_SHA224_DIGEST_LENGTH;
}
static unsigned
int_sha224_block_len(PX_MD *h)
{
- return SHA224_BLOCK_LENGTH;
+ return PG_SHA224_BLOCK_LENGTH;
}
static void
int_sha224_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Update(ctx, data, dlen);
+ pg_sha224_update(ctx, data, dlen);
}
static void
int_sha224_reset(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Init(ctx);
+ pg_sha224_init(ctx);
}
static void
int_sha224_finish(PX_MD *h, uint8 *dst)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
- SHA224_Final(dst, ctx);
+ pg_sha224_final(ctx, dst);
}
static void
int_sha224_free(PX_MD *h)
{
- SHA224_CTX *ctx = (SHA224_CTX *) h->p.ptr;
+ pg_sha224_ctx *ctx = (pg_sha224_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -94,43 +94,43 @@ int_sha224_free(PX_MD *h)
static unsigned
int_sha256_len(PX_MD *h)
{
- return SHA256_DIGEST_LENGTH;
+ return PG_SHA256_DIGEST_LENGTH;
}
static unsigned
int_sha256_block_len(PX_MD *h)
{
- return SHA256_BLOCK_LENGTH;
+ return PG_SHA256_BLOCK_LENGTH;
}
static void
int_sha256_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Update(ctx, data, dlen);
+ pg_sha256_update(ctx, data, dlen);
}
static void
int_sha256_reset(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Init(ctx);
+ pg_sha256_init(ctx);
}
static void
int_sha256_finish(PX_MD *h, uint8 *dst)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
- SHA256_Final(dst, ctx);
+ pg_sha256_final(ctx, dst);
}
static void
int_sha256_free(PX_MD *h)
{
- SHA256_CTX *ctx = (SHA256_CTX *) h->p.ptr;
+ pg_sha256_ctx *ctx = (pg_sha256_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -142,43 +142,43 @@ int_sha256_free(PX_MD *h)
static unsigned
int_sha384_len(PX_MD *h)
{
- return SHA384_DIGEST_LENGTH;
+ return PG_SHA384_DIGEST_LENGTH;
}
static unsigned
int_sha384_block_len(PX_MD *h)
{
- return SHA384_BLOCK_LENGTH;
+ return PG_SHA384_BLOCK_LENGTH;
}
static void
int_sha384_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Update(ctx, data, dlen);
+ pg_sha384_update(ctx, data, dlen);
}
static void
int_sha384_reset(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Init(ctx);
+ pg_sha384_init(ctx);
}
static void
int_sha384_finish(PX_MD *h, uint8 *dst)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
- SHA384_Final(dst, ctx);
+ pg_sha384_final(ctx, dst);
}
static void
int_sha384_free(PX_MD *h)
{
- SHA384_CTX *ctx = (SHA384_CTX *) h->p.ptr;
+ pg_sha384_ctx *ctx = (pg_sha384_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -190,43 +190,43 @@ int_sha384_free(PX_MD *h)
static unsigned
int_sha512_len(PX_MD *h)
{
- return SHA512_DIGEST_LENGTH;
+ return PG_SHA512_DIGEST_LENGTH;
}
static unsigned
int_sha512_block_len(PX_MD *h)
{
- return SHA512_BLOCK_LENGTH;
+ return PG_SHA512_BLOCK_LENGTH;
}
static void
int_sha512_update(PX_MD *h, const uint8 *data, unsigned dlen)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Update(ctx, data, dlen);
+ pg_sha512_update(ctx, data, dlen);
}
static void
int_sha512_reset(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Init(ctx);
+ pg_sha512_init(ctx);
}
static void
int_sha512_finish(PX_MD *h, uint8 *dst)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
- SHA512_Final(dst, ctx);
+ pg_sha512_final(ctx, dst);
}
static void
int_sha512_free(PX_MD *h)
{
- SHA512_CTX *ctx = (SHA512_CTX *) h->p.ptr;
+ pg_sha512_ctx *ctx = (pg_sha512_ctx *) h->p.ptr;
px_memset(ctx, 0, sizeof(*ctx));
px_free(ctx);
@@ -238,7 +238,7 @@ int_sha512_free(PX_MD *h)
void
init_sha224(PX_MD *md)
{
- SHA224_CTX *ctx;
+ pg_sha224_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -258,7 +258,7 @@ init_sha224(PX_MD *md)
void
init_sha256(PX_MD *md)
{
- SHA256_CTX *ctx;
+ pg_sha256_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -278,7 +278,7 @@ init_sha256(PX_MD *md)
void
init_sha384(PX_MD *md)
{
- SHA384_CTX *ctx;
+ pg_sha384_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
@@ -298,7 +298,7 @@ init_sha384(PX_MD *md)
void
init_sha512(PX_MD *md)
{
- SHA512_CTX *ctx;
+ pg_sha512_ctx *ctx;
ctx = px_alloc(sizeof(*ctx));
memset(ctx, 0, sizeof(*ctx));
diff --git a/contrib/pgcrypto/sha2.h b/contrib/pgcrypto/sha2.h
deleted file mode 100644
index 501f0e0446..0000000000
--- a/contrib/pgcrypto/sha2.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* contrib/pgcrypto/sha2.h */
-/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
-
-/*
- * FILE: sha2.h
- * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
- *
- * Copyright (c) 2000-2001, Aaron D. Gifford
- * 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 copyright holder nor the names of contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
- *
- * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
- */
-
-#ifndef _SHA2_H
-#define _SHA2_H
-
-/* avoid conflict with OpenSSL */
-#define SHA256_Init pg_SHA256_Init
-#define SHA256_Update pg_SHA256_Update
-#define SHA256_Final pg_SHA256_Final
-#define SHA384_Init pg_SHA384_Init
-#define SHA384_Update pg_SHA384_Update
-#define SHA384_Final pg_SHA384_Final
-#define SHA512_Init pg_SHA512_Init
-#define SHA512_Update pg_SHA512_Update
-#define SHA512_Final pg_SHA512_Final
-
-/*** SHA-224/256/384/512 Various Length Definitions ***********************/
-#define SHA224_BLOCK_LENGTH 64
-#define SHA224_DIGEST_LENGTH 28
-#define SHA224_DIGEST_STRING_LENGTH (SHA224_DIGEST_LENGTH * 2 + 1)
-#define SHA256_BLOCK_LENGTH 64
-#define SHA256_DIGEST_LENGTH 32
-#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1)
-#define SHA384_BLOCK_LENGTH 128
-#define SHA384_DIGEST_LENGTH 48
-#define SHA384_DIGEST_STRING_LENGTH (SHA384_DIGEST_LENGTH * 2 + 1)
-#define SHA512_BLOCK_LENGTH 128
-#define SHA512_DIGEST_LENGTH 64
-#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1)
-
-
-/*** SHA-256/384/512 Context Structures *******************************/
-typedef struct _SHA256_CTX
-{
- uint32 state[8];
- uint64 bitcount;
- uint8 buffer[SHA256_BLOCK_LENGTH];
-} SHA256_CTX;
-typedef struct _SHA512_CTX
-{
- uint64 state[8];
- uint64 bitcount[2];
- uint8 buffer[SHA512_BLOCK_LENGTH];
-} SHA512_CTX;
-
-typedef SHA256_CTX SHA224_CTX;
-typedef SHA512_CTX SHA384_CTX;
-
-void SHA224_Init(SHA224_CTX *);
-void SHA224_Update(SHA224_CTX *, const uint8 *, size_t);
-void SHA224_Final(uint8[SHA224_DIGEST_LENGTH], SHA224_CTX *);
-
-void SHA256_Init(SHA256_CTX *);
-void SHA256_Update(SHA256_CTX *, const uint8 *, size_t);
-void SHA256_Final(uint8[SHA256_DIGEST_LENGTH], SHA256_CTX *);
-
-void SHA384_Init(SHA384_CTX *);
-void SHA384_Update(SHA384_CTX *, const uint8 *, size_t);
-void SHA384_Final(uint8[SHA384_DIGEST_LENGTH], SHA384_CTX *);
-
-void SHA512_Init(SHA512_CTX *);
-void SHA512_Update(SHA512_CTX *, const uint8 *, size_t);
-void SHA512_Final(uint8[SHA512_DIGEST_LENGTH], SHA512_CTX *);
-
-#endif /* _SHA2_H */
diff --git a/src/common/Makefile b/src/common/Makefile
index 03dfaa19c4..5ddfff8b44 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -44,6 +44,12 @@ OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
string.o username.o wait_error.o
+ifeq ($(with_openssl),yes)
+OBJS_COMMON += sha2_openssl.o
+else
+OBJS_COMMON += sha2.o
+endif
+
OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
diff --git a/contrib/pgcrypto/sha2.c b/src/common/sha2.c
similarity index 82%
rename from contrib/pgcrypto/sha2.c
rename to src/common/sha2.c
index 231f9dfbb0..496467507d 100644
--- a/contrib/pgcrypto/sha2.c
+++ b/src/common/sha2.c
@@ -1,5 +1,20 @@
-/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
+/*-------------------------------------------------------------------------
+ *
+ * sha2.c
+ * Set of SHA functions for SHA-224, SHA-256, SHA-384 and SHA-512.
+ *
+ * This is the set of in-core functions used when there are no other
+ * alternative options like OpenSSL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/* $OpenBSD: sha2.c,v 1.6 2004/05/03 02:57:36 millert Exp $ */
/*
* FILE: sha2.c
* AUTHOR: Aaron D. Gifford <me@aarongifford.com>
@@ -32,16 +47,18 @@
* SUCH DAMAGE.
*
* $From: sha2.c,v 1.1 2001/11/08 00:01:51 adg Exp adg $
- *
- * contrib/pgcrypto/sha2.c
*/
+
+#ifndef FRONTEND
#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
#include <sys/param.h>
-#include "px.h"
-#include "sha2.h"
+#include "common/sha2.h"
/*
* UNROLLED TRANSFORM LOOP NOTE:
@@ -58,11 +75,9 @@
*/
/*** SHA-256/384/512 Various Length Definitions ***********************/
-/* NOTE: Most of these are in sha2.h */
-#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8)
-#define SHA384_SHORT_BLOCK_LENGTH (SHA384_BLOCK_LENGTH - 16)
-#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16)
-
+#define PG_SHA256_SHORT_BLOCK_LENGTH (PG_SHA256_BLOCK_LENGTH - 8)
+#define PG_SHA384_SHORT_BLOCK_LENGTH (PG_SHA384_BLOCK_LENGTH - 16)
+#define PG_SHA512_SHORT_BLOCK_LENGTH (PG_SHA512_BLOCK_LENGTH - 16)
/*** ENDIAN REVERSAL MACROS *******************************************/
#ifndef WORDS_BIGENDIAN
@@ -130,10 +145,9 @@
* library -- they are intended for private internal visibility/use
* only.
*/
-static void SHA512_Last(SHA512_CTX *);
-static void SHA256_Transform(SHA256_CTX *, const uint8 *);
-static void SHA512_Transform(SHA512_CTX *, const uint8 *);
-
+static void SHA512_Last(pg_sha512_ctx *context);
+static void SHA256_Transform(pg_sha256_ctx *context, const uint8 *data);
+static void SHA512_Transform(pg_sha512_ctx *context, const uint8 *data);
/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/
/* Hash constant words K for SHA-256: */
@@ -251,12 +265,12 @@ static const uint64 sha512_initial_hash_value[8] = {
/*** SHA-256: *********************************************************/
void
-SHA256_Init(SHA256_CTX *context)
+pg_sha256_init(pg_sha256_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
+ memcpy(context->state, sha256_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_BLOCK_LENGTH);
context->bitcount = 0;
}
@@ -287,7 +301,7 @@ SHA256_Init(SHA256_CTX *context)
} while(0)
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+SHA256_Transform(pg_sha256_ctx *context, const uint8 *data)
{
uint32 a,
b,
@@ -358,7 +372,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA256_Transform(SHA256_CTX *context, const uint8 *data)
+SHA256_Transform(pg_sha256_ctx *context, const uint8 *data)
{
uint32 a,
b,
@@ -448,7 +462,7 @@ SHA256_Transform(SHA256_CTX *context, const uint8 *data)
#endif /* SHA2_UNROLL_TRANSFORM */
void
-SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
+pg_sha256_update(pg_sha256_ctx *context, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -457,11 +471,11 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
+ usedspace = (context->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA256_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA256_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
@@ -482,13 +496,13 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
return;
}
}
- while (len >= SHA256_BLOCK_LENGTH)
+ while (len >= PG_SHA256_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
SHA256_Transform(context, data);
- context->bitcount += SHA256_BLOCK_LENGTH << 3;
- len -= SHA256_BLOCK_LENGTH;
- data += SHA256_BLOCK_LENGTH;
+ context->bitcount += PG_SHA256_BLOCK_LENGTH << 3;
+ len -= PG_SHA256_BLOCK_LENGTH;
+ data += PG_SHA256_BLOCK_LENGTH;
}
if (len > 0)
{
@@ -501,11 +515,11 @@ SHA256_Update(SHA256_CTX *context, const uint8 *data, size_t len)
}
static void
-SHA256_Last(SHA256_CTX *context)
+SHA256_Last(pg_sha256_ctx *context)
{
unsigned int usedspace;
- usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH;
+ usedspace = (context->bitcount >> 3) % PG_SHA256_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
/* Convert FROM host byte order */
REVERSE64(context->bitcount, context->bitcount);
@@ -515,41 +529,41 @@ SHA256_Last(SHA256_CTX *context)
/* Begin padding with a 1 bit: */
context->buffer[usedspace++] = 0x80;
- if (usedspace <= SHA256_SHORT_BLOCK_LENGTH)
+ if (usedspace <= PG_SHA256_SHORT_BLOCK_LENGTH)
{
/* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA256_SHORT_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA256_SHORT_BLOCK_LENGTH - usedspace);
}
else
{
- if (usedspace < SHA256_BLOCK_LENGTH)
+ if (usedspace < PG_SHA256_BLOCK_LENGTH)
{
- memset(&context->buffer[usedspace], 0, SHA256_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA256_BLOCK_LENGTH - usedspace);
}
/* Do second-to-last transform: */
SHA256_Transform(context, context->buffer);
/* And set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
}
}
else
{
/* Set-up for the last transform: */
- memset(context->buffer, 0, SHA256_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_SHORT_BLOCK_LENGTH);
/* Begin padding with a 1 bit: */
*context->buffer = 0x80;
}
/* Set the bit count: */
- *(uint64 *) &context->buffer[SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
+ *(uint64 *) &context->buffer[PG_SHA256_SHORT_BLOCK_LENGTH] = context->bitcount;
/* Final transform: */
SHA256_Transform(context, context->buffer);
}
void
-SHA256_Final(uint8 digest[], SHA256_CTX *context)
+pg_sha256_final(pg_sha256_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -567,22 +581,22 @@ SHA256_Final(uint8 digest[], SHA256_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA256_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA256_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha256_ctx));
}
/*** SHA-512: *********************************************************/
void
-SHA512_Init(SHA512_CTX *context)
+pg_sha512_init(pg_sha512_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH);
+ memcpy(context->state, sha512_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA512_BLOCK_LENGTH);
context->bitcount[0] = context->bitcount[1] = 0;
}
@@ -616,7 +630,7 @@ SHA512_Init(SHA512_CTX *context)
} while(0)
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+SHA512_Transform(pg_sha512_ctx *context, const uint8 *data)
{
uint64 a,
b,
@@ -684,7 +698,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#else /* SHA2_UNROLL_TRANSFORM */
static void
-SHA512_Transform(SHA512_CTX *context, const uint8 *data)
+SHA512_Transform(pg_sha512_ctx *context, const uint8 *data)
{
uint64 a,
b,
@@ -774,7 +788,7 @@ SHA512_Transform(SHA512_CTX *context, const uint8 *data)
#endif /* SHA2_UNROLL_TRANSFORM */
void
-SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
+pg_sha512_update(pg_sha512_ctx *context, const uint8 *data, size_t len)
{
size_t freespace,
usedspace;
@@ -783,11 +797,11 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
if (len == 0)
return;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (context->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
if (usedspace > 0)
{
/* Calculate how much free space is available in the buffer */
- freespace = SHA512_BLOCK_LENGTH - usedspace;
+ freespace = PG_SHA512_BLOCK_LENGTH - usedspace;
if (len >= freespace)
{
@@ -808,13 +822,13 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
return;
}
}
- while (len >= SHA512_BLOCK_LENGTH)
+ while (len >= PG_SHA512_BLOCK_LENGTH)
{
/* Process as many complete blocks as we can */
SHA512_Transform(context, data);
- ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3);
- len -= SHA512_BLOCK_LENGTH;
- data += SHA512_BLOCK_LENGTH;
+ ADDINC128(context->bitcount, PG_SHA512_BLOCK_LENGTH << 3);
+ len -= PG_SHA512_BLOCK_LENGTH;
+ data += PG_SHA512_BLOCK_LENGTH;
}
if (len > 0)
{
@@ -827,11 +841,11 @@ SHA512_Update(SHA512_CTX *context, const uint8 *data, size_t len)
}
static void
-SHA512_Last(SHA512_CTX *context)
+SHA512_Last(pg_sha512_ctx *context)
{
unsigned int usedspace;
- usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH;
+ usedspace = (context->bitcount[0] >> 3) % PG_SHA512_BLOCK_LENGTH;
#ifndef WORDS_BIGENDIAN
/* Convert FROM host byte order */
REVERSE64(context->bitcount[0], context->bitcount[0]);
@@ -842,42 +856,42 @@ SHA512_Last(SHA512_CTX *context)
/* Begin padding with a 1 bit: */
context->buffer[usedspace++] = 0x80;
- if (usedspace <= SHA512_SHORT_BLOCK_LENGTH)
+ if (usedspace <= PG_SHA512_SHORT_BLOCK_LENGTH)
{
/* Set-up for the last transform: */
- memset(&context->buffer[usedspace], 0, SHA512_SHORT_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA512_SHORT_BLOCK_LENGTH - usedspace);
}
else
{
- if (usedspace < SHA512_BLOCK_LENGTH)
+ if (usedspace < PG_SHA512_BLOCK_LENGTH)
{
- memset(&context->buffer[usedspace], 0, SHA512_BLOCK_LENGTH - usedspace);
+ memset(&context->buffer[usedspace], 0, PG_SHA512_BLOCK_LENGTH - usedspace);
}
/* Do second-to-last transform: */
SHA512_Transform(context, context->buffer);
/* And set-up for the last transform: */
- memset(context->buffer, 0, SHA512_BLOCK_LENGTH - 2);
+ memset(context->buffer, 0, PG_SHA512_BLOCK_LENGTH - 2);
}
}
else
{
/* Prepare for final transform: */
- memset(context->buffer, 0, SHA512_SHORT_BLOCK_LENGTH);
+ memset(context->buffer, 0, PG_SHA512_SHORT_BLOCK_LENGTH);
/* Begin padding with a 1 bit: */
*context->buffer = 0x80;
}
/* Store the length of input data (in bits): */
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
- *(uint64 *) &context->buffer[SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
+ *(uint64 *) &context->buffer[PG_SHA512_SHORT_BLOCK_LENGTH] = context->bitcount[1];
+ *(uint64 *) &context->buffer[PG_SHA512_SHORT_BLOCK_LENGTH + 8] = context->bitcount[0];
/* Final transform: */
SHA512_Transform(context, context->buffer);
}
void
-SHA512_Final(uint8 digest[], SHA512_CTX *context)
+pg_sha512_final(pg_sha512_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -896,38 +910,38 @@ SHA512_Final(uint8 digest[], SHA512_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA512_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA512_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha512_ctx));
}
/*** SHA-384: *********************************************************/
void
-SHA384_Init(SHA384_CTX *context)
+pg_sha384_init(pg_sha384_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha384_initial_hash_value, SHA512_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA384_BLOCK_LENGTH);
+ memcpy(context->state, sha384_initial_hash_value, PG_SHA512_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA384_BLOCK_LENGTH);
context->bitcount[0] = context->bitcount[1] = 0;
}
void
-SHA384_Update(SHA384_CTX *context, const uint8 *data, size_t len)
+pg_sha384_update(pg_sha384_ctx *context, const uint8 *data, size_t len)
{
- SHA512_Update((SHA512_CTX *) context, data, len);
+ pg_sha512_update((pg_sha512_ctx *) context, data, len);
}
void
-SHA384_Final(uint8 digest[], SHA384_CTX *context)
+pg_sha384_final(pg_sha384_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
{
- SHA512_Last((SHA512_CTX *) context);
+ SHA512_Last((pg_sha512_ctx *) context);
/* Save the hash data for output: */
#ifndef WORDS_BIGENDIAN
@@ -941,32 +955,32 @@ SHA384_Final(uint8 digest[], SHA384_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA384_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA384_DIGEST_LENGTH);
}
/* Zero out state data */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha384_ctx));
}
/*** SHA-224: *********************************************************/
void
-SHA224_Init(SHA224_CTX *context)
+pg_sha224_init(pg_sha224_ctx *context)
{
if (context == NULL)
return;
- memcpy(context->state, sha224_initial_hash_value, SHA256_DIGEST_LENGTH);
- memset(context->buffer, 0, SHA256_BLOCK_LENGTH);
+ memcpy(context->state, sha224_initial_hash_value, PG_SHA256_DIGEST_LENGTH);
+ memset(context->buffer, 0, PG_SHA256_BLOCK_LENGTH);
context->bitcount = 0;
}
void
-SHA224_Update(SHA224_CTX *context, const uint8 *data, size_t len)
+pg_sha224_update(pg_sha224_ctx *context, const uint8 *data, size_t len)
{
- SHA256_Update((SHA256_CTX *) context, data, len);
+ pg_sha256_update((pg_sha256_ctx *) context, data, len);
}
void
-SHA224_Final(uint8 digest[], SHA224_CTX *context)
+pg_sha224_final(pg_sha224_ctx *context, uint8 *digest)
{
/* If no digest buffer is passed, we don't bother doing this: */
if (digest != NULL)
@@ -984,9 +998,9 @@ SHA224_Final(uint8 digest[], SHA224_CTX *context)
}
}
#endif
- memcpy(digest, context->state, SHA224_DIGEST_LENGTH);
+ memcpy(digest, context->state, PG_SHA224_DIGEST_LENGTH);
}
/* Clean up state data: */
- px_memset(context, 0, sizeof(*context));
+ memset(context, 0, sizeof(pg_sha224_ctx));
}
diff --git a/src/common/sha2_openssl.c b/src/common/sha2_openssl.c
new file mode 100644
index 0000000000..91d0c3924b
--- /dev/null
+++ b/src/common/sha2_openssl.c
@@ -0,0 +1,102 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2_openssl.c
+ * Set of wrapper routines on top of OpenSSL to support SHA-224
+ * SHA-256, SHA-384 and SHA-512 functions.
+ *
+ * This should only be used if code is compiled with OpenSSL support.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/sha2_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <openssl/sha.h>
+
+#include "common/sha2.h"
+
+
+/* Interface routines for SHA-256 */
+void
+pg_sha256_init(pg_sha256_ctx *ctx)
+{
+ SHA256_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha256_update(pg_sha256_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA256_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest)
+{
+ SHA256_Final(dest, (SHA256_CTX *) ctx);
+}
+
+/* Interface routines for SHA-512 */
+void
+pg_sha512_init(pg_sha512_ctx *ctx)
+{
+ SHA512_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha512_update(pg_sha512_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA512_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest)
+{
+ SHA512_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-384 */
+void
+pg_sha384_init(pg_sha384_ctx *ctx)
+{
+ SHA384_Init((SHA512_CTX *) ctx);
+}
+
+void
+pg_sha384_update(pg_sha384_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA384_Update((SHA512_CTX *) ctx, data, len);
+}
+
+void
+pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest)
+{
+ SHA384_Final(dest, (SHA512_CTX *) ctx);
+}
+
+/* Interface routines for SHA-224 */
+void
+pg_sha224_init(pg_sha224_ctx *ctx)
+{
+ SHA224_Init((SHA256_CTX *) ctx);
+}
+
+void
+pg_sha224_update(pg_sha224_ctx *ctx, const uint8 *data, size_t len)
+{
+ SHA224_Update((SHA256_CTX *) ctx, data, len);
+}
+
+void
+pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest)
+{
+ SHA224_Final(dest, (SHA256_CTX *) ctx);
+}
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
new file mode 100644
index 0000000000..015a9053ac
--- /dev/null
+++ b/src/include/common/sha2.h
@@ -0,0 +1,115 @@
+/*-------------------------------------------------------------------------
+ *
+ * sha2.h
+ * Generic headers for SHA224, 256, 384 AND 512 functions of PostgreSQL.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/sha2.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* $OpenBSD: sha2.h,v 1.2 2004/04/28 23:11:57 millert Exp $ */
+
+/*
+ * FILE: sha2.h
+ * AUTHOR: Aaron D. Gifford <me@aarongifford.com>
+ *
+ * Copyright (c) 2000-2001, Aaron D. Gifford
+ * 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 copyright holder nor the names of contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``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 AUTHOR OR CONTRIBUTOR(S) 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.
+ *
+ * $From: sha2.h,v 1.1 2001/11/08 00:02:01 adg Exp adg $
+ */
+
+#ifndef _PG_SHA2_H_
+#define _PG_SHA2_H_
+
+#ifdef USE_SSL
+#include <openssl/sha.h>
+#endif
+
+/*** SHA224/256/384/512 Various Length Definitions ***********************/
+#define PG_SHA224_BLOCK_LENGTH 64
+#define PG_SHA224_DIGEST_LENGTH 28
+#define PG_SHA224_DIGEST_STRING_LENGTH (PG_SHA224_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA256_BLOCK_LENGTH 64
+#define PG_SHA256_DIGEST_LENGTH 32
+#define PG_SHA256_DIGEST_STRING_LENGTH (PG_SHA256_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA384_BLOCK_LENGTH 128
+#define PG_SHA384_DIGEST_LENGTH 48
+#define PG_SHA384_DIGEST_STRING_LENGTH (PG_SHA384_DIGEST_LENGTH * 2 + 1)
+#define PG_SHA512_BLOCK_LENGTH 128
+#define PG_SHA512_DIGEST_LENGTH 64
+#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
+
+/* Context Structures for SHA-1/224/256/384/512 */
+#ifdef USE_SSL
+typedef SHA256_CTX pg_sha256_ctx;
+typedef SHA512_CTX pg_sha512_ctx;
+typedef SHA256_CTX pg_sha224_ctx;
+typedef SHA512_CTX pg_sha384_ctx;
+#else
+typedef struct pg_sha256_ctx
+{
+ uint32 state[8];
+ uint64 bitcount;
+ uint8 buffer[PG_SHA256_BLOCK_LENGTH];
+} pg_sha256_ctx;
+typedef struct pg_sha512_ctx
+{
+ uint64 state[8];
+ uint64 bitcount[2];
+ uint8 buffer[PG_SHA512_BLOCK_LENGTH];
+} pg_sha512_ctx;
+typedef struct pg_sha256_ctx pg_sha224_ctx;
+typedef struct pg_sha512_ctx pg_sha384_ctx;
+#endif /* USE_SSL */
+
+/* Interface routines for SHA224/256/384/512 */
+extern void pg_sha224_init(pg_sha224_ctx *ctx);
+extern void pg_sha224_update(pg_sha224_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha224_final(pg_sha224_ctx *ctx, uint8 *dest);
+
+extern void pg_sha256_init(pg_sha256_ctx *ctx);
+extern void pg_sha256_update(pg_sha256_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha256_final(pg_sha256_ctx *ctx, uint8 *dest);
+
+extern void pg_sha384_init(pg_sha384_ctx *ctx);
+extern void pg_sha384_update(pg_sha384_ctx *ctx,
+ const uint8 *, size_t len);
+extern void pg_sha384_final(pg_sha384_ctx *ctx, uint8 *dest);
+
+extern void pg_sha512_init(pg_sha512_ctx *ctx);
+extern void pg_sha512_update(pg_sha512_ctx *ctx,
+ const uint8 *input0, size_t len);
+extern void pg_sha512_final(pg_sha512_ctx *ctx, uint8 *dest);
+
+#endif /* _PG_SHA2_H_ */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index db566f9c88..992f4db0e3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -114,6 +114,15 @@ sub mkvcbuild
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
+ if ($solution->{options}->{openssl})
+ {
+ push(@pgcommonallfiles, 'sha2_openssl.c');
+ }
+ else
+ {
+ push(@pgcommonallfiles, 'sha2.c');
+ }
+
our @pgcommonfrontendfiles = (
@pgcommonallfiles, qw(fe_memutils.c file_utils.c
restricted_token.c));
@@ -421,13 +430,14 @@ sub mkvcbuild
else
{
$pgcrypto->AddFiles(
- 'contrib/pgcrypto', 'md5.c',
- 'sha1.c', 'sha2.c',
- 'internal.c', 'internal-sha2.c',
- 'blf.c', 'rijndael.c',
- 'pgp-mpi-internal.c', 'imath.c');
+ 'contrib/pgcrypto', 'md5.c',
+ 'sha1.c', 'internal.c',
+ 'internal-sha2.c', 'blf.c',
+ 'rijndael.c', 'pgp-mpi-internal.c',
+ 'imath.c');
}
$pgcrypto->AddReference($postgres);
+ $pgcrypto->AddReference($libpgcommon);
$pgcrypto->AddLibrary('ws2_32.lib');
my $mf = Project::read_file('contrib/pgcrypto/Makefile');
GenerateContribSqlFiles('pgcrypto', $mf);
--
2.11.0
0002-Add-encoding-routines-for-base64-without-whitespace-.patchapplication/x-download; name=0002-Add-encoding-routines-for-base64-without-whitespace-.patchDownload
From d51fc3b0c7bcfa05b2666ab46f399cf0d0a4436a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Oct 2016 15:28:45 +0900
Subject: [PATCH 2/7] Add encoding routines for base64 without whitespace in
src/common/
Those routines are taken from the backend's encode.c, and adapted for
SCRAM-SHA-256, where base64 should not support whitespaces as per RFC5802.
---
src/common/Makefile | 6 +-
src/common/base64.c | 201 ++++++++++++++++++++++++++++++++++++++++++++
src/include/common/base64.h | 19 +++++
src/tools/msvc/Mkvcbuild.pm | 2 +-
4 files changed, 224 insertions(+), 4 deletions(-)
create mode 100644 src/common/base64.c
create mode 100644 src/include/common/base64.h
diff --git a/src/common/Makefile b/src/common/Makefile
index 5ddfff8b44..49e41cf846 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -40,9 +40,9 @@ override CPPFLAGS += -DVAL_LDFLAGS_EX="\"$(LDFLAGS_EX)\""
override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
-OBJS_COMMON = config_info.o controldata_utils.o exec.o ip.o keywords.o \
- md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o rmtree.o \
- string.o username.o wait_error.o
+OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
+ keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
+ rmtree.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/base64.c b/src/common/base64.c
new file mode 100644
index 0000000000..0c9eba45b6
--- /dev/null
+++ b/src/common/base64.c
@@ -0,0 +1,201 @@
+/*-------------------------------------------------------------------------
+ *
+ * base64.c
+ * Set of encoding and decoding routines for base64 without support
+ * for whitespace. In case of failure, those routines return -1 in case
+ * of error to let the caller do any error handling.
+ *
+ * Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/common/base64.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/base64.h"
+
+/*
+ * 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,
+};
+
+/*
+ * pg_b64_encode
+ *
+ * Encode into base64 the given string. Returns the length of the encoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_encode(const char *src, int len, char *dst)
+{
+ char *p;
+ 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 (pos != 2)
+ {
+ *p++ = _base64[(buf >> 18) & 0x3f];
+ *p++ = _base64[(buf >> 12) & 0x3f];
+ *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '=';
+ *p++ = '=';
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_decode
+ *
+ * Decode the given base64 string. Returns the length of the decoded
+ * string on success, and -1 in the event of an error.
+ */
+int
+pg_b64_decode(const char *src, int 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++;
+
+ /* Leave if a whitespace is found */
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ return -1;
+
+ if (c == '=')
+ {
+ /* end sequence */
+ if (!end)
+ {
+ if (pos == 2)
+ end = 1;
+ else if (pos == 3)
+ end = 2;
+ else
+ {
+ /*
+ * Unexpected "=" character found while decoding base64
+ * sequence.
+ */
+ return -1;
+ }
+ }
+ b = 0;
+ }
+ else
+ {
+ b = -1;
+ if (c > 0 && c < 127)
+ b = b64lookup[(unsigned char) c];
+ if (b < 0)
+ {
+ /* invalid symbol found */
+ return -1;
+ }
+ }
+ /* 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)
+ {
+ /*
+ * base64 end sequence is invalid. Input data is missing padding,
+ * is truncated or is otherwise corrupted.
+ */
+ return -1;
+ }
+
+ return p - dst;
+}
+
+/*
+ * pg_b64_enc_len
+ *
+ * Returns to caller the length of the string if it were encoded with
+ * base64 based on the length provided by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual encoding.
+ */
+int
+pg_b64_enc_len(int srclen)
+{
+ /* 3 bytes will be converted to 4 */
+ return (srclen + 2) * 4 / 3;
+}
+
+/*
+ * pg_b64_dec_len
+ *
+ * Returns to caller the length of the string if it were to be decoded
+ * with base64, based on the length given by caller. This is useful to
+ * estimate how large a buffer allocation needs to be done before doing
+ * the actual decoding.
+ */
+int
+pg_b64_dec_len(int srclen)
+{
+ return (srclen * 3) >> 2;
+}
diff --git a/src/include/common/base64.h b/src/include/common/base64.h
new file mode 100644
index 0000000000..8ad8eb6f62
--- /dev/null
+++ b/src/include/common/base64.h
@@ -0,0 +1,19 @@
+/*
+ * base64.h
+ * Encoding and decoding routines for base64 without whitespace
+ * support.
+ *
+ * Portions Copyright (c) 2001-2016, PostgreSQL Global Development Group
+ *
+ * src/include/common/base64.h
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+/* base 64 */
+int pg_b64_encode(const char *src, int len, char *dst);
+int pg_b64_decode(const char *src, int len, char *dst);
+int pg_b64_enc_len(int srclen);
+int pg_b64_dec_len(int srclen);
+
+#endif /* BASE64_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 992f4db0e3..bb5c833bbb 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -110,7 +110,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- config_info.c controldata_utils.c exec.c ip.c keywords.c
+ base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
string.c username.c wait_error.c);
--
2.11.0
0003-Refactor-decision-making-of-password-encryption-into.patchapplication/x-download; name=0003-Refactor-decision-making-of-password-encryption-into.patchDownload
From 3ddd7617c70b1618e3e8498cb9d7c9ab10c636cd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 18:08:40 +0900
Subject: [PATCH 3/7] Refactor decision-making of password encryption into a
single routine
This routine was duplicated for CREATE ROLE and ALTER ROLE, and while
there is little gain by doing it now if there is only plain password
and md5-encryption support, this eases the decision-making regarding
if and how a password needs to be encrypted if there are more protocols
supported, like SCRAM.
---
src/backend/commands/user.c | 83 ++++++++++++++++++++++++++++++++-------------
1 file changed, 59 insertions(+), 24 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99b21..def8ea11a8 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static char *encrypt_password(char *passwd, char *rolname,
+ int passwd_type);
/* Check if current user has createrole privileges */
@@ -64,6 +66,49 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Encrypt a password if necessary for insertion in pg_authid.
+ *
+ * If a password is found as already MD5-encrypted, no error is raised
+ * to ease the dump and reload of such data. Returns a palloc'ed string
+ * holding the encrypted password if any transformation on the input
+ * string has been done.
+ */
+static char *
+encrypt_password(char *password, char *rolname, int passwd_type)
+{
+ char *res;
+
+ Assert(password != NULL);
+
+ /*
+ * If a password is already identified as MD5-encrypted, it is used
+ * as such. If the password given is not encrypted, adapt it depending
+ * on the type wanted by the caller of this routine.
+ */
+ if (isMD5(password))
+ res = password;
+ else
+ {
+ switch (passwd_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ res = password;
+ break;
+ case PASSWORD_TYPE_MD5:
+ res = (char *) palloc(MD5_PASSWD_LEN + 1);
+ if (!pg_md5_encrypt(password, rolname,
+ strlen(rolname),
+ res))
+ elog(ERROR, "password encryption failed");
+ break;
+ default:
+ elog(ERROR, "incorrect password type");
+ }
+ }
+
+ return res;
+}
/*
* CREATE ROLE
@@ -81,7 +126,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -393,17 +438,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ stmt->role,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +546,7 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
+ char *encrypted_passwd;
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -804,17 +844,12 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ encrypted_passwd = encrypt_password(password,
+ rolename,
+ password_type);
+
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(encrypted_passwd);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
--
2.11.0
0004-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchapplication/x-download; name=0004-Add-clause-PASSWORD-val-USING-protocol-to-CREATE-ALT.patchDownload
From fed41e4d45f89bface44f6b63de3a638d52fafec Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 18:24:10 +0900
Subject: [PATCH 4/7] Add clause PASSWORD (val USING protocol) to CREATE/ALTER
ROLE
This clause allows users to be able to enforce with which protocol
a given password is used with. if the value given is already encrypted,
the value is used as-is. This extension is useful to support future
protocols, particularly SCRAM-SHA-256.
---
doc/src/sgml/ref/alter_role.sgml | 10 +++++
doc/src/sgml/ref/create_role.sgml | 26 +++++++++++
src/backend/commands/user.c | 90 ++++++++++++++++++++++++++++++++++++---
src/backend/parser/gram.y | 7 +++
4 files changed, 126 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index da36ad9696..43e45d504d 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -34,6 +34,7 @@ ALTER ROLE <replaceable class="PARAMETER">role_specification</replaceable> [ WIT
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD ( '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">method</replaceable>' )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
ALTER ROLE <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@@ -169,6 +170,7 @@ ALTER ROLE { <replaceable class="PARAMETER">role_specification</replaceable> | A
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
+ <term><literal>PASSWORD</> ( '<replaceable class="parameter">password</replaceable>' USING '<replaceable class="parameter">method</replaceable>' )</term>
<term><literal>ENCRYPTED</></term>
<term><literal>UNENCRYPTED</></term>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
@@ -280,6 +282,14 @@ ALTER ROLE davide WITH PASSWORD 'hu8jmn3';
</para>
<para>
+ Change a role's password using MD5-encryption:
+
+<programlisting>
+ALTER ROLE lionel WITH PASSWORD ('hu8jmn3' USING 'md5');
+</programlisting>
+ </para>
+
+ <para>
Remove a role's password:
<programlisting>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8369..25911ec8ac 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -34,6 +34,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
+ | PASSWORD ( '<replaceable class="PARAMETER">password</replaceable>' USING '<replaceable class="PARAMETER">method</replaceable>' )
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
| IN ROLE <replaceable class="PARAMETER">role_name</replaceable> [, ...]
| IN GROUP <replaceable class="PARAMETER">role_name</replaceable> [, ...]
@@ -245,6 +246,23 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</varlistentry>
<varlistentry>
+ <term><literal>PASSWORD</> ( '<replaceable class="parameter">password</replaceable>' USING '<replaceable class="parameter">method</replaceable>' )</term>
+ <listitem>
+ <para>
+ Sets the role's password using the requested method. (A password
+ is only of use for roles having the <literal>LOGIN</literal>
+ attribute, but you can nonetheless define one for roles without it.)
+ If you do not plan to use password authentication you can omit this
+ option. The methods supported are <literal>md5</> to enforce
+ a password to be MD5-encrypted, and <literal>plain</> to use an
+ unencrypted password. If the password string is already in
+ MD5-encrypted format, then it is stored encrypted even if
+ <literal>plain</> is specified.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALID UNTIL</literal> '<replaceable class="parameter">timestamp</replaceable>'</term>
<listitem>
<para>
@@ -426,6 +444,14 @@ CREATE USER davide WITH PASSWORD 'jw8s0F4';
</para>
<para>
+ Create a role with a MD5-encrypted password:
+
+<programlisting>
+CREATE USER lionel WITH PASSWORD ('asdh7as' USING 'md5');
+</programlisting>
+ </para>
+
+ <para>
Create a role with a password that is valid until the end of 2004.
After one second has ticked in 2005, the password is no longer
valid.
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index def8ea11a8..19eeb0434e 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -176,7 +176,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
- strcmp(defel->defname, "unencryptedPassword") == 0)
+ strcmp(defel->defname, "unencryptedPassword") == 0 ||
+ strcmp(defel->defname, "methodPassword") == 0)
{
if (dpassword)
ereport(ERROR,
@@ -184,10 +185,49 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errmsg("conflicting or redundant options"),
parser_errposition(pstate, defel->location)));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "methodPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the method wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *method = strVal(lsecond((List *) dpassword->arg));
+
+ password = strVal(linitial((List *) dpassword->arg));
+
+ if (strcmp(method, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(method, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password method %s", method)));
+ }
+ }
}
else if (strcmp(defel->defname, "sysid") == 0)
{
@@ -307,8 +347,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
@@ -582,6 +620,7 @@ AlterRole(AlterRoleStmt *stmt)
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
+ strcmp(defel->defname, "methodPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
@@ -589,10 +628,49 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
- if (strcmp(defel->defname, "encryptedPassword") == 0)
+ if (strcmp(defel->defname, "password") == 0)
+ {
+ /*
+ * Password type is enforced with GUC password_encryption
+ * here.
+ */
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "encryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_MD5;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
+ {
password_type = PASSWORD_TYPE_PLAINTEXT;
+ if (dpassword && dpassword->arg)
+ password = strVal(dpassword->arg);
+ }
+ else if (strcmp(defel->defname, "methodPassword") == 0)
+ {
+ /*
+ * This is a list of two elements, the password is first and
+ * then there is the method wanted by caller.
+ */
+ if (dpassword && dpassword->arg)
+ {
+ char *method = strVal(lsecond((List *) dpassword->arg));
+
+ if (strcmp(method, "md5") == 0)
+ password_type = PASSWORD_TYPE_MD5;
+ else if (strcmp(method, "plain") == 0)
+ password_type = PASSWORD_TYPE_PLAINTEXT;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unsupported password method %s", method)));
+
+ password = strVal(linitial((List *) dpassword->arg));
+ }
+ }
}
else if (strcmp(defel->defname, "superuser") == 0)
{
@@ -680,8 +758,6 @@ AlterRole(AlterRoleStmt *stmt)
defel->defname);
}
- if (dpassword && dpassword->arg)
- password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 414348b95b..9bb06475ff 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -938,6 +938,13 @@ AlterOptRoleElem:
{
$$ = makeDefElem("password", NULL, @1);
}
+ | PASSWORD '(' Sconst USING Sconst ')'
+ {
+ $$ = makeDefElem("methodPassword",
+ (Node *)list_make2(makeString($3),
+ makeString($5)),
+ @1);
+ }
| ENCRYPTED PASSWORD Sconst
{
$$ = makeDefElem("encryptedPassword",
--
2.11.0
0005-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchapplication/x-download; name=0005-Support-for-SCRAM-SHA-256-authentication-RFC-5802-an.patchDownload
From 6ec4ec5622cd270d531bf795d5e92293aa6325e2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 7 Dec 2016 15:32:25 +0900
Subject: [PATCH 5/7] Support for SCRAM-SHA-256 authentication (RFC 5802 and
7677)
SHA-256 is used as hashing function. This commit introduces the basic
SASL communication protocol plugged in on top of the existing
infrastructure.
Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
contrib/passwordcheck/passwordcheck.c | 20 +-
contrib/pgcrypto/Makefile | 11 +-
doc/src/sgml/catalogs.sgml | 19 +-
doc/src/sgml/client-auth.sgml | 37 +-
doc/src/sgml/config.sgml | 13 +-
doc/src/sgml/protocol.sgml | 147 +++-
doc/src/sgml/ref/create_role.sgml | 23 +-
src/backend/commands/user.c | 52 +-
src/backend/libpq/Makefile | 2 +-
src/backend/libpq/auth-scram.c | 974 ++++++++++++++++++++++++++
src/backend/libpq/auth.c | 111 +++
src/backend/libpq/crypt.c | 66 +-
src/backend/libpq/hba.c | 13 +
src/backend/libpq/pg_hba.conf.sample | 8 +-
src/backend/utils/misc/guc.c | 1 +
src/backend/utils/misc/postgresql.conf.sample | 2 +-
src/common/Makefile | 2 +-
src/common/scram-common.c | 195 ++++++
src/include/commands/user.h | 3 +-
src/include/common/scram-common.h | 56 ++
src/include/libpq/crypt.h | 8 +
src/include/libpq/hba.h | 1 +
src/include/libpq/libpq-be.h | 2 +
src/include/libpq/pqcomm.h | 2 +
src/include/libpq/scram.h | 35 +
src/interfaces/libpq/.gitignore | 5 +
src/interfaces/libpq/Makefile | 17 +-
src/interfaces/libpq/fe-auth-scram.c | 566 +++++++++++++++
src/interfaces/libpq/fe-auth.c | 108 +++
src/interfaces/libpq/fe-auth.h | 8 +
src/interfaces/libpq/fe-connect.c | 52 ++
src/interfaces/libpq/libpq-int.h | 5 +
src/tools/msvc/Mkvcbuild.pm | 10 +-
33 files changed, 2486 insertions(+), 88 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/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89bbbf..da9cc4413a 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
#include "commands/user.h"
#include "common/md5.h"
+#include "libpq/scram.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
{
int namelen = strlen(username);
int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
+ char *encrypted;
int i;
bool pwd_has_letter,
pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
switch (password_type)
{
case PASSWORD_TYPE_MD5:
+ case PASSWORD_TYPE_SCRAM:
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,24 @@ check_password(const char *username,
*
* We only check for username = password.
*/
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
+ if (password_type == PASSWORD_TYPE_MD5)
+ {
+ encrypted = palloc(MD5_PASSWD_LEN + 1);
+ if (pg_md5_encrypt(username, username, namelen, encrypted))
+ elog(ERROR, "password encryption failed");
+ }
+ else if (password_type == PASSWORD_TYPE_SCRAM)
+ {
+ encrypted = scram_build_verifier(username, password, 0);
+ }
+ else
+ elog(ERROR, "incorrect password method defined");
+
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
+ pfree(encrypted);
break;
case PASSWORD_TYPE_PLAINTEXT:
diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index 14e74f899c..573bc6df79 100644
--- a/contrib/pgcrypto/Makefile
+++ b/contrib/pgcrypto/Makefile
@@ -1,10 +1,10 @@
# contrib/pgcrypto/Makefile
-INT_SRCS = md5.c sha1.c sha2.c internal.c internal-sha2.c blf.c rijndael.c \
+INT_SRCS = md5.c sha1.c internal.c internal-sha2.c blf.c rijndael.c \
pgp-mpi-internal.c imath.c
INT_TESTS = sha2
-OSSL_SRCS = openssl.c pgp-mpi-openssl.c sha2_openssl.c
+OSSL_SRCS = openssl.c pgp-mpi-openssl.c
OSSL_TESTS = sha2 des 3des cast5
ZLIB_TST = pgp-compression
@@ -59,13 +59,6 @@ SHLIB_LINK += $(filter -leay32, $(LIBS))
SHLIB_LINK += -lws2_32
endif
-# Compiling pgcrypto with those two raw files is necessary as long
-# as none of their routines are used by the backend code. Note doing
-# so can either result in library loading failures or linking resolution
-# failures at compilation depending on the environment used.
-sha2.c sha2_openssl.c: % : $(top_srcdir)/src/common/%
- rm -f $@ && $(LN_S) $< .
-
rijndael.o: rijndael.tbl
rijndael.tbl:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c4246dcd86..15c454ff5b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
<entry><type>text</type></entry>
<entry>
Password (possibly encrypted); null if none. If the password
- is encrypted, this column will begin with the string <literal>md5</>
- followed by a 32-character hexadecimal MD5 hash. The MD5 hash
- will be of the user's password concatenated to their user name.
- For example, if user <literal>joe</> has password <literal>xyzzy</>,
- <productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ is encrypted with MD5, this column will begin with the string
+ <literal>md5</> followed by a 32-character hexadecimal MD5 hash.
+ The MD5 hash will be of the user's password concatenated to their
+ user name. For example, if user <literal>joe</> has password
+ <literal>xyzzy</>, <productname>PostgreSQL</> will store the md5
+ hash of <literal>xyzzyjoe</>. If the password is encrypted with
+ SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+ first field is a salt encoded in base-64. The second field is the
+ number of iterations used to generate the password. The third field
+ is a stored key, encoded in hexadecimal. The fourth field is a
+ server key encoded in hexadecimal. A password that does not follow
+ any of those formats is assumed to be unencrypted.
</entry>
</row>
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 960f5b5871..817ed57c65 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -421,6 +421,17 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>scram</></term>
+ <listitem>
+ <para>
+ Require the client to supply a password encrypted with
+ SCRAM-SHA-256 for authentication.
+ See <xref linkend="auth-password"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>password</></term>
<listitem>
<para>
@@ -650,17 +661,24 @@ host all all localhost trust
host postgres all 192.168.93.0/24 ident
# Allow any user from host 192.168.12.10 to connect to database
-# "postgres" if the user's password is correctly supplied.
+# "postgres" if the user's password is correctly supplied and is
+# using the correct password method.
#
# TYPE DATABASE USER ADDRESS METHOD
host postgres all 192.168.12.10/32 md5
+host postgres all 192.168.12.10/32 scram
# Allow any user from hosts in the example.com domain to connect to
-# any database if the user's password is correctly supplied.
+# any database if the user's password is correctly supplied and is using
+# a MD5-encrypted password.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all .example.com md5
+# Same as previous entry, except that the supplied password must be
+# encrypted with SCRAM-SHA-256.
+host all all .example.com scram
+
# In the absence of preceding "host" lines, these two lines will
# reject all connections from 192.168.54.1 (since that entry will be
# matched first), but allow GSSAPI connections from anywhere else
@@ -888,9 +906,9 @@ omicron bryanh guest1
<para>
The password-based authentication methods are <literal>md5</>
- and <literal>password</>. These methods operate
+ <literal>scram</> and <literal>password</>. These methods operate
similarly except for the way that the password is sent across the
- connection, namely MD5-hashed and clear-text respectively.
+ connection, namely MD5-hashed, SCRAM-SHA-256 and clear-text respectively.
</para>
<para>
@@ -905,6 +923,17 @@ omicron bryanh guest1
</para>
<para>
+ <literal>scram</> has more advantages than <literal>md5</> as it
+ protects from cases where the hashed password is taken directly from
+ <structname>pg_authid</structname> in which case a connection using
+ only the stolen hash is possible without knowing the password behind
+ it. It protects as well from password interception and data sniffing
+ where the password data could be directly obtained from the network
+ as well as man-in-the-middle (MITM) attacks. So it is strongly
+ encouraged to use it over <literal>md5</> for password-based deployments.
+ </para>
+
+ <para>
<productname>PostgreSQL</productname> database passwords are
separate from operating system user passwords. The password for
each database user is stored in the <literal>pg_authid</> system
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0fc4e57d90..de67a9c12d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1179,9 +1179,10 @@ include_dir 'conf.d'
password is to be encrypted. The default value is <literal>md5</>, which
stores the password as an MD5 hash. Setting this to <literal>plain</> stores
it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
- aliases for <literal>md5</> and <literal>plain</>, respectively.
- </para>
-
+ aliases for <literal>md5</> and <literal>plain</>, respectively. Setting
+ this parameter to <literal>scram</> will encrypt the password with
+ SCRAM-SHA-256.
+ </para>
</listitem>
</varlistentry>
@@ -1254,8 +1255,10 @@ include_dir 'conf.d'
Authentication checks are always done with the server's user name
so authentication methods must be configured for the
server's user name, not the client's. Because
- <literal>md5</> uses the user name as salt on both the
- client and server, <literal>md5</> cannot be used with
+ <literal>md5</>uses the user name as salt on both the
+ client and server, and <literal>scram</> uses the user name as
+ a portion of the salt used on both the client and server,
+ <literal>md5</> and <literal>scram</> cannot be used with
<varname>db_user_namespace</>.
</para>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 50cf527427..1c23c1d7bc 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>
@@ -2585,6 +2614,114 @@ 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>
@@ -4347,7 +4484,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 25911ec8ac..7750a886a7 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -229,16 +229,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
encrypted in the system catalogs. (If neither is specified,
the default behavior is determined by the configuration
parameter <xref linkend="guc-password-encryption">.) If the
- presented password string is already in MD5-encrypted format,
- then it is stored encrypted as-is, regardless of whether
- <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
- (since the system cannot decrypt the specified encrypted
- password string). This allows reloading of encrypted
- passwords during dump/restore.
+ presented password string is already in MD5-encrypted or
+ SCRAM-encrypted format, then it is stored encrypted as-is,
+ regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+ is specified (since the system cannot decrypt the specified encrypted
+ password string). This allows reloading of encrypted passwords
+ during dump/restore.
</para>
<para>
- Note that older clients might lack support for the MD5
+ Note that older clients might lack support for the MD5 or SCRAM
authentication mechanism that is needed to work with passwords
that are stored encrypted.
</para>
@@ -254,10 +254,11 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
attribute, but you can nonetheless define one for roles without it.)
If you do not plan to use password authentication you can omit this
option. The methods supported are <literal>md5</> to enforce
- a password to be MD5-encrypted, and <literal>plain</> to use an
- unencrypted password. If the password string is already in
- MD5-encrypted format, then it is stored encrypted even if
- <literal>plain</> is specified.
+ a password to be MD5-encrypted, <literal>scram</> to enforce a password
+ to be encrypted with SCRAM-SHA-256, and <literal>plain</> to use
+ an unencrypted password. If the password string is already in
+ MD5-encrypted or SCRAM-SHA-256 format, then it is stored encrypted
+ even if another protocol is specified.
</para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 19eeb0434e..21fe6c12c9 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 "common/md5.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -69,10 +70,10 @@ have_createrole_privilege(void)
/*
* Encrypt a password if necessary for insertion in pg_authid.
*
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password if any transformation on the input
- * string has been done.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password if any transformation on
+ * the input string has been done.
*/
static char *
encrypt_password(char *password, char *rolname, int passwd_type)
@@ -82,11 +83,12 @@ encrypt_password(char *password, char *rolname, int passwd_type)
Assert(password != NULL);
/*
- * If a password is already identified as MD5-encrypted, it is used
- * as such. If the password given is not encrypted, adapt it depending
- * on the type wanted by the caller of this routine.
+ * If a password is already identified as MD5-encrypted or
+ * SCRAM-encrypted, it is used as such. If the password given is not
+ * encrypted, adapt it depending on the type wanted by the caller of
+ * this routine.
*/
- if (isMD5(password))
+ if (isMD5(password) || is_scram_verifier(password))
res = password;
else
{
@@ -102,6 +104,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
res))
elog(ERROR, "password encryption failed");
break;
+ case PASSWORD_TYPE_SCRAM:
+ res = scram_build_verifier(rolname, password, 0);
+ break;
default:
elog(ERROR, "incorrect password type");
}
@@ -222,6 +227,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(method, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(method, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -451,11 +458,22 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
/*
* Build a tuple to insert
@@ -663,6 +681,8 @@ AlterRole(AlterRoleStmt *stmt)
password_type = PASSWORD_TYPE_MD5;
else if (strcmp(method, "plain") == 0)
password_type = PASSWORD_TYPE_PLAINTEXT;
+ else if (strcmp(method, "scram") == 0)
+ password_type = PASSWORD_TYPE_SCRAM;
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -859,11 +879,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ int type;
+
+ if (isMD5(password))
+ type = PASSWORD_TYPE_MD5;
+ else if (is_scram_verifier(password))
+ type = PASSWORD_TYPE_SCRAM;
+ else
+ type = PASSWORD_TYPE_PLAINTEXT;
+
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ type,
validUntil_datum,
validUntil_null);
+ }
+
/*
* Build an updated tuple, perusing the information just obtained
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8adde2..7fa2b02743 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 ifaddr.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 0000000000..55c5efa6f1
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,974 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ * Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/backend_random.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.h"
+
+/*
+ * Status data for SCRAM. This should be kept internal to this file.
+ */
+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];
+ char SASLSalt[SCRAM_SALT_LEN];
+
+ /* Fields of the first message from client */
+ char *client_first_message_bare;
+ char *client_username;
+ char *client_authzid;
+ char *client_nonce;
+
+ /* Fields from the last message from client */
+ char *client_final_message_without_proof;
+ char *client_final_nonce;
+ char ClientProof[SCRAM_KEY_LEN];
+
+ /* Server-side status fields */
+ char *server_first_message;
+ char *server_nonce; /* base64-encoded */
+ char *server_signature;
+} scram_state;
+
+/*
+ * Internal error codes for exchange functions.
+ * Callers of the exchange routines do not need to be aware of any of
+ * that and should just send messages generated here.
+ */
+typedef enum
+{
+ SASL_NO_ERROR = 0,
+ /* error codes */
+ SASL_INVALID_ENCODING,
+ SASL_EXTENSIONS_NOT_SUPPORTED,
+ SASL_CHANNEL_BINDING_UNMATCH,
+ SASL_CHANNEL_BINDING_NO_SUPPORT,
+ SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED,
+ SASL_UNKNOWN_USER,
+ SASL_INVALID_PROOF,
+ SASL_INVALID_USERNAME_ENCODING,
+ SASL_NO_RESOURCES,
+ /*
+ * Unrecognized errors should be treated as "other-error". In order to
+ * prevent information disclosure, the server may substitute the real
+ * reason with "other-error".
+ */
+ SASL_OTHER_ERROR
+} SASLStatus;
+
+
+static SASLStatus read_client_first_message(scram_state *state,
+ char *input, char **logdetail);
+static SASLStatus read_client_final_message(scram_state *state,
+ char *input, char **logdetail);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static char *build_error_message(SASLStatus status);
+static SASLStatus check_client_data(void *opaque, char **logdetail);
+static bool verify_client_proof(scram_state *state);
+static bool verify_final_nonce(scram_state *state);
+static bool parse_scram_verifier(const char *verifier, char **salt,
+ int *iterations, char **stored_key, char **server_key);
+static void generate_nonce(char *out, int len);
+
+/*
+ * build_error_message
+ *
+ * Build an error message for a problem that happened during the SASL
+ * message exchange. Those messages are formatted with e= as prefix
+ * and need to be sent back to the client.
+ */
+static char *
+build_error_message(SASLStatus status)
+{
+ char *res = NULL;
+
+ /*
+ * The following error format is respected here:
+ *
+ * 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.
+ */
+
+ switch (status)
+ {
+ case SASL_INVALID_ENCODING:
+ res = psprintf("e=invalid-encoding");
+ break;
+ case SASL_EXTENSIONS_NOT_SUPPORTED:
+ res = psprintf("e=extensions-not-supported");
+ break;
+ case SASL_INVALID_PROOF:
+ res = psprintf("e=invalid-proof");
+ break;
+ case SASL_CHANNEL_BINDING_UNMATCH:
+ res = psprintf("e=channel-bindings-dont-match");
+ break;
+ case SASL_CHANNEL_BINDING_NO_SUPPORT:
+ res = psprintf("e=server-does-support-channel-binding");
+ break;
+ case SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED:
+ res = psprintf("e=unsupported-channel-binding-type");
+ break;
+ case SASL_UNKNOWN_USER:
+ res = psprintf("e=unknown-user");
+ break;
+ case SASL_INVALID_USERNAME_ENCODING:
+ res = psprintf("e=invalid-username-encoding");
+ break;
+ case SASL_NO_RESOURCES:
+ res = psprintf("e=no-resources");
+ break;
+ case SASL_OTHER_ERROR:
+ res = psprintf("e=other-error");
+ break;
+ case SASL_NO_ERROR:
+ default:
+ Assert(0); /* should not happen */
+ }
+
+ Assert(res != NULL);
+ return res;
+}
+
+/*
+ * pg_be_scram_init
+ *
+ * Initialize a new SCRAM authentication exchange status tracker. This
+ * needs to be called before doing any exchange. It will be filled later
+ * after the beginning of the exchange with verifier data.
+ */
+void *
+pg_be_scram_init(char *username)
+{
+ scram_state *state;
+
+ state = (scram_state *) palloc0(sizeof(scram_state));
+ state->state = INIT;
+ state->username = username;
+ state->salt = NULL;
+
+ return state;
+}
+
+/*
+ * check_client_data
+ *
+ * Fill into the SASL exchange status all the information related to user and
+ * perform sanity checks.
+ */
+static SASLStatus
+check_client_data(void *opaque, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaque;
+ char *server_key;
+ char *stored_key;
+ char *salt;
+ int iterations;
+ int res;
+ char *passwd;
+ TimestampTz vuntil = 0;
+ bool vuntil_null;
+ int count = 0;
+
+ /* compute the salt to use for computing responses */
+ while (count < sizeof(MyProcPort->SASLSalt))
+ {
+ char byte;
+
+ if (!pg_backend_random(&byte, 1))
+ {
+ *logdetail = psprintf(_("Could not generate random salt"));
+ return SASL_OTHER_ERROR;
+ }
+
+ /*
+ * Only ASCII printable characters, except commas are accepted in
+ * the nonce.
+ */
+ if (byte < '!' || byte > '~' || byte == ',')
+ continue;
+
+ MyProcPort->SASLSalt[count] = byte;
+ count++;
+ }
+
+ /*
+ * Fetch details about role needed for password checks.
+ */
+ res = get_role_details(state->username, &passwd, &vuntil,
+ &vuntil_null,
+ logdetail);
+
+ /*
+ * Check if an error has happened. "logdetail" is already filled,
+ * here we just need to find what is the error mapping with the SASL
+ * exchange and let the client know what happened.
+ */
+ if (res == PG_ROLE_NOT_DEFINED)
+ return SASL_UNKNOWN_USER;
+ else if (res == PG_ROLE_NO_PASSWORD ||
+ res == PG_ROLE_EMPTY_PASSWORD)
+ return SASL_OTHER_ERROR;
+
+ /*
+ * Check password validity, there is nothing to do if the password
+ * validity field is null.
+ */
+ if (!vuntil_null)
+ {
+ if (vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+ }
+
+ /* The SCRAM verifier needs to be in correct shape as well. */
+ if (!parse_scram_verifier(passwd, &salt, &iterations,
+ &stored_key, &server_key))
+ {
+ *logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
+ state->username);
+ pfree(passwd);
+ return SASL_OTHER_ERROR;
+ }
+
+ /* OK to fill in everything */
+ state->salt = salt;
+ state->iterations = iterations;
+ memcpy(state->ServerKey, server_key, SCRAM_KEY_LEN);
+ memcpy(state->StoredKey, stored_key, SCRAM_KEY_LEN);
+ pfree(stored_key);
+ pfree(server_key);
+ pfree(passwd);
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ *
+ * The next message to send to client is saved in "output", for a length
+ * of "outputlen". In the case of an error, optionally store a palloc'd
+ * string at *logdetail that will be sent to the postmaster log (but not
+ * the client).
+ */
+int
+pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail)
+{
+ scram_state *state = (scram_state *) opaq;
+ SASLStatus status;
+ int result;
+
+ *output = NULL;
+ *outputlen = 0;
+
+ if (inputlen > 0)
+ elog(DEBUG4, "got SCRAM message: %s", input);
+
+ switch (state->state)
+ {
+ case INIT:
+ /*
+ * Initialization phase. Things happen in the following order:
+ * 1) Receive the first message from client and be sure that it
+ * is parsed correctly.
+ * 2) Validate the user information. A couple of things are done
+ * here, mainly validity checks on the password and the user.
+ * 3) Send the challenge to the client.
+ */
+ status = read_client_first_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* check validity of user data */
+ status = check_client_data(state, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* prepare message to send challenge */
+ *output = build_server_first_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+ *outputlen = strlen(*output);
+ state->state = SALT_SENT;
+ result = SASL_EXCHANGE_CONTINUE;
+ break;
+
+ case SALT_SENT:
+ /*
+ * Final phase for the server. First receive the response to
+ * the challenge previously sent and then let the client know
+ * that everything went well.
+ */
+ status = read_client_final_message(state, input, logdetail);
+ if (status != SASL_NO_ERROR)
+ {
+ *output = build_error_message(status);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Now check the final nonce and the client proof */
+ if (!verify_final_nonce(state) ||
+ !verify_client_proof(state))
+ {
+ *output = build_error_message(SASL_INVALID_PROOF);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Build final message for client */
+ *output = build_server_final_message(state);
+ if (*output == NULL)
+ {
+ *output = build_error_message(SASL_OTHER_ERROR);
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_FAILURE;
+ break;
+ }
+
+ /* Success! */
+ *outputlen = strlen(*output);
+ result = SASL_EXCHANGE_SUCCESS;
+ 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.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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[SCRAM_SALT_LEN];
+ char *encoded_salt;
+ int encoded_len;
+
+ if (iterations <= 0)
+ iterations = SCRAM_ITERATIONS_DEFAULT;
+
+ generate_nonce(salt, SCRAM_SALT_LEN);
+
+ encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
+ encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
+ encoded_salt[encoded_len] = '\0';
+
+ /* Calculate StoredKey, and encode it in hex */
+ scram_ClientOrServerKey(password, salt, SCRAM_SALT_LEN,
+ iterations, SCRAM_CLIENT_KEY_NAME, 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, SCRAM_SALT_LEN, iterations,
+ SCRAM_SERVER_KEY_NAME, 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);
+}
+
+
+/*
+ * Check if given verifier can be used for SCRAM authentication.
+ *
+ * Returns true if it is a SCRAM verifier, and false otherwise.
+ */
+bool
+is_scram_verifier(const char *verifier)
+{
+ return parse_scram_verifier(verifier, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+ * Parse and validate format of given SCRAM verifier.
+ *
+ * Returns true if the SCRAM verifier has been parsed, and false otherwise.
+ */
+static bool
+parse_scram_verifier(const char *verifier, char **salt, int *iterations,
+ char **stored_key, char **server_key)
+{
+ char *salt_res = NULL;
+ char *stored_key_res = NULL;
+ char *server_key_res = NULL;
+ char *v;
+ char *p;
+ int iterations_res;
+
+ /*
+ * The verifier is of form:
+ *
+ * salt:iterations:storedkey:serverkey
+ */
+ v = pstrdup(verifier);
+
+ /* salt */
+ if ((p = strtok(v, ":")) == NULL)
+ goto invalid_verifier;
+ salt_res = pstrdup(p);
+
+ /* iterations */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ errno = 0;
+ iterations_res = strtol(p, &p, SCRAM_ITERATION_LEN);
+ 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;
+
+ stored_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, stored_key_res);
+
+ /* serverkey */
+ if ((p = strtok(NULL, ":")) == NULL)
+ goto invalid_verifier;
+ if (strlen(p) != SCRAM_KEY_LEN * 2)
+ goto invalid_verifier;
+ server_key_res = (char *) palloc(SCRAM_KEY_LEN);
+ hex_decode(p, SCRAM_KEY_LEN * 2, server_key_res);
+
+ if (iterations)
+ *iterations = iterations_res;
+ if (salt)
+ *salt = salt_res;
+ else
+ pfree(salt_res);
+ if (stored_key)
+ *stored_key = stored_key_res;
+ else
+ pfree(stored_key_res);
+ if (server_key)
+ *server_key = server_key_res;
+ else
+ pfree(server_key_res);
+ pfree(v);
+ return true;
+
+invalid_verifier:
+ if (salt_res)
+ pfree(salt_res);
+ if (stored_key_res)
+ pfree(stored_key_res);
+ if (server_key_res)
+ pfree(server_key_res);
+ pfree(v);
+ return false;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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')))
+ return NULL;
+ if (attr_p)
+ *attr_p = attr;
+ begin++;
+
+ if (*begin != '=')
+ return NULL;
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message. In the event of an error, returns
+ * to caller a e= message to be used for the rest of the exchange, or
+ * NULL in case of success.
+ */
+static SASLStatus
+read_client_first_message(scram_state *state, char *input, char **logdetail)
+{
+ 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 */
+ *logdetail = psprintf(_("channel binding not supported."));
+ return SASL_CHANNEL_BINDING_NO_SUPPORT;
+ }
+
+ /* any mandatory extensions would go here. */
+ if (*input != ',')
+ {
+ *logdetail = psprintf(_("mandatory extension %c not supported."), *input);
+ return SASL_OTHER_ERROR;
+ }
+ 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 */
+ 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! */
+ return SASL_EXCHANGE_CONTINUE;
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(DEBUG4, "ClientSignature: %02X%02X", ClientSignature[0], ClientSignature[1]);
+ elog(DEBUG4, "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(DEBUG4, "client's ClientKey: %02X%02X", ClientKey[0], ClientKey[1]);
+ elog(DEBUG4, "client's StoredKey: %02X%02X", client_StoredKey[0], client_StoredKey[1]);
+ elog(DEBUG4, "StoredKey: %02X%02X", state->StoredKey[0], state->StoredKey[1]);
+
+ if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+ char nonce[SCRAM_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, SCRAM_NONCE_LEN);
+
+ state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+ if (encoded_len < 0)
+ return NULL;
+
+ 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;
+}
+
+
+/*
+ * Read and parse the final message received from client.
+ */
+static SASLStatus
+read_client_final_message(scram_state *state, char *input, char **logdetail)
+{
+ 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)
+ {
+ *logdetail = psprintf(_("invalid channel binding input."));
+ return SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED;
+ }
+ 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(pg_b64_dec_len(strlen(value)));
+ if (pg_b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+ {
+ *logdetail = psprintf(_("invalid client proof."));
+ return SASL_INVALID_PROOF;
+ }
+ memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+ pfree(client_proof);
+
+ if (*p != '\0')
+ {
+ *logdetail = psprintf(_("malformed SCRAM message (garbage at end of message %c)."),
+ attr);
+ return SASL_OTHER_ERROR;
+ }
+
+ 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';
+
+ /* XXX: check channel_binding field if support is added */
+ return SASL_NO_ERROR;
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+ 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(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+ siglen = pg_b64_encode((const char *) ServerSignature,
+ SCRAM_KEY_LEN, server_signature_base64);
+ if (siglen < 0)
+ return NULL;
+ server_signature_base64[siglen] = '\0';
+
+ /*
+ * The following error is generated:
+ *
+ * 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)
+{
+ /* Use the salt generated for SASL authentication */
+ memset(result, 0, len);
+ memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5d166db574..e0fbb247d3 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,6 +30,7 @@
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
@@ -217,6 +218,12 @@ static int CheckRADIUSAuth(Port *port);
/*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
@@ -278,6 +285,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;
@@ -556,6 +564,10 @@ ClientAuthentication(Port *port)
status = CheckPasswordAuth(port, &logdetail);
break;
+ case uaSASL:
+ status = CheckSASLAuth(port, &logdetail);
+ break;
+
case uaPAM:
#ifdef USE_PAM
status = CheckPAMAuth(port, port->user_name, "");
@@ -755,6 +767,105 @@ CheckPasswordAuth(Port *port, char **logdetail)
return result;
}
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+ int mtype;
+ StringInfoData buf;
+ void *scram_opaq;
+ 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.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SASL authentication is not supported in protocol version 2")));
+
+ /*
+ * Send first the authentication request to user. As for MD5, we want
+ * the user to send its password first even if nothing has been done
+ * yet. This avoids consistency issues where a user would be able to
+ * guess that a server is expecting SASL or MD5 depending on the answer
+ * given by the backend without the user providing a password first.
+ */
+ sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+ strlen(SCRAM_SHA256_NAME) + 1);
+
+ /* Initialize the status tracker for message exchanges */
+ scram_opaq = pg_be_scram_init(port->user_name);
+
+ /*
+ * Loop through SASL message exchange. This exchange can consist of
+ * multiple messages 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 = pg_be_scram_exchange(scram_opaq, buf.data, buf.len,
+ &output, &outputlen, logdetail);
+
+ /* 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);
+
+ /* Oops, Something bad happened */
+ if (result != SASL_EXCHANGE_SUCCESS)
+ {
+ /* an error should have been set during the exchange checks */
+ Assert(*logdetail != NULL);
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+}
/*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 35b657adbb..39ffe40d78 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
+ * Set of routines to look into the password file and check the
+ * encrypted password with the one passed in from the frontend.
*
* Original coding by Todd A. Brandys
*
@@ -30,23 +30,25 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and returns status code describing the error. In the case of an error,
+ * store a palloc'd string at *logdetail that will be sent to the postmaster
+ * log (but not the client!).
*/
int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
- char *md5_salt, int md5_salt_len, char **logdetail)
+get_role_details(const char *role,
+ char **password,
+ TimestampTz *vuntil,
+ bool *vuntil_null,
+ char **logdetail)
{
- int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
- TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *vuntil = 0;
+ *vuntil_null = true;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -63,24 +65,52 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return STATUS_ERROR; /* user has no password */
+ return PG_ROLE_NO_PASSWORD; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *password = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ {
+ *vuntil = DatumGetTimestampTz(datum);
+ *vuntil_null = false;
+ }
ReleaseSysCache(roleTup);
- if (*shadow_pass == '\0')
+ if (**password == '\0')
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
- return STATUS_ERROR; /* empty password */
+ pfree(*password);
+ return PG_ROLE_EMPTY_PASSWORD; /* empty password */
}
+ return PG_ROLE_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+ char *md5_salt, int md5_salt_len, char **logdetail)
+{
+ int retval = STATUS_ERROR;
+ char *shadow_pass,
+ *crypt_pwd;
+ TimestampTz vuntil;
+ char *crypt_client_pass = client_pass;
+ bool vuntil_null;
+
+ /* fetch details about role needed for password checks */
+ if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+ logdetail) != PG_ROLE_OK)
+ return STATUS_ERROR;
+
/*
* Compare with the encrypted or plain password depending on the
* authentication method being used for this connection. (We do not
@@ -152,7 +182,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
/*
* Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (isnull)
+ if (vuntil_null)
retval = STATUS_OK;
else if (vuntil < GetCurrentTimestamp())
{
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38c92..6fe79d7738 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,19 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
+ else if (strcmp(token->string, "scram") == 0)
+ {
+ 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 e0fbfcb026..73f7973ea2 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
# 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert". Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a02511754e..7e4b1f4602 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
static const struct config_enum_entry password_encryption_options[] = {
{"plain", PASSWORD_TYPE_PLAINTEXT, false},
{"md5", PASSWORD_TYPE_MD5, false},
+ {"scram", PASSWORD_TYPE_SCRAM, false},
{"off", PASSWORD_TYPE_PLAINTEXT, false},
{"on", PASSWORD_TYPE_MD5, false},
{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7f9acfda06..585ff360f2 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
#ssl_key_file = 'server.key' # (change requires restart)
#ssl_ca_file = '' # (change requires restart)
#ssl_crl_file = '' # (change requires restart)
-#password_encryption = md5 # md5 or plain
+#password_encryption = md5 # md5, scram or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf846..971ddd5ea7 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o ip.o \
keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
- rmtree.o string.o username.o wait_error.o
+ rmtree.o scram-common.o string.o username.o wait_error.o
ifeq ($(with_openssl),yes)
OBJS_COMMON += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000000..fb9a0b86da
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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
+ * Mechanism (SCRAM), per IETF's RFC 5802.
+ *
+ * Portions Copyright (c) 2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/common/scram-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#include "utils/memutils.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/scram-common.h"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+ uint8 k_ipad[SHA256_HMAC_B];
+ int i;
+ uint8 keybuf[SCRAM_KEY_LEN];
+
+ /*
+ * If the key is longer than the block size (64 bytes for SHA-256),
+ * pass it through SHA-256 once to shrink it down
+ */
+ if (keylen > SHA256_HMAC_B)
+ {
+ pg_sha256_ctx sha256_ctx;
+
+ pg_sha256_init(&sha256_ctx);
+ pg_sha256_update(&sha256_ctx, key, keylen);
+ pg_sha256_final(&sha256_ctx, keybuf);
+ key = keybuf;
+ keylen = SCRAM_KEY_LEN;
+ }
+
+ memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+ memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+ pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+ uint8 h[SCRAM_KEY_LEN];
+
+ pg_sha256_final(&ctx->sha256ctx, h);
+
+ /* H(K XOR opad, tmp) */
+ pg_sha256_init(&ctx->sha256ctx);
+ pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+ pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+ pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+ pg_sha256_ctx ctx;
+
+ pg_sha256_init(&ctx);
+ pg_sha256_update(&ctx, input, len);
+ pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+static void
+scram_Normalize(const char *password, char *result)
+{
+ /*
+ * XXX: Here SASLprep should be applied on password. However, per RFC5802,
+ * it is required that the password is encoded in UTF-8, something that is
+ * not guaranteed in this protocol. We may want to revisit this
+ * normalization function once encoding functions are available as well
+ * in the frontend in order to be able to encode properly this string,
+ * and then apply SASLprep on it.
+ */
+ memcpy(result, password, strlen(password) + 1);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+static void
+scram_SaltedPassword(const char *password, const char *salt, int saltlen, int iterations,
+ uint8 *result)
+{
+ char *pwbuf;
+
+ pwbuf = (char *) malloc(strlen(password) + 1);
+ scram_Normalize(password, pwbuf);
+ scram_Hi(pwbuf, salt, saltlen, iterations, result);
+ free(pwbuf);
+}
+
+/*
+ * 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, SCRAM_KEY_LEN);
+ scram_HMAC_update(&ctx, keystr, strlen(keystr));
+ scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5861..1ff441aab1 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
typedef enum PasswordType
{
PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
+ PASSWORD_TYPE_MD5,
+ PASSWORD_TYPE_SCRAM
} PasswordType;
extern int Password_encryption; /* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000000..e9028fbcd2
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ * Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, 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/sha2.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+#define SCRAM_NONCE_LEN 10
+
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN SCRAM_NONCE_LEN
+
+/* number of bytes used when sending iteration number during exchange */
+#define SCRAM_ITERATION_LEN 10
+
+/* default number of iterations when generating verifier */
+#define SCRAM_ITERATIONS_DEFAULT 4096
+
+/* Base name of keys used for proof generation */
+#define SCRAM_SERVER_KEY_NAME "Server Key"
+#define SCRAM_CLIENT_KEY_NAME "Client Key"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+ pg_sha256_ctx sha256ctx;
+ uint8 k_opad[SHA256_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/crypt.h b/src/include/libpq/crypt.h
index f51e0fd46b..1b8d47039b 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,14 @@
#include "libpq/libpq-be.h"
+/* Detailed error codes for get_role_details() */
+#define PG_ROLE_OK 0
+#define PG_ROLE_NOT_DEFINED 1
+#define PG_ROLE_NO_PASSWORD 2
+#define PG_ROLE_EMPTY_PASSWORD 3
+
+extern int get_role_details(const char *role, char **password,
+ TimestampTz *vuntil, bool *vuntil_null, char **logdetail);
extern int md5_crypt_verify(const Port *port, const char *role,
char *client_pass, char *md5_salt, int md5_salt_len, char **logdetail);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index dc7d2572ea..9c93a6ba6e 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/libpq-be.h b/src/include/libpq/libpq-be.h
index 66647ad003..299aaca1cf 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,6 +144,8 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
+ char SASLSalt[10]; /* SASL password salt, size of
+ * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 96484227f7..bbcb2f746b 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 0000000000..58638ebc48
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ * Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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
+
+/* Name of SCRAM-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE 0
+#define SASL_EXCHANGE_SUCCESS 1
+#define SASL_EXCHANGE_FAILURE 2
+
+/* Routines dedicated to authentication */
+extern void *pg_be_scram_init(char *username);
+extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+ char **output, int *outputlen, char **logdetail);
+
+/* Routines to handle and check SCRAM-SHA-256 verifier */
+extern char *scram_build_verifier(const char *username,
+ const char *password,
+ int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7176..2224ada731 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
/exports.list
+/base64.c
/chklocale.c
/crypt.c
/getaddrinfo.c
@@ -7,8 +8,12 @@
/inet_net_ntop.c
/noblock.c
/open.c
+/pg_strong_random.c
/pgstrcasecmp.c
/pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
/snprintf.c
/strerror.c
/strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb35e..460b0a1ca9 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
- thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
+ pgstrcasecmp.o pqsignal.o thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
endif
ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+base64.c scram-common.c sha2.c sha2_openssl.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 0000000000..a1dc7f1d83
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,566 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ * The front-end (client) implementation of SCRAM authentication.
+ *
+ * Portions Copyright (c) 1996-2016, 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef struct
+{
+ enum
+ {
+ INIT,
+ NONCE_SENT,
+ PROOF_SENT,
+ FINISHED
+ } state;
+
+ const char *username;
+ const char *password;
+
+ char *client_first_message_bare;
+ char *client_final_message_without_proof;
+
+ /* 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_final_message;
+ char ServerProof[SCRAM_KEY_LEN];
+} 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,
+ PQExpBuffer errormessage);
+static char *build_client_first_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static char *build_client_final_message(fe_scram_state *state,
+ PQExpBuffer errormessage);
+static bool verify_server_proof(fe_scram_state *state);
+static bool generate_nonce(char *buf, int len);
+static void calculate_client_proof(fe_scram_state *state,
+ const char *client_final_message_without_proof,
+ uint8 *result);
+
+/*
+ * Initialize SCRAM exchange status.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange status
+ */
+void
+pg_fe_scram_free(void *opaq)
+{
+ fe_scram_state *state = (fe_scram_state *) opaq;
+
+ /* client messages */
+ if (state->client_first_message_bare)
+ free(state->client_first_message_bare);
+ if (state->client_final_message_without_proof)
+ free(state->client_final_message_without_proof);
+
+ /* first message from server */
+ if (state->server_first_message)
+ free(state->server_first_message);
+ if (state->salt)
+ free(state->salt);
+ if (state->server_nonce)
+ free(state->server_nonce);
+
+ /* final message from server */
+ if (state->server_final_message)
+ free(state->server_final_message);
+
+ free(state);
+}
+
+/*
+ * Exchange a SCRAM message with backend.
+ */
+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, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = NONCE_SENT;
+ break;
+
+ case NONCE_SENT:
+ /* receive salt and server nonce, send response */
+ if (!read_server_first_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *output = build_client_final_message(state, errorMessage);
+ if (*output == NULL)
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *outputlen = strlen(*output);
+ *done = false;
+ state->state = PROOF_SENT;
+ break;
+
+ case PROOF_SENT:
+ /* receive server proof, and verify it */
+ if (!read_server_final_message(state, input, errorMessage))
+ {
+ *done = true;
+ *success = false;
+ break;
+ }
+ *success = verify_server_proof(state);
+ *done = true;
+ state->state = FINISHED;
+ break;
+
+ default:
+ /* shouldn't happen */
+ *done = true;
+ *success = false;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("invalid SCRAM exchange state\n"));
+ }
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ *
+ * This routine is able to handle error messages e= sent by the server
+ * during the exchange of SASL messages. Returns NULL in case of error,
+ * setting errorMessage as well.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+ char *begin = *input;
+ char *end;
+
+ if (*begin == 'e')
+ {
+ /* Received an error */
+ begin++;
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr e)\n"));
+ return NULL;
+ }
+
+ begin++;
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("error received from server in SASL exchange: %s\n"),
+ begin);
+ return NULL;
+ }
+
+ if (*begin != attr)
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (%c expected)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ if (*begin != '=')
+ {
+ printfPQExpBuffer(errorMessage,
+ libpq_gettext("malformed SCRAM message (expected = in attr %c)\n"),
+ attr);
+ return NULL;
+ }
+ begin++;
+
+ end = begin;
+ while (*end && *end != ',')
+ end++;
+
+ if (*end)
+ {
+ *end = '\0';
+ *input = end + 1;
+ }
+ else
+ *input = end;
+
+ return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ */
+static char *
+build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ char nonce[SCRAM_NONCE_LEN + 1];
+ char *buf;
+ char msglen;
+
+ if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
+ return NULL;
+ }
+
+ /* Generate message */
+ msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+ buf = malloc(msglen + 1);
+ if (buf == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+ 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)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ return buf;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+static char *
+build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+ 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 (pg_b64_enc_len(SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed client proof (%d found)\n"),
+ pg_b64_enc_len(SCRAM_KEY_LEN));
+ return NULL;
+ }
+
+ client_proof_len = pg_b64_encode((char *) client_proof,
+ SCRAM_KEY_LEN,
+ client_proof_base64);
+ if (client_proof_len < 0)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("failure when encoding client proof\n"));
+ return NULL;
+ }
+ client_proof_base64[client_proof_len] = '\0';
+
+ state->client_final_message_without_proof =
+ strdup(client_final_message_without_proof);
+ if (state->client_final_message_without_proof == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return NULL;
+ }
+
+ snprintf(buf, sizeof(buf), "%s,p=%s",
+ client_final_message_without_proof,
+ client_proof_base64);
+
+ return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *iterations_str;
+ char *endptr;
+ char *encoded_salt;
+ char *server_nonce;
+
+ state->server_first_message = strdup(input);
+ if (!state->server_first_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ server_nonce = read_attr_value(&input, 'r', errormessage);
+ if (server_nonce == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+
+ state->server_nonce = strdup(server_nonce);
+ if (state->server_nonce == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ encoded_salt = read_attr_value(&input, 's', errormessage);
+ if (encoded_salt == NULL)
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt)));
+ if (state->salt == NULL)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+ state->saltlen = pg_b64_decode(encoded_salt,
+ strlen(encoded_salt),
+ state->salt);
+ if (state->saltlen != SCRAM_SALT_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid salt length: found %d, expected %d\n"),
+ state->saltlen, SCRAM_SALT_LEN);
+ return false;
+ }
+
+ iterations_str = read_attr_value(&input, 'i', errormessage);
+ if (iterations_str == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error string */
+ return false;
+ }
+ state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN);
+ if (*endptr != '\0')
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("malformed SCRAM message for number of iterations\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+ char *input,
+ PQExpBuffer errormessage)
+{
+ char *encoded_server_proof;
+ int server_proof_len;
+
+ state->server_final_message = strdup(input);
+ if (!state->server_final_message)
+ {
+ printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+ return false;
+ }
+
+ /* parse the message */
+ encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+ if (encoded_server_proof == NULL || *input != '\0')
+ {
+ /* read_attr_value() has generated an error message */
+ return false;
+ }
+
+ server_proof_len = pg_b64_decode(encoded_server_proof,
+ strlen(encoded_server_proof),
+ state->ServerProof);
+ if (server_proof_len != SCRAM_KEY_LEN)
+ {
+ printfPQExpBuffer(errormessage,
+ libpq_gettext("invalid server proof\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_CLIENT_KEY_NAME, ClientKey);
+ scram_H(ClientKey, SCRAM_KEY_LEN, StoredKey);
+
+ scram_HMAC_init(&ctx, StoredKey, SCRAM_KEY_LEN);
+ 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);
+
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ result[i] = ClientKey[i] ^ ClientSignature[i];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ */
+static bool
+verify_server_proof(fe_scram_state *state)
+{
+ uint8 ServerSignature[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+ scram_HMAC_ctx ctx;
+
+ scram_ClientOrServerKey(state->password, state->salt, state->saltlen,
+ state->iterations, SCRAM_SERVER_KEY_NAME,
+ ServerKey);
+
+ /* calculate ServerSignature */
+ scram_HMAC_init(&ctx, ServerKey, SCRAM_KEY_LEN);
+ 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);
+
+ if (memcmp(ServerSignature, state->ServerProof, SCRAM_KEY_LEN) != 0)
+ return false;
+
+ return true;
+}
+
+/*
+ * Generate nonce with some randomness.
+ * Returns true of nonce has been succesfully generated, and false
+ * otherwise.
+ */
+static bool
+generate_nonce(char *buf, int len)
+{
+ int count = 0;
+
+ /* compute the salt to use for computing responses */
+ while (count < len)
+ {
+ char byte;
+
+#ifdef HAVE_STRONG_RANDOM
+ if (!pg_strong_random(&byte, 1))
+ return false;
+#else
+ byte = random() % 256;
+#endif
+
+ /*
+ * Only ASCII printable characters, except commas are accepted in
+ * the nonce.
+ */
+ if (byte < '!' || byte > '~' || byte == ',')
+ continue;
+
+ buf[count] = byte;
+ count++;
+ }
+
+ buf[len] = '\0';
+ return true;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index d861dc487b..a4fcd2332e 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
#include "common/md5.h"
#include "libpq-fe.h"
#include "fe-auth.h"
+#include "libpq/scram.h"
#ifdef ENABLE_GSS
@@ -433,6 +434,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
#endif /* ENABLE_SSPI */
/*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+ /*
+ * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+ * the moment.)
+ */
+ if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 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;
+ }
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
*
* Note: this is dead code as of Postgres 9.1, because current backends will
@@ -706,6 +785,35 @@ 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)
+ {
+ /* Use error message already if any set */
+ if (conn->errorMessage.len == 0)
+ 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 9d11654dd1..f779fb2803 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 101cce8673..e48decc863 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2697,6 +2697,49 @@ keep_going: /* We will come back to here until there is
}
}
#endif
+ /* Get additional payload for SASL, if any */
+ if ((areq == AUTH_REQ_SASL ||
+ areq == AUTH_REQ_SASL_CONT) &&
+ 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 SASL 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
@@ -3450,6 +3493,15 @@ closePGconn(PGconn *conn)
conn->sspictx = NULL;
}
#endif
+ if (conn->sasl_state)
+ {
+ /*
+ * XXX: if support for more authentication mechanisms is added, 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 a2f85895a1..aeaa3debb0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -453,7 +453,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 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index bb5c833bbb..953a0faca8 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
our @pgcommonallfiles = qw(
base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
- string.c username.c wait_error.c);
+ scram-common.c string.c username.c wait_error.c);
if ($solution->{options}->{openssl})
{
@@ -233,10 +233,16 @@ sub mkvcbuild
$libpq->AddReference($libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # if building without OpenSSL
+ # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+ # building with OpenSSL.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+ $libpq->RemoveFile('src/common/sha2_openssl.c');
+ }
+ else
+ {
+ $libpq->RemoveFile('src/common/sha2.c');
}
my $libpqwalreceiver =
--
2.11.0
0006-Add-regression-tests-for-passwords.patchapplication/x-download; name=0006-Add-regression-tests-for-passwords.patchDownload
From 602371c79061fdaa032080a5572c735eabae1b74 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 19:45:35 +0900
Subject: [PATCH 6/7] Add regression tests for passwords
---
src/test/regress/expected/password.out | 102 +++++++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/password.sql | 69 ++++++++++++++++++++++
4 files changed, 173 insertions(+), 1 deletion(-)
create mode 100644 src/test/regress/expected/password.out
create mode 100644 src/test/regress/sql/password.sql
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
new file mode 100644
index 0000000000..42c25a14cd
--- /dev/null
+++ b/src/test/regress/expected/password.out
@@ -0,0 +1,102 @@
+--
+-- Tests for password verifiers
+--
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+ERROR: invalid value for parameter "password_encryption": "novalue"
+HINT: Available values: plain, md5, scram, off, on.
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | role_pwd1
+ regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3
+ regress_passwd3 | md50e5699b6911d87f17a08b8d76a21e8b8
+ regress_passwd4 | AAAAAAAAAAAAAA==:4096:c32d0b9681e3d827fe5b5287c0ba9c9e276fe69e611dcc93cddd41f122b82e5b:51c60a9394db319302dc2727e2b8cb6c463a507312dbbf53a09adbc01ec276d3
+ regress_passwd5 |
+(5 rows)
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+NOTICE: MD5 password cleared because of role rename
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------------------+-------------
+ regress_passwd3_new |
+(1 row)
+
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+-------------------------------------
+ regress_passwd1 | foo
+ regress_passwd2 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd3 | md5530de4c298af94b3b9f7d20305d2a1bf
+ regress_passwd4 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd5 |
+(5 rows)
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD ('foo' USING 'non_existent');
+ERROR: unsupported password method non_existent
+ALTER ROLE regress_passwd1 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'); -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD ('foo' USING 'plain'); -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'); -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD ('kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'); -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+-----------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
+ regress_passwd1 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd2 | foo
+ regress_passwd3 | md5deaeed29b1cf796ea981d53e82cd5856
+ regress_passwd4 | kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5
+ regress_passwd5 |
+(5 rows)
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+ rolname | rolpassword
+---------+-------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769351..772e98497b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf3556c..ce2f5a43f9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -112,6 +112,7 @@ test: matview
test: lock
test: replica_identity
test: rowsecurity
+test: password
test: object_address
test: tablesample
test: groupingsets
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
new file mode 100644
index 0000000000..54b10df958
--- /dev/null
+++ b/src/test/regress/sql/password.sql
@@ -0,0 +1,69 @@
+--
+-- Tests for password verifiers
+--
+
+-- Tests for GUC password_encryption
+SET password_encryption = 'novalue'; -- error
+SET password_encryption = true; -- ok
+SET password_encryption = 'md5'; -- ok
+SET password_encryption = 'plain'; -- ok
+SET password_encryption = 'scram'; -- ok
+
+-- consistency of password entries
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
+SET password_encryption = 'md5';
+CREATE ROLE regress_passwd2 PASSWORD 'role_pwd2';
+SET password_encryption = 'on';
+CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3';
+SET password_encryption = 'scram';
+CREATE ROLE regress_passwd4 PASSWORD 'role_pwd4';
+SET password_encryption = 'plain';
+CREATE ROLE regress_passwd5 PASSWORD NULL;
+-- check list of created entries
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- Rename a role
+ALTER ROLE regress_passwd3 RENAME TO regress_passwd3_new;
+-- md5 entry should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd3_new'
+ ORDER BY rolname, rolpassword;
+ALTER ROLE regress_passwd3_new RENAME TO regress_passwd3;
+
+-- ENCRYPTED and UNENCRYPTED passwords
+ALTER ROLE regress_passwd1 UNENCRYPTED PASSWORD 'foo'; -- unencrypted
+ALTER ROLE regress_passwd2 UNENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+ALTER ROLE regress_passwd3 ENCRYPTED PASSWORD 'foo'; -- encrypted with MD5
+ALTER ROLE regress_passwd4 ENCRYPTED PASSWORD 'md5deaeed29b1cf796ea981d53e82cd5856'; -- encrypted with MD5
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+-- PASSWORD val USING protocol
+ALTER ROLE regress_passwd1 PASSWORD ('foo' USING 'non_existent');
+ALTER ROLE regress_passwd1 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'plain'); -- ok, as md5
+ALTER ROLE regress_passwd2 PASSWORD ('foo' USING 'plain'); -- ok, as plain
+ALTER ROLE regress_passwd3 PASSWORD ('md5deaeed29b1cf796ea981d53e82cd5856' USING 'scram'); -- ok, as md5
+ALTER ROLE regress_passwd4 PASSWORD ('kfSJjF3tdoxDNA==:4096:c52173111c7354ca17c66ba570e230ccec51c15c9f510b998d28297f723af5fa:a55cacd2a24bc2673c3d4266b8b90fa58231a674ae1b08e02236beba283fc2d5' USING 'plain'); -- ok, as scram
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
+
+DROP ROLE regress_passwd1;
+DROP ROLE regress_passwd2;
+DROP ROLE regress_passwd3;
+DROP ROLE regress_passwd4;
+DROP ROLE regress_passwd5;
+
+-- all entries should have been removed
+SELECT rolname, rolpassword
+ FROM pg_authid
+ WHERE rolname LIKE 'regress_passwd%'
+ ORDER BY rolname, rolpassword;
--
2.11.0
0007-Add-TAP-tests-for-authentication-methods.patchapplication/x-download; name=0007-Add-TAP-tests-for-authentication-methods.patchDownload
From 9837ba0deec314988cc0cf2e6a51dd49137b38fe Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 14:45:44 -0800
Subject: [PATCH 7/7] Add TAP tests for authentication methods
Those are useful to test what is expected from users having either plain,
MD5-encrypted or SCRAM passwords.
---
src/test/recovery/t/009_authentication.pl | 84 +++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 src/test/recovery/t/009_authentication.pl
diff --git a/src/test/recovery/t/009_authentication.pl b/src/test/recovery/t/009_authentication.pl
new file mode 100644
index 0000000000..4713d0b971
--- /dev/null
+++ b/src/test/recovery/t/009_authentication.pl
@@ -0,0 +1,84 @@
+# Set of tests for authentication and pg_hba.conf. The following password
+# methods are checked through this test:
+# - Plain
+# - MD5-encrypted
+# - SCRAM-encrypted
+# This test cannot run on Windows as Postgres cannot be set up with Unix
+# sockets and needs to go through SSPI.
+
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 12;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ $node->append_conf('pg_hba.conf', "local all all $hba_method");
+ $node->reload;
+}
+
+# Test access for a single role, useful to wrap all tests into one.
+sub test_role
+{
+ my $node = shift;
+ my $role = shift;
+ my $method = shift;
+ my $expected_res = shift;
+ my $status_string = 'failed';
+
+ $status_string = 'success' if ($expected_res eq 0);
+
+ my $res = $node->psql('postgres', 'SELECT 1', extra_params => ['-U', $role]);
+ is($res, $expected_res,
+ "authentication $status_string for method $method, role $role");
+}
+
+SKIP:
+{
+ skip "authentication tests cannot run on Windows", 12 if ($windows_os);
+
+ # Initialize master node
+ my $node = get_new_node('master');
+ $node->init;
+ $node->start;
+
+ # Create 3 roles with different password methods for each one. The same
+ # password is used for all of them.
+ $node->safe_psql('postgres', "CREATE ROLE scram_role LOGIN PASSWORD ('pass' USING 'scram');");
+ $node->safe_psql('postgres', "CREATE ROLE md5_role LOGIN PASSWORD ('pass' USING 'md5');");
+ $node->safe_psql('postgres', "CREATE ROLE plain_role LOGIN PASSWORD ('pass' USING 'plain');");
+ $ENV{"PGPASSWORD"} = 'pass';
+
+ # For "trust" method, all users should be able to connect.
+ reset_pg_hba($node, 'trust');
+ test_role($node, 'scram_role', 'trust', 0);
+ test_role($node, 'md5_role', 'trust', 0);
+ test_role($node, 'plain_role', 'trust', 0);
+
+ # For "plain" method, users "plain_role" and "md5_role" should be able to
+ # connect.
+ reset_pg_hba($node, 'password');
+ test_role($node, 'scram_role', 'password', 2);
+ test_role($node, 'md5_role', 'password', 0);
+ test_role($node, 'plain_role', 'password', 0);
+
+ # For "scram" method, only user "scram_role" should be able to connect.
+ reset_pg_hba($node, 'scram');
+ test_role($node, 'scram_role', 'scram', 0);
+ test_role($node, 'md5_role', 'scram', 2);
+ test_role($node, 'plain_role', 'scram', 2);
+
+ # For "md5" method, users "plain_role" and "md5_role" should be able to
+ # connect.
+ reset_pg_hba($node, 'md5');
+ test_role($node, 'scram_role', 'md5', 2);
+ test_role($node, 'md5_role', 'md5', 0);
+ test_role($node, 'plain_role', 'md5', 0);
+}
--
2.11.0
On 12/07/2016 08:39 AM, Michael Paquier wrote:
On Tue, Nov 29, 2016 at 1:36 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:Nothing more will likely happen in this CF, so I have moved it to
2017-01 with the same status of "Needs Review".Attached is a new set of patches using the new routines
pg_backend_random() and pg_strong_random() to handle the randomness in
SCRAM:
- 0001 refactors the SHA2 routines. pgcrypto uses raw files from
src/common when compiling with this patch. That works on any platform,
and this is the simplified version of upthread.
- 0002 adds base64 routines to src/common.
- 0003 does some refactoring regarding the password encryption in
ALTER/CREATE USER queries.
- 0004 adds the clause PASSWORD (val USING method) in CREATE/ALTER USER.
- 0005 is the code patch for SCRAM. Note that this switches pgcrypto
to link to libpgcommon as SHA2 routines are used by the backend.
- 0006 adds some regression tests for passwords.
- 0007 adds some TAP tests for authentication.
This is added to the upcoming CF.
I spent a little time reading through this once again. Steady progress,
did some small fixes:
* Rewrote the nonce generation. In the server-side, it first generated a
string of ascii-printable characters, then base64-encoded them, which is
superfluous. Also, avoid calling pg_strong_random() one byte at a time,
for performance reasons.
* Added a more sophisticated fallback implementation in libpq, for the
--disable-strong-random cases, similar to pg_backend_random().
* No need to disallow SCRAM with db_user_namespace. It doesn't include
the username in the salt like MD5 does.
Attached those here, as add-on patches to your latest patch set. I'll
continue reviewing, but a couple of things caught my eye that you may
want to jump on, in the meanwhile:
On error messages, the spec says:
o e: This attribute specifies an error that occurred during
authentication exchange. It is sent by the server in its final
message and can help diagnose the reason for the authentication
exchange failure. On failed authentication, the entire server-
final-message is OPTIONAL; specifically, a server implementation
MAY conclude the SASL exchange with a failure without sending the
server-final-message. This results in an application-level error
response without an extra round-trip. If the server-final-message
is sent on authentication failure, then the "e" attribute MUST be
included.
Note that it says that the server can send the error message with the e=
attribute, in the *final message*. It's not a valid response in the
earlier state, before sending server-first-message. I think we need to
change the INIT state handling in pg_be_scram_exchange() to not send e=
messages to the client. On an error at that state, it needs to just bail
out without a message. The spec allows that. We can always log the
detailed reason in the server log, anyway.
As Peter E pointed out earlier, the documentation is lacking, on how to
configure MD5 and/or SCRAM. If you put "scram" as the authentication
method in pg_hba.conf, what does it mean? If you have a line for both
"scram" and "md5" in pg_hba.conf, with the same database/user/hostname
combo, what does that mean? Answer: The first one takes effect, the
second one has no effect. Yet the example in the docs now has that,
which is nonsense :-). Hopefully we'll have some kind of a "both"
option, before the release, but in the meanwhile, we need describe how
this works now in the docs.
- Heikki
Attachments:
0008-Rewrite-nonce-generation.patchtext/x-patch; name=0008-Rewrite-nonce-generation.patchDownload
From 4d3a59ae1cb5742499c71b0c1e048d30dcef6836 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 7 Dec 2016 15:24:55 +0200
Subject: [PATCH 08/11] Rewrite nonce generation.
In the server, the nonce was generated using only ASCII-printable
characters, and the result was base64-encoded. The base64 encoding is
pointless, if we use only ASCII-printable chars to begin with.
Calling pg_strong_random() can be somewhat expensive, as with the
/dev/urandom implementation, it has to open the device, read the bytes,
and close, on every call. So avoid calling it in a loop, generating only
one byte in each call.
I went back to using base64-encoding method of turning the raw bytes into
the final nonce. That was more convenient than writing something that
encodes to the whole ASCII-printable range. That means that we're not using
the whole range of chars allowed in the nonce, but I believe that doesn't
make any difference. (Both the frontend and backend will still accept the
full range from the other side of the connection).
---
src/backend/libpq/auth-scram.c | 52 ++++++++-----------------------
src/include/common/scram-common.h | 6 +++-
src/include/libpq/libpq-be.h | 2 --
src/interfaces/libpq/fe-auth-scram.c | 60 ++++++++++--------------------------
4 files changed, 34 insertions(+), 86 deletions(-)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 55c5efa..cda663b 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -72,7 +72,7 @@ typedef struct
/* Server-side status fields */
char *server_first_message;
- char *server_nonce; /* base64-encoded */
+ char *server_nonce;
char *server_signature;
} scram_state;
@@ -115,7 +115,6 @@ static bool verify_client_proof(scram_state *state);
static bool verify_final_nonce(scram_state *state);
static bool parse_scram_verifier(const char *verifier, char **salt,
int *iterations, char **stored_key, char **server_key);
-static void generate_nonce(char *out, int len);
/*
* build_error_message
@@ -237,29 +236,6 @@ check_client_data(void *opaque, char **logdetail)
char *passwd;
TimestampTz vuntil = 0;
bool vuntil_null;
- int count = 0;
-
- /* compute the salt to use for computing responses */
- while (count < sizeof(MyProcPort->SASLSalt))
- {
- char byte;
-
- if (!pg_backend_random(&byte, 1))
- {
- *logdetail = psprintf(_("Could not generate random salt"));
- return SASL_OTHER_ERROR;
- }
-
- /*
- * Only ASCII printable characters, except commas are accepted in
- * the nonce.
- */
- if (byte < '!' || byte > '~' || byte == ',')
- continue;
-
- MyProcPort->SASLSalt[count] = byte;
- count++;
- }
/*
* Fetch details about role needed for password checks.
@@ -450,7 +426,7 @@ scram_build_verifier(const char *username, const char *password,
if (iterations <= 0)
iterations = SCRAM_ITERATIONS_DEFAULT;
- generate_nonce(salt, SCRAM_SALT_LEN);
+ pg_backend_random(salt, SCRAM_SALT_LEN);
encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
@@ -806,9 +782,6 @@ verify_client_proof(scram_state *state)
static char *
build_server_first_message(scram_state *state)
{
- char nonce[SCRAM_NONCE_LEN];
- int encoded_len;
-
/*
* server-first-message =
* [reserved-mext ","] nonce "," salt ","
@@ -830,10 +803,19 @@ build_server_first_message(scram_state *state)
*
* r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
*/
- generate_nonce(nonce, SCRAM_NONCE_LEN);
+
+ /*
+ * Per the spec, the nonce may consist of any printable ASCII characters.
+ * For convenience, however, we don't use the whole range available, rather,
+ * we generate some random bytes, and base64 encode them.
+ */
+ char raw_nonce[SCRAM_NONCE_LEN];
+ int encoded_len;
+
+ pg_backend_random(raw_nonce, SCRAM_NONCE_LEN);
state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
- encoded_len = pg_b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+ encoded_len = pg_b64_encode(raw_nonce, SCRAM_NONCE_LEN, state->server_nonce);
if (encoded_len < 0)
return NULL;
@@ -964,11 +946,3 @@ build_server_final_message(scram_state *state)
*/
return psprintf("v=%s", server_signature_base64);
}
-
-static void
-generate_nonce(char *result, int len)
-{
- /* Use the salt generated for SASL authentication */
- memset(result, 0, len);
- memcpy(result, MyProcPort->SASLSalt, Min(sizeof(MyProcPort->SASLSalt), len));
-}
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index e9028fb..34c6527 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -21,7 +21,11 @@
/* length of HMAC */
#define SHA256_HMAC_B PG_SHA256_BLOCK_LENGTH
-/* length of random nonce generated in the authentication exchange */
+/*
+ * Size of random nonce generated in the authentication exchange. This is
+ * in "raw" number of bytes, the actual nonces sent over the wire are
+ * encoded using only ASCII-printable characters.
+ */
#define SCRAM_NONCE_LEN 10
/* length of salt when generating new verifiers */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 299aaca..66647ad 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,8 +144,6 @@ typedef struct Port
* Information that needs to be held during the authentication cycle.
*/
HbaLine *hba;
- char SASLSalt[10]; /* SASL password salt, size of
- * SCRAM_SALT_LEN */
/*
* Information that really has no business at all being in struct Port,
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index a1dc7f1..12884e5 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -61,7 +61,6 @@ static char *build_client_first_message(fe_scram_state *state,
static char *build_client_final_message(fe_scram_state *state,
PQExpBuffer errormessage);
static bool verify_server_proof(fe_scram_state *state);
-static bool generate_nonce(char *buf, int len);
static void calculate_client_proof(fe_scram_state *state,
const char *client_final_message_without_proof,
uint8 *result);
@@ -257,25 +256,35 @@ read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
static char *
build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
{
- char nonce[SCRAM_NONCE_LEN + 1];
+ char raw_nonce[SCRAM_NONCE_LEN + 1];
char *buf;
- char msglen;
+ char buflen;
+ int n;
+ int encoded_len;
- if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+ /* XXX: Check max length of username? */
+
+ /*
+ * Generate a "raw" nonce. This is converted to ASCII-printable form by
+ * base64-encoding it.
+ */
+ if (!pg_strong_random(raw_nonce, SCRAM_NONCE_LEN))
{
printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
return NULL;
}
/* Generate message */
- msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
- buf = malloc(msglen + 1);
+ buflen = 5 + strlen(state->username) + 3 + pg_b64_enc_len(SCRAM_NONCE_LEN) + 1;
+ buf = malloc(buflen);
if (buf == NULL)
{
printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
return NULL;
}
- snprintf(buf, msglen + 1, "n,,n=%s,r=%s", state->username, nonce);
+ n = snprintf(buf, buflen, "n,,n=%s,r=", state->username);
+ encoded_len = pg_b64_encode(raw_nonce, SCRAM_NONCE_LEN, buf + n);
+ buf[n + encoded_len] = '\0';
state->client_first_message_bare = strdup(buf + 3);
if (!state->client_first_message_bare)
@@ -527,40 +536,3 @@ verify_server_proof(fe_scram_state *state)
return true;
}
-
-/*
- * Generate nonce with some randomness.
- * Returns true of nonce has been succesfully generated, and false
- * otherwise.
- */
-static bool
-generate_nonce(char *buf, int len)
-{
- int count = 0;
-
- /* compute the salt to use for computing responses */
- while (count < len)
- {
- char byte;
-
-#ifdef HAVE_STRONG_RANDOM
- if (!pg_strong_random(&byte, 1))
- return false;
-#else
- byte = random() % 256;
-#endif
-
- /*
- * Only ASCII printable characters, except commas are accepted in
- * the nonce.
- */
- if (byte < '!' || byte > '~' || byte == ',')
- continue;
-
- buf[count] = byte;
- count++;
- }
-
- buf[len] = '\0';
- return true;
-}
--
2.10.2
0009-Random-number-fixes.patchtext/x-patch; name=0009-Random-number-fixes.patchDownload
From d969c8c48febae6ea27035fceccc160778ebb093 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 7 Dec 2016 17:12:52 +0200
Subject: [PATCH 09/11] Random number fixes.
* Check return value of pg_backend_random() and pg_strong_random()
* Add a fallback implementation for libpq, for !HAVE_STRONG_RANDOM
* Rename SCRAM_NONCE_LEN to SCRAM_RAW_NONCE_LEN, for clarity (unrelated to
the other changes in this commit, really)
---
src/backend/libpq/auth-scram.c | 22 ++++++++++---
src/include/common/scram-common.h | 4 +--
src/interfaces/libpq/Makefile | 13 ++++++--
src/interfaces/libpq/fe-auth-scram.c | 62 +++++++++++++++++++++++++++++++++---
4 files changed, 87 insertions(+), 14 deletions(-)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index cda663b..6b129af 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -426,7 +426,13 @@ scram_build_verifier(const char *username, const char *password,
if (iterations <= 0)
iterations = SCRAM_ITERATIONS_DEFAULT;
- pg_backend_random(salt, SCRAM_SALT_LEN);
+ if (!pg_backend_random(salt, SCRAM_SALT_LEN))
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not generate random salt")));
+ return NULL;
+ }
encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
encoded_len = pg_b64_encode(salt, SCRAM_SALT_LEN, encoded_salt);
@@ -809,13 +815,19 @@ build_server_first_message(scram_state *state)
* For convenience, however, we don't use the whole range available, rather,
* we generate some random bytes, and base64 encode them.
*/
- char raw_nonce[SCRAM_NONCE_LEN];
+ char raw_nonce[SCRAM_RAW_NONCE_LEN];
int encoded_len;
- pg_backend_random(raw_nonce, SCRAM_NONCE_LEN);
+ if (!pg_backend_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not generate random nonce")));
+ return NULL;
+ }
- state->server_nonce = palloc(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
- encoded_len = pg_b64_encode(raw_nonce, SCRAM_NONCE_LEN, state->server_nonce);
+ state->server_nonce = palloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1);
+ encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->server_nonce);
if (encoded_len < 0)
return NULL;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 34c6527..024059b 100644
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -26,10 +26,10 @@
* in "raw" number of bytes, the actual nonces sent over the wire are
* encoded using only ASCII-printable characters.
*/
-#define SCRAM_NONCE_LEN 10
+#define SCRAM_RAW_NONCE_LEN 10
/* length of salt when generating new verifiers */
-#define SCRAM_SALT_LEN SCRAM_NONCE_LEN
+#define SCRAM_SALT_LEN 10
/* number of bytes used when sending iteration number during exchange */
#define SCRAM_ITERATION_LEN 10
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 460b0a1..4599b03 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -35,10 +35,17 @@ OBJS= fe-auth.o fe-auth-scram.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-l
fe-protocol2.o fe-protocol3.o pqexpbuffer.o fe-secure.o \
libpq-events.o
# libpgport C files we always use
-OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
- pgstrcasecmp.o pqsignal.o thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
+ thread.o
# libpgport C files that are needed if identified by configure
OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
+
+ifeq ($(enable_strong_random), yes)
+OBJS += pg_strong_random.o
+else
+OBJS += erand48.o
+endif
+
# src/backend/utils/mb
OBJS += encnames.o wchar.o
# src/common
@@ -95,7 +102,7 @@ backend_src = $(top_srcdir)/src/backend
# For some libpgport modules, this only happens if configure decides
# the module is needed (see filter hack in OBJS, above).
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c erand48.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
rm -f $@ && $(LN_S) $< .
ip.c md5.c: % : $(top_srcdir)/src/common/%
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 12884e5..12943ff 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -18,6 +18,12 @@
#include "common/scram-common.h"
#include "fe-auth.h"
+/* These are needed for getpid(), in the fallback implementation */
+#ifndef HAVE_STRONG_RANDOM
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
/*
* Status of exchange messages used for SCRAM authentication via the
* SASL protocol.
@@ -250,13 +256,61 @@ read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
return begin;
}
+static bool
+pg_frontend_random(char *dst, int len)
+{
+#ifdef HAVE_STRONG_RANDOM
+ return pg_strong_random(dst, len);
+#else
+ int i;
+ char *end = dst + len;
+
+ static unsigned short seed[3];
+ static int mypid = 0;
+
+ pglock_thread();
+
+ if (mypid != getpid())
+ {
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ seed[0] = now.tv_sec ^ getpid();
+ seed[1] = (unsigned short) (now.tv_usec);
+ seed[2] = (unsigned short) (now.tv_usec >> 16);
+ }
+
+ for (i = 0; dst < end; i++)
+ {
+ uint32 r;
+ int j;
+
+ /*
+ * pg_jrand48 returns a 32-bit integer. Fill the next 4 bytes from it.
+ */
+ r = (uint32) pg_jrand48(seed);
+
+ for (j = 0; j < 4 && dst < end; j++)
+ {
+ *(dst++) = (char) (r & 0xFF);
+ r >>= 8;
+ }
+ }
+
+ pgunlock_thread();
+
+ return true;
+#endif
+}
+
/*
* Build the first exchange message sent by the client.
*/
static char *
build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
{
- char raw_nonce[SCRAM_NONCE_LEN + 1];
+ char raw_nonce[SCRAM_RAW_NONCE_LEN + 1];
char *buf;
char buflen;
int n;
@@ -268,14 +322,14 @@ build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
* Generate a "raw" nonce. This is converted to ASCII-printable form by
* base64-encoding it.
*/
- if (!pg_strong_random(raw_nonce, SCRAM_NONCE_LEN))
+ if (!pg_frontend_random(raw_nonce, SCRAM_RAW_NONCE_LEN))
{
printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
return NULL;
}
/* Generate message */
- buflen = 5 + strlen(state->username) + 3 + pg_b64_enc_len(SCRAM_NONCE_LEN) + 1;
+ buflen = 5 + strlen(state->username) + 3 + pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1;
buf = malloc(buflen);
if (buf == NULL)
{
@@ -283,7 +337,7 @@ build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
return NULL;
}
n = snprintf(buf, buflen, "n,,n=%s,r=", state->username);
- encoded_len = pg_b64_encode(raw_nonce, SCRAM_NONCE_LEN, buf + n);
+ encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, buf + n);
buf[n + encoded_len] = '\0';
state->client_first_message_bare = strdup(buf + 3);
--
2.10.2
0010-No-need-to-disallow-SCRAM-with-db_user_namespace.patchtext/x-patch; name=0010-No-need-to-disallow-SCRAM-with-db_user_namespace.patchDownload
From 7d2a08dd94a96d37aa5ac821fbb963ee03798f0b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 7 Dec 2016 17:13:58 +0200
Subject: [PATCH 10/11] No need to disallow SCRAM with db_user_namespace.
---
src/backend/libpq/hba.c | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 6fe79d7..f1e9a38 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,19 +1183,6 @@ parse_hba_line(List *line, int line_num, char *raw_line)
}
parsedline->auth_method = uaMD5;
}
- else if (strcmp(token->string, "scram") == 0)
- {
- 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;
--
2.10.2
On Thu, Dec 8, 2016 at 5:54 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Attached those here, as add-on patches to your latest patch set.
Thanks for looking at it!
I'll continue reviewing, but a couple of things caught my eye that you may want
to jump on, in the meanwhile:On error messages, the spec says:
o e: This attribute specifies an error that occurred during
authentication exchange. It is sent by the server in its final
message and can help diagnose the reason for the authentication
exchange failure. On failed authentication, the entire server-
final-message is OPTIONAL; specifically, a server implementation
MAY conclude the SASL exchange with a failure without sending the
server-final-message. This results in an application-level error
response without an extra round-trip. If the server-final-message
is sent on authentication failure, then the "e" attribute MUST be
included.Note that it says that the server can send the error message with the e=
attribute, in the *final message*. It's not a valid response in the earlier
state, before sending server-first-message. I think we need to change the
INIT state handling in pg_be_scram_exchange() to not send e= messages to the
client. On an error at that state, it needs to just bail out without a
message. The spec allows that. We can always log the detailed reason in the
server log, anyway.
Hmmm. How do we handle the case where the user name does not match
then? The spec gives an error message e= specifically for this case.
If this is taken into account we need to perform sanity checks at
initialization phase I am afraid as the number of iterations and the
salt are part of the verifier. So you mean that just sending out a
normal ERROR message is fine at an earlier step (with *logdetails
filled for the backend)? I just want to be sure I understand what you
mean here.
As Peter E pointed out earlier, the documentation is lacking, on how to
configure MD5 and/or SCRAM. If you put "scram" as the authentication method
in pg_hba.conf, what does it mean? If you have a line for both "scram" and
"md5" in pg_hba.conf, with the same database/user/hostname combo, what does
that mean? Answer: The first one takes effect, the second one has no effect.
Yet the example in the docs now has that, which is nonsense :-). Hopefully
we'll have some kind of a "both" option, before the release, but in the
meanwhile, we need describe how this works now in the docs.
OK, it would be better to add a paragraph in client-auth.sgml
regarding the mapping of the two settings. For the example of file in
postgresql.conf, I would have really thought that adding directly a
line with "scram" listed was enough though. Perhaps a comment to say
that if md5 and scram are specified the first one wins where a user
and database name map?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/08/2016 10:18 AM, Michael Paquier wrote:
On Thu, Dec 8, 2016 at 5:54 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Attached those here, as add-on patches to your latest patch set.
Thanks for looking at it!
I'll continue reviewing, but a couple of things caught my eye that you may want
to jump on, in the meanwhile:On error messages, the spec says:
o e: This attribute specifies an error that occurred during
authentication exchange. It is sent by the server in its final
message and can help diagnose the reason for the authentication
exchange failure. On failed authentication, the entire server-
final-message is OPTIONAL; specifically, a server implementation
MAY conclude the SASL exchange with a failure without sending the
server-final-message. This results in an application-level error
response without an extra round-trip. If the server-final-message
is sent on authentication failure, then the "e" attribute MUST be
included.Note that it says that the server can send the error message with the e=
attribute, in the *final message*. It's not a valid response in the earlier
state, before sending server-first-message. I think we need to change the
INIT state handling in pg_be_scram_exchange() to not send e= messages to the
client. On an error at that state, it needs to just bail out without a
message. The spec allows that. We can always log the detailed reason in the
server log, anyway.Hmmm. How do we handle the case where the user name does not match
then? The spec gives an error message e= specifically for this case.
Hmm, interesting. I wonder how/when they imagine that error message to
be used. I suppose you could send a dummy server-first message, with a
made-up salt and iteration count, if the user is not found, so that you
can report that in the server-final message. But that seems
unnecessarily complicated, compared to just sending the error
immediately. I could imagine using a dummy server-first messaage to hide
whether the user exists, but that argument doesn't hold water if you're
going to report an "unknown-user" error, anyway.
Actually, we don't give away that information currently. If you try to
log in with password or MD5 authentication, and the user doesn't exist,
you get the same error as with an incorrect password. So, I think we do
need to give the client a made-up salt and iteration count in that case,
to hide the fact that the user doesn't exist. Furthermore, you can't
just generate random salt and iteration count, because then you could
simply try connecting twice, and see if you get the same salt and
iteration count. We need to deterministically derive the salt from the
username, so that you get the same salt/iteration count every time you
try connecting with that username. But it needs indistinguishable from a
random salt, to the client. Perhaps a SHA hash of the username and some
per-cluster secret value, created by initdb. There must be research
papers out there on how to do this..
To be really pedantic about that, we should also ward off timing
attacks, by making sure that the dummy authentication is no
faster/slower than a real one..
If this is taken into account we need to perform sanity checks at
initialization phase I am afraid as the number of iterations and the
salt are part of the verifier. So you mean that just sending out a
normal ERROR message is fine at an earlier step (with *logdetails
filled for the backend)? I just want to be sure I understand what you
mean here.
That's right, we can send a normal ERROR message. (But not for the
"user-not-found" case, as discussed above.)
As Peter E pointed out earlier, the documentation is lacking, on how to
configure MD5 and/or SCRAM. If you put "scram" as the authentication method
in pg_hba.conf, what does it mean? If you have a line for both "scram" and
"md5" in pg_hba.conf, with the same database/user/hostname combo, what does
that mean? Answer: The first one takes effect, the second one has no effect.
Yet the example in the docs now has that, which is nonsense :-). Hopefully
we'll have some kind of a "both" option, before the release, but in the
meanwhile, we need describe how this works now in the docs.OK, it would be better to add a paragraph in client-auth.sgml
regarding the mapping of the two settings. For the example of file in
postgresql.conf, I would have really thought that adding directly a
line with "scram" listed was enough though. Perhaps a comment to say
that if md5 and scram are specified the first one wins where a user
and database name map?
So, I think this makes no sense:
# Allow any user from host 192.168.12.10 to connect to database -# "postgres" if the user's password is correctly supplied. +# "postgres" if the user's password is correctly supplied and is +# using the correct password method. # # TYPE DATABASE USER ADDRESS METHOD host postgres all 192.168.12.10/32 md5 +host postgres all 192.168.12.10/32 scram
But this is OK:
+# Same as previous entry, except that the supplied password must be +# encrypted with SCRAM-SHA-256. +host all all .example.com scram +
Although, currently, the whole pg_hba.conf file in that example is a
valid file that someone might have on a real server. With the above
addition, it would not be. You would never have the two lines with the
same host/database/user combination in pg_hba.conf.
Overall, I think something like this would make sense in the example:
# Allow any user from hosts in the example.com domain to connect to
# any database, if the user's password is correctly supplied.
#
# Most users use SCRAM authentication, but some users use older clients
# that don't support SCRAM authentication, and need to be able to log
# in using MD5 authentication. Such users are put in the @md5users
# group, everyone else must use SCRAM.
#
# TYPE DATABASE USER ADDRESS METHOD
host all @md5users .example.com md5
host all all .example.com scram
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Dec 8, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 12/08/2016 10:18 AM, Michael Paquier wrote:
Hmmm. How do we handle the case where the user name does not match
then? The spec gives an error message e= specifically for this case.Hmm, interesting. I wonder how/when they imagine that error message to be
used. I suppose you could send a dummy server-first message, with a made-up
salt and iteration count, if the user is not found, so that you can report
that in the server-final message. But that seems unnecessarily complicated,
compared to just sending the error immediately. I could imagine using a
dummy server-first messaage to hide whether the user exists, but that
argument doesn't hold water if you're going to report an "unknown-user"
error, anyway.
Using directly an error message would map with MD5 and plain, but
that's definitely a new protocol piece so I'd rather think that using
e= once the client has sent its first message in the exchange should
be answered with an appropriate SASL error...
Actually, we don't give away that information currently. If you try to log
in with password or MD5 authentication, and the user doesn't exist, you get
the same error as with an incorrect password. So, I think we do need to give
the client a made-up salt and iteration count in that case, to hide the fact
that the user doesn't exist. Furthermore, you can't just generate random
salt and iteration count, because then you could simply try connecting
twice, and see if you get the same salt and iteration count. We need to
deterministically derive the salt from the username, so that you get the
same salt/iteration count every time you try connecting with that username.
But it needs indistinguishable from a random salt, to the client. Perhaps a
SHA hash of the username and some per-cluster secret value, created by
initdb. There must be research papers out there on how to do this..
A simple idea would be to use the system ID when generating this fake
salt? That's generated by initdb, once per cluster. I am wondering if
it would be risky to use it for the salt. For the number of iterations
the default number could be used.
To be really pedantic about that, we should also ward off timing attacks, by
making sure that the dummy authentication is no faster/slower than a real
one..
There is one catalog lookup when extracting the verifier from
pg_authid, I'd guess that if we generate a fake verifier things should
get pretty close.
If this is taken into account we need to perform sanity checks at
initialization phase I am afraid as the number of iterations and the
salt are part of the verifier. So you mean that just sending out a
normal ERROR message is fine at an earlier step (with *logdetails
filled for the backend)? I just want to be sure I understand what you
mean here.That's right, we can send a normal ERROR message. (But not for the
"user-not-found" case, as discussed above.)
I'd think that the cases where the password is empty and the password
has passed valid duration should be returned with e=other-error. If
the caller sends a SCRAM request that would be impolite (?) to just
throw up an error once the exchange has begun.
Although, currently, the whole pg_hba.conf file in that example is a valid
file that someone might have on a real server. With the above addition, it
would not be. You would never have the two lines with the same
host/database/user combination in pg_hba.conf.
Okay.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Dec 8, 2016 at 10:05 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Thu, Dec 8, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 12/08/2016 10:18 AM, Michael Paquier wrote:
Hmmm. How do we handle the case where the user name does not match
then? The spec gives an error message e= specifically for this case.Hmm, interesting. I wonder how/when they imagine that error message to be
used. I suppose you could send a dummy server-first message, with a made-up
salt and iteration count, if the user is not found, so that you can report
that in the server-final message. But that seems unnecessarily complicated,
compared to just sending the error immediately. I could imagine using a
dummy server-first message to hide whether the user exists, but that
argument doesn't hold water if you're going to report an "unknown-user"
error, anyway.Using directly an error message would map with MD5 and plain, but
that's definitely a new protocol piece so I'd rather think that using
e= once the client has sent its first message in the exchange should
be answered with an appropriate SASL error...Actually, we don't give away that information currently. If you try to log
in with password or MD5 authentication, and the user doesn't exist, you get
the same error as with an incorrect password. So, I think we do need to give
the client a made-up salt and iteration count in that case, to hide the fact
that the user doesn't exist. Furthermore, you can't just generate random
salt and iteration count, because then you could simply try connecting
twice, and see if you get the same salt and iteration count. We need to
deterministically derive the salt from the username, so that you get the
same salt/iteration count every time you try connecting with that username.
But it needs indistinguishable from a random salt, to the client. Perhaps a
SHA hash of the username and some per-cluster secret value, created by
initdb. There must be research papers out there on how to do this..A simple idea would be to use the system ID when generating this fake
salt? That's generated by initdb, once per cluster. I am wondering if
it would be risky to use it for the salt. For the number of iterations
the default number could be used.
I have been thinking more about this part quite a bit, and here is the
most simple thing that we could do while respecting the protocol.
That's more or less what I think you have in mind by re-reading
upthread, but it does not hurt to rewrite the whole flow to be clear:
1) Server gets the startup packet, maps pg_hba.conf and moves on to
the scram authentication code path.
2) Server sends back sendAuthRequest() to request user to provide a
password. This maps to the plain/md5 behavior as no errors would be
issued to user until he has provided a password.
3) Client sends back the password, and the first message with the user name.
4) Server receives it, and checks the data. If a failure happens at
this stage, just ERROR on PG-side without sending back a e= message.
This includes the username-mismatch, empty password and end of
password validity. So we would never use e=unknown-user. This sticks
with what you quoted upthread that the server may end the exchange
before sending the final message.
5) Server sends back the challenge, and client answers back with its
reply to it.
Then enters the final stage of the exchange, at which point the server
would issue its final message that would be e= in case of errors. If
something like an OOM happens, no message would be sent so failing on
an OOM ERROR on PG side would be fine as well.
6) Read final message from client and validate.
7) issue final message of server.
On failure at steps 6) or 7), an e= message is returned instead of the
final message. Does that look right?
One thing is: when do we look up at pg_authid? After receiving the
first message from client or before beginning the exchange? As the
first message from client has the user name, it would make sense to do
the lookup after receiving it, but from PG prospective it would just
make sense to use the data already present in the startup packet. The
current patch does the latter. What do you think?
By the way, I have pushed the extra patches you sent into this branch:
https://github.com/michaelpq/postgres/tree/scram
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/09/2016 05:58 AM, Michael Paquier wrote:
On Thu, Dec 8, 2016 at 10:05 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:On Thu, Dec 8, 2016 at 5:55 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Actually, we don't give away that information currently. If you try to log
in with password or MD5 authentication, and the user doesn't exist, you get
the same error as with an incorrect password. So, I think we do need to give
the client a made-up salt and iteration count in that case, to hide the fact
that the user doesn't exist. Furthermore, you can't just generate random
salt and iteration count, because then you could simply try connecting
twice, and see if you get the same salt and iteration count. We need to
deterministically derive the salt from the username, so that you get the
same salt/iteration count every time you try connecting with that username.
But it needs indistinguishable from a random salt, to the client. Perhaps a
SHA hash of the username and some per-cluster secret value, created by
initdb. There must be research papers out there on how to do this..A simple idea would be to use the system ID when generating this fake
salt? That's generated by initdb, once per cluster. I am wondering if
it would be risky to use it for the salt. For the number of iterations
the default number could be used.
I think I'd feel better with a completely separate randomly-generated
value for this. System ID is not too difficult to guess, and there's no
need to skimp on this. Yes, default number of iterations makes sense.
We cannot completely avoid leaking information through this,
unfortunately. For example, if you have a user with a non-default number
of iterations, and an attacker probes that, he'll know that the username
was valid, because he got back a non-default number of iterations. But
let's do our best.
I have been thinking more about this part quite a bit, and here is the
most simple thing that we could do while respecting the protocol.
That's more or less what I think you have in mind by re-reading
upthread, but it does not hurt to rewrite the whole flow to be clear:
1) Server gets the startup packet, maps pg_hba.conf and moves on to
the scram authentication code path.
2) Server sends back sendAuthRequest() to request user to provide a
password. This maps to the plain/md5 behavior as no errors would be
issued to user until he has provided a password.
3) Client sends back the password, and the first message with the user name.
4) Server receives it, and checks the data. If a failure happens at
this stage, just ERROR on PG-side without sending back a e= message.
This includes the username-mismatch, empty password and end of
password validity. So we would never use e=unknown-user. This sticks
with what you quoted upthread that the server may end the exchange
before sending the final message.
If we want to mimic the current behavior with MD5 authentication, I
think we need to follow through with the challenge, and only fail in the
last step, even if we know the password was empty or expired. MD5
authentication doesn't currently give away that information to the user.
But it's OK to bail out early on OOM, or if the client sends an outright
broken message. Those don't give away any information on the user account.
5) Server sends back the challenge, and client answers back with its
reply to it.
Then enters the final stage of the exchange, at which point the server
would issue its final message that would be e= in case of errors. If
something like an OOM happens, no message would be sent so failing on
an OOM ERROR on PG side would be fine as well.
6) Read final message from client and validate.
7) issue final message of server.On failure at steps 6) or 7), an e= message is returned instead of the
final message. Does that look right?
Yep.
One thing is: when do we look up at pg_authid? After receiving the
first message from client or before beginning the exchange? As the
first message from client has the user name, it would make sense to do
the lookup after receiving it, but from PG prospective it would just
make sense to use the data already present in the startup packet. The
current patch does the latter. What do you think?
Let's see what fits the program flow best. Probably best to do it before
beginning the exchange. I'm hacking on this right now...
By the way, I have pushed the extra patches you sent into this branch:
https://github.com/michaelpq/postgres/tree/scram
Thanks! We had a quick chat with Michael, and agreed that we'd hack
together on that github repository, to avoid stepping on each other's
toes, and cut rebased patch sets from there to pgsql-hackers every now
and then.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Couple of things I should write down before I forget:
1. It's a bit cumbersome that the scram verifiers stored in
pg_authid.rolpassword don't have any clear indication that they're scram
verifiers. MD5 hashes are readily identifiable by the "md5" prefix. I
think we should use a "scram-sha-256:" for scram verifiers.
Actually, I think it'd be awfully nice to also prefix plaintext
passwords with "plain:", but I'm not sure it's worth breaking the
compatibility, if there are tools out there that peek into rolpassword.
Thoughts?
2. It's currently not possible to use the plaintext "password"
authentication method, for a user that has a SCRAM verifier in
rolpassword. That seems like an oversight. We can't do MD5
authentication with a SCRAM verifier, but "password" we could.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Dec 9, 2016 at 5:11 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Couple of things I should write down before I forget:
1. It's a bit cumbersome that the scram verifiers stored in
pg_authid.rolpassword don't have any clear indication that they're scram
verifiers. MD5 hashes are readily identifiable by the "md5" prefix. I think
we should use a "scram-sha-256:" for scram verifiers.
scram-sha-256 would make the most sense to me.
Actually, I think it'd be awfully nice to also prefix plaintext passwords
with "plain:", but I'm not sure it's worth breaking the compatibility, if
there are tools out there that peek into rolpassword. Thoughts?
pgbouncer is the only thing coming up in mind. It looks at pg_shadow
for password values. pg_dump'ing data from pre-10 instances will also
need to adapt. I see tricky the compatibility with the exiting CREATE
USER PASSWORD command though, so I am wondering if that's worth the
complication.
2. It's currently not possible to use the plaintext "password"
authentication method, for a user that has a SCRAM verifier in rolpassword.
That seems like an oversight. We can't do MD5 authentication with a SCRAM
verifier, but "password" we could.
Yeah, that should be possible...
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/09/2016 05:58 AM, Michael Paquier wrote:
One thing is: when do we look up at pg_authid? After receiving the
first message from client or before beginning the exchange? As the
first message from client has the user name, it would make sense to do
the lookup after receiving it, but from PG prospective it would just
make sense to use the data already present in the startup packet. The
current patch does the latter. What do you think?
While hacking on this, I came up with the attached refactoring, against
current master. I think it makes the current code more readable, anyway,
and it provides a get_role_password() function that SCRAM can use, to
look up the stored password. (This is essentially the same refactoring
that was included in the SCRAM patch set, that introduced the
get_role_details() function.)
Barring objections, I'll go ahead and commit this first.
- Heikki
Attachments:
0001-Refactor-the-code-for-verifying-user-s-password.patchtext/x-diff; name=0001-Refactor-the-code-for-verifying-user-s-password.patchDownload
From 30be98cf09e8807d477827257a1e55c979dbe877 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Fri, 9 Dec 2016 11:49:36 +0200
Subject: [PATCH 1/1] Refactor the code for verifying user's password.
Split md5_crypt_verify() into three functions:
* get_role_password() to fetch user's password from pg_authid, and check
its expiration.
* md5_crypt_verify() to check an MD5 authentication challenge
* plain_crypt_verify() to check a plaintext password.
get_role_password() will be needed as a separate function by the upcoming
SCRAM authentication patch set. Most of the remaining functionality in
md5_crypt_verify() was different for MD5 and plaintext authentication, so
split that for readability.
While we're at it, simplify the *_crypt_verify functions by using
stack-allocated buffers to hold the temporary MD5 hashes, instead of
pallocing.
---
src/backend/libpq/auth.c | 18 +++-
src/backend/libpq/crypt.c | 214 ++++++++++++++++++++++++++++------------------
src/include/libpq/crypt.h | 9 +-
3 files changed, 151 insertions(+), 90 deletions(-)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index f8bffe3..5c9ee06 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -707,6 +707,7 @@ CheckMD5Auth(Port *port, char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
+ char *shadow_pass;
int result;
if (Db_user_namespace)
@@ -720,12 +721,16 @@ CheckMD5Auth(Port *port, char **logdetail)
sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);
passwd = recv_password_packet(port);
-
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- result = md5_crypt_verify(port->user_name, passwd, md5Salt, 4, logdetail);
+ result = get_role_password(port->user_name, &shadow_pass, logdetail);
+ if (result == STATUS_OK)
+ result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+ md5Salt, 4, logdetail);
+ if (shadow_pass)
+ pfree(shadow_pass);
pfree(passwd);
return result;
@@ -741,16 +746,21 @@ CheckPasswordAuth(Port *port, char **logdetail)
{
char *passwd;
int result;
+ char *shadow_pass;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
-
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- result = md5_crypt_verify(port->user_name, passwd, NULL, 0, logdetail);
+ result = get_role_password(port->user_name, &shadow_pass, logdetail);
+ if (result == STATUS_OK)
+ result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
+ logdetail);
+ if (shadow_pass)
+ pfree(shadow_pass);
pfree(passwd);
return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index b4ca174..fb6d1af 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -30,28 +30,28 @@
/*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * Fetch stored password for a user, for authentication.
*
- * 'client_pass' is the password response given by the remote user. If
- * 'md5_salt' is not NULL, it is a response to an MD5 authentication
- * challenge, with the given salt. Otherwise, it is a plaintext password.
+ * Returns STATUS_OK on success. On error, returns STATUS_ERROR, and stores
+ * a palloc'd string describing the reason, for the postmaster log, in
+ * *logdetail. The error reason should *not* be sent to the client, to avoid
+ * giving away user information!
*
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * If the password is expired, it is still returned in *shadow_pass, but the
+ * return code is STATUS_ERROR. On other errors, *shadow_pass is set to
+ * NULL.
*/
int
-md5_crypt_verify(const char *role, char *client_pass,
- char *md5_salt, int md5_salt_len, char **logdetail)
+get_role_password(const char *role, char **shadow_pass, char **logdetail)
{
int retval = STATUS_ERROR;
- char *shadow_pass,
- *crypt_pwd;
TimestampTz vuntil = 0;
- char *crypt_client_pass = client_pass;
HeapTuple roleTup;
Datum datum;
bool isnull;
+ *shadow_pass = NULL;
+
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
@@ -70,7 +70,7 @@ md5_crypt_verify(const char *role, char *client_pass,
role);
return STATUS_ERROR; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
+ *shadow_pass = TextDatumGetCString(datum);
datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
@@ -83,100 +83,146 @@ md5_crypt_verify(const char *role, char *client_pass,
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
+ *shadow_pass = NULL;
return STATUS_ERROR; /* empty password */
}
/*
- * Compare with the encrypted or plain password depending on the
- * authentication method being used for this connection. (We do not
- * bother setting logdetail for pg_md5_encrypt failure: the only possible
- * error is out-of-memory, which is unlikely, and if it did happen adding
- * a psprintf call would only make things worse.)
+ * Password OK, now check to be sure we are not past rolvaliduntil
*/
- if (md5_salt)
+ if (isnull)
+ retval = STATUS_OK;
+ else if (vuntil < GetCurrentTimestamp())
{
- /* MD5 authentication */
- Assert(md5_salt_len > 0);
- crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
- if (isMD5(shadow_pass))
- {
- /* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- pfree(crypt_pwd);
- return STATUS_ERROR;
- }
- }
- else
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ role);
+ retval = STATUS_ERROR;
+ }
+ else
+ retval = STATUS_OK;
+
+ return retval;
+}
+
+/*
+ * Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password or password hash, as stored
+ * in pg_authid.rolpassword.
+ * 'client_pass' is the response given by the remote user to the MD5 challenge.
+ * 'md5_salt' is the salt used in the MD5 authentication challenge.
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass,
+ const char *md5_salt, int md5_salt_len,
+ char **logdetail)
+{
+ int retval;
+ char crypt_pwd[MD5_PASSWD_LEN + 1];
+ char crypt_pwd2[MD5_PASSWD_LEN + 1];
+
+ Assert(md5_salt_len > 0);
+
+ /*
+ * Compute the correct answer for the MD5 challenge.
+ *
+ * We do not bother setting logdetail for any pg_md5_encrypt failure
+ * below: the only possible error is out-of-memory, which is unlikely, and
+ * if it did happen adding a psprintf call would only make things worse.
+ */
+ if (isMD5(shadow_pass))
+ {
+ /* stored password already encrypted, only do salt */
+ if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
{
- /* stored password is plain, double-encrypt */
- char *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
-
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- pfree(crypt_pwd);
- pfree(crypt_pwd2);
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- pfree(crypt_pwd);
- pfree(crypt_pwd2);
- return STATUS_ERROR;
- }
- pfree(crypt_pwd2);
+ return STATUS_ERROR;
}
}
else
{
- /* Client sent password in plaintext */
- if (isMD5(shadow_pass))
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(shadow_pass,
+ role,
+ strlen(role),
+ crypt_pwd2))
{
- /* Encrypt user-supplied password to match stored MD5 */
- crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
- pfree(crypt_client_pass);
- return STATUS_ERROR;
- }
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
}
- crypt_pwd = shadow_pass;
}
- if (strcmp(crypt_client_pass, crypt_pwd) == 0)
+ if (strcmp(client_pass, crypt_pwd) == 0)
+ retval = STATUS_OK;
+ else
{
- /*
- * Password OK, now check to be sure we are not past rolvaliduntil
- */
- if (isnull)
- retval = STATUS_OK;
- else if (vuntil < GetCurrentTimestamp())
+ *logdetail = psprintf(_("Password does not match for user \"%s\"."),
+ role);
+ retval = STATUS_ERROR;
+ }
+
+ return retval;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ *
+ * 'shadow_pass' is the user's correct password or password hash, as stored
+ * in pg_authid.rolpassword.
+ * 'client_pass' is the password given by the remote user
+ *
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+plain_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass,
+ char **logdetail)
+{
+ int retval;
+ char crypt_client_pass[MD5_PASSWD_LEN + 1];
+
+ /*
+ * Client sent password in plaintext. If we have an MD5 hash stored, hash
+ * the password the client sent, and compare the hashes. Otherwise
+ * compare the plaintext passwords directly.
+ */
+ if (isMD5(shadow_pass))
+ {
+ if (!pg_md5_encrypt(client_pass,
+ role,
+ strlen(role),
+ crypt_client_pass))
{
- *logdetail = psprintf(_("User \"%s\" has an expired password."),
- role);
- retval = STATUS_ERROR;
+ /*
+ * We do not bother setting logdetail for pg_md5_encrypt failure:
+ * the only possible error is out-of-memory, which is unlikely,
+ * and if it did happen adding a psprintf call would only make
+ * things worse.
+ */
+ return STATUS_ERROR;
}
- else
- retval = STATUS_OK;
+ client_pass = crypt_client_pass;
}
+
+ if (strcmp(client_pass, shadow_pass) == 0)
+ retval = STATUS_OK;
else
+ {
*logdetail = psprintf(_("Password does not match for user \"%s\"."),
role);
-
- if (crypt_pwd != shadow_pass)
- pfree(crypt_pwd);
- if (crypt_client_pass != client_pass)
- pfree(crypt_client_pass);
+ retval = STATUS_ERROR;
+ }
return retval;
}
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 4ca8a75..229ce76 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,7 +15,12 @@
#include "datatype/timestamp.h"
-extern int md5_crypt_verify(const char *role, char *client_pass,
- char *md5_salt, int md5_salt_len, char **logdetail);
+extern int get_role_password(const char *role, char **shadow_pass, char **logdetail);
+
+extern int md5_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass, const char *md5_salt,
+ int md5_salt_len, char **logdetail);
+extern int plain_crypt_verify(const char *role, const char *shadow_pass,
+ const char *client_pass, char **logdetail);
#endif
--
2.10.2
On Fri, Dec 09, 2016 at 11:51:45AM +0200, Heikki Linnakangas wrote:
On 12/09/2016 05:58 AM, Michael Paquier wrote:
One thing is: when do we look up at pg_authid? After receiving the
first message from client or before beginning the exchange? As the
first message from client has the user name, it would make sense to do
the lookup after receiving it, but from PG prospective it would just
make sense to use the data already present in the startup packet. The
current patch does the latter. What do you think?While hacking on this, I came up with the attached refactoring, against
current master. I think it makes the current code more readable, anyway, and
it provides a get_role_password() function that SCRAM can use, to look up
the stored password. (This is essentially the same refactoring that was
included in the SCRAM patch set, that introduced the get_role_details()
function.)Barring objections, I'll go ahead and commit this first.
Here are some comments.
@@ -720,12 +721,16 @@ CheckMD5Auth(Port *port, char **logdetail)
sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);passwd = recv_password_packet(port);
-
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
This looks like useless noise.
- shadow_pass = TextDatumGetCString(datum); + *shadow_pass = TextDatumGetCString(datum);datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
@@ -83,100 +83,146 @@ md5_crypt_verify(const char *role, char *client_pass,
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
+ *shadow_pass = NULL;
return STATUS_ERROR; /* empty password */
}
Here the password is allocated by text_to_cstring(), that's only 1 byte
but it should be free()'d.
--
Michael
On 12/09/2016 01:10 PM, Michael Paquier wrote:
On Fri, Dec 09, 2016 at 11:51:45AM +0200, Heikki Linnakangas wrote:
On 12/09/2016 05:58 AM, Michael Paquier wrote:
One thing is: when do we look up at pg_authid? After receiving the
first message from client or before beginning the exchange? As the
first message from client has the user name, it would make sense to do
the lookup after receiving it, but from PG prospective it would just
make sense to use the data already present in the startup packet. The
current patch does the latter. What do you think?While hacking on this, I came up with the attached refactoring, against
current master. I think it makes the current code more readable, anyway, and
it provides a get_role_password() function that SCRAM can use, to look up
the stored password. (This is essentially the same refactoring that was
included in the SCRAM patch set, that introduced the get_role_details()
function.)Barring objections, I'll go ahead and commit this first.
Ok, committed.
- shadow_pass = TextDatumGetCString(datum); + *shadow_pass = TextDatumGetCString(datum);datum = SysCacheGetAttr(AUTHNAME, roleTup,
Anum_pg_authid_rolvaliduntil, &isnull);
@@ -83,100 +83,146 @@ md5_crypt_verify(const char *role, char *client_pass,
{
*logdetail = psprintf(_("User \"%s\" has an empty password."),
role);
+ *shadow_pass = NULL;
return STATUS_ERROR; /* empty password */
}Here the password is allocated by text_to_cstring(), that's only 1 byte
but it should be free()'d.
Fixed. Thanks, good catch! It doesn't matter in practice as we'll
disconnect shortly afterwards anyway, but given that the callers pfree()
other things on error, let's be tidy.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
A few couple more things that caught my eye while hacking on this:
1. We don't use SASLPrep to scrub username's and passwords. That's by
choice, for usernames, because historically in PostgreSQL usernames can
be stored in any encoding, but SASLPrep assumes UTF-8. We dodge that by
passing an empty username in the authentication exchange anyway, because
we always use the username we got from the startup packet. But for
passwords, I think we need to fix that. The spec is very clear on that:
Note that implementations MUST either implement SASLprep or disallow
use of non US-ASCII Unicode codepoints in "str".
2. I think we should check nonces, etc. more carefully, to not contain
invalid characters. For example, in the server, we use the
read_attr_value() function to read the client's nonce. Per the spec, the
nonce should consist of ASCII printable characters, but we will accept
anything except the comma. That's no trouble to the server, but let's be
strict.
To summarize, here's the overall TODO list so far:
* Use SASLPrep for passwords.
* Check nonces, etc. to not contain invalid characters.
* Derive mock SCRAM verifier for non-existent users deterministically
from username.
* Allow plain 'password' authentication for users with a SCRAM verifier
in rolpassword.
* Throw an error if an "authorization identity" is given. ATM, we just
ignore it, but seems better to reject the attempt than do something that
might not be what the client expects.
* Add "scram-sha-256" prefix to SCRAM verifiers stored in
pg_authid.rolpassword.
Anything else I'm missing?
I've created a wiki page, mostly to host that TODO list, while we hack
this to completion:
https://wiki.postgresql.org/wiki/SCRAM_authentication. Feel free to add
stuff that comes to mind, and remove stuff as you push patches to the
branch on github.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12 December 2016 at 22:39, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
* Throw an error if an "authorization identity" is given. ATM, we just
ignore it, but seems better to reject the attempt than do something that
might not be what the client expects.
Yeah. That might be an opportunity to make admins' and connection
poolers' lives much happier down the track, but first we'd need a way
of specifying a mapping for the other users a given user is permitted
to masquerade as (like we have for roles and role membership). We have
SET SESSION AUTHORIZATION already, which has all the same benefits and
security problems as allowing connect-time selection of authorization
identity without such a framework. And we have SET ROLE.
ERRORing is the right thing to do here, so we can safely use this
protocol functionality later if we want to allow user masquerading.
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Dec 12, 2016 at 11:39 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
A few couple more things that caught my eye while hacking on this:
1. We don't use SASLPrep to scrub username's and passwords. That's by
choice, for usernames, because historically in PostgreSQL usernames can be
stored in any encoding, but SASLPrep assumes UTF-8. We dodge that by passing
an empty username in the authentication exchange anyway, because we always
use the username we got from the startup packet. But for passwords, I think
we need to fix that. The spec is very clear on that:Note that implementations MUST either implement SASLprep or disallow
use of non US-ASCII Unicode codepoints in "str".2. I think we should check nonces, etc. more carefully, to not contain
invalid characters. For example, in the server, we use the read_attr_value()
function to read the client's nonce. Per the spec, the nonce should consist
of ASCII printable characters, but we will accept anything except the comma.
That's no trouble to the server, but let's be strict.To summarize, here's the overall TODO list so far:
* Use SASLPrep for passwords.
* Check nonces, etc. to not contain invalid characters.
* Derive mock SCRAM verifier for non-existent users deterministically from
username.* Allow plain 'password' authentication for users with a SCRAM verifier in
rolpassword.* Throw an error if an "authorization identity" is given. ATM, we just
ignore it, but seems better to reject the attempt than do something that
might not be what the client expects.* Add "scram-sha-256" prefix to SCRAM verifiers stored in
pg_authid.rolpassword.Anything else I'm missing?
I've created a wiki page, mostly to host that TODO list, while we hack this
to completion: https://wiki.postgresql.org/wiki/SCRAM_authentication. Feel
free to add stuff that comes to mind, and remove stuff as you push patches
to the branch on github.
Based on the current code, I think you have the whole list. I'll try
to look once again at the code to see I have anything else in mind.
Improving the TAP regression tests is also an item, with SCRAM
authentication support when a plain password is stored.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Dec 13, 2016 at 10:43 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Dec 12, 2016 at 11:39 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
A few couple more things that caught my eye while hacking on this:
Looking at what we have now, in the branch...
* Use SASLPrep for passwords.
SASLPrep is defined here:
https://tools.ietf.org/html/rfc4013
And stringprep is here:
https://tools.ietf.org/html/rfc3454
So that's roughly applying a conversion from the mapping table, taking
into account prohibited, bi-directional, mapping characters, etc. The
spec says that the password should be in unicode. But we cannot be
sure of that, right? Those mapping tables should be likely a separated
thing.. (perl has Unicode::Stringprep::Mapping for example).
* Check nonces, etc. to not contain invalid characters.
Fixed this one.
* Derive mock SCRAM verifier for non-existent users deterministically from
username.
You have put in place the facility to allow that. The only thing that
comes in mind to generate something per-cluster is to have
BootStrapXLOG() generate an "authentication secret identifier" with a
uint64 and add that in the control file. Using pg_backend_random()
would be a good idea here.
* Allow plain 'password' authentication for users with a SCRAM verifier in
rolpassword.
Done.
* Throw an error if an "authorization identity" is given. ATM, we just
ignore it, but seems better to reject the attempt than do something that
might not be what the client expects.
Done.
* Add "scram-sha-256" prefix to SCRAM verifiers stored in
pg_authid.rolpassword.
You did it.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/09/2016 10:19 AM, Michael Paquier wrote:
On Fri, Dec 9, 2016 at 5:11 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Couple of things I should write down before I forget:
1. It's a bit cumbersome that the scram verifiers stored in
pg_authid.rolpassword don't have any clear indication that they're scram
verifiers. MD5 hashes are readily identifiable by the "md5" prefix. I think
we should use a "scram-sha-256:" for scram verifiers.scram-sha-256 would make the most sense to me.
Actually, I think it'd be awfully nice to also prefix plaintext passwords
with "plain:", but I'm not sure it's worth breaking the compatibility, if
there are tools out there that peek into rolpassword. Thoughts?pgbouncer is the only thing coming up in mind. It looks at pg_shadow
for password values. pg_dump'ing data from pre-10 instances will also
need to adapt. I see tricky the compatibility with the exiting CREATE
USER PASSWORD command though, so I am wondering if that's worth the
complication.2. It's currently not possible to use the plaintext "password"
authentication method, for a user that has a SCRAM verifier in rolpassword.
That seems like an oversight. We can't do MD5 authentication with a SCRAM
verifier, but "password" we could.Yeah, that should be possible...
The tip of the work branch can now do SCRAM authentication, when a user
has a plaintext password in pg_authid.rolpassword. The reverse doesn't
work, however: you cannot do plain "password" authentication, when the
user has a SCRAM verifier in pg_authid.rolpassword. It gets worse: plain
"password" authentication doesn't check if the string stored in
pg_authid.rolpassword is a SCRAM authenticator, and treats it as a
plaintext password, so you can do this:
PGPASSWORD="scram-sha-256:mDBuqO1mEekieg==:4096:17dc259499c1a184c26ee5b19715173d9354195f510b4d3af8be585acb39ae33:d3d713149c6becbbe56bae259aafe4e95b79ab7e3b50f2fbd850ea7d7b7c114f"
psql postgres -h localhost -U scram_user
I think we're going to have a more bugs like this, if we don't start to
explicitly label plaintext passwords as such.
So, let's add "plain:" prefix to plaintext passwords, in
pg_authid.rolpassword. With that, these would be valid values in
pg_authid.rolpassword:
plain:foo
md55a962ce7a24371a10e85627a484cac28
scram-sha-256:mDBuqO1mEekieg==:4096:17dc259499c1a184c26ee5b19715173d9354195f510b4d3af8be585acb39ae33:d3d713149c6becbbe56bae259aafe4e95b79ab7e3b50f2fbd850ea7d7b7c114f
But anything that doesn't begin with "plain:", "md5", or
"scram-sha-256:" would be invalid. You shouldn't have invalid values in
the column, but if you do, all the authentication mechanisms would
reject it.
It would be nice to also change the format of MD5 passwords to have a
colon, as in "md5:<hash>", but that's probably not worth breaking
compatibility for. Almost no-one stores passwords in plaintext, so
changing the format of that wouldn't affect many people, but there might
well be tools out there that peek into MD5 hashes.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 14, 2016 at 5:51 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
The tip of the work branch can now do SCRAM authentication, when a user has
a plaintext password in pg_authid.rolpassword. The reverse doesn't work,
however: you cannot do plain "password" authentication, when the user has a
SCRAM verifier in pg_authid.rolpassword. It gets worse: plain "password"
authentication doesn't check if the string stored in pg_authid.rolpassword
is a SCRAM authenticator, and treats it as a plaintext password, so you can
do this:PGPASSWORD="scram-sha-256:mDBuqO1mEekieg==:4096:17dc259499c1a184c26ee5b19715173d9354195f510b4d3af8be585acb39ae33:d3d713149c6becbbe56bae259aafe4e95b79ab7e3b50f2fbd850ea7d7b7c114f"
psql postgres -h localhost -U scram_user
This one's fun.
I think we're going to have a more bugs like this, if we don't start to
explicitly label plaintext passwords as such.So, let's add "plain:" prefix to plaintext passwords, in
pg_authid.rolpassword. With that, these would be valid values in
pg_authid.rolpassword:[...]
But anything that doesn't begin with "plain:", "md5", or "scram-sha-256:"
would be invalid. You shouldn't have invalid values in the column, but if
you do, all the authentication mechanisms would reject it.
I would be tempted to suggest adding the verifier type as a new column
of pg_authid, but as CREATE USER PASSWORD accepts strings with md5
prefix as-is for ages using the "plain:" prefix is definitely a better
plan. My opinion on the matter has changed compared to a couple of
months back.
It would be nice to also change the format of MD5 passwords to have a colon,
as in "md5:<hash>", but that's probably not worth breaking compatibility
for. Almost no-one stores passwords in plaintext, so changing the format of
that wouldn't affect many people, but there might well be tools out there
that peek into MD5 hashes.
Yes, let's not take this road.
This work is definitely something that should be done before anything
else. Need a patch or are you on it?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 14, 2016 at 9:51 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 12/09/2016 10:19 AM, Michael Paquier wrote:
On Fri, Dec 9, 2016 at 5:11 PM, Heikki Linnakangas <hlinnaka@iki.fi>
wrote:Couple of things I should write down before I forget:
1. It's a bit cumbersome that the scram verifiers stored in
pg_authid.rolpassword don't have any clear indication that they're scram
verifiers. MD5 hashes are readily identifiable by the "md5" prefix. I
think
we should use a "scram-sha-256:" for scram verifiers.scram-sha-256 would make the most sense to me.
Actually, I think it'd be awfully nice to also prefix plaintext passwords
with "plain:", but I'm not sure it's worth breaking the compatibility, if
there are tools out there that peek into rolpassword. Thoughts?pgbouncer is the only thing coming up in mind. It looks at pg_shadow
for password values. pg_dump'ing data from pre-10 instances will also
need to adapt. I see tricky the compatibility with the exiting CREATE
USER PASSWORD command though, so I am wondering if that's worth the
complication.2. It's currently not possible to use the plaintext "password"
authentication method, for a user that has a SCRAM verifier in
rolpassword.
That seems like an oversight. We can't do MD5 authentication with a SCRAM
verifier, but "password" we could.Yeah, that should be possible...
The tip of the work branch can now do SCRAM authentication, when a user
has a plaintext password in pg_authid.rolpassword. The reverse doesn't
work, however: you cannot do plain "password" authentication, when the user
has a SCRAM verifier in pg_authid.rolpassword. It gets worse: plain
"password" authentication doesn't check if the string stored in
pg_authid.rolpassword is a SCRAM authenticator, and treats it as a
plaintext password, so you can do this:PGPASSWORD="scram-sha-256:mDBuqO1mEekieg==:4096:17dc259499c1
a184c26ee5b19715173d9354195f510b4d3af8be585acb39ae33:d3d7131
49c6becbbe56bae259aafe4e95b79ab7e3b50f2fbd850ea7d7b7c114f" psql
postgres -h localhost -U scram_userI think we're going to have a more bugs like this, if we don't start to
explicitly label plaintext passwords as such.So, let's add "plain:" prefix to plaintext passwords, in
pg_authid.rolpassword. With that, these would be valid values in
pg_authid.rolpassword:plain:foo
md55a962ce7a24371a10e85627a484cac28
scram-sha-256:mDBuqO1mEekieg==:4096:17dc259499c1a184c26ee5b1
9715173d9354195f510b4d3af8be585acb39ae33:d3d713149c6becbbe56
bae259aafe4e95b79ab7e3b50f2fbd850ea7d7b7c114f
I would so like to just drop support for plain passwords completely :) But
there's a backwards compatibility issue to think about of course.
But -- is there any actual usecase for them anymore?
If not, another option could be to just specifically check that it's *not*
"md5<something>" or "scram-<something>:<something>". That would invalidate
plaintext passwords that have those texts in them of course, but what's the
likelyhood of that in reality?
Though I guess that might at least in theory be more bug-prone, so going
with a "plain:" prefix seems like a good idea as well.
But anything that doesn't begin with "plain:", "md5", or "scram-sha-256:"
would be invalid. You shouldn't have invalid values in the column, but if
you do, all the authentication mechanisms would reject it.It would be nice to also change the format of MD5 passwords to have a
colon, as in "md5:<hash>", but that's probably not worth breaking
compatibility for. Almost no-one stores passwords in plaintext, so changing
the format of that wouldn't affect many people, but there might well be
tools out there that peek into MD5 hashes.
There are definitely tools that do that, so +1 on leaving that alone.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
On 12/14/2016 12:15 PM, Michael Paquier wrote:
This work is definitely something that should be done before anything
else. Need a patch or are you on it?
I'm on it..
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/14/2016 12:27 PM, Magnus Hagander wrote:
I would so like to just drop support for plain passwords completely :) But
there's a backwards compatibility issue to think about of course.But -- is there any actual usecase for them anymore?
Hmm. At the moment, I don't think there is.
But, a password stored in plaintext works with either MD5 or SCRAM, or
any future authentication mechanism. So as soon as we have SCRAM
authentication, it becomes somewhat useful again.
In a nutshell:
auth / stored MD5 SCRAM plaintext
-----------------------------------------
password Y Y Y
md5 Y N Y
scram N Y Y
If a password is stored in plaintext, it can be used with any
authentication mechanism. And the plaintext 'password' authentication
mechanism works with any kind of a stored password. But an MD5 hash
cannot be used with SCRAM authentication, or vice versa.
I just noticed that the manual for CREATE ROLE says:
Note that older clients might lack support for the MD5 authentication
mechanism that is needed to work with passwords that are stored
encrypted.
That's is incorrect. The alternative to MD5 authentication is plain
'password' authentication, and that works just fine with MD5-hashed
passwords. I think that sentence is a leftover from when we still
supported "crypt" authentication (so I actually get to blame you for
that ;-), commit 53a5026b). Back then, it was true that if an MD5 hash
was stored in pg_authid, you couldn't do "crypt" authentication. That
might have left old clients out in the cold.
Now that we're getting SCRAM authentication, we'll need a similar notice
there again, for the incompatibility of a SCRAM verifier with MDD5
authentication and vice versa.
If not, another option could be to just specifically check that it's *not*
"md5<something>" or "scram-<something>:<something>". That would invalidate
plaintext passwords that have those texts in them of course, but what's the
likelyhood of that in reality?
Hmm, we have dismissed that risk for the MD5 hashes (and we also have a
length check for them), but as we get new hash formats, the risk
increases. Someone might well want to use "plain:of:jars" as password.
Perhaps we should use a more complicated pattern.
I googled around for how others store SCRAM and other password hashes.
Many other systems seem to have similar naming schemes. The closest
thing to a standard I could find was:
https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
Perhaps we should also use something like "$plain$<password>" or
"$scram-sha-256$<iterations>$<salt>$<key>$"?
There's also https://tools.ietf.org/html/rfc5803, which specifies how to
store SCRAM verifiers in LDAP. I don't understand enough of LDAP to
understand what those actually look like, though, and there were no
examples in the RFC.
I wonder if we should also worry about storing multiple verifiers in
rolpassword? We don't support that now, but we might in the future. It
might come handy, if you could easily store multiple hashes in a single
string, separated by commas for example.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authid
Yes please.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authidYes please.
This discussion seems to continue to come up and I don't entirely
understand why we keep trying to shove more things into pg_authid, or
worse, into rolpassword.
We should have an independent table for the verifiers, which has a
different column for the verifier type, and either starts off supporting
multiple verifiers per role or at least gives us the ability to add that
easily later. We should also move rolvaliduntil to that new table.
No, I am specifically *not* concerned with "backwards compatibility" of
that table- we continually add to it and change it and applications
which are so closely tied to PG that they look at pg_authid need to be
updated with nearly every release anyway. What we *do* need to make
sure we get correct is what pg_dump/pg_upgrade do, but that's entirely
within our control to manage and shouldn't be that much of an issue to
implement.
Thanks!
Stephen
On Wed, Dec 14, 2016 at 11:27:15AM +0100, Magnus Hagander wrote:
I would so like to just drop support for plain passwords completely :) But
there's a backwards compatibility issue to think about of course.But -- is there any actual usecase for them anymore?
I thought we recommended 'password' for SSL connections because if you
use MD5 passwords the password text layout is known and that simplifies
cryptanalysis.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ As you are, so once was I. As I am, so you will be. +
+ Ancient Roman grave inscription +
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 14 December 2016 20:12:05 EET, Bruce Momjian <bruce@momjian.us> wrote:
On Wed, Dec 14, 2016 at 11:27:15AM +0100, Magnus Hagander wrote:
I would so like to just drop support for plain passwords completely
:) But
there's a backwards compatibility issue to think about of course.
But -- is there any actual usecase for them anymore?
I thought we recommended 'password' for SSL connections because if you
use MD5 passwords the password text layout is known and that simplifies
cryptanalysis.
No, that makes no sense. And whether you use 'password' or 'md5' authentication is a different question than whether you store passwords in plaintext or as md5 hashes. Magnus was asking whether it ever makes sense to *store* passwords in plaintext.
Since you brought it up, there is a legitimate argument to be made that 'password' authentication is more secure than 'md5', when SSL is used. Namely, if an attacker can acquire contents of pg_authid e.g. by stealing a backup tape, with 'md5' authentication he can log in as any user, using just the stolen hashes. But with 'password', he needs to reverse the hash first. It's not a great difference, but it's something.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
On 14 December 2016 20:12:05 EET, Bruce Momjian <bruce@momjian.us> wrote:
On Wed, Dec 14, 2016 at 11:27:15AM +0100, Magnus Hagander wrote:
I would so like to just drop support for plain passwords completely
:) But
there's a backwards compatibility issue to think about of course.
But -- is there any actual usecase for them anymore?
I thought we recommended 'password' for SSL connections because if you
use MD5 passwords the password text layout is known and that simplifies
cryptanalysis.No, that makes no sense. And whether you use 'password' or 'md5' authentication is a different question than whether you store passwords in plaintext or as md5 hashes. Magnus was asking whether it ever makes sense to *store* passwords in plaintext.
Right.
Since you brought it up, there is a legitimate argument to be made that 'password' authentication is more secure than 'md5', when SSL is used. Namely, if an attacker can acquire contents of pg_authid e.g. by stealing a backup tape, with 'md5' authentication he can log in as any user, using just the stolen hashes. But with 'password', he needs to reverse the hash first. It's not a great difference, but it's something.
Tunnelled passwords which are stored as hashes is also well understood
and comparable to SSH with passwords in /etc/passwd.
Storing plaintext passwords has been bad form for just about forever and
I wouldn't be sad to see our support of it go. At the least, as was
discussed somewhere, but I'm not sure where it ended up, we should give
administrators the ability to control what ways a password can be
stored. In particular, once a user has migrated all of their users to
SCRAM, they should be able to say "don't let new passwords be in any
format other than SCRAM-SHA-256".
Thanks!
Stephen
On 12/14/2016 11:41 AM, Stephen Frost wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
On 14 December 2016 20:12:05 EET, Bruce Momjian <bruce@momjian.us> wrote:
On Wed, Dec 14, 2016 at 11:27:15AM +0100, Magnus Hagander wrote:
Storing plaintext passwords has been bad form for just about forever and
I wouldn't be sad to see our support of it go. At the least, as was
discussed somewhere, but I'm not sure where it ended up, we should give
administrators the ability to control what ways a password can be
stored. In particular, once a user has migrated all of their users to
SCRAM, they should be able to say "don't let new passwords be in any
format other than SCRAM-SHA-256".
It isn't as bad as it used to be. I remember with PASSWORD was the
default. I agree that we should be able to set a policy that says, "we
only allow X for password storage".
JD
Thanks!
Stephen
--
Command Prompt, Inc. http://the.postgres.company/
+1-503-667-4564
PostgreSQL Centered full stack support, consulting and development.
Everyone appreciates your honesty, until you are honest with them.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 14, 2016 at 8:33 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
But, a password stored in plaintext works with either MD5 or SCRAM, or any
future authentication mechanism. So as soon as we have SCRAM authentication,
it becomes somewhat useful again.In a nutshell:
auth / stored MD5 SCRAM plaintext
-----------------------------------------
password Y Y Y
md5 Y N Y
scram N Y YIf a password is stored in plaintext, it can be used with any authentication
mechanism. And the plaintext 'password' authentication mechanism works with
any kind of a stored password. But an MD5 hash cannot be used with SCRAM
authentication, or vice versa.
So.. I have been thinking about this portion of the thread. And what I
find the most scary is not the fact that we use plain passwords for
SCRAM authentication, it is the fact that we would need to do a
catalog lookup earlier in the connection workflow to decide what is
the connection protocol to use depending on the username provided in
the startup packet if the pg_hba.conf entry matching the user and
database names uses "password".
And, honestly, why do we actually need to have a support table that
spread? SCRAM is designed to be secure, so it seems to me that it
would on the contrary a bad idea to encourage the use of plain
passwords if we actually think that they should never be used (they
are actually useful for located, development instances, not production
ones). So what I would suggest would be to have a support table like
that:
auth / stored MD5 SCRAM plaintext
-----------------------------------------
password Y Y N
md5 Y N Y
scram N N Y
So here is an idea for things to do now:
1) do not change the format of the existing passwords
2) do not change pg_authid
3) block access to instances if "password" or "md5" are used in
pg_hba.conf if the user have a SCRAM verifier.
4) block access if "scram" is used and if user has a plain or md5 verifier.
5) Allow access if "scram" is used and if user has a SCRAM verifier.
We had a similar discussion regarding verifier/password formats last
year but that did not end well. It would be sad to fall back again
into this discussion and get no result. If somebody wants to support
access to SCRAM with plain password entries, why not. But that would
gain a -1 from me regarding the earlier lookup of pg_authid needed to
do the decision making on the protocol to use. And I think that we
want SCRAM to be designed to be a maximum stable and secure.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Dec 13, 2016 at 2:44 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
SASLPrep is defined here:
https://tools.ietf.org/html/rfc4013
And stringprep is here:
https://tools.ietf.org/html/rfc3454
So that's roughly applying a conversion from the mapping table, taking
into account prohibited, bi-directional, mapping characters, etc. The
spec says that the password should be in unicode. But we cannot be
sure of that, right? Those mapping tables should be likely a separated
thing.. (perl has Unicode::Stringprep::Mapping for example).
OK. I have look at that and I have bumped into libidn, that offers a
couple of APIs that could be used directly for this purpose.
Particularly, what has caught my eyes is stringprep_profile():
https://www.gnu.org/software/libidn/manual/html_node/Stringprep-Functions.html
res = stringprep_profile (input, output, "SASLprep", STRINGPREP_NO_UNASSIGNED);
libidn can be installed on Windows, and I have found packages for
cygwin, mingw, linux, freebsd and macos via brew. In the case where
libidn is not installed, I think that the safest path would be to
check if the input string has any high bits set (0x80) and bail out
because that would mean that it is a UTF-8 string that we cannot
change. Any thoughts about using libidn?
Also, after discussion with Heikki, here are the things that we need to do:
1) In libpq, we need to check if the string is valid utf-8. If that's
valid utf-8, apply SASLprep. if not, copy the string as-is. We could
error as well in this case... Perhaps a WARNING could be more adapted,
that's the most tricky case, and if the client does not use utf-8 that
may lead to unexpected behavior.
2) In server, when the password verifier is created. If
client_encoding is utf-8, but not server_encoding, convert the
password to utf-8 and build the verifier after applying SASLprep.
In the case where the binaries are *not* built with libidn, I think
that we had better reject valid UTF-8 string directly and just allow
ASCII? SASLprep is a no-op on ASCII characters.
Thoughts about this approach?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/14/2016 04:57 PM, Stephen Frost wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authidYes please.
This discussion seems to continue to come up and I don't entirely
understand why we keep trying to shove more things into pg_authid, or
worse, into rolpassword.
I understand the relational beauty of having a separate column for the
verifier type, but I don't think it would be practical. For starters,
we'd still like to have a self-identifying string format like
"scram-sha-256:<stuff>", so that you can conveniently pass the verifier
as a string to CREATE USER. I think it'll be much better to stick to one
format, than try to split the verifier into type and the string, when it
enters the catalog table.
We should have an independent table for the verifiers, which has a
different column for the verifier type, and either starts off supporting
multiple verifiers per role or at least gives us the ability to add that
easily later. We should also move rolvaliduntil to that new table.
I agree we'll probably need a new table for verifiers. Or turn
rolpassword into an array or something. We discussed that before,
however, and it didn't really go anywhere, so right now I'd like to get
SCRAM in with minimal changes to the rest of the system. There is a lot
of room for improvement once it's in.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/15/2016 03:00 AM, Michael Paquier wrote:
On Wed, Dec 14, 2016 at 8:33 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
But, a password stored in plaintext works with either MD5 or SCRAM, or any
future authentication mechanism. So as soon as we have SCRAM authentication,
it becomes somewhat useful again.In a nutshell:
auth / stored MD5 SCRAM plaintext
-----------------------------------------
password Y Y Y
md5 Y N Y
scram N Y YIf a password is stored in plaintext, it can be used with any authentication
mechanism. And the plaintext 'password' authentication mechanism works with
any kind of a stored password. But an MD5 hash cannot be used with SCRAM
authentication, or vice versa.So.. I have been thinking about this portion of the thread. And what I
find the most scary is not the fact that we use plain passwords for
SCRAM authentication, it is the fact that we would need to do a
catalog lookup earlier in the connection workflow to decide what is
the connection protocol to use depending on the username provided in
the startup packet if the pg_hba.conf entry matching the user and
database names uses "password".
I don't see why we would need to do a catalog lookup any earlier. With
"password" authentication, the server can simply request the client to
send its password. When it receives it, it performs the catalog lookup
to get pg_authid.rolpassword. If it's in plaintext, just compare it, if
it's an MD5 hash, hash the client's password and compare, and if it's a
SCRAM verifier, build a verifier with the same salt and iteration count
and compare.
And, honestly, why do we actually need to have a support table that
spread? SCRAM is designed to be secure, so it seems to me that it
would on the contrary a bad idea to encourage the use of plain
passwords if we actually think that they should never be used (they
are actually useful for located, development instances, not production
ones).
I agree we should not encourage bad password practices. But as long as
we support passwords to be stored in plaintext at all, it makes no sense
to not allow them to be used with SCRAM. The fact that you can use a
password stored in plaintext with both MD5 and SCRAM is literally the
only reason you would store a password in plaintext, so if we don't want
to allow that, we should disallow storing passwords in plaintext altogether.
So what I would suggest would be to have a support table like
that:
auth / stored MD5 SCRAM plaintext
-----------------------------------------
password Y Y N
md5 Y N Y
scram N N Y
I was using 'Y' to indicate that the combination works, and 'N' to
indicate that it does not. Assuming you're using the same notation, the
above doesn't make any sense.
So here is an idea for things to do now:
1) do not change the format of the existing passwords
2) do not change pg_authid
3) block access to instances if "password" or "md5" are used in
pg_hba.conf if the user have a SCRAM verifier.
4) block access if "scram" is used and if user has a plain or md5 verifier.
5) Allow access if "scram" is used and if user has a SCRAM verifier.
We had a similar discussion regarding verifier/password formats last
year but that did not end well. It would be sad to fall back again
into this discussion and get no result. If somebody wants to support
access to SCRAM with plain password entries, why not. But that would
gain a -1 from me regarding the earlier lookup of pg_authid needed to
do the decision making on the protocol to use. And I think that we
want SCRAM to be designed to be a maximum stable and secure.
The bottom line is that at the moment, when plaintext passwords are
stored as is, without any indicator that it's a plaintext password, it's
ambiguous whether a password is a SCRAM verifier, or if it's a plaintext
password that just happens to begin with the word "scram:". That is
completely unrelated to which combinations of stored passwords and
authentication mechanisms we actually support or allow to work.
The only way to distinguish, is to know about every verifier kind there
is, and check whether rolpassword looks valid as anything else than a
plaintext password. And we already got tripped by a bug-of-omission on
that once. If we add more verifier formats in the future, it's bound to
happen again. Let's nip that source of bugs in the bud. Attached is a
patch to implement what I have in mind.
Alternatively, you could argue that we should forbid storing passwords
in plaintext altogether. I'm OK with that, too, if that's what people
prefer. Then you cannot have a user that can log in with both MD5 and
SCRAM authentication, but it's certainly more secure, and it's easier to
document.
- Heikki
Attachments:
0001-Use-plain-prefix-for-plaintext-passwords-stored-in-p.patchinvalid/octet-stream; name=0001-Use-plain-prefix-for-plaintext-passwords-stored-in-p.patchDownload
From 25670580136d6d0cc5f7b519be2176870bca71a1 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Thu, 15 Dec 2016 14:25:54 +0200
Subject: [PATCH 1/1] Use "plain:" prefix for plaintext passwords stored in
pg_authid.
So far, the rule has been that if pg_authid.rolpassword begins with "md5"
and has the right length, it's an MD5 hash, otherwise it's a plaintext
password. That kind of pattern matching gets more awkward when we add
new kinds of verifiers, like the upcoming SCRAM verifiers. To be able to
distinguish different kinds of password hashes, use a "plain:" prefix
for plaintext passwords. With that, every password stored in pg_authid
has either "plain:" or "md5" prefix, and future password schemes can
likewise use different prefixes.
Note that this doesn't change the format of MD5 hashed passwords, which
is what almost everyone uses.
This changes check_password_hook in an incompatible way. The
'password_type' argument is removed, and the hook is expected to call
get_password_type() on the password verifier to determine its type.
Moreover, when a plaintext password is passed to the hook, it will have
the "plain:" prefix. That is a more subtle change!
In the passing, remove the bogus notice from the documentation of CREATE
ROLE, claiming that older clients might not work with MD5 hashed passwords.
That was written when we still had "crypt" authentication, and it was
referring to the fact that an older client might support "crypt"
authentication but not "md5". But we haven't supported "crypt" for years.
(As soon as we add a new authentication mechanism that doesn't work with
MD5 hashes, we'll need a similar notice again, though!)
Discussion: https://www.postgresql.org/message-id/2d07165c-1793-e243-a2a9-e45b624c7580@iki.fi
---
contrib/passwordcheck/passwordcheck.c | 133 +++++++++++------------
doc/src/sgml/catalogs.sgml | 4 +-
doc/src/sgml/ref/create_role.sgml | 6 --
src/backend/commands/user.c | 66 ++++++------
src/backend/libpq/crypt.c | 192 +++++++++++++++++++++++++++-------
src/backend/utils/misc/guc.c | 1 +
src/include/commands/user.h | 13 +--
src/include/common/md5.h | 4 -
src/include/libpq/crypt.h | 20 +++-
9 files changed, 273 insertions(+), 166 deletions(-)
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89b..3d06964c 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -21,7 +21,7 @@
#endif
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -50,87 +50,78 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ const char *shadow_pass,
Datum validuntil_time,
bool validuntil_null)
{
- int namelen = strlen(username);
- int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
- int i;
- bool pwd_has_letter,
- pwd_has_nonletter;
-
- switch (password_type)
+ if (get_password_type(shadow_pass) != PASSWORD_TYPE_PLAINTEXT)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ char *logdetail;
+
+ if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ }
+ else
+ {
+ /*
+ * For unencrypted passwords we can perform better checks
+ */
+ char *password = get_plain_password(shadow_pass);
+ int pwdlen = strlen(password);
+ int i;
+ bool pwd_has_letter,
+ pwd_has_nonletter;
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(password, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
/*
- * For unencrypted passwords we can perform better checks
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
*/
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
- /*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
- */
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ if (isalpha((unsigned char) password[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(password, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
+ pfree(password);
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 11c2019..abf98af 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1320,8 +1320,8 @@
will be of the user's password concatenated to their user name.
For example, if user <literal>joe</> has password <literal>xyzzy</>,
<productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ <literal>xyzzyjoe</>. If a password is stored unencrypted, the
+ column will begin with the string <literal>plain:</literal>.
</entry>
</row>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..a3b8ed9 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -235,12 +235,6 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
password string). This allows reloading of encrypted
passwords during dump/restore.
</para>
-
- <para>
- Note that older clients might lack support for the MD5
- authentication mechanism that is needed to work with passwords
- that are stored encrypted.
- </para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99..e9061f8 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -29,7 +29,7 @@
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -81,7 +81,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -368,11 +367,23 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encode_password(PASSWORD_TYPE_PLAINTEXT, stmt->role,
+ password);
+
(*check_password_hook) (stmt->role,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build a tuple to insert
@@ -393,17 +404,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encode the password to the requested format. */
+ password = encode_password(password_type, stmt->role, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(password);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +510,6 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -743,11 +746,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encode_password(PASSWORD_TYPE_PLAINTEXT, rolename,
+ password);
+
(*check_password_hook) (rolename,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build an updated tuple, perusing the information just obtained
@@ -804,17 +819,8 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encode the password to the requested format. */
+ password = encode_password(password_type, rolename, password);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
@@ -1232,7 +1238,7 @@ RenameRole(const char *oldname, const char *newname)
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 7e9124f..5df049f 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,10 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
- *
- * Original coding by Todd A. Brandys
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -106,6 +104,98 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail)
}
/*
+ * What kind of a password verifier is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ if (strncmp(shadow_pass, "plain:", 6) == 0 && strlen(shadow_pass) >= 6)
+ return PASSWORD_TYPE_PLAINTEXT;
+ if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
+ return PASSWORD_TYPE_MD5;
+ return PASSWORD_TYPE_UNKNOWN;
+}
+
+/*
+ * Given a plaintext password verifier, "plain:<password>", return the
+ * actual plaintext password without the prefix. The returned password is
+ * palloc'd.
+ */
+char *
+get_plain_password(const char *shadow_pass)
+{
+ if (strlen(shadow_pass) < 6 || memcmp(shadow_pass, "plain:", 6) != 0)
+ elog(ERROR, "password is not a plaintext password verifier");
+
+ return pstrdup(&shadow_pass[strlen("plain:")]);
+}
+
+/*
+ * Given a user-supplied password, convert it into a verifier of
+ * 'target_type' kind.
+ *
+ * If the password looks like a valid MD5 hash, it is stored as it is.
+ * We cannot reverse the hash, so even if the caller requested a plaintext
+ * plaintext password, the MD5 hash is returned.
+ *
+ * If the password follows the "plain:<password>" format, it is interpreted
+ * as a plaintext password. If an MD5 hash was requested, it is hashed,
+ * otherwise it is returned as it is.
+ */
+char *
+encode_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ const char *plain_password;
+ char *encrypted_password;
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ elog(ERROR, "unknown target password type");
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ return psprintf("plain:%s", password);
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ /* We cannot convert an MD5 hash back to plaintext. Store the hash. */
+ return pstrdup(password);
+ }
+ break;
+
+ case PASSWORD_TYPE_MD5:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ case PASSWORD_TYPE_PLAINTEXT:
+ encrypted_password = palloc(MD5_PASSWD_LEN + 1);
+
+ /* strip the possible "plain:" prefix, and hash */
+ if (guessed_type == PASSWORD_TYPE_PLAINTEXT)
+ plain_password = &password[strlen("plain:")];
+ else
+ plain_password = password;
+ if (!pg_md5_encrypt(plain_password, role, strlen(role),
+ encrypted_password))
+ elog(ERROR, "password encryption failed");
+ return encrypted_password;
+
+ case PASSWORD_TYPE_MD5:
+ return pstrdup(password);
+ }
+ }
+
+ elog(ERROR, "unrecognized password type conversion");
+}
+
+/*
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
@@ -135,32 +225,40 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* below: the only possible error is out-of-memory, which is unlikely, and
* if it did happen adding a psprintf call would only make things worse.
*/
- if (isMD5(shadow_pass))
- {
- /* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- return STATUS_ERROR;
- }
- }
- else
+ switch(get_password_type(shadow_pass))
{
- /* stored password is plain, double-encrypt */
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
+ case PASSWORD_TYPE_MD5:
+ /* stored password already encrypted, only do salt */
+ if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(&shadow_pass[strlen("plain:")],
+ role,
+ strlen(role),
+ crypt_pwd2))
+ {
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ default:
+ /* unknown password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
return STATUS_ERROR;
- }
}
if (strcmp(client_pass, crypt_pwd) == 0)
@@ -198,22 +296,36 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
* the password the client sent, and compare the hashes. Otherwise
* compare the plaintext passwords directly.
*/
- if (isMD5(shadow_pass))
+ switch (get_password_type(shadow_pass))
{
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
+ case PASSWORD_TYPE_MD5:
+ if (!pg_md5_encrypt(client_pass,
+ role,
+ strlen(role),
+ crypt_client_pass))
+ {
+ /*
+ * We do not bother setting logdetail for pg_md5_encrypt failure:
+ * the only possible error is out-of-memory, which is unlikely,
+ * and if it did happen adding a psprintf call would only make
+ * things worse.
+ */
+ return STATUS_ERROR;
+ }
+ client_pass = crypt_client_pass;
+ break;
+ case PASSWORD_TYPE_PLAINTEXT:
+ shadow_pass = &shadow_pass[strlen("plain:")];
+ break;
+
+ default:
/*
- * We do not bother setting logdetail for pg_md5_encrypt failure:
- * the only possible error is out-of-memory, which is unlikely,
- * and if it did happen adding a psprintf call would only make
- * things worse.
+ * This shouldn't happen. Plain "password" authentication should be
+ * possible with any kind of stored password hash.
*/
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
return STATUS_ERROR;
- }
- client_pass = crypt_client_pass;
}
if (strcmp(client_pass, shadow_pass) == 0)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a025117..ab71f47 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "commands/trigger.h"
#include "funcapi.h"
#include "libpq/auth.h"
+#include "libpq/crypt.h"
#include "libpq/be-fsstubs.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5..9cb3629 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -15,21 +15,10 @@
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-
-/*
- * Types of password, for Password_encryption GUC and the password_type
- * argument of the check-password hook.
- */
-typedef enum PasswordType
-{
- PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
-} PasswordType;
-
extern int Password_encryption; /* GUC */
/* Hook to check passwords in CreateRole() and AlterRole() */
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, Datum validuntil_time, bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/common/md5.h b/src/include/common/md5.h
index 4a04320..a29731b 100644
--- a/src/include/common/md5.h
+++ b/src/include/common/md5.h
@@ -18,10 +18,6 @@
#define MD5_PASSWD_LEN 35
-#define isMD5(passwd) (strncmp(passwd, "md5", 3) == 0 && \
- strlen(passwd) == MD5_PASSWD_LEN)
-
-
extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
extern bool pg_md5_encrypt(const char *passwd, const char *salt,
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 229ce76..ed47c05 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,7 +15,25 @@
#include "datatype/timestamp.h"
-extern int get_role_password(const char *role, char **shadow_pass, char **logdetail);
+/*
+ * Types of password.
+ *
+ * This is also used for the password_encryption GUC.
+ */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_UNKNOWN = -1,
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern PasswordType get_password_type(const char *shadow_pass);
+extern char *get_plain_password(const char *shadow_pass);
+extern char *encode_password(PasswordType target_type, const char *role,
+ const char *password);
+
+extern int get_role_password(const char *role, char **shadow_pass,
+ char **logdetail);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
--
2.10.2
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
On 12/14/2016 04:57 PM, Stephen Frost wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authidYes please.
This discussion seems to continue to come up and I don't entirely
understand why we keep trying to shove more things into pg_authid, or
worse, into rolpassword.I understand the relational beauty of having a separate column for
the verifier type, but I don't think it would be practical.
I disagree.
For
starters, we'd still like to have a self-identifying string format
like "scram-sha-256:<stuff>", so that you can conveniently pass the
verifier as a string to CREATE USER.
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently. Generally speaking, I don't
expect *users* to be providing actual encoded *verifiers* very often, so
it seems like a bit of extra syntax that pg_dump has to use isn't that
big of a deal.
I think it'll be much better to
stick to one format, than try to split the verifier into type and
the string, when it enters the catalog table.
Apparently, multiple people disagree with this approach. I don't think
history is really on your side here either.
We should have an independent table for the verifiers, which has a
different column for the verifier type, and either starts off supporting
multiple verifiers per role or at least gives us the ability to add that
easily later. We should also move rolvaliduntil to that new table.I agree we'll probably need a new table for verifiers. Or turn
rolpassword into an array or something. We discussed that before,
however, and it didn't really go anywhere, so right now I'd like to
get SCRAM in with minimal changes to the rest of the system. There
is a lot of room for improvement once it's in.
Using an array strikes me as an absolutely terrible idea- how are you
going to handle having different valid_until times then?
I do agree with trying to get SCRAM in without changing too much of the
rest of the system, but I wanted to make it clear that it's the only
point that I agree with for continuing down this path and that we should
absolutely be looking to change the CREATE USER syntax to specify the
verifier independently, plan to use a different table for the verifiers
with an independent column for the verifier type, support multiple
verifiers per role, etc, in the (hopefully very near...) future.
Thanks!
Stephen
On Thu, Dec 15, 2016 at 9:48 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
The only way to distinguish, is to know about every verifier kind there is,
and check whether rolpassword looks valid as anything else than a plaintext
password. And we already got tripped by a bug-of-omission on that once. If
we add more verifier formats in the future, it's bound to happen again.
Let's nip that source of bugs in the bud. Attached is a patch to implement
what I have in mind.
OK, I had a look at the patch proposed.
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
This patch removes the only possible check for MD5 hashes that it has
never been done in passwordcheck. It may be fine to remove it, but I would
think that it is a good source of example regarding what could be done with
MD5 hashes, though limited. So it seems to me that this check should involve
as well pg_md5_encrypt on the username and compare if with the MD5 hash
given by the caller. The new code is being careful about trying to pass
down a plain password, but it is possible to load MD5 hashes directly as
well, aka pg_dumpall.
A simple ALTER USER role PASSWORD 'foo' causes a crash:
#0 0x00000000004764d7 in heap_compute_data_size (tupleDesc=0x277f090, values=0x27504b8, isnull=0x2750550 "") at heaptuple.c:106
106 VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
(gdb) bt
#0 0x00000000004764d7 in heap_compute_data_size (tupleDesc=0x277f090, values=0x27504b8, isnull=0x2750550 "") at heaptuple.c:106
#1 0x00000000004781e9 in heap_form_tuple (tupleDescriptor=0x277f090, values=0x27504b8, isnull=0x2750550 "") at heaptuple.c:736
#2 0x00000000004784d0 in heap_modify_tuple (tuple=0x277adc8, tupleDesc=0x277f090, replValues=0x7fff1369d030, replIsnull=0x7fff1369d020 "", doReplace=0x7fff1369d010 "")
at heaptuple.c:833
#3 0x0000000000673788 in AlterRole (stmt=0x27a4f78) at user.c:845
#4 0x000000000082aa49 in standard_ProcessUtility (parsetree=0x27a4f78, queryString=0x27a43e8 "alter role ioltas password 'toto';", context=PROCESS_UTILITY_TOPLEVEL,
params=0x0, dest=0x27a5300, completionTag=0x7fff1369d5b0 "") at utility.c:711
+ case PASSWORD_TYPE_PLAINTEXT:
+ shadow_pass = &shadow_pass[strlen("plain:")];
+ break;
It would be a good idea to have a generic routine able to get the plain
password value. In short I think that we should reduce the amount of
locations where "plain:" prefix is hardcoded.
Alternatively, you could argue that we should forbid storing passwords in
plaintext altogether. I'm OK with that, too, if that's what people prefer.
Then you cannot have a user that can log in with both MD5 and SCRAM
authentication, but it's certainly more secure, and it's easier to document.
At the end this may prove to be a bad idea for some developers. In local
deployments when working on a backend application with Postgres as backend,
it is actually useful to have plain passwords. At least I have found that
useful in some stuff I did many years ago.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Dec 15, 2016 at 8:40 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
On 12/14/2016 04:57 PM, Stephen Frost wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authidYes please.
This discussion seems to continue to come up and I don't entirely
understand why we keep trying to shove more things into pg_authid, or
worse, into rolpassword.I understand the relational beauty of having a separate column for
the verifier type, but I don't think it would be practical.I disagree.
Me, too. I think the idea of moving everything into a separate table
that allows multiple verifiers is probably not a good thing to do just
right now, because that introduces a bunch of additional issues above
and beyond what we need to do to get SCRAM implemented. There are
administration and policy decisions to be made there that we should
not conflate with SCRAM proper.
However, Heikki's proposal seems to be that it's reasonable to force
rolpassword to be of the form 'type:verifier' in all cases but not
reasonable to have separate columns for type and verifier. Eh?
For
starters, we'd still like to have a self-identifying string format
like "scram-sha-256:<stuff>", so that you can conveniently pass the
verifier as a string to CREATE USER.I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently. Generally speaking, I don't
expect *users* to be providing actual encoded *verifiers* very often, so
it seems like a bit of extra syntax that pg_dump has to use isn't that
big of a deal.
We don't have to change the CREATE USER syntax at all. It could just
split on the first colon and put the two halves of the string in
different places. Of course, changing the syntax might be a good idea
anyway -- or not --- but the point is, right now, when you look at
rolpassword, there's not a clear rule for what kind of thing you've
got in there. That's absolutely terrible design and has got to be
fixed. Heikki's proposal of prefixing every entry with a type and a
':' will solve that problem and I'm not going to roll over in my grave
if we do it that way, but there is such a thing as normalization and
that technique could be applied here.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/15/16 8:40 AM, Stephen Frost wrote:
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently.
That's what the last patch set I looked at actually does.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/15/16 8:40 AM, Stephen Frost wrote:
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently.That's what the last patch set I looked at actually does.
Well, same here, but it was quite a while ago and things have progressed
since then wrt SCRAM, as I understand it...
Thanks!
Stephen
On Sat, Dec 17, 2016 at 5:42 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/15/16 8:40 AM, Stephen Frost wrote:
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently.That's what the last patch set I looked at actually does.
Well, same here, but it was quite a while ago and things have progressed
since then wrt SCRAM, as I understand it...
From the discussions of last year on -hackers, it was decided to *not*
have an additional column per complains from a couple of hackers
(Robert you were in this set at this point), and the same thing was
concluded during the informal lunch meeting at PGcon. The point is,
the existing SCRAM patch set can survive without touching at *all* the
format of pg_authid. We could block SCRAM authentication when
"password" is used in pg_hba.conf and as well as when "scram" is used
with a plain password stored in pg_authid. Or look at the format of
the string in the catalog if "password" is defined and decide the
authentication protocol to follow based on that.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Michael,
* Michael Paquier (michael.paquier@gmail.com) wrote:
On Sat, Dec 17, 2016 at 5:42 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/15/16 8:40 AM, Stephen Frost wrote:
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently.That's what the last patch set I looked at actually does.
Well, same here, but it was quite a while ago and things have progressed
since then wrt SCRAM, as I understand it...From the discussions of last year on -hackers, it was decided to *not*
have an additional column per complains from a couple of hackers
It seems that, at best, we didn't have consensus on it. Hopefully we
are moving in a direction of consensus.
(Robert you were in this set at this point), and the same thing was
concluded during the informal lunch meeting at PGcon. The point is,
the existing SCRAM patch set can survive without touching at *all* the
format of pg_authid. We could block SCRAM authentication when
"password" is used in pg_hba.conf and as well as when "scram" is used
with a plain password stored in pg_authid. Or look at the format of
the string in the catalog if "password" is defined and decide the
authentication protocol to follow based on that.
As I mentioned up-thread, moving forward with minimal changes to get
SCRAM in certainly makes sense, but I do think we should be open to
(and, ideally, encouraging people to work towards) having a seperate
table for verifiers with independent columns for type and verifier.
Thanks!
Stephen
On Sat, Dec 17, 2016 at 10:23 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Michael Paquier (michael.paquier@gmail.com) wrote:
(Robert you were in this set at this point), and the same thing was
concluded during the informal lunch meeting at PGcon. The point is,
the existing SCRAM patch set can survive without touching at *all* the
format of pg_authid. We could block SCRAM authentication when
"password" is used in pg_hba.conf and as well as when "scram" is used
with a plain password stored in pg_authid. Or look at the format of
the string in the catalog if "password" is defined and decide the
authentication protocol to follow based on that.As I mentioned up-thread, moving forward with minimal changes to get
SCRAM in certainly makes sense, but I do think we should be open to
(and, ideally, encouraging people to work towards) having a seperate
table for verifiers with independent columns for type and verifier.
Definitely, and you know my position on the matter or I would not have
written last year's patch series. Both things are just orthogonal IMO
at this point. And it would be good to focus just on one problem at
the moment to get it out.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Dec 16, 2016 at 5:30 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Sat, Dec 17, 2016 at 5:42 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/15/16 8:40 AM, Stephen Frost wrote:
I don't follow why we can't change the syntax for CREATE USER to allow
specifying the verifier type independently.That's what the last patch set I looked at actually does.
Well, same here, but it was quite a while ago and things have progressed
since then wrt SCRAM, as I understand it...From the discussions of last year on -hackers, it was decided to *not*
have an additional column per complains from a couple of hackers
(Robert you were in this set at this point), ...
Hmm, I don't recall taking that position, but then there are a lot of
things that I ought to recall and don't. (Ask my wife!)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Dec 18, 2016 at 3:59 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Dec 16, 2016 at 5:30 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:From the discussions of last year on -hackers, it was decided to *not*
have an additional column per complains from a couple of hackers
(Robert you were in this set at this point), ...Hmm, I don't recall taking that position, but then there are a lot of
things that I ought to recall and don't. (Ask my wife!)
[... digging objects of the past ...]
From the past thread:
/messages/by-id/CA+TgmoY790rphHBogXMbTG6MzSeNdoxdBXebEkAet9ZpZ8gvtw@mail.gmail.com
The complain is directed directly to multiple verifiers per users
though, not to have the type in a separate column.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Dec 15, 2016 at 3:17 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
In the case where the binaries are *not* built with libidn, I think
that we had better reject valid UTF-8 string directly and just allow
ASCII? SASLprep is a no-op on ASCII characters.Thoughts about this approach?
And Heikki has mentioned me that he'd prefer not having an extra
dependency for the normalization, which is LGPL-licensed by the way.
So I have looked at the SASLprep business to see what should be done
to get a complete implementation in core, completely independent of
anything known.
The first thing is to be able to understand in the SCRAM code if a
string is UTF-8 or not, and this code is in src/common/. pg_wchar.c
offers a set of routines exactly for this purpose, which is built with
libpq but that's not available for src/common/. So instead of moving
all the file, I'd like to create a new file in src/common/utf8.c which
includes pg_utf_mblen() and pg_utf8_islegal(). On top of that I think
that having a routine able to check a full string would be useful for
many users, as pg_utf8_islegal() can only check one set of characters.
If the password string is found to be of UTF-8 format, SASLprepare is
applied. If not, the string is copied as-is with perhaps unexpected
effects for the client But he's in trouble already if client is not
using UTF-8.
Then comes the real business... Note that's my first time touching
encoding, particularly UTF-8 in depth, so please be nice. I may write
things that are incorrect or sound so from here :)
The second thing is the normalization itself. Per RFC4013, NFKC needs
to be applied to the string. The operation is described in [1]http://www.unicode.org/reports/tr15/#Description_Norm
completely, and it is named as doing 1) a compatibility decomposition
of the bytes of the string, followed by 2) a canonical composition.
About 1). The compatibility decomposition is defined in [2]http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Character_Decomposition_Mappings, "by
recursively applying the canonical and compatibility mappings, then
applying the canonical reordering algorithm". Canonical and
compatibility mapping are some data available in UnicodeData.txt, the
6th column of the set defined in [3]http://www.unicode.org/Public/5.1.0/ucd/UCD.html#UnicodeData.txt to be precise. The meaning of the
decomposition mappings is defined in [2]http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Character_Decomposition_Mappings as well. The canonical
decomposition is basically to look for a given UTF-8 character, and
then apply the multiple characters resulting in its new shape. The
compatibility mapping should as well be applied, but [5]https://www.w3.org/International/charlint/, a perl tool
called charlint.pl doing this normalization work, does not care about
this phase... Do we?
About 2)... Once the decomposition has been applied, those bytes need
to be recomposed using the Canonical_Combining_Class field of
UnicodeData.txt in [3]http://www.unicode.org/Public/5.1.0/ucd/UCD.html#UnicodeData.txt, which is the 3rd column of the set. Its values
are defined in [4]http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Canonical_Combining_Class_Values. An other interesting thing, charlint.pl [5]https://www.w3.org/International/charlint/ does
not care about this phase. I am wondering if we should as well not
just drop this part as well...
Once 1) and 2) are done, NKFC is complete, and so is SASLPrepare.
So what we need from Postgres side is a mapping table to, having the
following fields:
1) Hexa sequence of UTF8 character.
2) Its canonical combining class.
3) The kind of decomposition mapping if defined.
4) The decomposition mapping, in hexadecimal format.
Based on what I looked at, either perl or python could be used to
process UnicodeData.txt and to generate a header file that would be
included in the tree. There are 30k entries in UnicodeData.txt, 5k of
them have a mapping, so that will result in many tables. One thing to
improve performance would be to store the length of the table in a
static variable, order the entries by their hexadecimal keys and do a
dichotomy lookup to find an entry. We could as well use more fancy
things like a set of tables using a Radix tree using decomposed by
bytes. We should finish by just doing one lookup of the table for each
character sets anyway.
In conclusion, at this point I am looking for feedback regarding the
following items:
1) Where to put the UTF8 check routines and what to move.
2) How to generate the mapping table using UnicodeData.txt. I'd think
that using perl would be better.
3) The shape of the mapping table, which depends on how many
operations we want to support in the normalization of the strings.
The decisions for those items will drive the implementation in one
sense or another.
[1]: http://www.unicode.org/reports/tr15/#Description_Norm
[2]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Character_Decomposition_Mappings
[3]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#UnicodeData.txt
[4]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Canonical_Combining_Class_Values
[5]: https://www.w3.org/International/charlint/
Heikki, others, thoughts?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Dec 17, 2016 at 5:48 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Sun, Dec 18, 2016 at 3:59 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Dec 16, 2016 at 5:30 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:From the discussions of last year on -hackers, it was decided to *not*
have an additional column per complains from a couple of hackers
(Robert you were in this set at this point), ...Hmm, I don't recall taking that position, but then there are a lot of
things that I ought to recall and don't. (Ask my wife!)[... digging objects of the past ...]
From the past thread:
/messages/by-id/CA+TgmoY790rphHBogXMbTG6MzSeNdoxdBXebEkAet9ZpZ8gvtw@mail.gmail.com
The complain is directed directly to multiple verifiers per users
though, not to have the type in a separate column.
Yes, I rather like the separate column. But since Heikki is doing the
work (or if he is) I'm not going to gripe too much.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/16/2016 05:48 PM, Robert Haas wrote:
On Thu, Dec 15, 2016 at 8:40 AM, Stephen Frost <sfrost@snowman.net> wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
On 12/14/2016 04:57 PM, Stephen Frost wrote:
* Peter Eisentraut (peter.eisentraut@2ndquadrant.com) wrote:
On 12/14/16 5:15 AM, Michael Paquier wrote:
I would be tempted to suggest adding the verifier type as a new column
of pg_authidYes please.
This discussion seems to continue to come up and I don't entirely
understand why we keep trying to shove more things into pg_authid, or
worse, into rolpassword.I understand the relational beauty of having a separate column for
the verifier type, but I don't think it would be practical.I disagree.
Me, too. I think the idea of moving everything into a separate table
that allows multiple verifiers is probably not a good thing to do just
right now, because that introduces a bunch of additional issues above
and beyond what we need to do to get SCRAM implemented. There are
administration and policy decisions to be made there that we should
not conflate with SCRAM proper.However, Heikki's proposal seems to be that it's reasonable to force
rolpassword to be of the form 'type:verifier' in all cases but not
reasonable to have separate columns for type and verifier. Eh?
I fear we'll just have to agree to disagree here, but I'll try to
explain myself one more time.
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the verifier and
verifier type columns. You will always need to look at the verifier type
to make sense of the verifier itself.
It's more convenient to carry the type information with the verifier
itself, in backend code, in pg_dump, etc. Sure, you could have a
separate "transfer" text format that has the prefix, and strip it out
when the datum enters the system. But it is even simpler to have only
one format, with the prefix, and use that everywhere.
It might make sense to add a separate column, to e.g. make it easier to
e.g. query for users that have an MD5 verifier. You could do "WHERE
rolverifiertype = 'md5'", instead of "WHERE rolpassword LIKE 'md5%'".
It's not a big difference, though. But even if we did that, I would
still love to have the type information *also* included with the
verifier itself, for convenience. And if we include it in the verifier
itself, adding a separate type column seems more trouble than it's worth.
For comparison, imagine that we added a column to pg_authid for a
picture of the user, stored as a bytea. The picture can be in JPEG or
PNG format. Looking at the first few bytes of the image, you can tell
which one it is. Would it make sense to add a separate "type" column, to
tell what format the image is in? I think it would be more convenient
and robust to rely on the first bytes of the image data instead.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/16/2016 03:31 AM, Michael Paquier wrote:
On Thu, Dec 15, 2016 at 9:48 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
The only way to distinguish, is to know about every verifier kind there is,
and check whether rolpassword looks valid as anything else than a plaintext
password. And we already got tripped by a bug-of-omission on that once. If
we add more verifier formats in the future, it's bound to happen again.
Let's nip that source of bugs in the bud. Attached is a patch to implement
what I have in mind.OK, I had a look at the patch proposed.
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));This patch removes the only possible check for MD5 hashes that it has
never been done in passwordcheck. It may be fine to remove it, but I would
think that it is a good source of example regarding what could be done with
MD5 hashes, though limited. So it seems to me that this check should involve
as well pg_md5_encrypt on the username and compare if with the MD5 hash
given by the caller.
Actually, it does still perform that check. There's a new function,
plain_crypt_verify, that passwordcheck uses now. plain_crypt_verify() is
intended to work with any future hash formats we might introduce in the
future (including SCRAM), so that passwordcheck doesn't need to know
about all the hash formats.
A simple ALTER USER role PASSWORD 'foo' causes a crash:
Ah, fixed.
+ case PASSWORD_TYPE_PLAINTEXT: + shadow_pass = &shadow_pass[strlen("plain:")]; + break; It would be a good idea to have a generic routine able to get the plain password value. In short I think that we should reduce the amount of locations where "plain:" prefix is hardcoded.
There is such a function included in the patch, get_plain_password(char
*shadow_pass), actually. Contrib/passwordcheck uses it. I figured that
in crypt.c itself, it's OK to do the above directly, but
get_plain_password() is intended to be used elsewhere.
Thanks for having a look! Attached is a new version, with that bug fixed.
- Heikki
Attachments:
0001-Use-plain-prefix-for-plaintext-passwords-stored-in-p-2.patchinvalid/octet-stream; name=0001-Use-plain-prefix-for-plaintext-passwords-stored-in-p-2.patchDownload
From b4320ff0d1b28a1b501fb77ea8edad5feb60d4b9 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 20 Dec 2016 14:14:09 +0200
Subject: [PATCH 1/1] Use "plain:" prefix for plaintext passwords stored in
pg_authid.
So far, the rule has been that if pg_authid.rolpassword begins with "md5"
and has the right length, it's an MD5 hash, otherwise it's a plaintext
password. That kind of pattern matching gets more awkward when we add
new kinds of verifiers, like the upcoming SCRAM verifiers. To be able to
distinguish different kinds of password hashes, use a "plain:" prefix
for plaintext passwords. With that, every password stored in pg_authid
has either "plain:" or "md5" prefix, and future password schemes can
likewise use different prefixes.
Note that this doesn't change the format of MD5 hashed passwords, which
is what almost everyone uses.
This changes check_password_hook in an incompatible way. The
'password_type' argument is removed, and the hook is expected to call
get_password_type() on the password verifier to determine its type.
Moreover, when a plaintext password is passed to the hook, it will have
the "plain:" prefix. That is a more subtle change!
In the passing, remove the bogus notice from the documentation of CREATE
ROLE, claiming that older clients might not work with MD5 hashed passwords.
That was written when we still had "crypt" authentication, and it was
referring to the fact that an older client might support "crypt"
authentication but not "md5". But we haven't supported "crypt" for years.
(As soon as we add a new authentication mechanism that doesn't work with
MD5 hashes, we'll need a similar notice again, though!)
Discussion: https://www.postgresql.org/message-id/2d07165c-1793-e243-a2a9-e45b624c7580@iki.fi
---
contrib/passwordcheck/passwordcheck.c | 133 +++++++++++------------
doc/src/sgml/catalogs.sgml | 4 +-
doc/src/sgml/ref/create_role.sgml | 6 --
src/backend/commands/user.c | 68 ++++++------
src/backend/libpq/crypt.c | 192 +++++++++++++++++++++++++++-------
src/backend/utils/misc/guc.c | 1 +
src/include/commands/user.h | 13 +--
src/include/common/md5.h | 4 -
src/include/libpq/crypt.h | 20 +++-
9 files changed, 275 insertions(+), 166 deletions(-)
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89bbbf..3d06964cab 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -21,7 +21,7 @@
#endif
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -50,87 +50,78 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ const char *shadow_pass,
Datum validuntil_time,
bool validuntil_null)
{
- int namelen = strlen(username);
- int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
- int i;
- bool pwd_has_letter,
- pwd_has_nonletter;
-
- switch (password_type)
+ if (get_password_type(shadow_pass) != PASSWORD_TYPE_PLAINTEXT)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ char *logdetail;
+
+ if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ }
+ else
+ {
+ /*
+ * For unencrypted passwords we can perform better checks
+ */
+ char *password = get_plain_password(shadow_pass);
+ int pwdlen = strlen(password);
+ int i;
+ bool pwd_has_letter,
+ pwd_has_nonletter;
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(password, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
/*
- * For unencrypted passwords we can perform better checks
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
*/
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
- /*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
- */
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ if (isalpha((unsigned char) password[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(password, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
+ pfree(password);
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 11c2019106..abf98af5ba 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1320,8 +1320,8 @@
will be of the user's password concatenated to their user name.
For example, if user <literal>joe</> has password <literal>xyzzy</>,
<productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ <literal>xyzzyjoe</>. If a password is stored unencrypted, the
+ column will begin with the string <literal>plain:</literal>.
</entry>
</row>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8369..a3b8ed9ab4 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -235,12 +235,6 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
password string). This allows reloading of encrypted
passwords during dump/restore.
</para>
-
- <para>
- Note that older clients might lack support for the MD5
- authentication mechanism that is needed to work with passwords
- that are stored encrypted.
- </para>
</listitem>
</varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index adc6b99b21..2d513f4041 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -29,7 +29,7 @@
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -81,7 +81,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -368,11 +367,23 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encode_password(PASSWORD_TYPE_PLAINTEXT, stmt->role,
+ password);
+
(*check_password_hook) (stmt->role,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build a tuple to insert
@@ -393,17 +404,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encode the password to the requested format. */
+ password = encode_password(password_type, stmt->role, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(password);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +510,6 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -743,11 +746,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encode_password(PASSWORD_TYPE_PLAINTEXT, rolename,
+ password);
+
(*check_password_hook) (rolename,
- password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build an updated tuple, perusing the information just obtained
@@ -804,17 +819,10 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encode the password to the requested format. */
+ password = encode_password(password_type, rolename, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(password);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
@@ -1232,7 +1240,7 @@ RenameRole(const char *oldname, const char *newname)
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 7e9124f39e..5df049fe21 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,10 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
- *
- * Original coding by Todd A. Brandys
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -106,6 +104,98 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail)
}
/*
+ * What kind of a password verifier is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ if (strncmp(shadow_pass, "plain:", 6) == 0 && strlen(shadow_pass) >= 6)
+ return PASSWORD_TYPE_PLAINTEXT;
+ if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
+ return PASSWORD_TYPE_MD5;
+ return PASSWORD_TYPE_UNKNOWN;
+}
+
+/*
+ * Given a plaintext password verifier, "plain:<password>", return the
+ * actual plaintext password without the prefix. The returned password is
+ * palloc'd.
+ */
+char *
+get_plain_password(const char *shadow_pass)
+{
+ if (strlen(shadow_pass) < 6 || memcmp(shadow_pass, "plain:", 6) != 0)
+ elog(ERROR, "password is not a plaintext password verifier");
+
+ return pstrdup(&shadow_pass[strlen("plain:")]);
+}
+
+/*
+ * Given a user-supplied password, convert it into a verifier of
+ * 'target_type' kind.
+ *
+ * If the password looks like a valid MD5 hash, it is stored as it is.
+ * We cannot reverse the hash, so even if the caller requested a plaintext
+ * plaintext password, the MD5 hash is returned.
+ *
+ * If the password follows the "plain:<password>" format, it is interpreted
+ * as a plaintext password. If an MD5 hash was requested, it is hashed,
+ * otherwise it is returned as it is.
+ */
+char *
+encode_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ const char *plain_password;
+ char *encrypted_password;
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ elog(ERROR, "unknown target password type");
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ return psprintf("plain:%s", password);
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ /* We cannot convert an MD5 hash back to plaintext. Store the hash. */
+ return pstrdup(password);
+ }
+ break;
+
+ case PASSWORD_TYPE_MD5:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ case PASSWORD_TYPE_PLAINTEXT:
+ encrypted_password = palloc(MD5_PASSWD_LEN + 1);
+
+ /* strip the possible "plain:" prefix, and hash */
+ if (guessed_type == PASSWORD_TYPE_PLAINTEXT)
+ plain_password = &password[strlen("plain:")];
+ else
+ plain_password = password;
+ if (!pg_md5_encrypt(plain_password, role, strlen(role),
+ encrypted_password))
+ elog(ERROR, "password encryption failed");
+ return encrypted_password;
+
+ case PASSWORD_TYPE_MD5:
+ return pstrdup(password);
+ }
+ }
+
+ elog(ERROR, "unrecognized password type conversion");
+}
+
+/*
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
@@ -135,32 +225,40 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* below: the only possible error is out-of-memory, which is unlikely, and
* if it did happen adding a psprintf call would only make things worse.
*/
- if (isMD5(shadow_pass))
- {
- /* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- return STATUS_ERROR;
- }
- }
- else
+ switch(get_password_type(shadow_pass))
{
- /* stored password is plain, double-encrypt */
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
+ case PASSWORD_TYPE_MD5:
+ /* stored password already encrypted, only do salt */
+ if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(&shadow_pass[strlen("plain:")],
+ role,
+ strlen(role),
+ crypt_pwd2))
+ {
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ default:
+ /* unknown password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
return STATUS_ERROR;
- }
}
if (strcmp(client_pass, crypt_pwd) == 0)
@@ -198,22 +296,36 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
* the password the client sent, and compare the hashes. Otherwise
* compare the plaintext passwords directly.
*/
- if (isMD5(shadow_pass))
+ switch (get_password_type(shadow_pass))
{
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
+ case PASSWORD_TYPE_MD5:
+ if (!pg_md5_encrypt(client_pass,
+ role,
+ strlen(role),
+ crypt_client_pass))
+ {
+ /*
+ * We do not bother setting logdetail for pg_md5_encrypt failure:
+ * the only possible error is out-of-memory, which is unlikely,
+ * and if it did happen adding a psprintf call would only make
+ * things worse.
+ */
+ return STATUS_ERROR;
+ }
+ client_pass = crypt_client_pass;
+ break;
+ case PASSWORD_TYPE_PLAINTEXT:
+ shadow_pass = &shadow_pass[strlen("plain:")];
+ break;
+
+ default:
/*
- * We do not bother setting logdetail for pg_md5_encrypt failure:
- * the only possible error is out-of-memory, which is unlikely,
- * and if it did happen adding a psprintf call would only make
- * things worse.
+ * This shouldn't happen. Plain "password" authentication should be
+ * possible with any kind of stored password hash.
*/
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
return STATUS_ERROR;
- }
- client_pass = crypt_client_pass;
}
if (strcmp(client_pass, shadow_pass) == 0)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a02511754e..ab71f4759a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "commands/trigger.h"
#include "funcapi.h"
#include "libpq/auth.h"
+#include "libpq/crypt.h"
#include "libpq/be-fsstubs.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5861..9cb3629d18 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -15,21 +15,10 @@
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-
-/*
- * Types of password, for Password_encryption GUC and the password_type
- * argument of the check-password hook.
- */
-typedef enum PasswordType
-{
- PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
-} PasswordType;
-
extern int Password_encryption; /* GUC */
/* Hook to check passwords in CreateRole() and AlterRole() */
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, Datum validuntil_time, bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/common/md5.h b/src/include/common/md5.h
index 4a0432076a..a29731b9bc 100644
--- a/src/include/common/md5.h
+++ b/src/include/common/md5.h
@@ -18,10 +18,6 @@
#define MD5_PASSWD_LEN 35
-#define isMD5(passwd) (strncmp(passwd, "md5", 3) == 0 && \
- strlen(passwd) == MD5_PASSWD_LEN)
-
-
extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
extern bool pg_md5_encrypt(const char *passwd, const char *salt,
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 229ce76b61..ed47c0549f 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,7 +15,25 @@
#include "datatype/timestamp.h"
-extern int get_role_password(const char *role, char **shadow_pass, char **logdetail);
+/*
+ * Types of password.
+ *
+ * This is also used for the password_encryption GUC.
+ */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_UNKNOWN = -1,
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern PasswordType get_password_type(const char *shadow_pass);
+extern char *get_plain_password(const char *shadow_pass);
+extern char *encode_password(PasswordType target_type, const char *role,
+ const char *password);
+
+extern int get_role_password(const char *role, char **shadow_pass,
+ char **logdetail);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
--
2.11.0
On Tue, Dec 20, 2016 at 6:37 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
It's more convenient to carry the type information with the verifier itself,
in backend code, in pg_dump, etc. Sure, you could have a separate "transfer"
text format that has the prefix, and strip it out when the datum enters the
system. But it is even simpler to have only one format, with the prefix, and
use that everywhere.
I see your point.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki,
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the verifier
and verifier type columns. You will always need to look at the
verifier type to make sense of the verifier itself.
That's true- but you don't need to look at the verifier, or even have
*access* to the verifier, to look at the verifier type. That is
actually very useful when you start thinking about the downstream side
of this- what about the monitoring tool which will want to check and
make sure there are only certain verifier types being used? It'll have
to be a superuser, or have access to some superuser security defined
function, and that really sucks. I'm not saying that we would
necessairly want the verifier type to be publicly visible, but being
able to see it without being a superuser would be good, imv.
It's more convenient to carry the type information with the verifier
itself, in backend code, in pg_dump, etc. Sure, you could have a
separate "transfer" text format that has the prefix, and strip it
out when the datum enters the system. But it is even simpler to have
only one format, with the prefix, and use that everywhere.
It's more convenient when you need to look at both- it's not more
convenient when you only wish to look at the verifier type. Further, it
means that we have to have a construct that assumes things about the
verifier type and verifier- what if a verifier type came along that used
a colon? We'd have to do some special magic to handle that correctly,
and that just sucks, and anyone who is writing code to generically deal
with these fields will end up writing that same code (or forgetting to,
and not handling the case correctly).
It might make sense to add a separate column, to e.g. make it easier
to e.g. query for users that have an MD5 verifier. You could do
"WHERE rolverifiertype = 'md5'", instead of "WHERE rolpassword LIKE
'md5%'". It's not a big difference, though. But even if we did that,
I would still love to have the type information *also* included with
the verifier itself, for convenience. And if we include it in the
verifier itself, adding a separate type column seems more trouble
than it's worth.
I don't agree that it's "not a big difference." As I argue above- your
approach also assumes that anyone who would like to investigate the
verifier type should have access to the verifier itself, which I do not
agree with. I also have a hard time buying the argument that it's
really so much more convenient to have the verifier type included in the
same string as the verifier that we should duplicate that information
and then run the risk that we end up with the two not matching or that
we won't ever run into complications down the road when our chosen
separator causes us difficulties.
Thanks!
Stephen
On Tue, Dec 20, 2016 at 08:34:19AM -0500, Stephen Frost wrote:
Heikki,
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the
verifier and verifier type columns. You will always need to look
at the verifier type to make sense of the verifier itself.That's true- but you don't need to look at the verifier, or even
have *access* to the verifier, to look at the verifier type.
Would a view that shows only what's to the left of the first semicolon
suit this purpose?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 21, 2016 at 1:08 AM, David Fetter <david@fetter.org> wrote:
Would a view that shows only what's to the left of the first semicolon
suit this purpose?
Of course it would, you would just need to make the routines now
checking the shape of MD5 and SCRAM identifiers available at SQL level
and feed the strings into them. Now I am not sure that it's worth
having a new superuser view for that. pg_roles and pg_shadow hide the
information about verifiers.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David,
* David Fetter (david@fetter.org) wrote:
On Tue, Dec 20, 2016 at 08:34:19AM -0500, Stephen Frost wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the
verifier and verifier type columns. You will always need to look
at the verifier type to make sense of the verifier itself.That's true- but you don't need to look at the verifier, or even
have *access* to the verifier, to look at the verifier type.Would a view that shows only what's to the left of the first semicolon
suit this purpose?
Obviously a (security barrier...) view or a (security definer) function
could be used, but I don't believe either is actually a good idea.
Thanks!
Stephen
On Tue, Dec 20, 2016 at 06:14:40PM -0500, Stephen Frost wrote:
David,
* David Fetter (david@fetter.org) wrote:
On Tue, Dec 20, 2016 at 08:34:19AM -0500, Stephen Frost wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the
verifier and verifier type columns. You will always need to look
at the verifier type to make sense of the verifier itself.That's true- but you don't need to look at the verifier, or even
have *access* to the verifier, to look at the verifier type.Would a view that shows only what's to the left of the first semicolon
suit this purpose?Obviously a (security barrier...) view or a (security definer) function
could be used, but I don't believe either is actually a good idea.
Would you be so kind as to help me understand what's wrong with that idea?
Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david(dot)fetter(at)gmail(dot)com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David,
* David Fetter (david@fetter.org) wrote:
On Tue, Dec 20, 2016 at 06:14:40PM -0500, Stephen Frost wrote:
* David Fetter (david@fetter.org) wrote:
On Tue, Dec 20, 2016 at 08:34:19AM -0500, Stephen Frost wrote:
* Heikki Linnakangas (hlinnaka@iki.fi) wrote:
Even if you have a separate "verifier type" column, it's not fully
normalized, because there's still a dependency between the
verifier and verifier type columns. You will always need to look
at the verifier type to make sense of the verifier itself.That's true- but you don't need to look at the verifier, or even
have *access* to the verifier, to look at the verifier type.Would a view that shows only what's to the left of the first semicolon
suit this purpose?Obviously a (security barrier...) view or a (security definer) function
could be used, but I don't believe either is actually a good idea.Would you be so kind as to help me understand what's wrong with that idea?
For starters, it doubles-down on the assumption that we'll always be
happy with that particular separator and implies to anyone watching that
they'll be able to trust it. Further, it's additional complication
which, at least to my eyes, is entirely in the wrong direction.
We could push everything in pg_authid into a single colon-separated text
field and call it simpler because we don't have to deal with those silly
column things, and we'd have something a lot closer to a unix passwd
file too!, but it wouldn't make it a terribly smart thing to do. We
aren't a bunch of individual C programs having to parse out things out
of flat text files, after all.
Thanks!
Stephen
On Tue, Dec 20, 2016 at 9:23 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 12/16/2016 03:31 AM, Michael Paquier wrote:
Actually, it does still perform that check. There's a new function,
plain_crypt_verify, that passwordcheck uses now. plain_crypt_verify() is
intended to work with any future hash formats we might introduce in the
future (including SCRAM), so that passwordcheck doesn't need to know about
all the hash formats.
Bah. I have misread the first version of the patch, and it is indeed
keeping the username checks. Now that things don't crash that behaves
as expected:
=# load 'passwordcheck';
LOAD
=# alter role mpaquier password 'mpaquier';
ERROR: 22023: password must not contain user name
LOCATION: check_password, passwordcheck.c:101
=# alter role mpaquier password 'md58349d3a1bc8f4f7399b1ff9dea493b15';
ERROR: 22023: password must not contain user name
LOCATION: check_password, passwordcheck.c:82
With the patch:
+ case PASSWORD_TYPE_PLAINTEXT: + shadow_pass = &shadow_pass[strlen("plain:")]; + break; It would be a good idea to have a generic routine able to get the plain password value. In short I think that we should reduce the amount of locations where "plain:" prefix is hardcoded.There is such a function included in the patch, get_plain_password(char
*shadow_pass), actually. Contrib/passwordcheck uses it. I figured that in
crypt.c itself, it's OK to do the above directly, but get_plain_password()
is intended to be used elsewhere.
The idea would be to have the function not return an allocated string,
just a position to it. That would be useful in plain_crypt_verify()
for example, for a total of 4 places, including get_plain_password()
where the new string allocation is done. Well, it's not like this
prefix "plain:" would change anyway in the future nor that it is going
to spread much.
Thanks for having a look! Attached is a new version, with that bug fixed.
I have been able more advanced testing without the crash and things
seem to work properly. The attached set of tests is also able to pass
for all the combinations of hba configurations and password formats.
And looking at the code I don't have more comments.
--
Michael
Attachments:
On 12/14/2016 01:33 PM, Heikki Linnakangas wrote:
I just noticed that the manual for CREATE ROLE says:
Note that older clients might lack support for the MD5 authentication
mechanism that is needed to work with passwords that are stored
encrypted.That's is incorrect. The alternative to MD5 authentication is plain
'password' authentication, and that works just fine with MD5-hashed
passwords. I think that sentence is a leftover from when we still
supported "crypt" authentication (so I actually get to blame you for
that ;-), commit 53a5026b). Back then, it was true that if an MD5 hash
was stored in pg_authid, you couldn't do "crypt" authentication. That
might have left old clients out in the cold.Now that we're getting SCRAM authentication, we'll need a similar notice
there again, for the incompatibility of a SCRAM verifier with MDD5
authentication and vice versa.
I went ahead and removed the current bogus notice from the docs. We
might need to put back something like it, with the SCRAM patch, but it
needs to be rewritten anyway.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/21/2016 04:09 AM, Michael Paquier wrote:
Thanks for having a look! Attached is a new version, with that bug fixed.
I have been able more advanced testing without the crash and things
seem to work properly. The attached set of tests is also able to pass
for all the combinations of hba configurations and password formats.
And looking at the code I don't have more comments.
Thanks!
Since not everyone agrees with this approach, I split this patch into
two. The first patch refactors things, replacing the isMD5() function
with get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial. And the second
patch adds the "plain:" prefix, which not everyone agrees on.
Barring objections I'm going to at least commit the first patch. I think
we should commit the second one too, but it's not as critical, and the
first patch matters more for the SCRAM patch, too.
- Heikki
Attachments:
0001-Replace-isMD5-with-a-more-future-proof-way-to-check-.patchinvalid/octet-stream; name=0001-Replace-isMD5-with-a-more-future-proof-way-to-check-.patchDownload
From 2abe24b64311cfcf22e3891a08f947507206b1e0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 3 Jan 2017 13:57:52 +0200
Subject: [PATCH 1/2] Replace isMD5() with a more future-proof way to check if
pw is encrypted.
The rule has been that if pg_authid.rolpassword begins with "md5" and has
the right length, it's an MD5 hash, otherwise it's a plaintext password.
The idiom has been to use isMD5() to check for that, but that gets awkward,
when we add new kinds of verifiers, like the verifiers for SCRAM
authentication, in the pending patch set. Replace isMD5() with a new
get_password_type() function, so that when new verifier types are added, we
don't need to remember to modify every place that currently calls isMD5(),
to also recognize the new kinds of verifiers.
This doesn't change the representation of passwords in the catalogs. There
was a long discussion on pgsql-hackers on adding a "plain:" prefix to
plaintext passwords, but this patch doesn't do that yet.
For the benefit of passwordcheck contrib module, and any 3rd party modules
using password_check_hook, add a new function called "plain_crypt_verify",
which can be used to check if a password matches. This allows conveniently
guessing or "brute-forcing" the password, whether it's stored as an MD5
hash, or in the future, as some other kind of a hash or verifier.
In the passing, remove the bogus notice from the documentation of CREATE
ROLE, claiming that older clients might not work with MD5 hashed passwords.
That was written when we still had "crypt" authentication, and it was
referring to the fact that an older client might support "crypt"
authentication but not "md5". But we haven't supported "crypt" for years.
(As soon as we add a new authentication mechanism that doesn't work with
MD5 hashes, we'll need a similar notice again, though!)
Discussion: https://www.postgresql.org/message-id/2d07165c-1793-e243-a2a9-e45b624c7580@iki.fi
---
contrib/passwordcheck/passwordcheck.c | 134 ++++++++++++++---------------
src/backend/commands/user.c | 40 +++------
src/backend/libpq/crypt.c | 155 +++++++++++++++++++++++++---------
src/include/commands/user.h | 17 +---
src/include/common/md5.h | 4 -
src/include/libpq/crypt.h | 18 +++-
6 files changed, 210 insertions(+), 158 deletions(-)
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index a0db89bbbf..03dfdfd0e1 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -21,7 +21,7 @@
#endif
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
@@ -50,87 +50,77 @@ extern void _PG_init(void);
*/
static void
check_password(const char *username,
- const char *password,
- int password_type,
+ const char *shadow_pass,
+ PasswordType password_type,
Datum validuntil_time,
bool validuntil_null)
{
- int namelen = strlen(username);
- int pwdlen = strlen(password);
- char encrypted[MD5_PASSWD_LEN + 1];
- int i;
- bool pwd_has_letter,
- pwd_has_nonletter;
-
- switch (password_type)
+ if (password_type != PASSWORD_TYPE_PLAINTEXT)
{
- case PASSWORD_TYPE_MD5:
-
- /*
- * Unfortunately we cannot perform exhaustive checks on encrypted
- * passwords - we are restricted to guessing. (Alternatively, we
- * could insist on the password being presented non-encrypted, but
- * that has its own security disadvantages.)
- *
- * We only check for username = password.
- */
- if (!pg_md5_encrypt(username, username, namelen, encrypted))
- elog(ERROR, "password encryption failed");
- if (strcmp(password, encrypted) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
- break;
-
- case PASSWORD_TYPE_PLAINTEXT:
-
+ /*
+ * Unfortunately we cannot perform exhaustive checks on encrypted
+ * passwords - we are restricted to guessing. (Alternatively, we
+ * could insist on the password being presented non-encrypted, but
+ * that has its own security disadvantages.)
+ *
+ * We only check for username = password.
+ */
+ char *logdetail;
+
+ if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+ }
+ else
+ {
+ /*
+ * For unencrypted passwords we can perform better checks
+ */
+ const char *password = shadow_pass;
+ int pwdlen = strlen(password);
+ int i;
+ bool pwd_has_letter,
+ pwd_has_nonletter;
+
+ /* enforce minimum length */
+ if (pwdlen < MIN_PWD_LENGTH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is too short")));
+
+ /* check if the password contains the username */
+ if (strstr(password, username))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password must not contain user name")));
+
+ /* check if the password contains both letters and non-letters */
+ pwd_has_letter = false;
+ pwd_has_nonletter = false;
+ for (i = 0; i < pwdlen; i++)
+ {
/*
- * For unencrypted passwords we can perform better checks
+ * isalpha() does not work for multibyte encodings but let's
+ * consider non-ASCII characters non-letters
*/
-
- /* enforce minimum length */
- if (pwdlen < MIN_PWD_LENGTH)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is too short")));
-
- /* check if the password contains the username */
- if (strstr(password, username))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password must not contain user name")));
-
- /* check if the password contains both letters and non-letters */
- pwd_has_letter = false;
- pwd_has_nonletter = false;
- for (i = 0; i < pwdlen; i++)
- {
- /*
- * isalpha() does not work for multibyte encodings but let's
- * consider non-ASCII characters non-letters
- */
- if (isalpha((unsigned char) password[i]))
- pwd_has_letter = true;
- else
- pwd_has_nonletter = true;
- }
- if (!pwd_has_letter || !pwd_has_nonletter)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ if (isalpha((unsigned char) password[i]))
+ pwd_has_letter = true;
+ else
+ pwd_has_nonletter = true;
+ }
+ if (!pwd_has_letter || !pwd_has_nonletter)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must contain both letters and nonletters")));
#ifdef USE_CRACKLIB
- /* call cracklib to check password */
- if (FascistCheck(password, CRACKLIB_DICTPATH))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("password is easily cracked")));
+ /* call cracklib to check password */
+ if (FascistCheck(password, CRACKLIB_DICTPATH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("password is easily cracked")));
#endif
- break;
-
- default:
- elog(ERROR, "unrecognized password type: %d", password_type);
- break;
}
/* all checks passed, password is ok */
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index a3521e7757..48969d3a47 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -29,7 +29,7 @@
#include "commands/dbcommands.h"
#include "commands/seclabel.h"
#include "commands/user.h"
-#include "common/md5.h"
+#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -81,7 +81,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -370,7 +369,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
@@ -393,17 +392,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encrypt the password to the requested format. */
+ password = encrypt_password(password_type, stmt->role, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(password);
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
@@ -506,7 +498,6 @@ AlterRole(AlterRoleStmt *stmt)
char *rolename = NULL;
char *password = NULL; /* user password */
int password_type = Password_encryption;
- char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
@@ -745,7 +736,7 @@ AlterRole(AlterRoleStmt *stmt)
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
- isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+ get_password_type(password),
validUntil_datum,
validUntil_null);
@@ -804,17 +795,10 @@ AlterRole(AlterRoleStmt *stmt)
/* password */
if (password)
{
- if (password_type == PASSWORD_TYPE_PLAINTEXT || 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);
- }
+ /* Encrypt the password to the requested format. */
+ password = encrypt_password(password_type, rolename, password);
+ new_record[Anum_pg_authid_rolpassword - 1] =
+ CStringGetTextDatum(password);
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
@@ -1232,7 +1216,7 @@ RenameRole(const char *oldname, const char *newname)
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
- if (!isnull && isMD5(TextDatumGetCString(datum)))
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 7e9124f39e..5ec5951ece 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,10 +1,8 @@
/*-------------------------------------------------------------------------
*
* crypt.c
- * Look into the password file and check the encrypted password with
- * the one passed in from the frontend.
- *
- * Original coding by Todd A. Brandys
+ * Functions for dealing with encrypted passwords stored in
+ * pg_authid.rolpassword.
*
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -106,6 +104,61 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail)
}
/*
+ * What kind of a password verifier is 'shadow_pass'?
+ */
+PasswordType
+get_password_type(const char *shadow_pass)
+{
+ if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
+ return PASSWORD_TYPE_MD5;
+ return PASSWORD_TYPE_PLAINTEXT;
+}
+
+/*
+ * Given a user-supplied password, convert it into a verifier of
+ * 'target_type' kind.
+ *
+ * If the password looks like a valid MD5 hash, it is stored as it is.
+ * We cannot reverse the hash, so even if the caller requested a plaintext
+ * plaintext password, the MD5 hash is returned.
+ */
+char *
+encrypt_password(PasswordType target_type, const char *role,
+ const char *password)
+{
+ PasswordType guessed_type = get_password_type(password);
+ char *encrypted_password;
+
+ switch (target_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ /*
+ * We cannot convert an MD5 hash back to plaintext,
+ * so no need to what what the input was. Just store
+ * it as it is.
+ */
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_PLAINTEXT:
+ encrypted_password = palloc(MD5_PASSWD_LEN + 1);
+
+ if (!pg_md5_encrypt(password, role, strlen(role),
+ encrypted_password))
+ elog(ERROR, "password encryption failed");
+ return encrypted_password;
+
+ case PASSWORD_TYPE_MD5:
+ return pstrdup(password);
+ }
+ }
+
+ elog(ERROR, "unrecognized password type conversion");
+}
+
+/*
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
@@ -135,32 +188,40 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* below: the only possible error is out-of-memory, which is unlikely, and
* if it did happen adding a psprintf call would only make things worse.
*/
- if (isMD5(shadow_pass))
- {
- /* stored password already encrypted, only do salt */
- if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
- return STATUS_ERROR;
- }
- }
- else
+ switch(get_password_type(shadow_pass))
{
- /* stored password is plain, double-encrypt */
- if (!pg_md5_encrypt(shadow_pass,
- role,
- strlen(role),
- crypt_pwd2))
- {
- return STATUS_ERROR;
- }
- if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
- md5_salt, md5_salt_len,
- crypt_pwd))
- {
+ case PASSWORD_TYPE_MD5:
+ /* stored password already encrypted, only do salt */
+ if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ /* stored password is plain, double-encrypt */
+ if (!pg_md5_encrypt(&shadow_pass[strlen("plain:")],
+ role,
+ strlen(role),
+ crypt_pwd2))
+ {
+ return STATUS_ERROR;
+ }
+ if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
+ md5_salt, md5_salt_len,
+ crypt_pwd))
+ {
+ return STATUS_ERROR;
+ }
+ break;
+
+ default:
+ /* unknown password hash format. */
+ *logdetail = psprintf(_("User \"%s\" has a password that cannot be used with MD5 authentication."),
+ role);
return STATUS_ERROR;
- }
}
if (strcmp(client_pass, crypt_pwd) == 0)
@@ -198,22 +259,36 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
* the password the client sent, and compare the hashes. Otherwise
* compare the plaintext passwords directly.
*/
- if (isMD5(shadow_pass))
+ switch (get_password_type(shadow_pass))
{
- if (!pg_md5_encrypt(client_pass,
- role,
- strlen(role),
- crypt_client_pass))
- {
+ case PASSWORD_TYPE_MD5:
+ if (!pg_md5_encrypt(client_pass,
+ role,
+ strlen(role),
+ crypt_client_pass))
+ {
+ /*
+ * We do not bother setting logdetail for pg_md5_encrypt failure:
+ * the only possible error is out-of-memory, which is unlikely,
+ * and if it did happen adding a psprintf call would only make
+ * things worse.
+ */
+ return STATUS_ERROR;
+ }
+ client_pass = crypt_client_pass;
+ break;
+ case PASSWORD_TYPE_PLAINTEXT:
+ shadow_pass = &shadow_pass[strlen("plain:")];
+ break;
+
+ default:
/*
- * We do not bother setting logdetail for pg_md5_encrypt failure:
- * the only possible error is out-of-memory, which is unlikely,
- * and if it did happen adding a psprintf call would only make
- * things worse.
+ * This shouldn't happen. Plain "password" authentication should be
+ * possible with any kind of stored password hash.
*/
+ *logdetail = psprintf(_("Password of user \"%s\" is in unrecognized format."),
+ role);
return STATUS_ERROR;
- }
- client_pass = crypt_client_pass;
}
if (strcmp(client_pass, shadow_pass) == 0)
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 102c2a5861..08037e0f81 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -12,24 +12,15 @@
#define USER_H
#include "catalog/objectaddress.h"
+#include "libpq/crypt.h"
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
-
-/*
- * Types of password, for Password_encryption GUC and the password_type
- * argument of the check-password hook.
- */
-typedef enum PasswordType
-{
- PASSWORD_TYPE_PLAINTEXT = 0,
- PASSWORD_TYPE_MD5
-} PasswordType;
-
-extern int Password_encryption; /* GUC */
+/* GUC. Is actually of type PasswordType. */
+extern int Password_encryption;
/* Hook to check passwords in CreateRole() and AlterRole() */
-typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/common/md5.h b/src/include/common/md5.h
index 4a0432076a..a29731b9bc 100644
--- a/src/include/common/md5.h
+++ b/src/include/common/md5.h
@@ -18,10 +18,6 @@
#define MD5_PASSWD_LEN 35
-#define isMD5(passwd) (strncmp(passwd, "md5", 3) == 0 && \
- strlen(passwd) == MD5_PASSWD_LEN)
-
-
extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
extern bool pg_md5_encrypt(const char *passwd, const char *salt,
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 229ce76b61..338be28b9d 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,7 +15,23 @@
#include "datatype/timestamp.h"
-extern int get_role_password(const char *role, char **shadow_pass, char **logdetail);
+/*
+ * Types of password.
+ *
+ * This is also used for the password_encryption GUC.
+ */
+typedef enum PasswordType
+{
+ PASSWORD_TYPE_PLAINTEXT = 0,
+ PASSWORD_TYPE_MD5
+} PasswordType;
+
+extern PasswordType get_password_type(const char *shadow_pass);
+extern char *encrypt_password(PasswordType target_type, const char *role,
+ const char *password);
+
+extern int get_role_password(const char *role, char **shadow_pass,
+ char **logdetail);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
--
2.11.0
0002-Use-plain-prefix-for-plaintext-passwords-stored-in-p.patchinvalid/octet-stream; name=0002-Use-plain-prefix-for-plaintext-passwords-stored-in-p.patchDownload
From 5bbda2ed5c1e8fc06c2a6761e4924224ecb34adf Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Tue, 3 Jan 2017 13:37:49 +0200
Subject: [PATCH 2/2] Use "plain:" prefix for plaintext passwords stored in
pg_authid.
So far, the rule has been that if pg_authid.rolpassword begins with "md5"
and has the right length, it's an MD5 hash, otherwise it's a plaintext
password. That kind of pattern matching gets more awkward when we add
new kinds of verifiers, like the upcoming SCRAM verifiers. To be able to
distinguish different kinds of password hashes, use a "plain:" prefix
for plaintext passwords. With that, every password stored in pg_authid
has either "plain:" or "md5" prefix, and future password schemes can
likewise use different prefixes.
Note that this doesn't change the format of MD5 hashed passwords, which
is what almost everyone uses.
This changes check_password_hook in an incompatible way. The
'password_type' argument is removed, and the hook is expected to call
get_password_type() on the password verifier to determine its type.
Moreover, when a plaintext password is passed to the hook, it will have
the "plain:" prefix. That is a more subtle change!
Discussion: https://www.postgresql.org/message-id/2d07165c-1793-e243-a2a9-e45b624c7580@iki.fi
---
contrib/passwordcheck/passwordcheck.c | 7 +++--
doc/src/sgml/catalogs.sgml | 4 +--
src/backend/commands/user.c | 32 ++++++++++++++++++---
src/backend/libpq/crypt.c | 53 +++++++++++++++++++++++++++++------
src/include/commands/user.h | 2 +-
src/include/libpq/crypt.h | 1 +
6 files changed, 81 insertions(+), 18 deletions(-)
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
index 03dfdfd0e1..3d06964cab 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -51,11 +51,10 @@ extern void _PG_init(void);
static void
check_password(const char *username,
const char *shadow_pass,
- PasswordType password_type,
Datum validuntil_time,
bool validuntil_null)
{
- if (password_type != PASSWORD_TYPE_PLAINTEXT)
+ if (get_password_type(shadow_pass) != PASSWORD_TYPE_PLAINTEXT)
{
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
@@ -77,7 +76,7 @@ check_password(const char *username,
/*
* For unencrypted passwords we can perform better checks
*/
- const char *password = shadow_pass;
+ char *password = get_plain_password(shadow_pass);
int pwdlen = strlen(password);
int i;
bool pwd_has_letter,
@@ -121,6 +120,8 @@ check_password(const char *username,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password is easily cracked")));
#endif
+
+ pfree(password);
}
/* all checks passed, password is ok */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 493050618d..5df31797e6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1325,8 +1325,8 @@
will be of the user's password concatenated to their user name.
For example, if user <literal>joe</> has password <literal>xyzzy</>,
<productname>PostgreSQL</> will store the md5 hash of
- <literal>xyzzyjoe</>. A password that does not follow that
- format is assumed to be unencrypted.
+ <literal>xyzzyjoe</>. If a password is stored unencrypted, the
+ column will begin with the string <literal>plain:</literal>.
</entry>
</row>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 48969d3a47..6811f0fa3d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -367,11 +367,23 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encrypt_password(PASSWORD_TYPE_PLAINTEXT, stmt->role,
+ password);
+
(*check_password_hook) (stmt->role,
- password,
- get_password_type(password),
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build a tuple to insert
@@ -734,11 +746,23 @@ AlterRole(AlterRoleStmt *stmt)
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
+ {
+ char *plain_password;
+
+ /*
+ * We prefer to pass a plaintext verifier to the password hook, if
+ * possible, even if it's eventually stored in a hashed format, so
+ * that the hook can perform more extensive tests on it.
+ */
+ plain_password = encrypt_password(PASSWORD_TYPE_PLAINTEXT, rolename,
+ password);
+
(*check_password_hook) (rolename,
- password,
- get_password_type(password),
+ plain_password,
validUntil_datum,
validUntil_null);
+ pfree(plain_password);
+ }
/*
* Build an updated tuple, perusing the information just obtained
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 5ec5951ece..57806a38e8 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -109,9 +109,25 @@ get_role_password(const char *role, char **shadow_pass, char **logdetail)
PasswordType
get_password_type(const char *shadow_pass)
{
+ if (strncmp(shadow_pass, "plain:", 6) == 0 && strlen(shadow_pass) >= 6)
+ return PASSWORD_TYPE_PLAINTEXT;
if (strncmp(shadow_pass, "md5", 3) == 0 && strlen(shadow_pass) == MD5_PASSWD_LEN)
return PASSWORD_TYPE_MD5;
- return PASSWORD_TYPE_PLAINTEXT;
+ return PASSWORD_TYPE_UNKNOWN;
+}
+
+/*
+ * Given a plaintext password verifier, "plain:<password>", return the
+ * actual plaintext password without the prefix. The returned password is
+ * palloc'd.
+ */
+char *
+get_plain_password(const char *shadow_pass)
+{
+ if (strlen(shadow_pass) < 6 || memcmp(shadow_pass, "plain:", 6) != 0)
+ elog(ERROR, "password is not a plaintext password verifier");
+
+ return pstrdup(&shadow_pass[strlen("plain:")]);
}
/*
@@ -121,31 +137,52 @@ get_password_type(const char *shadow_pass)
* If the password looks like a valid MD5 hash, it is stored as it is.
* We cannot reverse the hash, so even if the caller requested a plaintext
* plaintext password, the MD5 hash is returned.
+ *
+ * If the password follows the "plain:<password>" format, it is interpreted
+ * as a plaintext password. If an MD5 hash was requested, it is hashed,
+ * otherwise it is returned as it is.
*/
char *
encrypt_password(PasswordType target_type, const char *role,
const char *password)
{
PasswordType guessed_type = get_password_type(password);
+ const char *plain_password;
char *encrypted_password;
switch (target_type)
{
+ case PASSWORD_TYPE_UNKNOWN:
+ elog(ERROR, "unknown target password type");
+
case PASSWORD_TYPE_PLAINTEXT:
- /*
- * We cannot convert an MD5 hash back to plaintext,
- * so no need to what what the input was. Just store
- * it as it is.
- */
- return pstrdup(password);
+ switch (guessed_type)
+ {
+ case PASSWORD_TYPE_UNKNOWN:
+ return psprintf("plain:%s", password);
+
+ case PASSWORD_TYPE_PLAINTEXT:
+ return pstrdup(password);
+
+ case PASSWORD_TYPE_MD5:
+ /* We cannot convert an MD5 hash back to plaintext. Store the hash. */
+ return pstrdup(password);
+ }
+ break;
case PASSWORD_TYPE_MD5:
switch (guessed_type)
{
+ case PASSWORD_TYPE_UNKNOWN:
case PASSWORD_TYPE_PLAINTEXT:
encrypted_password = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(password, role, strlen(role),
+ /* strip the possible "plain:" prefix, and hash */
+ if (guessed_type == PASSWORD_TYPE_PLAINTEXT)
+ plain_password = &password[strlen("plain:")];
+ else
+ plain_password = password;
+ if (!pg_md5_encrypt(plain_password, role, strlen(role),
encrypted_password))
elog(ERROR, "password encryption failed");
return encrypted_password;
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 08037e0f81..c1c4d431d4 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -20,7 +20,7 @@
extern int Password_encryption;
/* Hook to check passwords in CreateRole() and AlterRole() */
-typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
+typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, Datum validuntil_time, bool validuntil_null);
extern PGDLLIMPORT check_password_hook_type check_password_hook;
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 338be28b9d..4c94054718 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -22,6 +22,7 @@
*/
typedef enum PasswordType
{
+ PASSWORD_TYPE_UNKNOWN = -1,
PASSWORD_TYPE_PLAINTEXT = 0,
PASSWORD_TYPE_MD5
} PasswordType;
--
2.11.0
On Tue, Jan 3, 2017 at 11:09 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Since not everyone agrees with this approach, I split this patch into two.
The first patch refactors things, replacing the isMD5() function with
get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial. And the second
patch adds the "plain:" prefix, which not everyone agrees on.Barring objections I'm going to at least commit the first patch. I think we
should commit the second one too, but it's not as critical, and the first
patch matters more for the SCRAM patch, too.
The split does not look correct to me. 0001 has references to the
prefix "plain:".
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 1/3/17 9:09 AM, Heikki Linnakangas wrote:
Since not everyone agrees with this approach, I split this patch into
two. The first patch refactors things, replacing the isMD5() function
with get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial. And the second
patch adds the "plain:" prefix, which not everyone agrees on.Barring objections I'm going to at least commit the first patch. I think
we should commit the second one too, but it's not as critical, and the
first patch matters more for the SCRAM patch, too.
Is there currently anything to review here for the commit fest?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jan 5, 2017 at 10:31 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 1/3/17 9:09 AM, Heikki Linnakangas wrote:
Since not everyone agrees with this approach, I split this patch into
two. The first patch refactors things, replacing the isMD5() function
with get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial. And the second
patch adds the "plain:" prefix, which not everyone agrees on.Barring objections I'm going to at least commit the first patch. I think
we should commit the second one too, but it's not as critical, and the
first patch matters more for the SCRAM patch, too.Is there currently anything to review here for the commit fest?
The patches sent here make sense as part of the SCRAM set:
/messages/by-id/6831df67-7641-1a66-4985-268609a4821f@iki.fi
I was just waiting for Heikki to fix the split of the patches before
moving on with an extra lookup though.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 1/3/17 9:09 AM, Heikki Linnakangas wrote:
Since not everyone agrees with this approach, I split this patch into
two. The first patch refactors things, replacing the isMD5() function
with get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial.
I have checked these patches.
The refactoring in the first patch seems sensible. As Michael pointed
out, there is still a reference to "plain:" in the first patch.
The commit message needs to be updated, because the function
plain_crypt_verify() was already added in a previous patch.
I'm not fond of this kind of coding
password = encrypt_password(password_type, stmt->role, password);
where the 'password' variable has a different meaning before and after.
This error message might be a mistake:
elog(ERROR, "unrecognized password type conversion");
I think some pieces from the second patch could be included in the first
patch, e.g., the parts for passwordcheck.c and user.c.
And the second
patch adds the "plain:" prefix, which not everyone agrees on.
The code also gets a little bit dubious, as it introduces an "unknown"
password type, which is sometimes treated as plaintext and sometimes as
an error. I think this is going be messy.
I would skip this patch for now at least. Too much controversy, and we
don't know how the rest of the patches for this feature will look like
to be able to know if it's worth it.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 15, 2016 at 07:52:06AM +0900, Michael Paquier wrote:
On Sat, Nov 5, 2016 at 9:36 PM, Michael Paquier <michael.paquier@gmail.com> wrote:
On Sat, Nov 5, 2016 at 12:58 AM, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
pg_hba.conf uses "scram" as keyword, but scram refers to a family of
authentication methods. There is as well SCRAM-SHA-1, SCRAM-SHA-256
(what this patch does). Hence wouldn't it make sense to use
scram_sha256 in pg_hba.conf instead? If for example in the future
there is a SHA-512 version of SCRAM we could switch easily to that and
define scram_sha512.OK, I have added more docs regarding the use of scram in pg_hba.conf,
particularly in client-auth.sgml to describe what scram is better than
md5 in terms of protection, and also completed the data of pg_hba.conf
about the new keyword used in it.
The latest versions document this precisely, but I agree with Peter's concern
about plain "scram". Suppose it's 2025 and PostgreSQL support SASL mechanisms
OAUTHBEARER, SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and SCRAM-SHA3-512. What
should the pg_hba.conf options look like at that time? I don't think having a
single "scram" option fits in such a world. I see two strategies that fit:
1. Single "sasl" option, with a GUC, similar to ssl_ciphers, controlling the
mechanisms to offer.
2. Separate options "scram_sha_256", "scram_sha3_512", "oauthbearer", etc.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 18, 2017 at 2:23 PM, Noah Misch <noah@leadboat.com> wrote:
The latest versions document this precisely, but I agree with Peter's concern
about plain "scram". Suppose it's 2025 and PostgreSQL support SASL mechanisms
OAUTHBEARER, SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and SCRAM-SHA3-512. What
should the pg_hba.conf options look like at that time? I don't think having a
single "scram" option fits in such a world.
Sure.
I see two strategies that fit:
1. Single "sasl" option, with a GUC, similar to ssl_ciphers, controlling the
mechanisms to offer.
2. Separate options "scram_sha_256", "scram_sha3_512", "oauthbearer", etc.
Or we could have a sasl option, with a mandatory array of mechanisms
to define one or more items, so method entries in pg_hba.conf would
look llke that:
sasl mechanism=scram_sha_256,scram_sha3_512
Users could define different methods in each hba line once a user and
a database map. I am not sure if many people would care about that
though.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Dec 20, 2016 at 10:47 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
And Heikki has mentioned me that he'd prefer not having an extra
dependency for the normalization, which is LGPL-licensed by the way.
So I have looked at the SASLprep business to see what should be done
to get a complete implementation in core, completely independent of
anything known.The first thing is to be able to understand in the SCRAM code if a
string is UTF-8 or not, and this code is in src/common/. pg_wchar.c
offers a set of routines exactly for this purpose, which is built with
libpq but that's not available for src/common/. So instead of moving
all the file, I'd like to create a new file in src/common/utf8.c which
includes pg_utf_mblen() and pg_utf8_islegal(). On top of that I think
that having a routine able to check a full string would be useful for
many users, as pg_utf8_islegal() can only check one set of characters.
If the password string is found to be of UTF-8 format, SASLprepare is
applied. If not, the string is copied as-is with perhaps unexpected
effects for the client But he's in trouble already if client is not
using UTF-8.Then comes the real business... Note that's my first time touching
encoding, particularly UTF-8 in depth, so please be nice. I may write
things that are incorrect or sound so from here :)The second thing is the normalization itself. Per RFC4013, NFKC needs
to be applied to the string. The operation is described in [1]
completely, and it is named as doing 1) a compatibility decomposition
of the bytes of the string, followed by 2) a canonical composition.About 1). The compatibility decomposition is defined in [2], "by
recursively applying the canonical and compatibility mappings, then
applying the canonical reordering algorithm". Canonical and
compatibility mapping are some data available in UnicodeData.txt, the
6th column of the set defined in [3] to be precise. The meaning of the
decomposition mappings is defined in [2] as well. The canonical
decomposition is basically to look for a given UTF-8 character, and
then apply the multiple characters resulting in its new shape. The
compatibility mapping should as well be applied, but [5], a perl tool
called charlint.pl doing this normalization work, does not care about
this phase... Do we?About 2)... Once the decomposition has been applied, those bytes need
to be recomposed using the Canonical_Combining_Class field of
UnicodeData.txt in [3], which is the 3rd column of the set. Its values
are defined in [4]. An other interesting thing, charlint.pl [5] does
not care about this phase. I am wondering if we should as well not
just drop this part as well...Once 1) and 2) are done, NKFC is complete, and so is SASLPrepare.
So what we need from Postgres side is a mapping table to, having the
following fields:
1) Hexa sequence of UTF8 character.
2) Its canonical combining class.
3) The kind of decomposition mapping if defined.
4) The decomposition mapping, in hexadecimal format.
Based on what I looked at, either perl or python could be used to
process UnicodeData.txt and to generate a header file that would be
included in the tree. There are 30k entries in UnicodeData.txt, 5k of
them have a mapping, so that will result in many tables. One thing to
improve performance would be to store the length of the table in a
static variable, order the entries by their hexadecimal keys and do a
dichotomy lookup to find an entry. We could as well use more fancy
things like a set of tables using a Radix tree using decomposed by
bytes. We should finish by just doing one lookup of the table for each
character sets anyway.In conclusion, at this point I am looking for feedback regarding the
following items:
1) Where to put the UTF8 check routines and what to move.
2) How to generate the mapping table using UnicodeData.txt. I'd think
that using perl would be better.
3) The shape of the mapping table, which depends on how many
operations we want to support in the normalization of the strings.
The decisions for those items will drive the implementation in one
sense or another.[1]: http://www.unicode.org/reports/tr15/#Description_Norm
[2]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Character_Decomposition_Mappings
[3]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#UnicodeData.txt
[4]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Canonical_Combining_Class_Values
[5]: https://www.w3.org/International/charlint/Heikki, others, thoughts?
FWIW, this patch is on a "waiting on author" state and that's right.
As the discussion on SASLprepare() and the decisions regarding the way
to implement it, or at least have it, are still pending, I am not
planning to move on with any implementation until we have a plan about
what to do. Just using libidn (LGPL) for a first shot is rather
painless but... I am not alone here.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 18, 2017 at 02:30:38PM +0900, Michael Paquier wrote:
On Wed, Jan 18, 2017 at 2:23 PM, Noah Misch <noah@leadboat.com> wrote:
The latest versions document this precisely, but I agree with Peter's concern
about plain "scram". Suppose it's 2025 and PostgreSQL support SASL mechanisms
OAUTHBEARER, SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and SCRAM-SHA3-512. What
should the pg_hba.conf options look like at that time? I don't think having a
single "scram" option fits in such a world.Sure.
I see two strategies that fit:
1. Single "sasl" option, with a GUC, similar to ssl_ciphers, controlling the
mechanisms to offer.
2. Separate options "scram_sha_256", "scram_sha3_512", "oauthbearer", etc.Or we could have a sasl option, with a mandatory array of mechanisms
to define one or more items, so method entries in pg_hba.conf would
look llke that:
sasl mechanism=scram_sha_256,scram_sha3_512
I like that.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 19 January 2017 at 06:32, Noah Misch <noah@leadboat.com> wrote:
On Wed, Jan 18, 2017 at 02:30:38PM +0900, Michael Paquier wrote:
On Wed, Jan 18, 2017 at 2:23 PM, Noah Misch <noah@leadboat.com> wrote:
The latest versions document this precisely, but I agree with Peter's concern
about plain "scram". Suppose it's 2025 and PostgreSQL support SASL mechanisms
OAUTHBEARER, SCRAM-SHA-256, SCRAM-SHA-256-PLUS, and SCRAM-SHA3-512. What
should the pg_hba.conf options look like at that time? I don't think having a
single "scram" option fits in such a world.Sure.
I see two strategies that fit:
1. Single "sasl" option, with a GUC, similar to ssl_ciphers, controlling the
mechanisms to offer.
2. Separate options "scram_sha_256", "scram_sha3_512", "oauthbearer", etc.Or we could have a sasl option, with a mandatory array of mechanisms
to define one or more items, so method entries in pg_hba.conf would
look llke that:
sasl mechanism=scram_sha_256,scram_sha3_512I like that.
Michael, I support your good work on this patch and its certainly shaping up.
Noah's general point is that we need to have a general, futureproof
design for the UI and I agree.
We seem to be caught between adding lots of new things as parameters
and adding new detail into pg_hba.conf.
Parameters like password_encryption are difficult here because they
essentially repeat what has already been said in the pg_hba.conf. If
we have two entries in pg_hba.conf, one saying md5 and the other
saying "scram" (or whatever), what would we set password_encryption
to? It seems clear to me that if the pg_hba.conf says md5 then
password_encryption should be md5 and if pg_hba.conf says scram then
it should be scram.
I'd like to float another idea, as a way of finding a way forwards
that will last over time
* pg_hba.conf entry would say sasl='methodX' (no spaces)
* we have a new catalog called pg_sasl that allows us to add new
methods, with appropriate function calls
* remove password_encryption parameter and always use default
encryption as specified for that session in pg_hba.conf
Which sounds nice, but many users will wish to upgrade their current
mechanisms from using md5 to scram. How will we update passwords
slowly, so that different users change from md5 to scram at different
times? Having to specify the mechanism in the pg_hba.conf makes that
almost impossible, forcing a big bang approach which subsequently may
never happen.
As a way of solving that problem, another idea would be to make the
mechanism session specific depending upon what is stored for a
particular user. That allows us to have a single pg_hba.conf entry of
"sasl", and then use md5, scram-256 or future-mechanism on a per user
basis.
I'm not sure I see a clear way forwards yet, these are just ideas and
questions to help the discussion.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jan 19, 2017 at 6:17 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
We seem to be caught between adding lots of new things as parameters
and adding new detail into pg_hba.conf.Parameters like password_encryption are difficult here because they
essentially repeat what has already been said in the pg_hba.conf. If
we have two entries in pg_hba.conf, one saying md5 and the other
saying "scram" (or whatever), what would we set password_encryption
to? It seems clear to me that if the pg_hba.conf says md5 then
password_encryption should be md5 and if pg_hba.conf says scram then
it should be scram.I'd like to float another idea, as a way of finding a way forwards
that will last over time* pg_hba.conf entry would say sasl='methodX' (no spaces)
* we have a new catalog called pg_sasl that allows us to add new
methods, with appropriate function calls
This would make sense if we support a mountain of protocols and that
we want to have a handler with a set of APIs used for authentication.
This is a grade higher than simple SCRAM, and this basically requires
to design a set of generic routines that are fine for covering *any*
protocol with this handler. I'd think this is rather hard per the
slight differences in SASL exchanges for different protocols.
* remove password_encryption parameter and always use default
encryption as specified for that session in pg_hba.conf
So if user X creates user Y with a password (defined by CREATE USER
PASSWORD) it should by default follow what pg_hba.conf dictates, which
could be pam or gss? That does not look very intuitive to me. The
advantage with the current system is that password creation and
protocol allowed for an authentication are two separate, independent
things, password_encryption being basically a wrapper for CREATE USER.
Mixing both makes things more confusing. If you are willing to move
away from password_encryption, one thing that could be used is just to
extend CREATE USER to be able to enforce the password protocol
associated, that's what the patches on this thread do with PASSWORD
(val USING protocol).
Which sounds nice, but many users will wish to upgrade their current
mechanisms from using md5 to scram. How will we update passwords
slowly, so that different users change from md5 to scram at different
times? Having to specify the mechanism in the pg_hba.conf makes that
almost impossible, forcing a big bang approach which subsequently may
never happen.
At this point comes the possibility to define multiple password types
for one single user instead of rolling multiple roles and renaming
htem.
As a way of solving that problem, another idea would be to make the
mechanism session specific depending upon what is stored for a
particular user. That allows us to have a single pg_hba.conf entry of
"sasl", and then use md5, scram-256 or future-mechanism on a per user
basis.
Isn't that specifying multiple users in a single sasl entry in
pg_hba.conf? Once a user is updated, you could just move him from one
line to the other of pg_hba.conf, or use a @file in the hba entry.
I'm not sure I see a clear way forwards yet, these are just ideas and
questions to help the discussion.
Thanks, I find the catalog idea interesting. That's hard though per
the potential range of SASL protocols that have likely different needs
in the way messages are exchanged.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 18, 2017 at 2:46 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:
FWIW, this patch is on a "waiting on author" state and that's right.
As the discussion on SASLprepare() and the decisions regarding the way
to implement it, or at least have it, are still pending, I am not
planning to move on with any implementation until we have a plan about
what to do. Just using libidn (LGPL) for a first shot is rather
painless but... I am not alone here.
With decisions on this matter pending, I am marking this patch as
"returned with feedback". If there is a consensus on what to do, I'll
be happy to do the implementation with the last CF in March in sight.
If no, that would mean that this feature will not be part of PG 10.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/17/2017 11:51 PM, Peter Eisentraut wrote:
On 1/3/17 9:09 AM, Heikki Linnakangas wrote:
Since not everyone agrees with this approach, I split this patch into
two. The first patch refactors things, replacing the isMD5() function
with get_password_type(), without changing the representation of
pg_authid.rolpassword. That is hopefully uncontroversial.I have checked these patches.
The refactoring in the first patch seems sensible. As Michael pointed
out, there is still a reference to "plain:" in the first patch.
Fixed.
The commit message needs to be updated, because the function
plain_crypt_verify() was already added in a previous patch.
Fixed.
I'm not fond of this kind of coding
password = encrypt_password(password_type, stmt->role, password);
where the 'password' variable has a different meaning before and after.
Added a new local variable to avoid the confusion.
This error message might be a mistake:
elog(ERROR, "unrecognized password type conversion");
I rephrased the error as "cannot encrypt password to requested type",
and added a comment explaining that it cannot happen. I hope that
helped, I'm not sure why you thought it might've been a mistake.
I think some pieces from the second patch could be included in the first
patch, e.g., the parts for passwordcheck.c and user.c.
I refrained from doing that for now. It would've changed the
passwordcheck hook API in an incompatible way. Breaking the API
explicitly would be a good thing, if we added the "plain:" prefix,
because modules would need to deal with the prefix anyway. But until we
do that, better to not break the API for no good reason.
And the second
patch adds the "plain:" prefix, which not everyone agrees on.The code also gets a little bit dubious, as it introduces an "unknown"
password type, which is sometimes treated as plaintext and sometimes as
an error. I think this is going be messy.I would skip this patch for now at least. Too much controversy, and we
don't know how the rest of the patches for this feature will look like
to be able to know if it's worth it.
Ok, I'll drop the second patch for now. I committed the first patch
after fixing the things you and Michael pointed out. Thanks for the review!
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2 February 2017 at 00:13, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Ok, I'll drop the second patch for now. I committed the first patch after
fixing the things you and Michael pointed out. Thanks for the review!
dbd69118 caused small compiler warning for me.
The attached fixed it.
--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
encrypt_password_warning_fix.patchapplication/octet-stream; name=encrypt_password_warning_fix.patchDownload
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 893ce6e..e7dd212 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -160,6 +160,7 @@ encrypt_password(PasswordType target_type, const char *role,
* handle every combination of source and target password types.
*/
elog(ERROR, "cannot encrypt password to requested type");
+ return NULL; /* keep compiler quiet */
}
/*
On 02/02/2017 05:50 AM, David Rowley wrote:
On 2 February 2017 at 00:13, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Ok, I'll drop the second patch for now. I committed the first patch after
fixing the things you and Michael pointed out. Thanks for the review!dbd69118 caused small compiler warning for me.
The attached fixed it.
Fixed, thanks!
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/20/2016 03:47 AM, Michael Paquier wrote:
The first thing is to be able to understand in the SCRAM code if a
string is UTF-8 or not, and this code is in src/common/. pg_wchar.c
offers a set of routines exactly for this purpose, which is built with
libpq but that's not available for src/common/. So instead of moving
all the file, I'd like to create a new file in src/common/utf8.c which
includes pg_utf_mblen() and pg_utf8_islegal().
Sounds reasonable. They're short functions, might also be ok to just
copy-paste them to scram-common.c.
On top of that I think that having a routine able to check a full
string would be useful for many users, as pg_utf8_islegal() can only
check one set of characters. If the password string is found to be of
UTF-8 format, SASLprepare is applied. If not, the string is copied
as-is with perhaps unexpected effects for the client But he's in
trouble already if client is not using UTF-8.
Yeah.
The second thing is the normalization itself. Per RFC4013, NFKC needs
to be applied to the string. The operation is described in [1]
completely, and it is named as doing 1) a compatibility decomposition
of the bytes of the string, followed by 2) a canonical composition.About 1). The compatibility decomposition is defined in [2], "by
recursively applying the canonical and compatibility mappings, then
applying the canonical reordering algorithm". Canonical and
compatibility mapping are some data available in UnicodeData.txt, the
6th column of the set defined in [3] to be precise. The meaning of the
decomposition mappings is defined in [2] as well. The canonical
decomposition is basically to look for a given UTF-8 character, and
then apply the multiple characters resulting in its new shape. The
compatibility mapping should as well be applied, but [5], a perl tool
called charlint.pl doing this normalization work, does not care about
this phase... Do we?
Not sure. We need to do whatever the "right thing" is, according to the
RFC. I would assume that the spec is not ambiguous this, but I haven't
looked into the details. If it's ambiguous, then I think we need to look
at some popular implementations to see what they do.
About 2)... Once the decomposition has been applied, those bytes need
to be recomposed using the Canonical_Combining_Class field of
UnicodeData.txt in [3], which is the 3rd column of the set. Its values
are defined in [4]. An other interesting thing, charlint.pl [5] does
not care about this phase. I am wondering if we should as well not
just drop this part as well...Once 1) and 2) are done, NKFC is complete, and so is SASLPrepare.
Ok.
So what we need from Postgres side is a mapping table to, having the
following fields:
1) Hexa sequence of UTF8 character.
2) Its canonical combining class.
3) The kind of decomposition mapping if defined.
4) The decomposition mapping, in hexadecimal format.
Based on what I looked at, either perl or python could be used to
process UnicodeData.txt and to generate a header file that would be
included in the tree. There are 30k entries in UnicodeData.txt, 5k of
them have a mapping, so that will result in many tables. One thing to
improve performance would be to store the length of the table in a
static variable, order the entries by their hexadecimal keys and do a
dichotomy lookup to find an entry. We could as well use more fancy
things like a set of tables using a Radix tree using decomposed by
bytes. We should finish by just doing one lookup of the table for each
character sets anyway.
Ok. I'm not too worried about the performance of this. It's only used
for passwords, which are not that long, and it's only done when
connecting. I'm more worried about the disk/memory usage. How small can
we pack the tables? 10kB? 100kB? Even a few MB would probably not be too
bad in practice, but I'd hate to bloat up libpq just for this.
In conclusion, at this point I am looking for feedback regarding the
following items:
1) Where to put the UTF8 check routines and what to move.
Covered that above.
2) How to generate the mapping table using UnicodeData.txt. I'd think
that using perl would be better.
Agreed, it needs to be in Perl. That's what we require to be present
when building PostgreSQL, it's what we use for generating other tables
and functions.
3) The shape of the mapping table, which depends on how many
operations we want to support in the normalization of the strings.
The decisions for those items will drive the implementation in one
sense or another.
Let's aim for small disk/memory footprint.
- Heikki
[1]: http://www.unicode.org/reports/tr15/#Description_Norm
[2]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Character_Decomposition_Mappings
[3]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#UnicodeData.txt
[4]: http://www.unicode.org/Public/5.1.0/ucd/UCD.html#Canonical_Combining_Class_Values
[5]: https://www.w3.org/International/charlint/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Feb 3, 2017 at 9:52 PM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 12/20/2016 03:47 AM, Michael Paquier wrote:
The first thing is to be able to understand in the SCRAM code if a
string is UTF-8 or not, and this code is in src/common/. pg_wchar.c
offers a set of routines exactly for this purpose, which is built with
libpq but that's not available for src/common/. So instead of moving
all the file, I'd like to create a new file in src/common/utf8.c which
includes pg_utf_mblen() and pg_utf8_islegal().Sounds reasonable. They're short functions, might also be ok to just
copy-paste them to scram-common.c.
Having a separate file makes the most sense to me I think, if we can
avoid code duplication that's better.
The second thing is the normalization itself. Per RFC4013, NFKC needs
to be applied to the string. The operation is described in [1]
completely, and it is named as doing 1) a compatibility decomposition
of the bytes of the string, followed by 2) a canonical composition.About 1). The compatibility decomposition is defined in [2], "by
recursively applying the canonical and compatibility mappings, then
applying the canonical reordering algorithm". Canonical and
compatibility mapping are some data available in UnicodeData.txt, the
6th column of the set defined in [3] to be precise. The meaning of the
decomposition mappings is defined in [2] as well. The canonical
decomposition is basically to look for a given UTF-8 character, and
then apply the multiple characters resulting in its new shape. The
compatibility mapping should as well be applied, but [5], a perl tool
called charlint.pl doing this normalization work, does not care aboutNot sure. We need to do whatever the "right thing" is, according to the RFC.
I would assume that the spec is not ambiguous this, but I haven't looked
into the details. If it's ambiguous, then I think we need to look at some
popular implementations to see what they do.
The spec defines quite correctly what should be done. The
implementations are sometimes quite loose on some points though (see
charlint.pl).
So what we need from Postgres side is a mapping table to, having the
following fields:
1) Hexa sequence of UTF8 character.
2) Its canonical combining class.
3) The kind of decomposition mapping if defined.
4) The decomposition mapping, in hexadecimal format.
Based on what I looked at, either perl or python could be used to
process UnicodeData.txt and to generate a header file that would be
included in the tree. There are 30k entries in UnicodeData.txt, 5k of
them have a mapping, so that will result in many tables. One thing to
improve performance would be to store the length of the table in a
static variable, order the entries by their hexadecimal keys and do a
dichotomy lookup to find an entry. We could as well use more fancy
things like a set of tables using a Radix tree using decomposed by
bytes. We should finish by just doing one lookup of the table for each
character sets anyway.Ok. I'm not too worried about the performance of this. It's only used for
passwords, which are not that long, and it's only done when connecting. I'm
more worried about the disk/memory usage. How small can we pack the tables?
10kB? 100kB? Even a few MB would probably not be too bad in practice, but
I'd hate to bloat up libpq just for this.
Indeed. I think I'll develop first a small utility able to do
operation. There is likely some knowledge in mb/Unicode that we can
use here. The radix tree patch would perhaps help?
3) The shape of the mapping table, which depends on how many
operations we want to support in the normalization of the strings.
The decisions for those items will drive the implementation in one
sense or another.Let's aim for small disk/memory footprint.
OK, I'll try to give it a shot in a couple of days in the shape of an
extention or something like that. Thanks for the feedback.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers