From: Michael Paquier Date: Mon, 23 Mar 2026 23:29:23 +0000 (+0900) Subject: Make implementation of SASLprep compliant for ASCII characters X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3d10ece612f535be15a9cb7ca31620c80db6f0e9;p=thirdparty%2Fpostgresql.git Make implementation of SASLprep compliant for ASCII characters This commit makes our implementation of SASLprep() compliant with RFC 3454 (Stringprep) and RFC 4013 (SASLprep). Originally, as introduced in 60f11b87a234, the operation considered a password made of only ASCII characters as valid, performing an optimization for this case to skip the internal NFKC transformation. However, the RFCs listed above use a different definition, with the following characters being prohibited: - 0x00~0x1F (0~31), control characters. - 0x7F (127, DEL). In its SCRAM protocol, Postgres has the idea to apply a password as-is if SASLprep() is not a success, so this change is safe on backward-compatibility grounds: - A libpq client with the compliant SASLprep can connect to a server with a non-compliant SASLprep. - A libpq client with the non-compliant SASLprep can connect to a server with a compliant SASLprep. This commit removes the all-ASCII optimization used in pg_saslprep() and applies SASLprep even if a password is made only of ASCII characters, making the operation compatible with the RFC. All the in-core callers of pg_saslprep() do that: - pg_be_scram_build_secret() in auth-scram.c, when generating a SCRAM verifier for rolpassword in the backend. - scram_init() in fe-auth-scram.c, when starting the SASL exchange. - pg_fe_scram_build_secret() in fe-auth-scram.c, when generating a SCRAM verifier for the frontend with libpq, to generate it for a ALTER/CREATE ROLE command for example. The test module test_saslprep shows the difference this change is leading to. Author: Michael Paquier Reviewed-by: John Naylor Discussion: https://postgr.es/m/aaEJ-El2seZHeFcG@paquier.xyz --- diff --git a/src/common/saslprep.c b/src/common/saslprep.c index 2ad2cefc14f..38d50dd823c 100644 --- a/src/common/saslprep.c +++ b/src/common/saslprep.c @@ -1061,18 +1061,6 @@ pg_saslprep(const char *input, char **output) /* Ensure we return *output as NULL on failure */ *output = NULL; - /* - * Quick check if the input is pure ASCII. An ASCII string requires no - * further processing. - */ - if (pg_is_ascii(input)) - { - *output = STRDUP(input); - if (!(*output)) - goto oom; - return SASLPREP_SUCCESS; - } - /* * Convert the input from UTF-8 to an array of Unicode codepoints. * diff --git a/src/test/modules/test_saslprep/expected/test_saslprep.out b/src/test/modules/test_saslprep/expected/test_saslprep.out index f72dbffa0a1..92f93365343 100644 --- a/src/test/modules/test_saslprep/expected/test_saslprep.out +++ b/src/test/modules/test_saslprep/expected/test_saslprep.out @@ -19,38 +19,38 @@ SELECT FROM generate_series(0,127) AS a; dat | byt | saslprep ----------+------+------------------- - | \x00 | ("\\x",SUCCESS) - | \x01 | ("\\x01",SUCCESS) - | \x02 | ("\\x02",SUCCESS) - | \x03 | ("\\x03",SUCCESS) - | \x04 | ("\\x04",SUCCESS) - | \x05 | ("\\x05",SUCCESS) - | \x06 | ("\\x06",SUCCESS) - | \x07 | ("\\x07",SUCCESS) - | \x08 | ("\\x08",SUCCESS) - | \x09 | ("\\x09",SUCCESS) - | \x0a | ("\\x0a",SUCCESS) - | \x0b | ("\\x0b",SUCCESS) - | \x0c | ("\\x0c",SUCCESS) - | \x0d | ("\\x0d",SUCCESS) - | \x0e | ("\\x0e",SUCCESS) - | \x0f | ("\\x0f",SUCCESS) - | \x10 | ("\\x10",SUCCESS) - | \x11 | ("\\x11",SUCCESS) - | \x12 | ("\\x12",SUCCESS) - | \x13 | ("\\x13",SUCCESS) - | \x14 | ("\\x14",SUCCESS) - | \x15 | ("\\x15",SUCCESS) - | \x16 | ("\\x16",SUCCESS) - | \x17 | ("\\x17",SUCCESS) - | \x18 | ("\\x18",SUCCESS) - | \x19 | ("\\x19",SUCCESS) - | \x1a | ("\\x1a",SUCCESS) - | \x1b | ("\\x1b",SUCCESS) - | \x1c | ("\\x1c",SUCCESS) - | \x1d | ("\\x1d",SUCCESS) - | \x1e | ("\\x1e",SUCCESS) - | \x1f | ("\\x1f",SUCCESS) + | \x00 | (,PROHIBITED) + | \x01 | (,PROHIBITED) + | \x02 | (,PROHIBITED) + | \x03 | (,PROHIBITED) + | \x04 | (,PROHIBITED) + | \x05 | (,PROHIBITED) + | \x06 | (,PROHIBITED) + | \x07 | (,PROHIBITED) + | \x08 | (,PROHIBITED) + | \x09 | (,PROHIBITED) + | \x0a | (,PROHIBITED) + | \x0b | (,PROHIBITED) + | \x0c | (,PROHIBITED) + | \x0d | (,PROHIBITED) + | \x0e | (,PROHIBITED) + | \x0f | (,PROHIBITED) + | \x10 | (,PROHIBITED) + | \x11 | (,PROHIBITED) + | \x12 | (,PROHIBITED) + | \x13 | (,PROHIBITED) + | \x14 | (,PROHIBITED) + | \x15 | (,PROHIBITED) + | \x16 | (,PROHIBITED) + | \x17 | (,PROHIBITED) + | \x18 | (,PROHIBITED) + | \x19 | (,PROHIBITED) + | \x1a | (,PROHIBITED) + | \x1b | (,PROHIBITED) + | \x1c | (,PROHIBITED) + | \x1d | (,PROHIBITED) + | \x1e | (,PROHIBITED) + | \x1f | (,PROHIBITED) | \x20 | ("\\x20",SUCCESS) ! | \x21 | ("\\x21",SUCCESS) " | \x22 | ("\\x22",SUCCESS) @@ -146,7 +146,7 @@ SELECT | | \x7c | ("\\x7c",SUCCESS) } | \x7d | ("\\x7d",SUCCESS) ~ | \x7e | ("\\x7e",SUCCESS) - | \x7f | ("\\x7f",SUCCESS) + | \x7f | (,PROHIBITED) (128 rows) DROP EXTENSION test_saslprep; diff --git a/src/test/modules/test_saslprep/t/001_saslprep_ranges.pl b/src/test/modules/test_saslprep/t/001_saslprep_ranges.pl index 3ff0e3b10e6..4a7cb5aaa58 100644 --- a/src/test/modules/test_saslprep/t/001_saslprep_ranges.pl +++ b/src/test/modules/test_saslprep/t/001_saslprep_ranges.pl @@ -25,15 +25,12 @@ $node->safe_psql('postgres', 'CREATE EXTENSION test_saslprep;'); # Among all the valid UTF-8 codepoint ranges, our implementation of # SASLprep should never return an empty password if the operation is # considered a success. -# The only exception is currently the nul character, prohibited in -# input of CREATE/ALTER ROLE. my $result = $node->safe_psql( 'postgres', qq[SELECT * FROM test_saslprep_ranges() WHERE status = 'SUCCESS' AND res IN (NULL, '') ]); -is($result, 'U+0000|SUCCESS|\x00|\x', - "valid codepoints returning an empty password"); +is($result, '', "valid codepoints returning an empty password"); $node->stop; done_testing();