From 91e16cd265a04539f6eb8af5179a2b46262cbad1 Mon Sep 17 00:00:00 2001 From: Jane Fan Date: Thu, 6 Sep 2018 15:55:01 +0000 Subject: [PATCH 1/2] Add the registration unit test --- tests/test_registration.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_registration.py diff --git a/tests/test_registration.py b/tests/test_registration.py new file mode 100644 index 0000000..92f43d1 --- /dev/null +++ b/tests/test_registration.py @@ -0,0 +1,48 @@ +from aiosip import auth + + +AUTH = { + 'auth_with_qop': 'Digest realm="asterisk",' + 'nonce="1535646722/5d9e709c8f2ccd74601946bfbd77b032",' + 'algorithm=md5,' + 'qop="auth",' + 'nc="00000001",' + 'response="7aafeb20b391dfb0af52c6d39bbef36e",' + 'cnonce="0a4f113b"', + 'auth_without_qop': 'Digest realm="asterisk",' + 'nonce="1535646722/5d9e709c8f2ccd74601946bfbd77b032",' + 'algorithm=md5,' + 'response="05d233c1f0c0ef3d2fa203512363ce64"', + 'method': 'REGISTER', + 'uri': 'sip:5000@10.10.26.12', + 'username': '5000', + 'password': 'sangoma', + 'response_with_qop': '7aafeb20b391dfb0af52c6d39bbef36e', + 'response_without_qop': '05d233c1f0c0ef3d2fa203512363ce64' +} + + +def test_with_qop(): + authenticate = auth.Auth.from_authenticate_header( + AUTH['auth_with_qop'], + AUTH['method'] + ) + assert authenticate.validate_authorization( + authenticate, + password=AUTH['password'], + username=AUTH['username'], + uri=AUTH['uri'] + ) + + +def test_without_qop(): + authenticate = auth.Auth.from_authenticate_header( + AUTH['auth_without_qop'], + AUTH['method'] + ) + assert authenticate.validate_authorization( + authenticate, + password=AUTH['password'], + username=AUTH['username'], + uri=AUTH['uri'] + ) From a72237d7d8370ed3000998c17ab01ed8a1735fe5 Mon Sep 17 00:00:00 2001 From: Simon Gomizelj Date: Tue, 16 Oct 2018 15:24:02 +0200 Subject: [PATCH 2/2] Tighten authentication logic Use enumerations for specifying authentication support. This will quickly throw a value error should we hit an unknown authentication scheme instead of falling back on default behaviours that might not be correct. Always throw a key error if part of the authentication header is missing, instead of defaulting to None or empty strings. This should also help quickly catch errors if we send out invalid authentication headers. --- aiosip/auth.py | 66 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/aiosip/auth.py b/aiosip/auth.py index 2ef1fda..10c1a48 100644 --- a/aiosip/auth.py +++ b/aiosip/auth.py @@ -1,7 +1,7 @@ -import logging - -from hashlib import md5 from collections import MutableMapping +from enum import Enum +from hashlib import md5 +import logging from . import utils @@ -9,6 +9,17 @@ LOG = logging.getLogger(__name__) +class Algorithm(Enum): + MD5 = 'md5' + MD5Sess = 'md5-sess' + + +class Directive(Enum): + Unspecified = '' + Auth = 'auth' + AuthInt = 'auth-int' + + def md5digest(*args): return md5(':'.join(args).encode()).hexdigest() @@ -78,35 +89,38 @@ def __parse_digest(cls, header): return params def _calculate_response(self, password, payload, username=None, uri=None, cnonce=None, nonce_count=None): + if self.mode != 'Digest': + raise ValueError('Authentication method not supported') + + algorithm = Algorithm(self.get('algorithm', 'md5').lower()) + qop = Directive(self.get('qop', '').lower()) + if username is None: username = self['username'] if uri is None: uri = self['uri'] - if cnonce is None: - cnonce = self.get('cnonce') - if nonce_count is None: - nonce_count = self.get('nc') - if self.mode == 'Digest': - algorithm = self.get('algorithm', 'md5') - if algorithm == 'md5-sess': - ha1 = md5digest(md5digest(username, self['realm'], password), self['nonce'], cnonce) - else: - ha1 = md5digest(username, self['realm'], password) - - qop = self.get('qop', '').lower() - if qop == 'auth-int': - ha2 = md5digest(self['method'], self['uri'], md5digest(payload)) - else: - ha2 = md5digest(self['method'], uri) - - if qop in ('auth', 'auth-int'): - response = md5digest(ha1, self['nonce'], nonce_count, cnonce, self['qop'], ha2) - else: - response = md5digest(ha1, self['nonce'], ha2) - return response + ha1 = md5digest(username, self['realm'], password) + if algorithm is Algorithm.MD5Sess: + ha1 = md5digest(ha1, self['nonce'], cnonce or self['cnonce']) + + if qop is Directive.AuthInt: + ha2 = md5digest(self['method'], uri, md5digest(payload)) else: - raise ValueError('Authentication method not supported') + ha2 = md5digest(self['method'], uri) + + # If there's no quality of prootection specified, we can return early, + # our computation is much simpler + if qop is Directive.Unspecified: + return md5digest(ha1, self['nonce'], ha2) + + return md5digest( + ha1, + self['nonce'], + nonce_count or self['nc'], + cnonce or self['cnonce'], + self['qop'], + ha2) # MutableMapping API def __eq__(self, other):