def _calculate_mac(self, credentials, artifacts): """Checks inputs and calculates MAC.""" if 'key' not in credentials or 'algorithm' not in credentials: raise MissingCredentials mac = hcrypto.calculate_mac('header', credentials, artifacts) return mac
def header(self, artifacts, options=None): """Generate a Server-Authorization header for a given response. :param artifacts: A dict received from authenticate(). Contains the following keys 'mac', 'hash', and 'ext'. :param options: A dict with the following structure: - ext: 'application-specific' Application specific data sent via the ext attribute. - payload: '{"some":"payload"}', UTF-8 encoded string for body hash generation (ignored if hash provided). - contentType: 'application/json', Payload content-type (ignored if hash provided) - hash: 'U4MKKSmiVxk37JCCrAVIjV=' Pre-calculated payload hash } """ if options is None: options = {} if not artifacts or False == isinstance(artifacts, dict) or False == isinstance(options, dict): return "" h_artifacts = copy.copy(artifacts) del h_artifacts["mac"] h_artifacts["hash"] = options.get("hash", None) if "ext" in options: h_artifacts["ext"] = options["ext"] credentials = self.credentials_fn(h_artifacts["id"]) if not credentials or "key" not in credentials or "algorithm" not in credentials: return "" if "hash" not in h_artifacts or h_artifacts["hash"] is None or len(h_artifacts["hash"]) == 0: if "payload" in options: h_artifacts["hash"] = hcrypto.calculate_payload_hash( options["payload"], credentials["algorithm"], options["contentType"] ) mac = hcrypto.calculate_mac("response", credentials, h_artifacts) header = 'Hawk mac="' + mac + '"' if "hash" in h_artifacts: header += ', hash="' + h_artifacts["hash"] + '"' if "ext" in h_artifacts and h_artifacts["ext"] is not None and len(h_artifacts["ext"]) > 0: h_ext = util.check_header_attribute(h_artifacts["ext"]).replace("\\", "\\\\").replace("\n", "\\n") header += ', ext="' + h_ext + '"' return header
def _calculate_mac(self, credentials, artifacts): """Checks inputs and calculates MAC.""" if "key" not in credentials or "algorithm" not in credentials: raise MissingCredentials mac = hcrypto.calculate_mac("header", credentials, artifacts) return mac
def header(self, artifacts, options=None): """Generate a Server-Authorization header for a given response. credentials: {}, // Object received from authenticate() artifacts: {} // Object received from authenticate(); 'mac', 'hash', and 'ext' - ignored options: { ext: 'application-specific', // Application specific data sent via the ext attribute payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided) contentType: 'application/json', // Payload content-type (ignored if hash provided) hash: 'U4MKKSmiVxk37JCCrAVIjV=' // Pre-calculated payload hash } """ if options is None: options = {} if not artifacts or False == isinstance(artifacts, dict) or \ False == isinstance(options, dict): return '' h_artifacts = copy.copy(artifacts) del h_artifacts['mac'] h_artifacts['hash'] = options.get('hash', None) if 'ext' in options: h_artifacts['ext'] = options['ext'] credentials = self.credentials_fn(h_artifacts['id']) if not credentials or 'key' not in credentials or \ 'algorithm' not in credentials: return '' if 'hash' not in h_artifacts or h_artifacts['hash'] is None or \ len(h_artifacts['hash']) == 0: if 'payload' in options: h_artifacts['hash'] = hcrypto.calculate_payload_hash( options['payload'], credentials['algorithm'], options['contentType']) mac = hcrypto.calculate_mac('response', credentials, h_artifacts) header = 'Hawk mac="' + mac + '"' if 'hash' in h_artifacts: header += ', hash="' + h_artifacts['hash'] + '"' if 'ext' in h_artifacts and h_artifacts['ext'] is not None and \ len(h_artifacts['ext']) > 0: h_ext = util.check_header_attribute( h_artifacts['ext']).replace('\\', '\\\\').replace('\n', '\\n') header += ', ext="' + h_ext + '"' return header
def authenticate_bewit(self, req, credentials, options): """Authenticate bewit one time requests. Compatibility Note: HAWK exposes this as hawk.uri.authenticate req is a dict with the keys: url method Optional options: 'hostHeaderName', 'localtimeOffsetMsec' """ if not valid_bewit_args(req, options): return False now = time.time() + int(options['localtime_offset_msec']) url = urlparse(req['url']) qs = parse_qs(url.query) if not 'bewit' in qs or len(qs['bewit']) != 1 or \ len(qs['bewit'][0]) == 0: print "No bewit query string parameter" return False bewit = hcrypto.explode_bewit(qs['bewit'][0]) original_url = normalize_url_without_bewit(req['url'], qs['bewit'][0]) if bewit['exp'] < now: raise BewitExpired options['ts'] = bewit['exp'] artifacts = { 'ts': bewit['exp'], 'nonce': '', 'method': 'GET', 'resource': original_url, 'host': req['host'], 'port': req['port'], 'ext': bewit['ext'] } mac = hcrypto.calculate_mac('bewit', credentials, artifacts, True) # TODO mitigate timing attack if mac != bewit['mac']: print "bewit " + mac + " didn't match " + bewit['mac'] raise BadRequest return True
def authenticate_bewit(self, options): """Authenticate bewit one time requests. Compatibility Note: HAWK exposes this as hawk.uri.authenticate :param options: A dict which may contain the 'hostHeaderName' and 'localtimeOffsetMsec' keys. """ if not valid_bewit_args(self.req, options): return False now = time.time() + int(options['localtime_offset_msec']) url = urlparse(self.req['url']) qs = parse_qs(url.query) if not 'bewit' in qs or len(qs['bewit']) != 1 or \ len(qs['bewit'][0]) == 0: log.info("No bewit query string parameter") return False bewit = hcrypto.explode_bewit(qs['bewit'][0]) original_url = normalize_url_without_bewit(self.req['url'], qs['bewit'][0]) if bewit['exp'] < now: raise BewitExpired options['ts'] = bewit['exp'] artifacts = { 'ts': bewit['exp'], 'nonce': '', 'method': 'GET', 'resource': original_url, 'host': self.req['host'], 'port': self.req['port'], 'ext': bewit['ext'] } credentials = self.credentials_fn(bewit['id']) mac = hcrypto.calculate_mac('bewit', credentials, artifacts, True) if not util.compare(mac, bewit['mac']): log.info("bewit " + mac + " didn't match " + bewit['mac']) raise BadRequest return True
def authenticate_bewit(self, options): """Authenticate bewit one time requests. Compatibility Note: HAWK exposes this as hawk.uri.authenticate :param options: A dict which may contain the 'hostHeaderName' and 'localtimeOffsetMsec' keys. """ if not valid_bewit_args(self.req, options): return False now = time.time() + int(options["localtime_offset_msec"]) url = urlparse(self.req["url"]) qs = parse_qs(url.query) if not "bewit" in qs or len(qs["bewit"]) != 1 or len(qs["bewit"][0]) == 0: log.info("No bewit query string parameter") return False bewit = hcrypto.explode_bewit(qs["bewit"][0]) original_url = normalize_url_without_bewit(self.req["url"], qs["bewit"][0]) if bewit["exp"] < now: raise BewitExpired options["ts"] = bewit["exp"] artifacts = { "ts": bewit["exp"], "nonce": "", "method": "GET", "resource": original_url, "host": self.req["host"], "port": self.req["port"], "ext": bewit["ext"], } credentials = self.credentials_fn(bewit["id"]) mac = hcrypto.calculate_mac("bewit", credentials, artifacts, True) if not util.compare(mac, bewit["mac"]): log.info("bewit " + mac + " didn't match " + bewit["mac"]) raise BadRequest return True
def header(url, method, options=None): """ :param uri: 'http://example.com/resource?a=b' :param method: HTTP verb ('GET', 'POST', etc) :param options: Required Options: credentials (id, key, algorithm) Optional: ext: Application specific data (string) timestamp: A pre-calculated timestamp nonce: '2334f34f': A pre-generated nonce localtimeOffsetMsec: Time offset to sync with server time (ignored if timestamp provided) (Example 400) payload: UTF-8 encoded string for body hash generation (ignored if hash provided) (Example '{"some":"payload"}') contentType: Payload content-type (ignored if hash provided) (Example 'application/json') hash: Pre-calculated payload hash (Example 'U4MKKSmiVxk37JCCrAVIjV=') app: Oz application id ('24s23423f34dx') dlg: Oz delegated-by application id - '234sz34tww3sd' """ result = {'field': '', 'artifacts': {}} if url is None or len(url) == 0: log.info("Bad URL skipping") return result if method is None or len(method) == 0: log.info("Bad method skipping") return result if not isinstance(options, dict): log.info("Bad options skipping") return result if 'credentials' not in options: log.info("Bad credentials skipping") return result cred = options['credentials'] if 'id' not in cred or 'key' not in cred or 'algorithm' not in cred: log.info("Bad credentail elements skipping") return result timestamp = math.floor(time.time()) if 'timestamp' in options: offset = 0 if 'localtimeOffsetMsec' in options: offset = int(options['localtimeOffsetMsec']) timestamp = math.floor(options['timestamp'] + offset) if 'nonce' not in options: options['nonce'] = hcrypto.random_string(6) url_parts = util.parse_normalized_url(url) # TODO use None or '' for these optional artifacts? if 'hash' not in options: options['hash'] = None if 'ext' not in options: options['ext'] = None if 'app' not in options: options['app'] = None if 'dlg' not in options: options['dlg'] = None resource = url_parts['resource'] log.debug('parsed URL parts: %s' % pprint.pformat(url_parts)) artifacts = { 'ts': int(timestamp), 'nonce': options['nonce'], 'method': method, 'resource': resource, 'host': url_parts['hostname'], 'port': url_parts['port'], 'hash': options['hash'], 'ext': options['ext'], 'app': options['app'], 'dlg': options['dlg'] } result['artifacts'] = artifacts if artifacts['hash'] is None and 'payload' in options: if 'contentType' not in options: options['contentType'] = 'text/plain' log.debug('about to hash payload: %s' % options['payload']) log.debug('algorithm=%s, contentType=%s' % (cred['algorithm'], options['contentType'])) artifacts['hash'] = hcrypto.calculate_payload_hash( options['payload'], cred['algorithm'], options['contentType']) log.debug('artifacts=%s' % pprint.pformat(artifacts)) mac = hcrypto.calculate_mac('header', cred, artifacts) _header = ''.join([ 'Hawk id="', cred['id'], '"', ', ts="', str(artifacts['ts']), '"', ', nonce="', artifacts['nonce'], '"', ]) if len(artifacts['hash']) > 0: _header += ', hash="' + artifacts['hash'] + '"' if artifacts['ext'] is not None and len(artifacts['ext']) > 0: util.check_header_attribute(artifacts['ext']) h_ext = artifacts['ext'].replace('\\', '\\\\').replace('\n', '\\n') _header += ', ext="' + h_ext + '"' _header += ', mac="' + mac + '"' if artifacts['app'] is not None: _header += ', app="' + artifacts['app'] + '"' if artifacts['dlg'] is not None: _header += ', dlg="' + artifacts['dlg'] + '"' result['field'] = _header return result
def authenticate(response, credentials, artifacts, options=None): """Validate server response. :param response: dictionary with server response :param artifacts: object recieved from header().artifacts :param options: { payload: optional payload received required: specifies if a Server-Authorization header is required. Defaults to 'false' } """ if not isinstance(response, dict) or 'headers' not in response: return False if 'content-type' not in response['headers']: log.warn("response lacked content-type") response['headers']['content-type'] = 'text/plain' if options is None: options = {} if 'required' not in options: options['required'] = False if 'www-authenticate' in response['headers']: www_auth_attrs = util.parse_authorization_header( response['headers']['www-authenticate'], ['ts', 'tsm', 'error']) if 'ts' in www_auth_attrs: ts_mac = hcrypto.calculate_ts_mac(www_auth_attrs['ts'], credentials) if not util.compare(ts_mac, www_auth_attrs['ts']): log.info(ts_mac + " didn't match " + www_auth_attrs['ts']) return False if 'server-authorization' not in response['headers'] and \ False == options['required']: return True if 'server-authorization' not in response['headers']: log.info("Unable to verify, no server-authorization header") return False s_auth_attrs = util.parse_authorization_header( response['headers']['server-authorization'], ['mac', 'ext', 'hash']) if 'ext' in s_auth_attrs: artifacts['ext'] = s_auth_attrs['ext'] else: artifacts['ext'] = '' artifacts['hash'] = s_auth_attrs['hash'] mac = hcrypto.calculate_mac('response', credentials, artifacts) if not util.compare(mac, s_auth_attrs['mac']): log.info("server mac mismatch " + mac + " != " + s_auth_attrs['mac']) return False if 'payload' not in options: return True if 'hash' not in s_auth_attrs: return False content_type = response['headers']['content-type'] p_mac = hcrypto.calculate_payload_hash(options['payload'], credentials['algorithm'], content_type) if not util.compare(p_mac, s_auth_attrs['hash']): log.info("p_mac " + p_mac + " != " + s_auth_attrs['hash']) return util.compare(p_mac, s_auth_attrs['hash'])
def header(url, method, options=None): """ :param uri: 'http://example.com/resource?a=b' :param method: HTTP verb ('GET', 'POST', etc) :param options: Required Options: credentials (id, key, algorithm) Optional: ext: Application specific data (string) timestamp: A pre-calculated timestamp nonce: '2334f34f': A pre-generated nonce localtimeOffsetMsec: Time offset to sync with server time (ignored if timestamp provided) (Example 400) payload: UTF-8 encoded string for body hash generation (ignored if hash provided) (Example '{"some":"payload"}') contentType: Payload content-type (ignored if hash provided) (Example 'application/json') hash: Pre-calculated payload hash (Example 'U4MKKSmiVxk37JCCrAVIjV=') app: Oz application id ('24s23423f34dx') dlg: Oz delegated-by application id - '234sz34tww3sd' """ result = {'field': '', 'artifacts': {}} if url is None or len(url) == 0: print "Bad URL skipping" return result if method is None or len(method) == 0: print "Bad method skipping" return result if not isinstance(options, dict): print "Bad options skipping" return result if 'credentials' not in options: print "Bad credentials skipping" return result cred = options['credentials'] if 'id' not in cred or 'key' not in cred or 'algorithm' not in cred: print "Bad credentail elements skipping" return result timestamp = math.floor(time.time()) if 'timestamp' in options: offset = 0 if 'localtimeOffsetMsec' in options: offset = int(options['localtimeOffsetMsec']) timestamp = math.floor(options['timestamp'] + offset) if 'nonce' not in options: options['nonce'] = hcrypto.random_string(6) url_parts = parse_normalized_url(url) # TODO use None or '' for these optional artifacts? if 'hash' not in options: options['hash'] = None if 'ext' not in options: options['ext'] = None if 'app' not in options: options['app'] = None if 'dlg' not in options: options['dlg'] = None resource = url_parts['path'] if len(url_parts['query']) > 0: resource += '?' + url_parts['query'] artifacts = { 'ts': int(timestamp), 'nonce': options['nonce'], 'method': method, 'resource': resource, 'host': url_parts['hostname'], 'port': url_parts['port'], 'hash': options['hash'], 'ext': options['ext'], 'app': options['app'], 'dlg': options['dlg'] } result['artifacts'] = artifacts if artifacts['hash'] is None and 'payload' in options: if 'contentType' not in options: options['contentType'] = 'text/plain' artifacts['hash'] = hcrypto.calculate_payload_hash( options['payload'], cred['algorithm'], options['contentType']) mac = hcrypto.calculate_mac('header', cred, artifacts) _header = ''.join([ 'Hawk id="', cred['id'], '"', ', ts="', str(artifacts['ts']), '"', ', nonce="', artifacts['nonce'], '"', ]) if len(artifacts['hash']) > 0: _header += ', hash="' + artifacts['hash'] + '"' if artifacts['ext'] is not None and len(artifacts['ext']) > 0: util.check_header_attribute(artifacts['ext']) h_ext = artifacts['ext'].replace('\\', '\\\\').replace('\n', '\\n') _header += ', ext="' + h_ext + '"' _header += ', mac="' + mac + '"' if artifacts['app'] is not None: _header += ', app="' + artifacts['app'] + '"' if artifacts['dlg'] is not None: _header += ', dlg="' + artifacts['dlg'] + '"' result['field'] = _header return result
def authenticate(response, credentials, artifacts, options=None): """Validate server response. :param response: dictionary with server response :param artifacts: object recieved from header().artifacts :param options: { payload: optional payload received required: specifies if a Server-Authorization header is required. Defaults to 'false' } """ if not isinstance(response, dict) or 'headers' not in response: return False if 'content-type' not in response['headers']: print "WARNING response lacked content-type" response['headers']['content-type'] = 'text/plain' if options is None: options = {} if 'required' not in options: options['required'] = False if 'www-authenticate' in response['headers']: www_auth_attrs = util.parse_authorization_header( response['headers']['www-authenticate'], ['ts', 'tsm', 'error']) if 'ts' in www_auth_attrs: ts_mac = hcrypto.calculate_ts_mac(www_auth_attrs['ts'], credentials) if not util.compare(ts_mac, www_auth_attrs['ts']): print ts_mac + " didn't match " + www_auth_attrs['ts'] return False if 'server-authorization' not in response['headers'] and \ False == options['required']: return True if 'server-authorization' not in response['headers']: print "Unable to verify, no server-authorization header" return False s_auth_attrs = util.parse_authorization_header( response['headers']['server-authorization'], ['mac', 'ext', 'hash']) if 'ext' in s_auth_attrs: artifacts['ext'] = s_auth_attrs['ext'] else: artifacts['ext'] = '' artifacts['hash'] = s_auth_attrs['hash'] mac = hcrypto.calculate_mac('response', credentials, artifacts) if not util.compare(mac, s_auth_attrs['mac']): print "server mac mismatch " + mac + " != " + s_auth_attrs['mac'] return False if 'payload' in options: return True if 'hash' not in s_auth_attrs: return False content_type = response['headers']['content-type'] p_mac = hcrypto.calculate_payload_hash(options['payload'], credentials['algorithm'], content_type) if not util.compare(p_mac, s_auth_attrs['hash']): print "p_mac " + p_mac + " != " + s_auth_attrs['hash'] return util.compare(p_mac, s_auth_attrs['hash'])
def header(self, artifacts, options=None): """Generate a Server-Authorization header for a given response. :param artifacts: A dict received from authenticate(). Contains the following keys 'mac', 'hash', and 'ext'. :param options: A dict with the following structure: - ext: 'application-specific' Application specific data sent via the ext attribute. - payload: '{"some":"payload"}', UTF-8 encoded string for body hash generation (ignored if hash provided). - contentType: 'application/json', Payload content-type (ignored if hash provided) - hash: 'U4MKKSmiVxk37JCCrAVIjV=' Pre-calculated payload hash } """ if options is None: options = {} if not artifacts or False == isinstance(artifacts, dict) or \ False == isinstance(options, dict): return '' h_artifacts = copy.copy(artifacts) del h_artifacts['mac'] h_artifacts['hash'] = options.get('hash', None) if 'ext' in options: h_artifacts['ext'] = options['ext'] credentials = self.credentials_fn(h_artifacts['id']) if not credentials or 'key' not in credentials or \ 'algorithm' not in credentials: return '' if 'hash' not in h_artifacts or h_artifacts['hash'] is None or \ len(h_artifacts['hash']) == 0: if 'payload' in options: h_artifacts['hash'] = hcrypto.calculate_payload_hash( options['payload'], credentials['algorithm'], options['contentType']) mac = hcrypto.calculate_mac('response', credentials, h_artifacts) header = 'Hawk mac="' + mac + '"' if 'hash' in h_artifacts: header += ', hash="' + h_artifacts['hash'] + '"' if 'ext' in h_artifacts and h_artifacts['ext'] is not None and \ len(h_artifacts['ext']) > 0: h_ext = util.check_header_attribute(h_artifacts['ext']).replace( '\\', '\\\\').replace('\n', '\\n') header += ', ext="' + h_ext + '"' return header