Пример #1
0
def test_encode_decode():
    key1 = bitjws.PrivateKey()
    pubkey1 = bitjws.pubkey_to_addr(key1.pubkey.serialize())
    key2 = bitjws.PrivateKey()
    pubkey2 = bitjws.pubkey_to_addr(key2.pubkey.serialize())

    ser = bitjws.multisig_sign_serialize([key1, key2])
    headers, payload = bitjws.multisig_validate_deserialize(ser)

    rawpayload = json.loads(ser)['payload']
    origpayload = bitjws.base64url_decode(rawpayload.encode('utf8'))

    keys_found = {pubkey1: False, pubkey2: False}
    assert len(headers) == 2
    for h in headers:
        assert len(h) == 3
        assert h['typ'] == 'JWT'
        assert h['alg'] == 'CUSTOM-BITCOIN-SIGN'
        assert h['kid'] in keys_found
        assert keys_found[h['kid']] == False
        keys_found[h['kid']] = True
    assert all(keys_found.values())

    assert isinstance(payload.get('exp', ''), (float, int))
    assert payload['aud'] is None
    assert len(payload) == 2
    assert payload == json.loads(origpayload.decode('utf8'))
Пример #2
0
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)
Пример #3
0
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
Пример #4
0
    def _load_config(self, config):
        if ENVCFG in os.environ:
            logging.info("Loading config from {}".format(ENVCFG))
            #self.config.from_envvar(ENVCFG)
            data = json.load(open(os.getenv(ENVCFG)))
            self.config.update(**data)
        else:
            logging.warn("{} not defined".format(ENVCFG))
        if isinstance(config, dict):
            self.config.update(**config)

        if not self.config.get('api_signing_key'):
            raise Exception("Invalid config: missing api_signing_key")
        # Server key used to sign responses. The client can optionally
        # check that the response was signed by a key it knows to belong
        # to this server.
        self._privkey = bitjws.PrivateKey(
            bitjws.wif_to_privkey(self.config['api_signing_key']))
        logging.info("Server key address: {}".format(
            bitjws.pubkey_to_addr(self._privkey.pubkey.serialize())))

        self._cosigner_server = self.config.get('cosigner_server')
        if not self._cosigner_server:
            logging.warning("cosigner_server not present in config, "
                            "cosigning will not be available.")

        self._dbcfg = self.config.get('api_database')
        if not self._dbcfg:
            raise Exception("Invalid config: missing api_database")
        if 'engine' not in self._dbcfg:
            raise Exception("Invalid config for api_databae: missing 'engine'")
Пример #5
0
def test_init_basepath():
    privkey = bitjws.PrivateKey(bitjws.wif_to_privkey(wif))
    fbj = FlaskBitjws(server.app, privkey=privkey, basepath="/v3")
    app = server.app.test_client()
    import time
    user = app.post('/echo', data={'username': str(time.time)})
    assert user.status_code == 401
Пример #6
0
def test_no_nonce():
    app = server.app.test_client()
    privkey = bitjws.PrivateKey()
    echo_msg = {'hello': 'server'}
    data = bitjws.sign_serialize(privkey, echo=echo_msg, requrl="/echo")
    echo = app.post('/echo', data=data, headers={'Content-Type': 'application/jose'})
    assert echo.status_code == 401
Пример #7
0
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]
Пример #8
0
def test_payload_nojson():
    key = bitjws.PrivateKey()

    # Use a payload that is not JSON encoded.
    ser = json.loads(bitjws.multisig_sign_serialize([key]))
    ser['payload'] = bitjws.base64url_encode(b'test').decode('utf8')

    # Sign the new payload.
    signdata = '{}.{}'.format(ser['signatures'][0]['protected'],
                              ser['payload'])
    sig = bitjws.ALGORITHM_AVAILABLE['CUSTOM-BITCOIN-SIGN'].sign(key, signdata)
    sig64 = bitjws.base64url_encode(sig).decode('utf8')
    ser['signatures'][0]['signature'] = sig64

    serenc = json.dumps(ser)
    with pytest.raises(bitjws.InvalidMessage):
        # The new payload was not JSON encoded, so it cannot be
        # decoded as that.
        bitjws.multisig_validate_deserialize(serenc)
    # But we can get its raw value.
    headers, payload = bitjws.multisig_validate_deserialize(
        serenc, decode_payload=False)

    assert len(headers) == 1
    assert payload == b'test'
Пример #9
0
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)
Пример #10
0
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
Пример #11
0
def test_multisig_missingkeys():
    key = bitjws.PrivateKey()

    ser = bitjws.multisig_sign_serialize([key])
    # This should work.
    headers, payload = bitjws.multisig_validate_deserialize(ser)
    assert len(headers) == 1 and payload

    serobj = json.loads(ser)

    payload = serobj.pop('payload')
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # Key 'payload' is missing.
        bitjws.multisig_validate_deserialize(ser)

    serobj['payload'] = payload
    signatures = serobj.pop('signatures')
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # Key 'signatures' is missing.
        bitjws.multisig_validate_deserialize(ser)

    serobj['signatures'] = 'hello'
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # 'signatures' is not a list.
        bitjws.multisig_validate_deserialize(ser)

    del serobj['signatures']
    del serobj['payload']
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        bitjws.multisig_validate_deserialize(ser)

    # Remove a key from one of the signatures.
    serobj['signatures'] = signatures
    serobj['payload'] = payload
    sig0 = serobj['signatures'][0].copy()

    del serobj['signatures'][0]['protected']
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # data['signatures'][0]['protected'] is missing
        bitjws.multisig_validate_deserialize(ser)

    serobj['signatures'][0] = sig0.copy()
    del serobj['signatures'][0]['signature']
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # data['signatures'][0]['signture'] is missing
        bitjws.multisig_validate_deserialize(ser)

    # Remove the only signature entry.
    serobj['signatures'].pop()
    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # No signatures.
        bitjws.multisig_validate_deserialize(ser)
