On 11/29/22 8:12 PM, Michael Paquier wrote:
On Tue, Nov 29, 2022 at 09:32:34PM +0100, Daniel Gustafsson wrote:
On the whole I tend to agree with Jacob upthread, while this does provide
consistency it doesn't seem to move the needle for best practices.  Allowing
scram_build_secret_sha256('password', 'a', 1); with the password potentially
going in cleartext over the wire and into the logs doesn't seem like a great
tradeoff for the (IMHO) niche usecases it would satisfy.

Should we try to make \password and libpq more flexible instead?  Two
things got discussed in this area since v10:
- The length of the random salt.
- The iteration number.

Or we could bump up the defaults, and come back to that in a few
years, again.. ;p

Here is another attempt at this patch that takes into account the SCRAM code refactor. I addressed some of Daniel's previous feedback, but will need to make another pass on the docs and the assert trace as the main focus of this revision was bringing the code inline with the recent changes.

This patch changes the function name to "scram_build_secret" and now accepts a new parameter of hash type. This sets it up to handle additional hashes in the future.

I do agree we should make libpq more flexible, but as mentioned in the original thread, this does not solve the *server side* cases where a user needs to build a SCRAM secret. For example, being able to precompute hashes on one server before sending them to another server, which can require no plaintext passwords if the server is randomly generating the data.

Another use case comes from the "pg_tle" project, specifically with the ability to write a "check_password_hook" from an available PL[1]. If a user does follow our best practices and sends a pre-built SCRAM secret over the wire, a hook can then verify that the secret is not contained within a common password dictionary.

Thanks,

Jonathan

[1] https://github.com/aws/pg_tle/blob/main/docs/04_hooks.md

From 756c93f83869b7f8cbb87a7e4ccd967cbd8e8553 Mon Sep 17 00:00:00 2001
From: "Jonathan S. Katz" <jonathan.k...@excoventures.com>
Date: Mon, 31 Oct 2022 16:13:08 -0400
Subject: [PATCH] Add "scram_build_secret" SQL function

