WIP config refactor

This commit is contained in:
2023-05-15 22:41:21 -04:00
parent 0350f1dd80
commit 11de5948c8
4 changed files with 149 additions and 0 deletions

34
mail4one/config.py Normal file
View File

@ -0,0 +1,34 @@
import json
from jata import Jata, MutableDefault
class Match(Jata):
name: str
alias: list[str] = MutableDefault(lambda: [])
alias_regex: list[str] = MutableDefault(lambda: [])
class Rule(Jata):
match_name: str
negate: bool = False
stop_check: bool = False
class Mbox(Jata):
name: str
rules: list[str]
class User(Jata):
username: str
password_hash: str
mbox: str
class Config(Jata):
certfile: str
keyfile: str
mails_path: str
rules: list[Rule]
boxes: list[Mbox]
users: list[User]

40
mail4one/pwhash.py Normal file
View File

@ -0,0 +1,40 @@
import os
from hashlib import scrypt
from struct import pack, unpack
from base64 import b32encode, b32decode
# Links
# https://pkg.go.dev/golang.org/x/crypto/scrypt#Key
# https://crypto.stackexchange.com/a/35434
SCRYPT_N = 16384
SCRYPT_R = 8
SCRYPT_P = 1
VERSION = b'\x01'
SALT_LEN = 32
PACK_FMT = f"<c{SALT_LEN}s64slhh"
def gen_pwhash(password: str) -> str:
salt = os.urandom(SALT_LEN)
sh = scrypt(password.encode(), salt=salt, n=SCRYPT_N, r=SCRYPT_R, p=SCRYPT_P)
pack_bytes = pack(PACK_FMT, VERSION, salt, sh, SCRYPT_N, SCRYPT_R, SCRYPT_P)
return b32encode(pack_bytes).decode()
class PWInfo:
def __init__(self, salt, sh):
self.salt = salt
self.scrypt_hash = sh
def parse_hash(pwhash: str) -> PWInfo:
decoded = b32decode(pwhash.encode())
ver, salt, sh, n, r, p = unpack(PACK_FMT, decoded)
if not (ver, n, r, p, len(salt)) == (VERSION, SCRYPT_N, SCRYPT_R, SCRYPT_P, SALT_LEN):
raise Exception(f"Invalid hash: {ver=}, {n=}, {r=}, {p=}, f{len(salt)=} != {VERSION=}, {SCRYPT_N=}, {SCRYPT_R=}, {SCRYPT_P=}, {SALT_LEN=}")
return PWInfo(salt, sh)
def check_pass(password: str, pwinfo: PWInfo) -> bool:
# No need for constant time compare for hashes. See https://security.stackexchange.com/a/46215
return pwinfo.scrypt_hash == scrypt(password.encode(), salt=pwinfo.salt, n=SCRYPT_N, r=SCRYPT_R, p=SCRYPT_P)

22
mail4one/pwhash_test.py Normal file
View File

@ -0,0 +1,22 @@
from .pwhash import *
def check_pass_from_hash(password: str, pwhash: str) -> bool:
try:
pwinfo = parse_hash(pwhash)
except:
return False
return check_pass(password, pwinfo)
test_hash = "AFWMBONQ2XGHWBTKVECDBBJWYEMS4DFIXIJML4VP76JQT5VWVLALE3KVKFEBAGWG3DOY53DK3H2EACWOBHJFYAIHDA3OFDQN2UAXI5TLBFOW4O2GWXNBGQ5QFMOJ5Z27HGYNO73DS5WPX2INNE47EGI6Z5UAAQAAAAEAAAIA"
def main():
print(gen_pwhash("helloworld"))
print("------------")
print(check_pass_from_hash("hElloworld", test_hash))
print(check_pass_from_hash("helloworld", "foobar"))
print("------------")
print(check_pass_from_hash("helloworld", test_hash))
if __name__ == '__main__':
main()