3
3
# Copyright © 2008 Pascal Halter
4
4
# Copyright © 2008-2017 Guillaume Ayoub
5
5
# Copyright © 2017-2019 Unrud <unrud@outlook.com>
6
+ # Copyright © 2024 Peter Bieringer <pb@bieringer.de>
6
7
#
7
8
# This library is free software: you can redistribute it and/or modify
8
9
# it under the terms of the GNU General Public License as published by
22
23
23
24
Apache's htpasswd command (httpd.apache.org/docs/programs/htpasswd.html)
24
25
manages a file for storing user credentials. It can encrypt passwords using
25
- different the methods BCRYPT or MD5-APR1 (a version of MD5 modified for
26
- Apache). MD5-APR1 provides medium security as of 2015. Only BCRYPT can be
26
+ different the methods BCRYPT/SHA256/SHA512 or MD5-APR1 (a version of MD5 modified for
27
+ Apache). MD5-APR1 provides medium security as of 2015. Only BCRYPT/SHA256/SHA512 can be
27
28
considered secure by current standards.
28
29
29
30
MD5-APR1-encrypted credentials can be written by all versions of htpasswd (it
30
- is the default, in fact), whereas BCRYPT requires htpasswd 2.4.x or newer.
31
+ is the default, in fact), whereas BCRYPT/SHA256/SHA512 requires htpasswd 2.4.x or newer.
31
32
32
33
The `is_authenticated(user, password)` function provided by this module
33
34
verifies the user-given credentials by parsing the htpasswd credential file
37
38
38
39
The following htpasswd password encrpytion methods are supported by Radicale
39
40
out-of-the-box:
40
-
41
- - plain-text (created by htpasswd -p...) -- INSECURE
42
- - MD5-APR1 (htpasswd -m...) -- htpasswd's default method
41
+ - plain-text (created by htpasswd -p ...) -- INSECURE
42
+ - MD5-APR1 (htpasswd -m ...) -- htpasswd's default method, INSECURE
43
+ - SHA256 (htpasswd -2 ...)
44
+ - SHA512 (htpasswd -5 ...)
43
45
44
46
When bcrypt is installed:
45
-
46
- - BCRYPT (htpasswd -B...) -- Requires htpasswd 2.4.x
47
+ - BCRYPT (htpasswd -B ...) -- Requires htpasswd 2.4.x
47
48
48
49
"""
49
50
50
51
import functools
51
52
import hmac
52
53
from typing import Any
53
54
54
- from passlib .hash import apr_md5_crypt
55
+ from passlib .hash import apr_md5_crypt , sha256_crypt , sha512_crypt
55
56
56
- from radicale import auth , config
57
+ from radicale import auth , config , logger
57
58
58
59
59
60
class Auth (auth .BaseAuth ):
@@ -67,18 +68,24 @@ def __init__(self, configuration: config.Configuration) -> None:
67
68
self ._encoding = configuration .get ("encoding" , "stock" )
68
69
encryption : str = configuration .get ("auth" , "htpasswd_encryption" )
69
70
71
+ logger .info ("auth password encryption: %s" , encryption )
72
+
70
73
if encryption == "plain" :
71
74
self ._verify = self ._plain
72
75
elif encryption == "md5" :
73
76
self ._verify = self ._md5apr1
74
- elif encryption == "bcrypt" :
77
+ elif encryption == "bcrypt" or encryption == "autodetect" :
75
78
try :
76
79
import bcrypt
77
80
except ImportError as e :
78
81
raise RuntimeError (
79
- "The htpasswd encryption method 'bcrypt' requires "
82
+ "The htpasswd encryption method 'bcrypt' or 'auto' requires "
80
83
"the bcrypt module." ) from e
81
- self ._verify = functools .partial (self ._bcrypt , bcrypt )
84
+ if encryption == "bcrypt" :
85
+ self ._verify = functools .partial (self ._bcrypt , bcrypt )
86
+ else :
87
+ self ._verify = self ._autodetect
88
+ self ._verify_bcrypt = functools .partial (self ._bcrypt , bcrypt )
82
89
else :
83
90
raise RuntimeError ("The htpasswd encryption method %r is not "
84
91
"supported." % encryption )
@@ -93,6 +100,29 @@ def _bcrypt(self, bcrypt: Any, hash_value: str, password: str) -> bool:
93
100
def _md5apr1 (self , hash_value : str , password : str ) -> bool :
94
101
return apr_md5_crypt .verify (password , hash_value .strip ())
95
102
103
+ def _sha256 (self , hash_value : str , password : str ) -> bool :
104
+ return sha256_crypt .verify (password , hash_value .strip ())
105
+
106
+ def _sha512 (self , hash_value : str , password : str ) -> bool :
107
+ return sha512_crypt .verify (password , hash_value .strip ())
108
+
109
+ def _autodetect (self , hash_value : str , password : str ) -> bool :
110
+ if hash_value .startswith ("$apr1$" , 0 , 6 ) and len (hash_value ) == 37 :
111
+ # MD5-APR1
112
+ return self ._md5apr1 (hash_value , password )
113
+ elif hash_value .startswith ("$2y$" , 0 , 4 ) and len (hash_value ) == 60 :
114
+ # BCRYPT
115
+ return self ._verify_bcrypt (hash_value , password )
116
+ elif hash_value .startswith ("$5$" , 0 , 3 ) and len (hash_value ) == 63 :
117
+ # SHA-256
118
+ return self ._sha256 (hash_value , password )
119
+ elif hash_value .startswith ("$6$" , 0 , 3 ) and len (hash_value ) == 106 :
120
+ # SHA-512
121
+ return self ._sha512 (hash_value , password )
122
+ else :
123
+ # assumed plaintext
124
+ return self ._plain (hash_value , password )
125
+
96
126
def login (self , login : str , password : str ) -> str :
97
127
"""Validate credentials.
98
128
0 commit comments