This function lets users build SCRAM secrets from SQL
functions and provides the ability for the user to select
the password, salt, and number of iterations for the password
hashing algorithm. Currently this only supports the "sha256"
hash, but can be modified to support additional hashes in the
future.
---
 doc/src/sgml/func.sgml                   |  46 ++++++++++
 src/backend/catalog/system_functions.sql |   7 ++
 src/backend/libpq/auth-scram.c           |  31 +++++--
 src/backend/libpq/crypt.c                |   2 +-
 src/backend/utils/adt/Makefile           |   1 +
 src/backend/utils/adt/authfuncs.c        | 109 +++++++++++++++++++++++
 src/backend/utils/adt/meson.build        |   1 +
 src/include/catalog/pg_proc.dat          |   4 +
 src/include/libpq/scram.h                |   4 +-
 src/test/regress/expected/scram.out      |  99 ++++++++++++++++++++
 src/test/regress/parallel_schedule       |   2 +-
 src/test/regress/sql/scram.sql           |  62 +++++++++++++
 12 files changed, 356 insertions(+), 12 deletions(-)
 create mode 100644 src/backend/utils/adt/authfuncs.c
 create mode 100644 src/test/regress/expected/scram.out
 create mode 100644 src/test/regress/sql/scram.sql

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b8dac9ef46..68f11e953a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -3513,6 +3513,52 @@ repeat('Pg', 4) <returnvalue>PgPgPgPg</returnvalue>
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>scram_build_secret</primary>
+        </indexterm>
+        <function>scram_build_secret</function> ( 
<parameter>hash_type</parameter> <type>text</type>
+        , <parameter>password</parameter> <type>text</type>
+        [, <parameter>salt</parameter> <type>bytea</type>
+        [, <parameter>iterations</parameter> <type>integer</type> ] ])
+        <returnvalue>text</returnvalue>
+       </para>
+       <para>
+        Using the values provided in <parameter>hash type</parameter> and
+        <parameter>password</parameter>, builds a SCRAM secret equilvaent to
+        what is stored in
+        <link 
linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.<structfield>rolpassword</structfield>
+        and used with <link 
linkend="sasl-scram-sha-256"><literal>scram-sha-256</literal></link>
+        authentication. If not provided or set to <literal>NULL</literal>,
+        <parameter>salt</parameter> is randomly generated and
+        <parameter>iterations</parameter> defaults to <literal>4096</literal>.
+        Currently <parameter>hash type</parameter> only supports
+        <literal>sha256</literal>.
+       </para>
+       <para>
+        <literal>SELECT scram_build_secret('sha256', 'secret password', 
decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'));</literal>
+        <returnvalue></returnvalue>
+<programlisting>
+  
SCRAM-SHA-256$4096:MTIzNDU2Nzg5MGFiY2RlZg==$D5BmucT796UQKargx2k3fdqjDYR7cH/L0viKKhGo6kA=:M33+iHFOESP8C3DKLDb2k5QAhkNVWEbp/YUIFd2CxN4=
+</programlisting>
+       </para>
+       <para>
+        <literal>SELECT scram_build_secret('sha256', 'secret password', 
'\xabba5432');</literal>
+        <returnvalue></returnvalue>
+<programlisting>
+  
SCRAM-SHA-256$4096:q7pUMg==$05Nb9QHwHkMA0CRcYaEfwtgZ+3kStIefz8fLMjTEtio=:P126h1ycyP938E69yxktEfhoAILbiwL/UMsMk3Efb6o=
+</programlisting>
+       </para>
+       <para>
+        <literal>SELECT scram_build_secret('sha256', 'secret password', 
decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'), 10000);</literal>
+        <returnvalue></returnvalue>
+<programlisting>
+  
SCRAM-SHA-256$10000:MTIzNDU2Nzg5MGFiY2RlZg==$9NkDu1TFpx3L30zMgHUqjRNSq3GRZRrdWU4TuGOnT3Q=:svuIH9L6HH8loyKWguT64XXoOLCrr4FkVViPd2JVR4M=
+</programlisting>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql 
b/src/backend/catalog/system_functions.sql
index 83ca893444..5a7444df84 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -627,6 +627,13 @@ CREATE OR REPLACE FUNCTION
  STABLE PARALLEL SAFE
  AS 'sql_localtimestamp';
 
+-- defaults for building a SCRAM secret
+CREATE OR REPLACE FUNCTION
+  scram_build_secret(text, text, bytea DEFAULT NULL, int DEFAULT NULL)
+RETURNS text
+LANGUAGE INTERNAL
+VOLATILE AS 'scram_build_secret_str';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 4441e0d774..44efffd044 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -468,10 +468,15 @@ scram_exchange(void *opaq, const char *input, int 
inputlen,
 /*
  * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
  *
+ * "salt_str" can be NULL. If it is, this function will generate a random salt.
+ *
+ * If "iterations" is 0 or less, this function will set it to the default 
value.
+ *
  * The result is palloc'd, so caller is responsible for freeing it.
  */
 char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, char *salt_str, int 
salt_str_len,
+                                                int iterations, 
pg_cryptohash_type hash_type)
 {
        char       *prep_password;
        pg_saslprep_rc rc;
@@ -488,15 +493,23 @@ pg_be_scram_build_secret(const char *password)
        if (rc == SASLPREP_SUCCESS)
                password = (const char *) prep_password;
 
-       /* Generate random salt */
-       if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
-               ereport(ERROR,
-                               (errcode(ERRCODE_INTERNAL_ERROR),
-                                errmsg("could not generate random salt")));
+       /* If salt_str is NULL, generate random salt */
+       if (salt_str == NULL)
+       {
+               if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INTERNAL_ERROR),
+                                        errmsg("could not generate random 
salt")));
+               salt_str = saltbuf;
+               salt_str_len = SCRAM_DEFAULT_SALT_LEN;
+       }
+
+       if (iterations <= 0)
+               iterations = SCRAM_DEFAULT_ITERATIONS;
 
-       result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
-                                                               saltbuf, 
SCRAM_DEFAULT_SALT_LEN,
-                                                               
SCRAM_DEFAULT_ITERATIONS, password,
+       result = scram_build_secret(hash_type, SCRAM_SHA_256_KEY_LEN,
+                                                               salt_str, 
salt_str_len,
+                                                               iterations, 
password,
                                                                &errstr);
 
        if (prep_password)
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index ef496a0bea..edf94444b6 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -140,7 +140,7 @@ encrypt_password(PasswordType target_type, const char *role,
                        return encrypted_password;
 
                case PASSWORD_TYPE_SCRAM_SHA_256:
-                       return pg_be_scram_build_secret(password);
+                       return pg_be_scram_build_secret(password, NULL, -1, 0, 
PG_SHA256);
 
                case PASSWORD_TYPE_PLAINTEXT:
                        elog(ERROR, "cannot encrypt password with 'plaintext'");
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 0de0bbb1b8..7ddb186f96 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -22,6 +22,7 @@ OBJS = \
        arraysubs.o \
        arrayutils.o \
        ascii.o \
+       authfuncs.o \
        bool.o \
        cash.o \
        char.o \
diff --git a/src/backend/utils/adt/authfuncs.c 
b/src/backend/utils/adt/authfuncs.c
new file mode 100644
index 0000000000..43631abbeb
--- /dev/null
+++ b/src/backend/utils/adt/authfuncs.c
@@ -0,0 +1,109 @@
+/*-------------------------------------------------------------------------
+ *
+ * authfuncs.c
+ *       Functions that assist with authentication management
+ *
+ * Portions Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/utils/adt/authfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "libpq/scram.h"
+#include "utils/builtins.h"
+
+#define SCRAM_BUILD_SECRET_HASH_STR_LEN        6
+
+void           parse_cryptohash_type(const char *hash_type_str,
+                                                                 
pg_cryptohash_type *hash_type);
+
+/*
+ * Build a SCRAM secret that can be used for SCRAM-SHA-256 authentication.
+ *
+ * This function can take four arguments:
+ *
+ * - hash_type_str: the type of hash to use when building the SCRAM secret.
+ *              Currently only "sha256" is supported.
+ * - password: a plaintext password. This argument is required. If none of the
+ *             other arguments is set, the function short circuits to use a
+ *             SCRAM secret generation function that relies on defaults.
+ * - salt_str_enc: a base64 encoded salt. If this is not provided, a salt using
+ *                 the defaults is generated.
+ * - iterations: the number of iterations to hash the password. If set to 0
+ *               or less, the default number of iterations is used.
+ */
+Datum
+scram_build_secret_str(PG_FUNCTION_ARGS)
+{
+       const char *hash_type_str;
+       pg_cryptohash_type hash_type;
+       const char *password;
+       char       *salt_str = NULL;
+       int                     salt_str_len = -1;
+       int                     iterations = 0;
+       char       *secret;
+
+       if (PG_ARGISNULL(0))
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("hash type must not be null")));
+       }
+
+       hash_type_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
+       parse_cryptohash_type(hash_type_str, &hash_type);
+
+       if (PG_ARGISNULL(1))
+       {
+               ereport(ERROR,
+                               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                                errmsg("password must not be null")));
+       }
+
+       password = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+       if (!PG_ARGISNULL(2))
+       {
+               salt_str = text_to_cstring((text *) PG_GETARG_BYTEA_PP(2));
+               salt_str_len = strlen(salt_str);
+       }
+
+       if (!PG_ARGISNULL(3))
+               iterations = PG_GETARG_INT32(3);
+
+       secret = pg_be_scram_build_secret(password, salt_str, salt_str_len,
+                                                                         
iterations, hash_type);
+
+       Assert(secret != NULL);
+
+       /*
+        * convert the SCRAM secret to text which matches the type for
+        * pg_authid.rolpassword
+        */
+       PG_RETURN_TEXT_P(cstring_to_text(secret));
+}
+
+/*
+ * If "hash_type_str" is a valid cryptohash type that can be used for SCRAM
+ * authetnication, set the value to "hash_type". Otherwise, return an
+ * unsupported error.
+ */
+void
+parse_cryptohash_type(const char *hash_type_str, pg_cryptohash_type *hash_type)
+{
+       pg_cryptohash_type result_type;
+
+       if (pg_strncasecmp(hash_type_str, "sha256", 
SCRAM_BUILD_SECRET_HASH_STR_LEN) == 0)
+               result_type = PG_SHA256;
+       else
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("hash not supported: \"%s\"", 
hash_type_str),
+                                errmsg("supported hashes are \"sha256\"")));
+
+       *hash_type = result_type;
+}
diff --git a/src/backend/utils/adt/meson.build 
b/src/backend/utils/adt/meson.build
index 8515cd9365..52363a2f26 100644
--- a/src/backend/utils/adt/meson.build
+++ b/src/backend/utils/adt/meson.build
@@ -11,6 +11,7 @@ backend_sources += files(
   'arraysubs.c',
   'arrayutils.c',
   'ascii.c',
+  'authfuncs.c',
   'bool.c',
   'cash.c',
   'char.c',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 86eb8e8c58..b55eac911c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7583,6 +7583,10 @@
 { oid => '3422', descr => 'SHA-512 hash',
   proname => 'sha512', proleakproof => 't', prorettype => 'bytea',
   proargtypes => 'bytea', prosrc => 'sha512_bytea' },
+{ oid => '8557', descr => 'Build a SCRAM secret',
+  proname => 'scram_build_secret', prorettype => 'text',
+  proisstrict => 'f', proargtypes => 'text text bytea int4',
+  prosrc => 'scram_build_secret_str' },
 
 # crosstype operations for date vs. timestamp and timestamptz
 { oid => '2338',
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index b275e1e87e..400f551778 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -22,7 +22,9 @@
 extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
 
 /* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, char *salt_str,
+                                                                         int 
salt_str_len, int iterations,
+                                                                         
pg_cryptohash_type hash_type);
 extern bool parse_scram_secret(const char *secret,
                                                           int *iterations,
                                                           pg_cryptohash_type 
*hash_type,
diff --git a/src/test/regress/expected/scram.out 
b/src/test/regress/expected/scram.out
new file mode 100644
index 0000000000..0f3c5467b1
--- /dev/null
+++ b/src/test/regress/expected/scram.out
@@ -0,0 +1,99 @@
+-- Test building SCRAM functions
+-- test all nulls
+-- fail
+SELECT scram_build_secret(NULL, NULL);
+ERROR:  hash type must not be null
+SELECT scram_build_secret('sha256', NULL);
+ERROR:  password must not be null
+SELECT scram_build_secret('sha256', NULL, NULL);
+ERROR:  password must not be null
+SELECT scram_build_secret('sha256', NULL, NULL, NULL);
+ERROR:  password must not be null
+-- test unsupported hashes
+-- fail
+SELECT scram_build_secret('sha384', 'password');
+ERROR:  supported hashes are "sha256"
+SELECT scram_build_secret('sha512', 'password');
+ERROR:  supported hashes are "sha256"
+-- generated a SCRAM secret from a plaintext password
+SELECT regexp_replace(
+  scram_build_secret('sha256', 'secret password'),
+    
'(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)',
+    '\1$\2:<salt>$<storedkey>:<serverkey>') AS scram_secret;
+                   scram_secret                    
+---------------------------------------------------
+ SCRAM-SHA-256$4096:<salt>$<storedkey>:<serverkey>
+(1 row)
+
+-- test building a SCRAM secret with a predefined salt with a valid base64
+-- encoded string
+SELECT scram_build_secret('sha256', 'secret password',
+    decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'));
+                                                          scram_build_secret   
                                                        
+---------------------------------------------------------------------------------------------------------------------------------------
+ 
SCRAM-SHA-256$4096:MTIzNDU2Nzg5MGFiY2RlZg==$D5BmucT796UQKargx2k3fdqjDYR7cH/L0viKKhGo6kA=:M33+iHFOESP8C3DKLDb2k5QAhkNVWEbp/YUIFd2CxN4=
+(1 row)
+
+-- test building a SCRAM secret with a predefined salt that is not a valid
+-- base64 string
+-- fail
+SELECT scram_build_secret('sha256', 'secret password',
+  decode('abc', 'base64'));
+ERROR:  invalid base64 end sequence
+HINT:  Input data is missing padding, is truncated, or is otherwise corrupted.
+-- test building a SCRAM secret with a salt that looks like a string but is
+-- cast to a bytea
+SELECT scram_build_secret('sha256', 'secret password', 'abc');
+                                                scram_build_secret             
                                    
+-------------------------------------------------------------------------------------------------------------------
+ 
SCRAM-SHA-256$4096:YWJj$L27WlKwqjMDY5ZNsyaxGSMii2mhmoUB7xONbxjykmw4=:u1ofGUXUqTbMwfiH+ANWDCpwEjk3j1Xrocy3va/jaCU=
+(1 row)
+
+-- test building a SCRAM secret with a bytea salt using the hex format
+SELECT scram_build_secret('sha256', 'secret password', '\xabba5432');
+                                                  scram_build_secret           
                                        
+-----------------------------------------------------------------------------------------------------------------------
+ 
SCRAM-SHA-256$4096:q7pUMg==$05Nb9QHwHkMA0CRcYaEfwtgZ+3kStIefz8fLMjTEtio=:P126h1ycyP938E69yxktEfhoAILbiwL/UMsMk3Efb6o=
+(1 row)
+
+-- test building a SCRAM secret with a valid salt and a different set of
+-- iterations
+SELECT scram_build_secret('sha256', 'secret password',
+  decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'), 10000);
+                                                           scram_build_secret  
                                                         
+----------------------------------------------------------------------------------------------------------------------------------------
+ 
SCRAM-SHA-256$10000:MTIzNDU2Nzg5MGFiY2RlZg==$9NkDu1TFpx3L30zMgHUqjRNSq3GRZRrdWU4TuGOnT3Q=:svuIH9L6HH8loyKWguT64XXoOLCrr4FkVViPd2JVR4M=
+(1 row)
+
+-- test what happens when the salt is a NULL value.
+-- this should build a SCRAM secret using a random salt.
+SELECT regexp_replace(
+  scram_build_secret('sha256', 'secret password', NULL, 10000),
+    
'(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]{24})\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)',
+    '\1$\2:<salt>$<storedkey>:<serverkey>') AS scram_secret;
+                    scram_secret                    
+----------------------------------------------------
+ SCRAM-SHA-256$10000:<salt>$<storedkey>:<serverkey>
+(1 row)
+
+-- test what happens when iterations is a null value. this should set 
iterations
+-- to be the default, currently 4096.
+SELECT
+  scram_build_secret('sha256', 'secret password',
+    decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'), NULL) ~
+  '^SCRAM-SHA-256\$4096' AS has_default_iterations;
+ has_default_iterations 
+------------------------
+ t
+(1 row)
+
+-- test SASLprep. This tests the case where a user supplies a non-ASCII space
+-- character.
+SELECT
+  scram_build_secret('sha256', U&'one\1680space', 
decode('h2y81+nUwWp5uIJc4PgyXA==', 'base64')) =
+  
'SCRAM-SHA-256$4096:h2y81+nUwWp5uIJc4PgyXA==$EiywEpO6rM3z3DGehubeoRpp8Orq0XuDUbdT9fQWwz8=:Wh7fq4C+bageihh3vTrkCr7YrlcDTG+JhfcFAuHn/6E=';
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule 
b/src/test/regress/parallel_schedule
index a930dfe48c..b526d610e2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -33,7 +33,7 @@ test: strings md5 numerology point lseg line box path polygon 
circle date time t
 # geometry depends on point, lseg, line, box, path, polygon, circle
 # horology depends on date, time, timetz, timestamp, timestamptz, interval
 # ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity 
comments expressions unicode xid mvcc
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity 
comments expressions unicode xid mvcc scram
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/sql/scram.sql b/src/test/regress/sql/scram.sql
new file mode 100644
index 0000000000..0ca3291969
--- /dev/null
+++ b/src/test/regress/sql/scram.sql
@@ -0,0 +1,62 @@
+-- Test building SCRAM functions
+
+-- test all nulls
+-- fail
+SELECT scram_build_secret(NULL, NULL);
+SELECT scram_build_secret('sha256', NULL);
+SELECT scram_build_secret('sha256', NULL, NULL);
+SELECT scram_build_secret('sha256', NULL, NULL, NULL);
+
+-- test unsupported hashes
+-- fail
+SELECT scram_build_secret('sha384', 'password');
+SELECT scram_build_secret('sha512', 'password');
+
+-- generated a SCRAM secret from a plaintext password
+SELECT regexp_replace(
+  scram_build_secret('sha256', 'secret password'),
+    
'(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)',
+    '\1$\2:<salt>$<storedkey>:<serverkey>') AS scram_secret;
+
+-- test building a SCRAM secret with a predefined salt with a valid base64
+-- encoded string
+SELECT scram_build_secret('sha256', 'secret password',
+    decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'));
+
+-- test building a SCRAM secret with a predefined salt that is not a valid
+-- base64 string
+-- fail
+SELECT scram_build_secret('sha256', 'secret password',
+  decode('abc', 'base64'));
+
+-- test building a SCRAM secret with a salt that looks like a string but is
+-- cast to a bytea
+SELECT scram_build_secret('sha256', 'secret password', 'abc');
+
+-- test building a SCRAM secret with a bytea salt using the hex format
+SELECT scram_build_secret('sha256', 'secret password', '\xabba5432');
+
+-- test building a SCRAM secret with a valid salt and a different set of
+-- iterations
+SELECT scram_build_secret('sha256', 'secret password',
+  decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'), 10000);
+
+-- test what happens when the salt is a NULL value.
+-- this should build a SCRAM secret using a random salt.
+SELECT regexp_replace(
+  scram_build_secret('sha256', 'secret password', NULL, 10000),
+    
'(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]{24})\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)',
+    '\1$\2:<salt>$<storedkey>:<serverkey>') AS scram_secret;
+
+-- test what happens when iterations is a null value. this should set 
iterations
+-- to be the default, currently 4096.
+SELECT
+  scram_build_secret('sha256', 'secret password',
+    decode('MTIzNDU2Nzg5MGFiY2RlZg==', 'base64'), NULL) ~
+  '^SCRAM-SHA-256\$4096' AS has_default_iterations;
+
+-- test SASLprep. This tests the case where a user supplies a non-ASCII space
+-- character.
+SELECT
+  scram_build_secret('sha256', U&'one\1680space', 
decode('h2y81+nUwWp5uIJc4PgyXA==', 'base64')) =
+  
'SCRAM-SHA-256$4096:h2y81+nUwWp5uIJc4PgyXA==$EiywEpO6rM3z3DGehubeoRpp8Orq0XuDUbdT9fQWwz8=:Wh7fq4C+bageihh3vTrkCr7YrlcDTG+JhfcFAuHn/6E=';
-- 
2.37.1 (Apple Git-137.1)

Attachment: OpenPGP_signature
Description: OpenPGP digital signature

Reply via email to