def fuzz(): jsonrpc_url = sys.argv[1] if len( sys.argv) > 1 else "http://localhost:8000/json/" trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) for i in range(100): keychain_uid = generate_uuid0() key_cipher_algo = random.choice(SUPPORTED_ASYMMETRIC_KEY_ALGOS) print("Fetching key of type %s... " % key_cipher_algo, end="") trustee_proxy.fetch_public_key(keychain_uid=keychain_uid, key_algo=key_cipher_algo) print("DONE")
def _get_proxy_for_escrow(escrow): if escrow == LOCAL_ESCROW_PLACEHOLDER: return LOCAL_ESCROW_API elif isinstance(escrow, dict): if "url" in escrow: return JsonRpcProxy(url=escrow) # TODO - Implement escrow lookup in global registry, shared-secret group, etc. raise ValueError("Unrecognized escrow identifiers: %s" % str(escrow))
def _get_proxy_for_escrow(self, escrow): if escrow == LOCAL_ESCROW_PLACEHOLDER: return self._local_escrow_api elif isinstance(escrow, dict): if "url" in escrow: return JsonRpcProxy( url=escrow["url"], response_error_handler=status_slugs_response_error_handler, ) # TODO - Implement escrow lookup in global registry, shared-secret group, etc. raise ValueError("Unrecognized escrow identifiers: %s" % str(escrow))
def test_jsonrpc_set_and_get_public_authenticator(live_server): jsonrpc_url = live_server.url + "/gateway/jsonrpc/" gateway_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler ) parameters = _generate_authenticator_parameter_tree(2) with pytest.raises(KeystoreDoesNotExist): gateway_proxy.get_public_authenticator(keystore_uid=parameters["keystore_uid"]) gateway_proxy.set_public_authenticator(keystore_uid=parameters["keystore_uid"], keystore_owner=parameters["keystore_owner"], keystore_secret=parameters["keystore_secret"], public_keys=parameters["public_keys"]) with pytest.raises(KeystoreAlreadyExists): gateway_proxy.set_public_authenticator(keystore_uid=parameters["keystore_uid"], keystore_owner=parameters["keystore_owner"], keystore_secret="whatever", public_keys=parameters["public_keys"]) # Check handling of secret hash, similar to a password! public_authenticator_obj: PublicAuthenticator = PublicAuthenticator.objects.get(keystore_uid=parameters["keystore_uid"]) _keystore_secret_hash = public_authenticator_obj.keystore_secret_hash assert _keystore_secret_hash assert _keystore_secret_hash != parameters["keystore_secret"] assert _keystore_secret_hash.startswith("pbkdf2_") assert public_authenticator_obj.has_usable_keystore_secret() assert public_authenticator_obj.check_keystore_secret(parameters["keystore_secret"]) assert not public_authenticator_obj.check_keystore_secret("whatever") public_authenticator_obj.set_unusable_keystore_secret() assert not public_authenticator_obj.has_usable_keystore_secret() public_authenticator_obj.refresh_from_db() # Unusable password was NOT saved assert public_authenticator_obj.has_usable_keystore_secret() public_authenticator = gateway_proxy.get_public_authenticator(keystore_uid=parameters["keystore_uid"]) del parameters["keystore_secret"] assert parameters == public_authenticator check_public_authenticator_sanity(public_authenticator)
def test_jsonrpc_trustee_signature(live_server): jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid = generate_uuid0() payload_signature_algo = "DSA_DSS" secret = get_random_bytes(101) secret_too_big = get_random_bytes(150) public_key_signature_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid, key_algo=payload_signature_algo) public_key_signature = load_asymmetric_key_from_pem_bytestring( key_pem=public_key_signature_pem, key_algo=payload_signature_algo) signature = trustee_proxy.get_message_signature( keychain_uid=keychain_uid, message=secret, signature_algo=payload_signature_algo) with pytest.raises(ValidationError, match="too big"): trustee_proxy.get_message_signature( keychain_uid=keychain_uid, message=secret_too_big, signature_algo=payload_signature_algo, ) verify_message_signature( message=secret, signature=signature, key=public_key_signature, signature_algo=payload_signature_algo, ) signature["signature_value"] += b"xyz" with pytest.raises(SignatureVerificationError, match="not authentic|Incorrect signature"): verify_message_signature( message=secret, signature=signature, key=public_key_signature, signature_algo=payload_signature_algo, )
def test_waserver_abnormal_error_masking(live_server): jsonrpc_url = live_server.url + "/gateway/jsonrpc/" gateway_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keystore_uid = UUID("cac682a8-809f-4de5-bbfd-72b533a37a21") with pytest.raises(KeystoreDoesNotExist): # Error well translated gateway_proxy.get_public_authenticator(keystore_uid=keystore_uid) # Patch the IMPORTED callable of view.py! with patch("waserver.apps.wagateway.views.get_public_authenticator", side_effect=KeyError("wrong key ABC")): with pytest.raises(TransportError): # Server error NOT translated! gateway_proxy.get_public_authenticator(keystore_uid=keystore_uid)
def test_jsonrpc_extended_json_calls(): uid = uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a") server = JsonRpcProxy("http://mock/xmlrpc") # rpc call with positional args def callback1(request): request_message = json.loads(request.body) assert request_message["params"] == [ { "$numberInt": "42" }, { "$binary": { "base64": b64encode(b"xyz").decode("ascii"), "subType": "00" } }, { "$binary": { "base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03" } }, ] return 200, {}, u'{"jsonrpc": "2.0", "result": {"$binary": {"base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03"}}, "id": 1}' responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback1, ) assert server.foobar(42, b"xyz", uid) == uid responses.reset() # rpc call with named parameters def callback2(request): request_message = json.loads(request.body) assert request_message["params"] == { "x": { "$numberInt": "42" }, "y": { "$binary": { "base64": "eHl6", "subType": "00" } }, "z": { "$binary": { "base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03" } }, } return 200, {}, u'{"jsonrpc": "2.0", "result": {"$binary": {"base64": "eHl6", "subType": "00"}}, "id": 1}' responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback2, ) assert server.foobar(x=42, y=b"xyz", z=uid) == b"xyz" responses.reset() # rpc call with a mapping type -> we disabled auto unpacking of arguments!! def callback3(request): request_message = json.loads(request.body) assert request_message["params"] == [{ "foo": "bar" }] # remains a LIST of 1 positional parameter! return 200, {}, u'{"jsonrpc": "2.0", "result": null}' responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback3, ) assert server.foobar({"foo": "bar"}) is None responses.reset() with pytest.raises( ProtocolError, match="spec forbids mixing arguments and keyword arguments"): server.foobar(33, a=22)
def test_jsonrpc_extended_json_calls(): uid = uuid.UUID("450fc293-b702-42d3-ae65-e9cc58e5a62a") server = JsonRpcProxy("http://mock/xmlrpc", response_error_handler=None) # rpc call with positional args def callback1(request): request_message = json.loads(request.body) assert request_message["params"] == [ { "$numberInt": "42" }, { "$binary": { "base64": b64encode(b"xyz").decode("ascii"), "subType": "00" } }, { "$binary": { "base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03" } }, ] return ( 200, {}, u'{"jsonrpc": "2.0", "result": {"$binary": {"base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03"}}, "id": 1}', ) responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback1, ) assert server.foobar(42, b"xyz", uid) == uid responses.reset() # rpc call with named parameters def callback2(request): request_message = json.loads(request.body) assert request_message["params"] == { "x": { "$numberInt": "42" }, "y": { "$binary": { "base64": "eHl6", "subType": "00" } }, "z": { "$binary": { "base64": "RQ/Ck7cCQtOuZenMWOWmKg==", "subType": "03" } }, } return ( 200, {}, u'{"jsonrpc": "2.0", "result": {"$binary": {"base64": "eHl6", "subType": "00"}}, "id": 1}', ) responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback2, ) assert server.foobar(x=42, y=b"xyz", z=uid) == b"xyz" responses.reset() # rpc call with a mapping type -> we disabled auto unpacking of arguments!! def callback3(request): request_message = json.loads(request.body) assert request_message["params"] == [{ "foo": "bar" }] # remains a LIST of 1 positional parameter! return 200, {}, u'{"jsonrpc": "2.0", "result": null}' responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback3, ) assert server.foobar({"foo": "bar"}) is None responses.reset() with pytest.raises( ProtocolError, match="spec forbids mixing arguments and keyword arguments"): server.foobar(33, a=22) def callback_protocol_error(request): return ( 200, {}, u'{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}', ) # Test exception handling responses.add_callback( responses.POST, "http://mock/xmlrpc", content_type="application/json", callback=callback_protocol_error, ) with pytest.raises(ProtocolError, match="Error: -32700 Parse error"): server.foobar({"foo": "bar"}) must_raise = True def _response_error_handler(exc_to_handle): nonlocal must_raise if not must_raise: return "some error occurred" raise RuntimeError(str(exc_to_handle)) server = JsonRpcProxy("http://mock/xmlrpc", response_error_handler=_response_error_handler) with pytest.raises(RuntimeError, match="Error: -32700 Parse error"): server.foobar({"foo": "bar"}) must_raise = False assert server.foobar({"foo": "bar"}) == "some error occurred" responses.reset()
def test_jsonrpc_trustee_request_decryption_authorization_for_free_keys( live_server): jsonrpc_url = jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid_free = generate_uuid0() free_key_algo1 = "RSA_OAEP" free_key_algo2 = "ECC_DSS" free_key_algo3 = "DSA_DSS" all_requested_keypair_identifiers = [ dict(keychain_uid=keychain_uid_free, key_algo=free_key_algo1), dict(keychain_uid=keychain_uid_free, key_algo=free_key_algo2), ] sql_keystore = SqlKeystore() with freeze_time( ) as frozen_datetime: # TEST RELATION WITH FREE KEYS ATTACHMENT for i in range(3): # Generate 1 free keypair per type has_generated = generate_free_keypair_for_least_provisioned_key_algo( keystore=sql_keystore, max_free_keys_per_algo=1, key_algos=[free_key_algo1, free_key_algo2, free_key_algo3]) assert has_generated keys_generated_before_datetime = timezone.now() public_key_pem1 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo1) assert public_key_pem1 # This key will not have early-enough request for authorization public_key_pem3 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo3) assert public_key_pem3 result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_requested_keypair_identifiers, request_message="I want early decryption!", ) assert result["success_count"] == 1 assert result["too_old_count"] == 0 assert result[ "not_found_count"] == 1 # free_key_algo2 is not attached yet frozen_datetime.tick(delta=timedelta(minutes=6)) public_key_pem2 = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid_free, key_algo=free_key_algo2) assert public_key_pem2 result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_requested_keypair_identifiers, request_message="I want later decryption!", ) assert result[ "success_count"] == 1 # It's attachment time which counts! assert result["too_old_count"] == 1 # First key is too old now assert result["not_found_count"] == 0 keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo1) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert keypair_obj.decryption_authorized_at first_authorized_at = keypair_obj.decryption_authorized_at keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo2) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert keypair_obj.decryption_authorized_at assert keypair_obj.decryption_authorized_at >= first_authorized_at + timedelta( minutes=5) keypair_obj = TrusteeKeypair.objects.get( keychain_uid=keychain_uid_free, key_algo=free_key_algo3) assert keypair_obj.created_at <= keys_generated_before_datetime assert keypair_obj.attached_at assert not keypair_obj.decryption_authorized_at # Never requested
def test_jsonrpc_trustee_request_decryption_authorization_for_normal_keys( live_server): jsonrpc_url = jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) key_cipher_algo = "RSA_OAEP" with freeze_time( ) as frozen_datetime: # TEST AUTHORIZATION REQUEST HANDLING keychain_uid1 = generate_uuid0() keychain_uid2 = generate_uuid0() keychain_uid3 = generate_uuid0() keychain_uid4 = generate_uuid0() keychain_uid_unexisting = generate_uuid0() all_keypair_identifiers = [ dict(keychain_uid=keychain_uid1, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid2, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid3, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid4, key_algo=key_cipher_algo), dict(keychain_uid=keychain_uid_unexisting, key_algo=key_cipher_algo), ] public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid1, key_algo=key_cipher_algo) assert public_key_pem assert not _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at # Non-pregenerated keys don't have that field set! assert not _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).attached_at result = trustee_proxy.request_decryption_authorization( keypair_identifiers=[], request_message="I want decryption!") assert result["success_count"] == 0 assert result["too_old_count"] == 0 assert result["not_found_count"] == 0 frozen_datetime.tick(delta=timedelta(minutes=2)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 1 assert result["too_old_count"] == 0 assert (result["not_found_count"] == 4 ) # keychain_uid2 and keychain_uid3 not created yet old_decryption_authorized_at = _fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at assert old_decryption_authorized_at public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid2, key_algo=key_cipher_algo) assert public_key_pem public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid3, key_algo=key_cipher_algo) assert public_key_pem frozen_datetime.tick(delta=timedelta(minutes=4)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 2 assert result["too_old_count"] == 1 assert result["not_found_count"] == 2 assert (_fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at == old_decryption_authorized_at) # Unchanged assert _fetch_key_object_or_raise( keychain_uid=keychain_uid2, key_algo=key_cipher_algo).decryption_authorized_at assert _fetch_key_object_or_raise( keychain_uid=keychain_uid3, key_algo=key_cipher_algo).decryption_authorized_at with pytest.raises(KeyDoesNotExist, match="not found"): _fetch_key_object_or_raise(keychain_uid=keychain_uid_unexisting, key_algo=key_cipher_algo) public_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid4, key_algo=key_cipher_algo) assert public_key_pem frozen_datetime.tick(delta=timedelta(minutes=6)) result = trustee_proxy.request_decryption_authorization( keypair_identifiers=all_keypair_identifiers, request_message="I want decryption!", ) assert result["success_count"] == 0 assert result["too_old_count"] == 4 assert result["not_found_count"] == 1 assert (_fetch_key_object_or_raise( keychain_uid=keychain_uid1, key_algo=key_cipher_algo).decryption_authorized_at == old_decryption_authorized_at) # Unchanged del all_keypair_identifiers
def test_jsonrpc_trustee_decryption_authorization_flags(live_server): jsonrpc_url = _get_trustee_jsonrpc_url(live_server) trustee_proxy = JsonRpcProxy( url=jsonrpc_url, response_error_handler=status_slugs_response_error_handler) keychain_uid = generate_uuid0() keychain_uid_bad = generate_uuid0() key_cipher_algo = "RSA_OAEP" secret = get_random_bytes(101) public_encryption_key_pem = trustee_proxy.fetch_public_key( keychain_uid=keychain_uid, key_algo=key_cipher_algo) public_encryption_key = load_asymmetric_key_from_pem_bytestring( key_pem=public_encryption_key_pem, key_algo=key_cipher_algo) cipherdict = _encrypt_via_rsa_oaep( plaintext=secret, key_dict=dict(key=public_encryption_key)) def _attempt_decryption(): return trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) with freeze_time() as frozen_datetime: with pytest.raises(AuthorizationError, match="Decryption not authorized"): _attempt_decryption() keypair_obj = TrusteeKeypair.objects.get(keychain_uid=keychain_uid, key_algo=key_cipher_algo) keypair_obj.decryption_authorized_at = timezone.now() + timedelta( hours=2) keypair_obj.save() with pytest.raises( AuthorizationError, match="Decryption authorization is only valid from"): _attempt_decryption() # Too early frozen_datetime.tick(delta=timedelta(hours=3)) decrypted = _attempt_decryption() assert decrypted == secret # It works! with pytest.raises(KeyDoesNotExist, match="not found"): trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid_bad, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) cipherdict["digest_list"].append(b"aaabbbccc") with pytest.raises(DecryptionError, match="Ciphertext with incorrect length"): trustee_proxy.decrypt_with_private_key( keychain_uid=keychain_uid, cipher_algo=key_cipher_algo, cipherdict=cipherdict, ) frozen_datetime.tick(delta=timedelta( hours=24)) # We hardcode DECRYPTION_AUTHORIZATION_LIFESPAN_H here with pytest.raises( AuthorizationError, match="Decryption authorization is only valid from"): _attempt_decryption( ) # Too late, cipherdict is not even used so no ValueError keypair_obj.decryption_authorized_at = None keypair_obj.save() with pytest.raises(AuthorizationError, match="Decryption not authorized"): _attempt_decryption() # No more authorization at all