Пример #12
0
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
Пример #13
0
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
Пример #14
0
def test_multisig_partiallyinvalid():
    key1 = bitjws.PrivateKey()
    key2 = bitjws.PrivateKey()
    key3 = bitjws.PrivateKey()

    ser = bitjws.multisig_sign_serialize([key1, key2, key3])
    assert all(bitjws.multisig_validate_deserialize(ser))

    serobj = json.loads(ser)
    # Swap signatures.
    sig0 = serobj['signatures'][0]['signature']
    sig1 = serobj['signatures'][1]['signature']
    sig0, sig1 = sig1, sig0
    serobj['signatures'][0]['signature'] = sig0
    serobj['signatures'][1]['signature'] = sig1

    ser = json.dumps(serobj)
    headers, payload = bitjws.multisig_validate_deserialize(ser)
    assert headers is None
    assert payload is None
Пример #15
0
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
Пример #16
0
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')
Пример #17
0
def test_register_user():
    # register a new user
    privkey2 = bitjws.PrivateKey()
    my_pubkey2 = privkey2.pubkey.serialize()
    my_address2 = bitjws.pubkey_to_addr(my_pubkey2)

    username2 = str(my_address2)[0:8]

    client2 = BitJWSSwaggerClient.from_url(specurl, privkey=privkey2)

    luser2 = client2.get_model('User')(username=username2)
    user2 = client2.user.addUser(user=luser2).result()
    assert hasattr(user2, 'id')
Пример #18
0
    def __init__(self, app, privkey=None,
                 loginmanager=None,
                 get_last_nonce=get_last_nonce,
                 get_user_by_key=get_user_by_key,
                 basepath=""):
        """
        Initialize a flask-bitjws Application with optional LoginManager.
        If you do not provide your own LoginManager one will be created.

        Create or set the private key to use with this application.

        :param str app: The flask application
        :param str privkey: the bitjws private key to use for signing responses
        :param flask.ext.login.LoginManager loginmanager: An optional LoginManager
        :param function get_last_nonce: A function to overwrite this class's stub.
        :param function get_user_by_key: A function to overwrite this class's stub.
        :param str basepath: The public basepath this app is deployed on.
             (only preceding slash required i.e. '/path')
        """
        if privkey is not None and isinstance(privkey, str):
            self._privkey = bitjws.PrivateKey(bitjws.wif_to_privkey(privkey))
        elif privkey is not None and isinstance(privkey, bitjws.PrivateKey):
            self._privkey = privkey
        else:
            self._privkey = bitjws.PrivateKey()
        self.pubkey = bitjws.pubkey_to_addr(self._privkey.pubkey.serialize())

        if loginmanager is None:
            loginmanager = login.LoginManager()
            loginmanager.anonymous_user = login.AnonymousUserMixin
            loginmanager.init_app(app)
        loginmanager.request_loader(load_user_from_request)

        self.get_last_nonce = get_last_nonce
        self.get_user_by_key = get_user_by_key

        self.basepath = basepath

        app.bitjws = self
Пример #19
0
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
Пример #20
0
def test_multisig_invalidsig():
    key = bitjws.PrivateKey()

    ser = bitjws.multisig_sign_serialize([key])
    assert all(bitjws.multisig_validate_deserialize(ser))

    serobj = json.loads(ser)
    dummy = bitjws.base64url_encode(b'a' * 88)
    serobj['signatures'][0]['signature'] = dummy.decode('utf8')

    ser = json.dumps(serobj)
    with pytest.raises(bitjws.jws.InvalidMessage):
        # Invalid signature length.
        bitjws.multisig_validate_deserialize(ser)
Пример #21
0
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')
Пример #22
0
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
Пример #23
0
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
Пример #24
0
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)
Пример #25
0
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
Пример #26
0
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)
Пример #27
0
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'
Пример #28
0
def test_big():
    key = bitjws.PrivateKey()

    ser = bitjws.sign_serialize(key, test='a' * 65536)
    h, p = bitjws.validate_deserialize(ser)
    assert h and p
Пример #29
0
# Install requirements: pip install flask flask-login
import flask
from flask.ext import login
import bitjws

# Server key used to sign responses. The client can optionally
# check that the response was signed by a key it knows to belong
# to this server.
privkey = bitjws.PrivateKey()
print("Server WIF key: {}".format(bitjws.privkey_to_wif(privkey.private_key)))
print("Server key address: {}".format(
    bitjws.pubkey_to_addr(privkey.pubkey.serialize())))

# Setup flask app with flask-login.
app = flask.Flask(__name__)
login_manager = login.LoginManager()
login_manager.init_app(app)


# Dummy User.
class User(login.UserMixin):
    def __init__(self, address):
        self.id = address


# Custom request authentication based on bitjws.
@login_manager.request_loader
def load_user_from_request(request):
    header, payload = bitjws.validate_deserialize(request.data.decode('utf8'))
    if header is None:
        # Validation failed.
Пример #30
0
def test_init_PrivateKey():
    privkey = bitjws.PrivateKey(bitjws.wif_to_privkey(wif))
    app = Flask(__name__)
    fbj = FlaskBitjws(app, privkey=privkey)
    assert wif == bitjws.privkey_to_wif(app.bitjws._privkey.private_key)