postgrest-docs icon indicating copy to clipboard operation
postgrest-docs copied to clipboard

Verify a password against a SCRAM-SHA-256 hash

Open fjf2002 opened this issue 3 years ago • 5 comments

Hello,

I once had postgrest running with "SQL user management" as described in https://postgrest.org/en/stable/auth.html#sql-user-management .

In the MD5 hashing days I even went one step further, not having a password hash in the described basic_auth.users table, but I used the MD5 password hash rolpassword from the pg_catalog.pg_authid table. I thought this is a nice idea, having the same password for plain database connections and the postgrest API.

Now MD5 is considered as too insecure, and SCRAM-SHA-256 is standard in the pg_catalog.pg_authid table. How can I acheive my goal with SCRAM?

This means in particular, how can I verify a plaintext password against a SCRAM-SHA-256 password hash stored in pg_catalog.pg_authid.rolpassword, using the pgcrypto extension?

fjf2002 avatar Aug 01 '22 05:08 fjf2002

Hey @fjf2002,

I thought this is a nice idea, having the same password for plain database connections and the postgrest API.

Ah, so you are using the one role for each webuser approach right.

This means in particular, how can I verify a plaintext password against a SCRAM-SHA-256 password hash stored in pg_catalog.pg_authid.rolpassword, using the pgcrypto extension?

Yeah, there's no builtin way in pgcrypto for doing that.

I was pointed out to this python script on IRC. It might be possible to wrap that into a plpython function to do the verifying there.

steve-chavez avatar Aug 03 '22 19:08 steve-chavez

@steve-chavez: Thank you very much for your investigation, even although this topic is not purely PostgREST-related. I managed to get it to work with pl/pgsql, using the pgcrypto extension and the PBKDF2 function from https://stackoverflow.com/questions/47162200/pbkdf2-function-in-postgresql .

My basic_auth.user_role function now looks like this (there are currently further minor deviations from your docs):

CREATE OR REPLACE FUNCTION basic_auth.user_role(username text, pass text)
 RETURNS name
 LANGUAGE plpgsql
AS $function$
begin
  return (
    select basic_auth."user".username from basic_auth."user"
    join pg_authid on rolname = basic_auth."user".username and rolpassword IS NOT NULL
    cross join lateral regexp_match(rolpassword, '^SCRAM-SHA-256\$(.*):(.*)\$(.*):(.*)$') as rm
    cross join lateral (SELECT rm[1]::integer AS iteration_count, decode(rm[2], 'base64') AS salt, decode(rm[3], 'base64') as stored_key, decode(rm[4], 'base64') as server_key, 32 AS digest_length) as stored_password_part
    cross join lateral (SELECT basic_auth.pbkdf2(salt, check_user_pass.pass, iteration_count, digest_length, 'sha256')) AS digest_key(digest_key)
    cross join lateral (SELECT ext_pgcrypto.digest(ext_pgcrypto.hmac('Client Key', digest_key, 'sha256'), 'sha256') as stored_key, ext_pgcrypto.hmac('Server Key', digest_key, 'sha256') as server_key) as check_password_part
    where basic_auth."user".username = check_user_pass.username
      and check_password_part.stored_key = stored_password_part.stored_key
      and check_password_part.server_key = stored_password_part.server_key
  );
end;
$function$;

If you are interested to add that into the docs, please let me know.

fjf2002 avatar Aug 04 '22 06:08 fjf2002

If you are interested to add that into the docs, please let me know.

@fjf2002 That would be great! Please go ahead.

steve-chavez avatar Aug 06 '22 01:08 steve-chavez

Gladly I will try that. Before that, some questions:

  • Shall I edit/replace parts of the chapter "Storing users and passwords", or present a new chapter, as an alternative to the other chapter?
  • If the latter shall happen: Should a chapter be added there or in the "how-to" section?
  • The whole section depends on the idea of the email address being used in the authentication and the JWT. My whole issue is re-using the user's database password for postgrest authentication, which somehow would sound saner in conjunction with the sql user name (instead of the mail address) being used in the JWT - what do you think?

fjf2002 avatar Aug 08 '22 07:08 fjf2002

Hmm, at first I was thinking we could have it as a tabbed snippet like we do for HTTP/CURL snippets on https://postgrest.org/en/stable/api.html.

But as you mention, since it differs from the "Storing users and passwords" I think it would be better as a new "how-to".

steve-chavez avatar Aug 08 '22 19:08 steve-chavez

Solved on https://github.com/PostgREST/postgrest-docs/pull/581

steve-chavez avatar Nov 16 '22 22:11 steve-chavez