-
Notifications
You must be signed in to change notification settings - Fork 450
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
extend htpasswd_encryption options with sha256/512/autodetect
- Loading branch information
Showing
1 changed file
with
43 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
# Copyright © 2008 Pascal Halter | ||
# Copyright © 2008-2017 Guillaume Ayoub | ||
# Copyright © 2017-2019 Unrud <[email protected]> | ||
# Copyright © 2024 Peter Bieringer <[email protected]> | ||
# | ||
# This library is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
|
@@ -22,12 +23,12 @@ | |
Apache's htpasswd command (httpd.apache.org/docs/programs/htpasswd.html) | ||
manages a file for storing user credentials. It can encrypt passwords using | ||
different the methods BCRYPT or MD5-APR1 (a version of MD5 modified for | ||
Apache). MD5-APR1 provides medium security as of 2015. Only BCRYPT can be | ||
different the methods BCRYPT/SHA256/SHA512 or MD5-APR1 (a version of MD5 modified for | ||
Apache). MD5-APR1 provides medium security as of 2015. Only BCRYPT/SHA256/SHA512 can be | ||
considered secure by current standards. | ||
MD5-APR1-encrypted credentials can be written by all versions of htpasswd (it | ||
is the default, in fact), whereas BCRYPT requires htpasswd 2.4.x or newer. | ||
is the default, in fact), whereas BCRYPT/SHA256/SHA512 requires htpasswd 2.4.x or newer. | ||
The `is_authenticated(user, password)` function provided by this module | ||
verifies the user-given credentials by parsing the htpasswd credential file | ||
|
@@ -37,23 +38,23 @@ | |
The following htpasswd password encrpytion methods are supported by Radicale | ||
out-of-the-box: | ||
- plain-text (created by htpasswd -p...) -- INSECURE | ||
- MD5-APR1 (htpasswd -m...) -- htpasswd's default method | ||
- plain-text (created by htpasswd -p ...) -- INSECURE | ||
- MD5-APR1 (htpasswd -m ...) -- htpasswd's default method, INSECURE | ||
- SHA256 (htpasswd -2 ...) | ||
- SHA512 (htpasswd -5 ...) | ||
When bcrypt is installed: | ||
- BCRYPT (htpasswd -B...) -- Requires htpasswd 2.4.x | ||
- BCRYPT (htpasswd -B ...) -- Requires htpasswd 2.4.x | ||
""" | ||
|
||
import functools | ||
import hmac | ||
from typing import Any | ||
|
||
from passlib.hash import apr_md5_crypt | ||
from passlib.hash import apr_md5_crypt, sha256_crypt, sha512_crypt | ||
|
||
from radicale import auth, config | ||
from radicale import auth, config, logger | ||
|
||
|
||
class Auth(auth.BaseAuth): | ||
|
@@ -67,18 +68,24 @@ def __init__(self, configuration: config.Configuration) -> None: | |
self._encoding = configuration.get("encoding", "stock") | ||
encryption: str = configuration.get("auth", "htpasswd_encryption") | ||
|
||
logger.info("auth password encryption: %s", encryption) | ||
|
||
if encryption == "plain": | ||
self._verify = self._plain | ||
elif encryption == "md5": | ||
self._verify = self._md5apr1 | ||
elif encryption == "bcrypt": | ||
elif encryption == "bcrypt" or encryption == "autodetect": | ||
try: | ||
import bcrypt | ||
except ImportError as e: | ||
raise RuntimeError( | ||
"The htpasswd encryption method 'bcrypt' requires " | ||
"The htpasswd encryption method 'bcrypt' or 'auto' requires " | ||
"the bcrypt module.") from e | ||
self._verify = functools.partial(self._bcrypt, bcrypt) | ||
if encryption == "bcrypt": | ||
self._verify = functools.partial(self._bcrypt, bcrypt) | ||
else: | ||
self._verify = self._autodetect | ||
self._verify_bcrypt = functools.partial(self._bcrypt, bcrypt) | ||
else: | ||
raise RuntimeError("The htpasswd encryption method %r is not " | ||
"supported." % encryption) | ||
|
@@ -93,6 +100,29 @@ def _bcrypt(self, bcrypt: Any, hash_value: str, password: str) -> bool: | |
def _md5apr1(self, hash_value: str, password: str) -> bool: | ||
return apr_md5_crypt.verify(password, hash_value.strip()) | ||
|
||
def _sha256(self, hash_value: str, password: str) -> bool: | ||
return sha256_crypt.verify(password, hash_value.strip()) | ||
|
||
def _sha512(self, hash_value: str, password: str) -> bool: | ||
return sha512_crypt.verify(password, hash_value.strip()) | ||
|
||
def _autodetect(self, hash_value: str, password: str) -> bool: | ||
if hash_value.startswith("$apr1$", 0, 6) and len(hash_value) == 37: | ||
# MD5-APR1 | ||
return self._md5apr1(hash_value, password) | ||
elif hash_value.startswith("$2y$", 0, 4) and len(hash_value) == 60: | ||
# BCRYPT | ||
return self._verify_bcrypt(hash_value, password) | ||
elif hash_value.startswith("$5$", 0, 3) and len(hash_value) == 63: | ||
# SHA-256 | ||
return self._sha256(hash_value, password) | ||
elif hash_value.startswith("$6$", 0, 3) and len(hash_value) == 106: | ||
# SHA-512 | ||
return self._sha512(hash_value, password) | ||
else: | ||
# assumed plaintext | ||
return self._plain(hash_value, password) | ||
|
||
def login(self, login: str, password: str) -> str: | ||
"""Validate credentials. | ||
|