def test_payload_expired(): key = bitjws.PrivateKey() print(bitjws.privkey_to_wif(key.private_key)) # expire_after must be greater than 0 or None. with pytest.raises(bitjws.jws.InvalidPayload): bitjws.sign_serialize(key, expire_after=0) ser = bitjws.sign_serialize(key, expire_after=0.01) time.sleep(0.02) with pytest.raises(bitjws.jws.InvalidPayload): # payload expired. bitjws.validate_deserialize(ser) # But the signature can still be verified and the msg decoded # if expiration checks are disabled. h, p = bitjws.validate_deserialize(ser, check_expiration=False) assert h and p # Check with multisig. ser = bitjws.multisig_sign_serialize([key], expire_after=0.01) time.sleep(0.02) with pytest.raises(bitjws.jws.InvalidPayload): # payload expired. bitjws.multisig_validate_deserialize(ser)
def test_dummy_algo(): key = bitjws.PrivateKey() # Setup a new algorithm for signing/verifying. # The sign function always take two parameters and return bytes. # The verify function always take three parameters and return a boolean. # The pubkey_serialize function take a pubkey object and return text. newalgo = bitjws.Algorithm( 'dummy', sign=lambda privkey, signdata: signdata.encode('utf8'), verify=lambda sig, data, pubkey: True, pubkey_serialize=lambda x: "hello") bitjws.ALGORITHM_AVAILABLE[newalgo.name] = newalgo ser = bitjws.sign_serialize(key) with pytest.raises(bitjws.InvalidMessage): # Algorithm mismatch. bitjws.validate_deserialize(ser, algorithm_name='dummy') ser = bitjws.sign_serialize(key, algorithm_name='dummy') with pytest.raises(bitjws.InvalidMessage): # Algorithm mismatch. bitjws.validate_deserialize(ser) result = bitjws.validate_deserialize(ser, algorithm_name='dummy') assert result[0] and result[1]
def test_get_coins(self): msg_data = {'method': 'GET', 'pubhash': pubhash, 'permissions': ['authenticate'], 'headers': None, 'model': 'coin'} bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data, iat=time.time()) self.client.send(bitjws_msg) mdata = {'metal': 'testinium', 'mint': 'testStream.py'} msg_data = {'method': 'RESPONSE', 'metal': mdata['metal'], 'mint': mdata['mint'], 'pubhash': pubhash, 'headers': {}, 'permissions': ['authenticate'], 'model': 'coin'} bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data, iat=time.time()) pika_channel.basic_publish(body=bitjws_msg, exchange=pikaconfig.EXCHANGE['exchange'], routing_key='') try: msg_response = client_wait_for(self.client, 'RESPONSE', 'coin') except Exception, e: self.fail("Unexpected error: %s" % e)
def test_get_coins_bad_sign(self): privkey2 = bitjws.PrivateKey() msg_data = {'method': 'GET', 'data': '', 'pubhash': pubhash, 'headers': None, 'permissions': ['authenticate'], 'model': 'coin'} # same data but different privkey bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data) bitjws_msg2 = bitjws.sign_serialize(privkey2, data=msg_data) # switches signature signature2 = bitjws_msg2.split('.')[2] bad_signed_msg = '.'.join(bitjws_msg.split('.')[0:2]) + '.' + signature2 self.client.send(bad_signed_msg) try: msg_response = client_wait_for(self.client, 'error') except Exception, e: self.fail("Unexpected error: %s" % e)
def test_connect_publish_coin(self): ctm = CommonTestMixin() ctm.setup() client = ctm.client # publish coin message which should not be received by client mdata = {'metal': 'testinium', 'mint': 'testStream.py'} msg_data = {'method': 'RESPONSE', 'metal': mdata['metal'], 'mint': mdata['mint'], 'pubhash': pubhash, 'headers': {}, 'model': 'coin', 'permissions': ['authenticate']} bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data) pika_channel.basic_publish(body=bitjws_msg, exchange=pikaconfig.EXCHANGE['exchange'], routing_key='') # publish pings to fill queue ping_msg_data = {'method': 'ping'} bitjws_ping_msg = bitjws.sign_serialize(privkey, data=ping_msg_data) for i in range(5): client.send(bitjws_ping_msg) try: data = client_wait_for(client, 'RESPONSE', 'coin', 5) except Exception, e: self.fail("Unexpected error: %s" % e)
def test_dummy_algo(): key = bitjws.PrivateKey() # Setup a new algorithm for signing/verifying. # The sign function always take two parameters and return bytes. # The verify function always take three parameters and return a boolean. # The pubkey_serialize function take a pubkey object and return text. newalgo = bitjws.Algorithm('dummy', sign=lambda privkey, signdata: signdata.encode('utf8'), verify=lambda sig, data, pubkey: True, pubkey_serialize=lambda x: "hello") bitjws.ALGORITHM_AVAILABLE[newalgo.name] = newalgo ser = bitjws.sign_serialize(key) with pytest.raises(bitjws.InvalidMessage): # Algorithm mismatch. bitjws.validate_deserialize(ser, algorithm_name='dummy') ser = bitjws.sign_serialize(key, algorithm_name='dummy') with pytest.raises(bitjws.InvalidMessage): # Algorithm mismatch. bitjws.validate_deserialize(ser) result = bitjws.validate_deserialize(ser, algorithm_name='dummy') assert result[0] and result[1]
def test_bad_nonce(): app = server.app.test_client() privkey = bitjws.PrivateKey() echo_msg = {'hello': 'server'} data = bitjws.sign_serialize(privkey, echo=echo_msg, iat=time.time(), requrl="/echo") data2 = bitjws.sign_serialize(privkey, echo=echo_msg, iat=time.time()-2, requrl="/echo") echo = app.post('/echo', data=data, headers={'Content-Type': 'application/jose'}) echo2 = app.post('/echo', data=data2, headers={'Content-Type': 'application/jose'}) assert echo2.status_code == 401
def test_bad_request(): app = server.app.test_client() privkey = bitjws.PrivateKey() echo_msg = {'hello': 'server'} data = bitjws.sign_serialize(privkey, echo=echo_msg, iat=time.time(), requrl="/echo") data2 = bitjws.sign_serialize(privkey, echo='not%s' % echo_msg) da = data.split('.') da2 = data2.split('.') baddata = "%s.%s.%s" % (da[0], da2[1], da[2]) echo = app.post('/echo', data=baddata, headers={'Content-Type': 'application/jose'}) assert echo.status_code == 401
def test_bad_response(): app = server.app.test_client() privkey = bitjws.PrivateKey() echo_msg = {'hello': 'server'} data = bitjws.sign_serialize(privkey, echo=echo_msg, iat=time.time(), requrl="/echo") data2 = bitjws.sign_serialize(privkey, echo='not%s' % echo_msg) da = data.split('.') da2 = data2.split('.') baddata = "%s.%s.%s" % (da[0], da2[1], da[2]) echo = app.post('/echo', data=data, headers={'Content-Type': 'application/jose'}) echo.data = baddata h, p = bitjws.validate_deserialize(echo.get_data().decode('utf8'), requrl='/response') assert h is None
def test_encode_decode(): key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key) header, payload = bitjws.validate_deserialize(ser) rawheader, rawpayload = ser.rsplit('.', 1)[0].split('.') origheader = bitjws.base64url_decode(rawheader.encode('utf8')) origpayload = bitjws.base64url_decode(rawpayload.encode('utf8')) assert header['typ'] == 'JWT' assert header['alg'] == 'CUSTOM-BITCOIN-SIGN' assert header['kid'] == bitjws.pubkey_to_addr(key.pubkey.serialize()) assert len(header) == 3 assert header == json.loads(origheader.decode('utf8')) assert isinstance(payload.get('exp', ''), (float, int)) assert payload['aud'] is None assert len(payload) == 2 assert payload == json.loads(origpayload.decode('utf8')) # Assumption: it takes mores than 0 seconds to perform the above # instructions but less than 1 second. 3600 is the default # expiration time. diff = 3600 - (payload['exp'] - time.time()) assert diff > 0 and diff < 1
def test_invalid_algorithm(): key1 = bitjws.PrivateKey() # Invalid algorithm name for signing. with pytest.raises(AssertionError): bitjws.sign_serialize(key1, algorithm_name='test') with pytest.raises(AssertionError): bitjws.multisig_sign_serialize([key1], algorithm_name='test') # Invalid algorithm name for verifying. ser = bitjws.sign_serialize(key1) with pytest.raises(AssertionError): bitjws.validate_deserialize(ser, algorithm_name='test') ser = bitjws.multisig_sign_serialize([key1]) with pytest.raises(AssertionError): bitjws.multisig_validate_deserialize(ser, algorithm_name='test')
def test_bad_signature(): wif = 'L2Ai1TBwKfyPshmqosKRBvJ47qUCDKesfZXh2zLoYoB7NHgdPS6d' key = bitjws.PrivateKey(bitjws.wif_to_privkey(wif)) assert bitjws.privkey_to_wif(key.private_key) == wif ser = bitjws.sign_serialize(key) # Drop the last byte from the signature. ser = ser[:-1] with pytest.raises(bitjws.jws.InvalidMessage): # It will fail to decode as base64 due to padding. bitjws.validate_deserialize(ser) # Drop another byte. ser = ser[:-1] with pytest.raises(bitjws.jws.InvalidMessage): # Although it can be decoded now, the length is incorrect. bitjws.validate_deserialize(ser) # Replace the signature by something that has the correct # length before decoding but becomes invalid after it. dummy = bitjws.base64url_encode(b'a' * 88) ser = ser[:ser.rfind('.')] + '.' + dummy.decode('utf8') with pytest.raises(bitjws.jws.InvalidMessage): # Now it fails because the dummy signature above produces # 66 bytes (instead of 65) after being decoded. bitjws.validate_deserialize(ser)
def test_no_expire(): key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key, expire_after=None) header, payload = bitjws.validate_deserialize(ser) assert header is not None assert payload['exp'] >= 2 ** 31
def apply(self, request): path = urlparse.urlsplit(request.url).path if len(request.data) > 0: data = bitjws.sign_serialize(self.privkey, requrl=path, iat=time.time(), data=json.loads(request.data)) else: data = bitjws.sign_serialize(self.privkey, requrl=path, iat=time.time(), data=request.params) request.params = {} request.data = data # not sure why some use Caps and others don't, but set both! request.headers['Content-Type'] = request.headers['content-type'] = \ 'application/jose' return request
def test_audience(): key = bitjws.PrivateKey() audience = 'https://example.com/api/login' ser = bitjws.sign_serialize(key, requrl=audience) header, payload = bitjws.validate_deserialize(ser, requrl=audience) assert header is not None assert payload is not None assert payload['aud'] == audience
def test_too_big(): key = bitjws.PrivateKey(rawkey) try: ser = bitjws.sign_serialize(key, test='a' * 4294967295) h, p = bitjws.validate_deserialize(ser) assert h and p except MemoryError: pytest.skip('Not enough memory to run this test') return
def test_ping(self): msg_data = {'method': 'ping'} bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data, iat=time.time()) self.client.send(bitjws_msg) try: response_msg = client_wait_for(self.client, 'pong') except Exception, e: self.fail("Unexpected error: %s" % e)
def test_invalid_audience(): key = bitjws.PrivateKey() print(bitjws.privkey_to_wif(key.private_key)) ser = bitjws.sign_serialize(key, requrl='https://example.com/api/login') with pytest.raises(bitjws.jws.InvalidPayload): # audience not specified. bitjws.validate_deserialize(ser) with pytest.raises(bitjws.jws.InvalidPayload): # audience does not match. bitjws.validate_deserialize(ser, requrl='https://example.com/api')
def create_response(self, payload): """ Create a signed bitjws response using the supplied payload. The response content-type will be 'application/jose'. :param payload: The response content. Must be json-serializable. :return: The signed Response with headers. :rtype: flask.Response """ signedmess = bitjws.sign_serialize(self._privkey, requrl='/response', iat=time.time(), data=payload) return Response(signedmess, mimetype='application/jose')
def test_get_bad_format(self): msg_data = {'data': '', # no method 'pubhash': pubhash, 'headers': None, 'permissions': ['authenticate'], 'model': 'coin'} bitjws_msg = bitjws.sign_serialize(privkey, data=msg_data, iat=time.time()) self.client.send(bitjws_msg) try: msg_response = client_wait_for(self.client, 'error') except Exception, e: self.fail("Unexpected error: %s" % e)
def test_echo_request(): privkey = bitjws.PrivateKey() pubkey = bitjws.pubkey_to_addr(privkey.pubkey.serialize()) echo_msg = {'hello': 'server'} data = bitjws.sign_serialize(privkey, data=echo_msg, iat=time.time(), requrl="/echo") fbj = FlaskBitjws(server.app) app = server.app.test_client() udata = json.dumps({'username': pubkey[0:8], 'kid': pubkey}) user = app.post('/user', data=udata) echo = app.post('/echo', data=data, headers={'Content-Type': 'application/jose'}) h, p = bitjws.validate_deserialize(echo.get_data().decode('utf8'), requrl='/response') assert 'alg' in h assert h['alg'] == 'CUSTOM-BITCOIN-SIGN' assert 'typ' in h assert 'kid' in h assert 'data' in p assert p['data'] == echo_msg
def test_response_good(): bjauth = BitJWSAuthenticator('0.0.0.0', privkey=wif) headers = {'content-type': 'application/jose'} params = {'mess': 'goeshere'} data = bitjws.sign_serialize(bjauth.privkey, requrl="/response", iat=time.time(), data=params) resp = requests.models.Response() resp.status_code = 200 resp._content = b'%s' % data resp.headers = headers response = BitJWSRequestsResponseAdapter(resp) assert response.json() == params
def test_response_bad_requrl(): bjauth = BitJWSAuthenticator('0.0.0.0', privkey=wif) headers = {'content-type': 'application/jose'} params = {'mess': 'couldgohere'} data = bitjws.sign_serialize(bjauth.privkey, requrl="/request/not/response", iat=time.time(), data=params) resp = requests.models.Response() resp.status_code = 200 resp._content = b'%s' % data resp.headers = headers response = BitJWSRequestsResponseAdapter(resp) pytest.raises(bitjws.jws.InvalidPayload, response.json)
def test_good_call(): url = "http://0.0.0.0:8002/coin" bjauth = BitJWSAuthenticator("0.0.0.0", privkey=wif) client = BitJWSSwaggerClient.from_spec(load_file(specurl), origin_url=url, privkey=wif) coin = [{"metal": "m", "mint": "n"}] data = bitjws.sign_serialize(bjauth.privkey, requrl="/response", iat=time.time(), data=coin) httpretty.enable() httpretty.register_uri(httpretty.GET, url, body=data, content_type="application/jose") resp = client.coin.findCoin().result() assert resp[0].metal == coin[0]["metal"] assert resp[0].mint == coin[0]["mint"] assert httpretty.last_request().headers["content-type"] == "application/jose" httpretty.disable() httpretty.reset()
def test_invalid_signature_key(): key = bitjws.PrivateKey() print(bitjws.privkey_to_wif(key.private_key)) ser = bitjws.sign_serialize(key) # Decode header. rawheader = ser.rsplit('.')[0] origheader = bitjws.base64url_decode(rawheader.encode('utf8')) header = json.loads(origheader.decode('utf8')) # Modify the key declared to be used in the signature. header['kid'] = '123' ser = _encode_header(header, ser) header, payload = bitjws.validate_deserialize(ser) # If both header or payload are None then it failed to validate # the signature (as expected). assert header is None assert payload is None
def test_none_algo(): # Register an algorithm named None. newalgo = bitjws.Algorithm(None, sign=lambda a, b: b.encode('utf8'), verify=lambda a, b, c: True, pubkey_serialize=lambda x: "hello") bitjws.ALGORITHM_AVAILABLE[None] = newalgo key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key, algorithm_name=None) with pytest.raises(bitjws.InvalidMessage): # Although it "signs" a message, decoding does complete # due to its name (None). bitjws.validate_deserialize(ser, algorithm_name=None) with pytest.raises(bitjws.InvalidMessage): # The algorithm name (None) is defined in the header, so it # fails to decode here too. bitjws.validate_deserialize(ser)
def test_invalid_header(): key = bitjws.PrivateKey() print(bitjws.privkey_to_wif(key.private_key)) ser = bitjws.sign_serialize(key) # Decode header. rawheader = ser.split('.')[0] origheader = bitjws.base64url_decode(rawheader.encode('utf8')) header = json.loads(origheader.decode('utf8')) # Modify the algorithm specified (by removing it). algorithm = header.pop('alg') # Encode header and try to deserialize. ser = _encode_header(header, ser) with pytest.raises(bitjws.jws.InvalidMessage): # Unknown algorithm. bitjws.validate_deserialize(ser) # Set some other algorithm. header['alg'] = 'SHA256' ser = _encode_header(header, ser) with pytest.raises(bitjws.jws.InvalidMessage): # Unknown algorithm. bitjws.validate_deserialize(ser) # Drop the key used to sign. header['alg'] = algorithm kid = header.pop('kid') ser = _encode_header(header, ser) with pytest.raises(bitjws.jws.InvalidMessage): # No address specified. bitjws.validate_deserialize(ser) # Try to decode the original one. ser = rawheader + '.' + ser.split('.', 1)[1] header, payload = bitjws.validate_deserialize(ser) assert header is not None assert payload is not None h, p = bitjws.validate_deserialize(ser, check_expiration=False) assert h == header assert p == payload
def test_malformed(): key = bitjws.PrivateKey() print(bitjws.privkey_to_wif(key.private_key)) ser = bitjws.sign_serialize(key) # Drop one of the segments. ser = ser.split('.', 1)[1] with pytest.raises(bitjws.jws.InvalidMessage): bitjws.validate_deserialize(ser) # Add an empty segment. ser = '.' + ser with pytest.raises(bitjws.jws.InvalidMessage): bitjws.validate_deserialize(ser) # Add an invalid segment. ser = ' ' + ser with pytest.raises(bitjws.jws.InvalidMessage): # This will fail while trying to parse it as JSON. bitjws.validate_deserialize(ser)
def test_payload_nojson(): key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key) header64 = ser.split('.')[0] # Sign a new payload, 'test' new_payload = bitjws.base64url_encode(b'test').decode('utf8') signdata = '{}.{}'.format(header64, new_payload) sig = bitjws.ALGORITHM_AVAILABLE['CUSTOM-BITCOIN-SIGN'].sign( key, signdata) sig64 = bitjws.base64url_encode(sig).decode('utf8') new = '{}.{}'.format(signdata, sig64) with pytest.raises(bitjws.InvalidMessage): # The new payload was not JSON encoded, so it cannot be # decoded as that. bitjws.validate_deserialize(new) # But we can get its raw value. header, payload = bitjws.validate_deserialize(new, decode_payload=False) assert header is not None assert payload == b'test'
def test_slightly_big(): key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key, test='a' * 254) h, p = bitjws.validate_deserialize(ser) assert h and p
def test_big(): key = bitjws.PrivateKey() ser = bitjws.sign_serialize(key, test='a' * 65536) h, p = bitjws.validate_deserialize(ser) assert h and p
def _sign(self, obj): iat = time.time() audience = request.base_url signed = bitjws.sign_serialize( self._privkey, requrl=audience, iat=iat, data=obj) return signed
def echo(): msg = flask.g.payload.get('echo', '') address = login.current_user.id response = bitjws.sign_serialize(privkey, address=address, echo=msg) return response
from bravado_bitjws.client import BitJWSSwaggerClient host = "swagxample.deginner.com" url = "http://swagxample.deginner.com/" specurl = "%sstatic/swagger.json" % url username = str(my_address)[0:8] client = BitJWSSwaggerClient.from_url(specurl, privkey=privkey) luser = client.get_model('User')(username=username) user = client.user.addUser(user=luser).result() print "registered as user\n%s" % user ##### Example of creating a coin with the more common requests package import requests import time rawcoin = {'metal': 'I_approve_CTF1_game_for_1_BTC', 'mint': '1AVV83Xa8E4yQGKrE4bZgi9rB2fDAEmySE'} jwsdata = bitjws.sign_serialize(privkey, requrl="/coin", iat=time.time(), data=rawcoin) resp = requests.post("http://swagxample.deginner.com/coin", data=jwsdata, headers={'content-type': 'application/jose'}) rawresp = resp.content.decode('utf8') print "raw response\n%s" % rawresp headers, payload = bitjws.validate_deserialize(rawresp, requrl="/response") print "bitjws response body\n%s" % payload print "bitjws response headers\n%s" % headers
import bitjws # Load existing private key. In this example the server always # create a new private key while the client loads an existing one. wif = "KxZUqanyzZEGptbauar66cQo8bfGHwDauHogkxCaqTeMGY1stH6E" priv = bitjws.wif_to_privkey(wif) privkey = bitjws.PrivateKey(priv) assert wif == bitjws.privkey_to_wif(privkey.private_key) # The resulting bitcoin address can be derived for debugging # purposes. my_pubkey = privkey.pubkey.serialize() my_address = bitjws.pubkey_to_addr(my_pubkey) assert my_address == "1G9sChbyDSmuAXNVpjuRwakTmcHdxKGqpp" print("My WIF key: {}".format(wif)) print("My key address: {}".format(my_address)) # Prepare a request to be sent. This one uses a single custom # parameter named 'echo'. echo_msg = 'hello' data = bitjws.sign_serialize(privkey, echo=echo_msg) # Send and receive signed requests. resp = requests.post('http://localhost:8001/', data=data) headers, payload = bitjws.validate_deserialize(resp.content.decode('utf8')) print(headers) # headers['kid'] contains the key used by the server. print(payload) # In this example the server returns a response containing the # echo parameter specified earlier and also a param named 'address'. assert payload['echo'] == echo_msg assert payload['address'] == my_address
import bitjws # Load existing private key. In this example the server always # create a new private key while the client loads an existing one. wif = "KxZUqanyzZEGptbauar66cQo8bfGHwDauHogkxCaqTeMGY1stH6E" priv = bitjws.wif_to_privkey(wif) privkey = bitjws.PrivateKey(priv) assert wif == bitjws.privkey_to_wif(privkey.private_key) # The resulting bitcoin address can be derived for debugging # purposes. my_pubkey = privkey.pubkey.serialize() my_address = bitjws.pubkey_to_addr(my_pubkey) assert my_address == "1G9sChbyDSmuAXNVpjuRwakTmcHdxKGqpp" print("My WIF key: {}".format(wif)) print("My key address: {}".format(my_address)) # Prepare a request to be sent. This one uses a single custom # parameter named 'echo'. echo_msg = "hello" data = bitjws.sign_serialize(privkey, echo=echo_msg) # Send and receive signed requests. resp = requests.post("http://localhost:8001/", data=data) headers, payload = bitjws.validate_deserialize(resp.content.decode("utf8")) print(headers) # headers['kid'] contains the key used by the server. print(payload) # In this example the server returns a response containing the # echo parameter specified earlier and also a param named 'address'. assert payload["echo"] == echo_msg assert payload["address"] == my_address