diff --git a/src/calibre/srv/auth.py b/src/calibre/srv/auth.py index ad4f649b4d..c3e8d6f1bc 100644 --- a/src/calibre/srv/auth.py +++ b/src/calibre/srv/auth.py @@ -9,6 +9,7 @@ __copyright__ = '2015, Kovid Goyal ' import binascii, os, random, struct, base64, httplib from hashlib import md5, sha256 from itertools import permutations +from threading import Lock from calibre.srv.errors import HTTPAuthRequired, HTTPSimpleResponse, InvalidCredentials from calibre.srv.http_request import parse_uri @@ -16,6 +17,7 @@ from calibre.srv.utils import parse_http_dict, encode_path from calibre.utils.monotonic import monotonic MAX_AGE_SECONDS = 3600 +nonce_counter, nonce_counter_lock = 0, Lock() def as_bytestring(x): if not isinstance(x, bytes): @@ -34,12 +36,18 @@ def base64_decode(s): def synthesize_nonce(key_order, realm, secret, timestamp=None): ''' Create a nonce. Can be used for either digest or cookie based auth. - The nonce is of the form timestamp:hash with has being a hash of the + The nonce is of the form timestamp:hash with hash being a hash of the timestamp, server secret and realm. This allows the timestamp to be validated and stale nonce's to be rejected. ''' if timestamp is None: - timestamp = binascii.hexlify(struct.pack(b'!d', float(monotonic()))) + global nonce_counter + with nonce_counter_lock: + nonce_counter += 1 + # The resolution of monotonic() on windows is very low (10s of + # milliseconds) so to ensure nonce values are not re-used, we have a + # global counter + timestamp = binascii.hexlify(struct.pack(b'!dQ', float(monotonic()), nonce_counter)) h = sha256_hex(key_order.format(timestamp, realm, secret)) nonce = ':'.join((timestamp, h)) return nonce @@ -51,7 +59,7 @@ def validate_nonce(key_order, nonce, realm, secret): def is_nonce_stale(nonce, max_age_seconds=MAX_AGE_SECONDS): try: - timestamp = struct.unpack(b'!d', binascii.unhexlify(as_bytestring(nonce.partition(':')[0])))[0] + timestamp = struct.unpack(b'!dQ', binascii.unhexlify(as_bytestring(nonce.partition(':')[0])))[0] return timestamp + max_age_seconds < monotonic() except Exception: pass diff --git a/src/calibre/srv/tests/loop.py b/src/calibre/srv/tests/loop.py index 9be3622008..820d0a4964 100644 --- a/src/calibre/srv/tests/loop.py +++ b/src/calibre/srv/tests/loop.py @@ -217,8 +217,8 @@ class LoopTest(BaseTest): b = monotonic() self.assertGreaterEqual(b, a) a = monotonic() - time.sleep(0.01) + time.sleep(0.1) b = monotonic() self.assertGreaterEqual(b, a) - self.assertGreaterEqual(b - a, 0.01) - self.assertLessEqual(b - a, 0.02) + self.assertGreaterEqual(b - a, 0.09) + self.assertLessEqual(b - a, 0.2) diff --git a/src/calibre/utils/monotonic.c b/src/calibre/utils/monotonic.c index 20f03a5cd9..00fb1dbc73 100644 --- a/src/calibre/utils/monotonic.c +++ b/src/calibre/utils/monotonic.c @@ -26,17 +26,20 @@ #ifdef _MSC_VER #include -static LARGE_INTEGER frequency = {0}, ts = {0}; - -/* static PyObject* monotonic(PyObject *self, PyObject *args) { */ -/* return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS); */ -/* } */ static PyObject* monotonic(PyObject *self, PyObject *args) { - if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; } - return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart); + return PyFloat_FromDouble(((double)GetTickCount64())/SEC_TO_MS); } +/* QueryPerformanceCounter() is wildly inaccurate, so we use the more stable + * the lower resolution GetTickCount64() (this is what python 3.x uses) + * static LARGE_INTEGER frequency = {0}, ts = {0}; + * static PyObject* monotonic(PyObject *self, PyObject *args) { + * if (!QueryPerformanceCounter(&ts)) { PyErr_SetFromWindowsErr(0); return NULL; } + * return PyFloat_FromDouble(((double)ts.QuadPart)/frequency.QuadPart); + * } + */ + #elif defined(__APPLE__) #include static mach_timebase_info_data_t timebase = {0}; @@ -72,7 +75,7 @@ PyMODINIT_FUNC initmonotonic(void) { PyObject *m; #ifdef _MSC_VER - if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } + /* if(!QueryPerformanceFrequency(&frequency)) { PyErr_SetFromWindowsErr(0); return; } */ #endif #ifdef __APPLE__ mach_timebase_info(&timebase);