async def test_negative_timestamp(self): signing_key = TEST_PRIVATE_KEY timestamp = int(time.time()) signature = sign_request(signing_key, "GET", "/", timestamp, None) resp = await self.fetch_signed("/", method="GET", address=TEST_ADDRESS, timestamp=timestamp, signature=signature) self.assertResponseCodeEqual(resp, 204) # try replay simply changing the timestamp to negative timestamp = -timestamp resp = await self.fetch_signed("/", method="GET", address=TEST_ADDRESS, timestamp=timestamp, signature=signature) self.assertResponseCodeEqual(resp, 400) # sign with negative timestamp signature = sign_request(signing_key, "GET", "/", timestamp, None) resp = await self.fetch_signed("/", method="GET", address=TEST_ADDRESS, timestamp=timestamp, signature=signature) self.assertResponseCodeEqual(resp, 400)
def fetch_signed(self, path, *, signing_key=None, signature=None, timestamp=None, address=None, **kwargs): if not isinstance(path, str) or not path.startswith("/"): # for simplicity's sake, don't accept HTTPRequest objects or external urls in the tests raise Exception("first argument must be path string starting with a / (e.g. /v1/tx)") # find out if there's a path prefix added by get_url prefix = "/{}".format(self.get_url(path).split('/', 3)[-1]).split(path)[0] headers = kwargs.setdefault('headers', tornado.httputil.HTTPHeaders()) if 'body' in kwargs: body = kwargs.pop('body') if isinstance(body, dict): headers['Content-Type'] = "application/json" body = tornado.escape.json_encode(body).encode('utf-8') elif isinstance(body, str): # try and find the charset to use to encode this if 'Content-Type' in headers: idx = headers['Content-Type'].find('charset=') if idx >= 0: charset = headers['Content-Type'][idx + 8:] idx = charset.find(';') if idx >= 0: charset = charset[:idx] else: charset = 'utf-8' else: charset = 'utf-8' # encode to a byte string body = body.encode(charset) elif not isinstance(body, bytes): raise Exception("Unable to handle bodys of type '{}'".format(type(body))) else: body = None method = kwargs.setdefault('method', 'GET').upper() if signing_key is None and (address is None or signature is None): raise Exception("signing_key is required unless address and signature is given") if timestamp is None and signature is not None: raise Exception("timestamp is required if signature is given explicitly") if address is None: address = private_key_to_address(signing_key) if timestamp is None: timestamp = int(time.time()) if signature is None: signature = sign_request(signing_key, method, "{}{}".format(prefix, path), timestamp, body) headers[TOSHI_ID_ADDRESS_HEADER] = address headers[TOSHI_SIGNATURE_HEADER] = signature headers[TOSHI_TIMESTAMP_HEADER] = str(timestamp) # because tornado doesn't like POSTs with body set to None if body is None and method == "POST": body = b"" return self.fetch(path, body=body, **kwargs)
async def test_urlencoded_data(self): # generate random 2048 byte "file" filedata = os.urandom(2048) content_type, body = encode_multipart_formdata( [], [("file", "test.bin", filedata)]) headers = { "Content-Type": content_type, 'content-length': str(len(body)) } timestamp = int(time.time()) signature = sign_request(TEST_PRIVATE_KEY, "POST", "/", timestamp, body) resp = await self.fetch_signed("/", method="POST", body=body, headers=headers, signature=signature, timestamp=timestamp, address=TEST_ADDRESS) self.assertResponseCodeEqual(resp, 204)
async def test_query_argument_fetch(self): signing_key = TEST_PRIVATE_KEY address = TEST_ADDRESS timestamp = int(time.time()) signature = sign_request(signing_key, "GET", "/", timestamp, None) resp = await self.fetch("/?{}".format( generate_query_args(signature=signature, address=address, timestamp=timestamp))) self.assertResponseCodeEqual(resp, 204)
def __call__(self, r): method = r.method path = "/{}".format(r.url.split('/', 3)[-1]) body = r.body timestamp = int(time.time()) signature = sign_request(self.signing_key, method, path, timestamp, body) r.headers[TOSHI_ID_ADDRESS_HEADER] = self.address r.headers[TOSHI_SIGNATURE_HEADER] = signature r.headers[TOSHI_TIMESTAMP_HEADER] = str(timestamp) return r
async def connect(self): # find out if there's a path prefix added by get_url path = "/{}".format(self.url.split('/', 3)[-1]) headers = {'User-Agent': 'Dgas-Test-Websocket-Client'} if self.signing_key: address = private_key_to_address(self.signing_key) timestamp = int(time.time()) signature = sign_request(self.signing_key, "GET", path, timestamp, None) headers.update({ TOSHI_ID_ADDRESS_HEADER: address, TOSHI_SIGNATURE_HEADER: signature, TOSHI_TIMESTAMP_HEADER: str(timestamp) }) request = tornado.httpclient.HTTPRequest(self.url, headers=headers) self.con = await tornado.websocket.websocket_connect(request) return self.con
async def test_invalid_signature(self): body = {"registration_id": "1234567890"} timestamp = int(time.time()) signature = sign_request(FAUCET_PRIVATE_KEY, "POST", "/", timestamp, json_encode(body).encode('utf-8')) resp = await self.fetch_signed("/", method="POST", body=body, address=TEST_ADDRESS, timestamp=timestamp, signature=signature) self.assertEqual(resp.code, 400, resp.body) # make sure query string also fails resp = await self.fetch("/?{}".format( generate_query_args(signature=signature, address=TEST_ADDRESS, timestamp=timestamp))) self.assertEqual(resp.code, 400, resp.body)