diff --git a/config b/config index e68699e..a0b04f9 100644 --- a/config +++ b/config @@ -78,6 +78,9 @@ # bcrypt and md5 require the passlib library to be installed. #htpasswd_encryption = bcrypt +# Incorrect authentication delay (seconds) +#delay = 1 + [rights] diff --git a/radicale/__init__.py b/radicale/__init__.py index 8365769..c40e81b 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -34,11 +34,13 @@ import itertools import os import posixpath import pprint +import random import socket import socketserver import ssl import sys import threading +import time import traceback import wsgiref.simple_server import zlib @@ -383,6 +385,13 @@ class Application: is_authenticated = False else: is_authenticated = self.Auth.is_authenticated(user, password) + if not is_authenticated: + # Random delay to avoid timing oracles and bruteforce attacks + delay = self.configuration.getfloat("auth", "delay") + if delay > 0: + random_delay = delay * (0.5 + random.random()) + self.logger.debug("Sleeping %.3f seconds", random_delay) + time.sleep(random_delay) # Create principal collection if user and is_authenticated: diff --git a/radicale/auth.py b/radicale/auth.py index 1fe3014..0d5a925 100644 --- a/radicale/auth.py +++ b/radicale/auth.py @@ -58,8 +58,6 @@ import functools import hashlib import hmac import os -import random -import time from importlib import import_module @@ -198,6 +196,4 @@ class Auth(BaseAuth): login, hash_value = line.split(":") if login == user and self.verify(hash_value, password): return True - # Random timer to avoid timing oracles and simple bruteforce attacks - time.sleep(1 + random.random()) return False diff --git a/radicale/config.py b/radicale/config.py index e37ee08..d7b4586 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -93,7 +93,10 @@ INITIAL_CONFIG = OrderedDict([ "help": "htpasswd filename"}), ("htpasswd_encryption", { "value": "bcrypt", - "help": "htpasswd encryption method"})])), + "help": "htpasswd encryption method"}), + ("delay", { + "value": "1", + "help": "incorrect authentication delay"})])), ("rights", OrderedDict([ ("type", { "value": "owner_only", diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index 014c885..5b80e86 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -47,6 +47,8 @@ class TestBaseAuthRequests(BaseTest): self.configuration.set("storage", "filesystem_fsync", "False") # Required on Windows, doesn't matter on Unix self.configuration.set("storage", "close_lock_file", "True") + # Set incorrect authentication delay to a very low value + self.configuration.set("auth", "delay", "0.002") def teardown(self): shutil.rmtree(self.colpath)