diff --git a/python/.flake8 b/python/.flake8 new file mode 100644 index 00000000..64290313 --- /dev/null +++ b/python/.flake8 @@ -0,0 +1,8 @@ +[flake8] +exclude = + __init__.py, + .git, + */.venv/, + */build/, + */dist/ +max-line-length = 88 diff --git a/python/config.py b/python/config.py deleted file mode 100644 index ba0d3312..00000000 --- a/python/config.py +++ /dev/null @@ -1 +0,0 @@ -KEYMASTER_URL = 'http://localhost:4226' diff --git a/python/keymaster_sdk.py b/python/keymaster_sdk.py deleted file mode 100644 index 9e9fbf08..00000000 --- a/python/keymaster_sdk.py +++ /dev/null @@ -1,65 +0,0 @@ -import requests -from config import KEYMASTER_URL - -KEYMASTER_API = KEYMASTER_URL + "/api/v1" - -class KeymasterError(Exception): - """An error occurred while communicating with the Keymaster API.""" - -def proxy_request(method, url, **kwargs): - """ - Send a request to the specified URL and handle any HTTP errors. - - Args: - method (str): The HTTP method to use for the request. - url (str): The URL to send the request to. - **kwargs: Additional arguments to pass to `requests.request`. - - Returns: - dict: The JSON response from the server. - - Raises: - HTTPException: If the request fails, with the status code and response text from the server. - """ - try: - response = requests.request(method, url, **kwargs) - response.raise_for_status() - return response.json() - except requests.HTTPError as e: - raise KeymasterError(f"Error {e.response.status_code}: {e.response.text}") - -def isReady(): - response = proxy_request('GET', f'{KEYMASTER_API}/ready') - return response['ready'] - -def getCurrendId(): - response = proxy_request('GET', f'{KEYMASTER_API}/ids/current') - return response['current'] - -def listIds(): - response = proxy_request('GET', f'{KEYMASTER_API}/ids') - return response['ids'] - -def resolveId(id): - response = proxy_request('GET', f'{KEYMASTER_API}/ids/{id}') - return response['docs'] - -def createSchema(schema, options={}): - response = proxy_request('POST', f'{KEYMASTER_API}/schemas', json={"schema": schema, "options": options}) - return response['did'] - -def createTemplate(schema): - response = proxy_request('POST', f'{KEYMASTER_API}/schemas/did/template', json={"schema": schema}) - return response['template'] - -def bindCredential(schema, subject, options={}): - response = proxy_request('POST', f'{KEYMASTER_API}/credentials/bind', json={"schema": schema, "subject": subject, "options": options}) - return response['credential'] - -def issueCredential(credential, options={}): - response = proxy_request('POST', f'{KEYMASTER_API}/credentials/issued', json={"credential": credential, "options": options}) - return response['did'] - -def decryptJSON(did): - response = proxy_request('POST', f'{KEYMASTER_API}/keys/decrypt/json', json={"did": did}) - return response['json'] diff --git a/python/.gitignore b/python/keymaster_sdk/.gitignore similarity index 53% rename from python/.gitignore rename to python/keymaster_sdk/.gitignore index c85233c5..e6ec0834 100644 --- a/python/.gitignore +++ b/python/keymaster_sdk/.gitignore @@ -1,2 +1,4 @@ __pycache__ **/*.pyc +*.egg-info/ +dist/ diff --git a/python/keymaster_sdk/LICENSE b/python/keymaster_sdk/LICENSE new file mode 100644 index 00000000..5c08e122 --- /dev/null +++ b/python/keymaster_sdk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 MDIP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/python/keymaster_sdk/README.md b/python/keymaster_sdk/README.md new file mode 100644 index 00000000..1cccb347 --- /dev/null +++ b/python/keymaster_sdk/README.md @@ -0,0 +1,26 @@ +# MDIP Keymaster + +Keymaster is a client library for the MDIP. It manages a wallet with any number of identities. + +### Installation + +```bash +pip install keymaster-sdk +``` + +### Requirements + +- Running keymaster instance + +### Usage + +```python +import keymaster_sdk as keymaster + +# Optional: URL defaults to http://localhost:4226 and can also +# be set using the environment variable KC_KEYMASTER_URL +keymaster.set_url('http://example.com:4226') + +ready = keymaster.is_ready() +print(f'Keymaster is ready: {ready}') +``` diff --git a/python/keymaster_sdk/pyproject.toml b/python/keymaster_sdk/pyproject.toml new file mode 100644 index 00000000..fb06186f --- /dev/null +++ b/python/keymaster_sdk/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "keymaster-sdk" +version = "0.1.0" +description = "Keymaster is a client library for the MDIP" +readme = "README.md" +license = { file = "LICENSE" } +authors = [ + { name="David McFadzean", email="david@selfid.com" }, + { name="Peter Bushnell", email="peter@selfid.com" } +] +dependencies = [ + "requests==2.32.0" +] + +[project.urls] +Homepage = "https://github.com/KeychainMDIP/kc/" diff --git a/python/keymaster_sdk/src/keymaster_sdk/__init__.py b/python/keymaster_sdk/src/keymaster_sdk/__init__.py new file mode 100644 index 00000000..dc8d31ba --- /dev/null +++ b/python/keymaster_sdk/src/keymaster_sdk/__init__.py @@ -0,0 +1,68 @@ +from .keymaster_sdk import ( + accept_credential, + add_group_member, + add_name, + add_signature, + backup_id, + backup_wallet, + bind_credential, + check_wallet, + create_id, + create_challenge, + create_group, + create_poll, + create_response, + create_schema, + create_template, + decrypt_json, + decrypt_message, + decrypt_mnemonic, + encrypt_json, + encrypt_message, + fix_wallet, + get_credential, + get_current_id, + get_group, + get_schema, + is_ready, + issue_credential, + KeymasterError, + list_credentials, + list_groups, + list_ids, + list_issued, + list_names, + list_registries, + list_schemas, + load_wallet, + new_wallet, + poll_template, + publish_credential, + publish_poll, + recover_id, + recover_wallet, + remove_credential, + remove_group_member, + remove_name, + remove_id, + resolve_asset, + resolve_did, + resolve_id, + revoke_credential, + rotate_keys, + set_current_id, + set_schema, + set_url, + save_wallet, + test_agent, + test_group, + test_schema, + update_credential, + update_poll, + unpublish_credential, + unpublish_poll, + verify_response, + verify_signature, + view_poll, + vote_poll, +) diff --git a/python/keymaster_sdk/src/keymaster_sdk/keymaster_sdk.py b/python/keymaster_sdk/src/keymaster_sdk/keymaster_sdk.py new file mode 100644 index 00000000..331c1674 --- /dev/null +++ b/python/keymaster_sdk/src/keymaster_sdk/keymaster_sdk.py @@ -0,0 +1,545 @@ +import os +import requests + +_base_url = os.environ.get("KC_KEYMASTER_URL", "http://localhost:4226") +_keymaster_api = _base_url + "/api/v1" + + +class KeymasterError(Exception): + """An error occurred while communicating with the Keymaster API.""" + + +def proxy_request(method, url, **kwargs): + """ + Send a request to the specified URL and handle any HTTP errors. + + Args: + method (str): The HTTP method to use for the request. + url (str): The URL to send the request to. + **kwargs: Additional arguments to pass to `requests.request`. + + Returns: + dict: The JSON response from the server. + + Raises: + HTTPException: If the request fails, with the status + code and response text from the server. + """ + try: + response = requests.request(method, url, **kwargs) + response.raise_for_status() + return response.json() + except requests.HTTPError as e: + raise KeymasterError(f"Error {e.response.status_code}: {e.response.text}") + + +def set_url(new_url: str): + global _base_url, _keymaster_api + _base_url = new_url + _keymaster_api = _base_url + "/api/v1" + + +def is_ready(): + response = proxy_request("GET", f"{_keymaster_api}/ready") + return response["ready"] + + +def create_id(name, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", f"{_keymaster_api}/ids", json={"name": name, "options": options} + ) + return response["did"] + + +def get_current_id(): + response = proxy_request("GET", f"{_keymaster_api}/ids/current") + return response["current"] + + +def set_current_id(name): + response = proxy_request( + "PUT", f"{_keymaster_api}/ids/current", json={"name": name} + ) + return response["ok"] + + +def remove_id(identifier): + response = proxy_request("DELETE", f"{_keymaster_api}/ids/{identifier}") + return response["ok"] + + +def backup_id(identifier): + response = proxy_request("POST", f"{_keymaster_api}/ids/{identifier}/backup") + return response["ok"] + + +def recover_id(did): + response = proxy_request( + "POST", f"{_keymaster_api}/ids/{did}/recover", json={"did": did} + ) + return response["recovered"] + + +def encrypt_message(msg, receiver, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/keys/encrypt/message", + json={"msg": msg, "receiver": receiver, "options": options}, + ) + return response["did"] + + +def decrypt_message(did): + response = proxy_request( + "POST", f"{_keymaster_api}/keys/decrypt/message", json={"did": did} + ) + return response["message"] + + +def list_ids(): + response = proxy_request("GET", f"{_keymaster_api}/ids") + return response["ids"] + + +def load_wallet(): + response = proxy_request("GET", f"{_keymaster_api}/wallet") + return response["wallet"] + + +def save_wallet(wallet): + response = proxy_request("PUT", f"{_keymaster_api}/wallet", json={"wallet": wallet}) + return response["ok"] + + +def backup_wallet(): + response = proxy_request( + "POST", + f"{_keymaster_api}/wallet/backup", + ) + return response["ok"] + + +def recover_wallet(): + response = proxy_request( + "POST", + f"{_keymaster_api}/wallet/recover", + ) + return response["wallet"] + + +def new_wallet(mnemonic, overwrite=False): + response = proxy_request( + "POST", + f"{_keymaster_api}/wallet/new", + json={"mnemonic": mnemonic, "overwrite": overwrite}, + ) + return response["wallet"] + + +def check_wallet(): + response = proxy_request( + "POST", + f"{_keymaster_api}/wallet/check", + ) + return response["check"] + + +def fix_wallet(): + response = proxy_request( + "POST", + f"{_keymaster_api}/wallet/fix", + ) + return response["fix"] + + +def decrypt_mnemonic(): + response = proxy_request( + "GET", + f"{_keymaster_api}/wallet/mnemonic", + ) + return response["mnemonic"] + + +def list_registries(): + response = proxy_request("GET", f"{_keymaster_api}/registries") + return response["registries"] + + +def resolve_id(identifier): + response = proxy_request("GET", f"{_keymaster_api}/ids/{identifier}") + return response["docs"] + + +def resolve_did(name): + response = proxy_request("GET", f"{_keymaster_api}/names/{name}") + return response["docs"] + + +def resolve_asset(name): + response = proxy_request("GET", f"{_keymaster_api}/assets/{name}") + return response["asset"] + + +def create_schema(schema, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", f"{_keymaster_api}/schemas", json={"schema": schema, "options": options} + ) + return response["did"] + + +def get_schema(identifier): + response = proxy_request("GET", f"{_keymaster_api}/schemas/{identifier}") + return response["schema"] + + +def set_schema(identifier, schema): + response = proxy_request( + "PUT", f"{_keymaster_api}/schemas/{identifier}", json={"schema": schema} + ) + return response["ok"] + + +def test_schema(identifier): + response = proxy_request("POST", f"{_keymaster_api}/schemas/{identifier}/test") + return response["test"] + + +def list_schemas(owner=None): + if owner is None: + owner = "" + response = proxy_request( + "GET", + f"{_keymaster_api}/schemas?owner={owner}", + ) + return response["schemas"] + + +def test_agent(identifier): + response = proxy_request("POST", f"{_keymaster_api}/agents/{identifier}/test") + return response["test"] + + +def create_template(schema): + response = proxy_request( + "POST", f"{_keymaster_api}/schemas/did/template", json={"schema": schema} + ) + return response["template"] + + +def bind_credential(schema, subject, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/bind", + json={"schema": schema, "subject": subject, "options": options}, + ) + return response["credential"] + + +def issue_credential(credential, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/issued", + json={"credential": credential, "options": options}, + ) + return response["did"] + + +def update_credential(did, credential): + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/issued/{did}", + json={"credential": credential}, + ) + return response["ok"] + + +def get_credential(did): + response = proxy_request( + "GET", + f"{_keymaster_api}/credentials/held/{did}", + ) + return response["credential"] + + +def list_credentials(): + response = proxy_request( + "GET", + f"{_keymaster_api}/credentials/held", + ) + return response["held"] + + +def publish_credential(did, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/held/{did}/publish", + json={"options": options}, + ) + return response["ok"] + + +def unpublish_credential(did): + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/held/{did}/unpublish", + ) + return response["ok"] + + +def remove_credential(did): + response = proxy_request( + "DELETE", + f"{_keymaster_api}/credentials/held/{did}", + ) + return response["ok"] + + +def revoke_credential(did): + response = proxy_request( + "DELETE", + f"{_keymaster_api}/credentials/issued/{did}", + ) + return response["ok"] + + +def list_issued(): + response = proxy_request( + "GET", + f"{_keymaster_api}/credentials/issued", + ) + return response["issued"] + + +def accept_credential(did): + response = proxy_request( + "POST", + f"{_keymaster_api}/credentials/held", + json={"did": did}, + ) + return response["ok"] + + +def decrypt_json(did): + response = proxy_request( + "POST", f"{_keymaster_api}/keys/decrypt/json", json={"did": did} + ) + return response["json"] + + +def encrypt_json(json, receiver, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/keys/encrypt/json", + json={"json": json, "receiver": receiver, "options": options}, + ) + return response["did"] + + +def list_names(): + response = proxy_request( + "GET", + f"{_keymaster_api}/names", + ) + return response["names"] + + +def add_name(name, did): + response = proxy_request( + "POST", f"{_keymaster_api}/names", json={"name": name, "did": did} + ) + return response["ok"] + + +def remove_name(name): + response = proxy_request("DELETE", f"{_keymaster_api}/names/{name}") + return response["ok"] + + +def create_challenge(challenge, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/challenge", + json={"challenge": challenge, "options": options}, + ) + return response["did"] + + +def create_response(challenge, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/response", + json={"challenge": challenge, "options": options}, + ) + return response["did"] + + +def verify_response(response, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/response/verify", + json={"response": response, "options": options}, + ) + return response["verify"] + + +def create_group(name, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/groups", + json={"name": name, "options": options}, + ) + return response["did"] + + +def get_group(group): + response = proxy_request( + "GET", + f"{_keymaster_api}/groups/{group}", + ) + return response["group"] + + +def add_group_member(group, member): + response = proxy_request( + "POST", + f"{_keymaster_api}/groups/{group}/add", + json={"group": group, "member": member}, + ) + return response["ok"] + + +def remove_group_member(group, member): + response = proxy_request( + "POST", + f"{_keymaster_api}/groups/{group}/remove", + json={"group": group, "member": member}, + ) + return response["ok"] + + +def test_group(group, member): + response = proxy_request( + "POST", + f"{_keymaster_api}/groups/{group}/test", + json={"group": group, "member": member}, + ) + return response["test"] + + +def list_groups(owner=None): + if owner is None: + owner = "" + response = proxy_request( + "GET", + f"{_keymaster_api}/groups?owner={owner}", + ) + return response["groups"] + + +def rotate_keys(): + response = proxy_request( + "POST", + f"{_keymaster_api}/keys/rotate", + ) + return response["ok"] + + +def add_signature(contents): + response = proxy_request( + "POST", + f"{_keymaster_api}/keys/sign", + json={"contents": contents}, + ) + return response["signed"] + + +def verify_signature(json): + response = proxy_request( + "POST", + f"{_keymaster_api}/keys/verify", + json={"json": json}, + ) + return response["ok"] + + +def poll_template(): + response = proxy_request( + "GET", + f"{_keymaster_api}/templates/poll", + ) + return response["template"] + + +def create_poll(poll, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/polls", + json={"poll": poll, "options": options}, + ) + return response["did"] + + +def view_poll(poll): + response = proxy_request( + "GET", + f"{_keymaster_api}/polls/{poll}/view", + ) + return response["poll"] + + +def vote_poll(poll, vote, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/polls/vote", + json={"poll": poll, "vote": vote, "options": options}, + ) + return response["did"] + + +def update_poll(ballot): + response = proxy_request( + "PUT", f"{_keymaster_api}/polls/update", json={"ballot": ballot} + ) + return response["ok"] + + +def publish_poll(poll, options=None): + if options is None: + options = {} + response = proxy_request( + "POST", + f"{_keymaster_api}/polls/{poll}/publish", + json={"poll": poll, "options": options}, + ) + return response["ok"] + + +def unpublish_poll(poll): + response = proxy_request("DELETE", f"{_keymaster_api}/polls/{poll}/unpublish") + return response["ok"] diff --git a/python/test.py b/python/keymaster_sdk/tests/performance_test.py similarity index 76% rename from python/test.py rename to python/keymaster_sdk/tests/performance_test.py index 2cf004cf..4dcd82be 100644 --- a/python/test.py +++ b/python/keymaster_sdk/tests/performance_test.py @@ -10,10 +10,10 @@ def main(): args = parser.parse_args() try: - ready = keymaster.isReady() + ready = keymaster.is_ready() print(f"Keymaster is ready: {ready}") - currentId = keymaster.getCurrendId() + currentId = keymaster.get_current_id() print(f"Current ID: {currentId}") expires = datetime.now(timezone.utc) + timedelta(minutes=1) @@ -23,19 +23,19 @@ def main(): 'validUntil': expires.isoformat() } - schema = keymaster.createSchema(None, test_options) + schema = keymaster.create_schema(None, test_options) - credential = keymaster.createTemplate(schema) + credential = keymaster.create_template(schema) print(json.dumps(credential, indent=4)) test_options['subject'] = currentId test_options['schema'] = schema for i in range(args.credentials): - vcDID = keymaster.issueCredential(credential, test_options) + vcDID = keymaster.issue_credential(credential, test_options) print(f"VC {i}: {vcDID}") - vc = keymaster.decryptJSON(vcDID) + vc = keymaster.decrypt_json(vcDID) print(json.dumps(vc, indent=4)) except Exception as e: @@ -43,3 +43,4 @@ def main(): if __name__ == "__main__": main() + diff --git a/python/keymaster_sdk/tests/test_keymaster_sdk.py b/python/keymaster_sdk/tests/test_keymaster_sdk.py new file mode 100644 index 00000000..6adfafde --- /dev/null +++ b/python/keymaster_sdk/tests/test_keymaster_sdk.py @@ -0,0 +1,375 @@ +import keymaster_sdk as keymaster +from datetime import datetime, timedelta, timezone +import random +import string + +# Test vars +expires = datetime.now(timezone.utc) + timedelta(minutes=1) +test_options = {"registry": "local", "validUntil": expires.isoformat()} +generated_ids = [] + + +# Tests +def test_isready(): + response = keymaster.is_ready() + assert_equal(response, True) + + +def test_ids(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + + response = keymaster.test_agent(alice_id) + assert_equal(response, True) + + response = keymaster.set_current_id(alice) + assert_equal(response, True) + + response = keymaster.get_current_id() + assert_equal(response, alice) + + response = keymaster.resolve_id(alice) + assert_equal(response["didDocument"]["id"], alice_id) + + response = keymaster.list_ids() + assert alice in response, "expected ID not found in list_ids response" + + +def test_schemas(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + keymaster.set_current_id(alice) + + did = keymaster.create_schema(None) + schema = keymaster.get_schema(did) + assert_equal(schema["$schema"], "http://json-schema.org/draft-07/schema#") + assert_equal(schema["type"], "object") + assert_equal(schema["properties"], {"propertyName": {"type": "string"}}) + assert_equal(schema["required"], ["propertyName"]) + + response = keymaster.list_schemas(alice_id) + assert_equal(response, [did]) + + response = keymaster.test_schema(did) + assert_equal(response, True) + + response = keymaster.set_schema(did, schema) + assert_equal(response, True) + + +def test_encrypt_decrypt_json(): + json = {"key": "value", "list": [1, 2, 3], "obj": {"name": "some object"}} + + alice = generate_id() + alice_id = keymaster.create_id(alice) + + did = keymaster.encrypt_json(json, alice_id) + data = keymaster.resolve_asset(did) + assert_equal(data["encrypted"]["sender"], alice_id) + + response = keymaster.decrypt_json(did) + assert_equal(response, json) + + +def test_issue_update_credentials(): + alice = generate_id() + keymaster.create_id(alice) + keymaster.set_current_id(alice) + + response = keymaster.list_credentials() + assert_equal(response, []) + + alice_id = keymaster.resolve_id(alice)["didDocument"]["id"] + schema = keymaster.create_schema(None, test_options) + credential = keymaster.create_template(schema) + assert_equal(credential["propertyName"], "TBD") + assert_equal(credential["$schema"], schema) + + options = { + **test_options, + "subject": alice, + "schema": schema, + } + + did = keymaster.issue_credential(credential, options) + vc = keymaster.get_credential(did) + assert_equal(vc["issuer"], alice_id) + assert_equal(vc["credentialSubject"]["id"], alice_id) + + response = keymaster.list_issued() + assert_equal(response, [did]) + + response = keymaster.decrypt_json(did) + assert_equal(response["type"], ["VerifiableCredential", schema]) + assert_equal(response["issuer"], alice_id) + assert_equal(response["credentialSubject"]["id"], alice_id) + + response = keymaster.update_credential(did, vc) + assert_equal(response, True) + + +def test_bind_credentials(): + alice = generate_id() + bob = generate_id() + keymaster.create_id(alice) + keymaster.create_id(bob) + keymaster.set_current_id(alice) + + alice_id = keymaster.resolve_id(alice)["didDocument"]["id"] + bob_id = keymaster.resolve_id(bob)["didDocument"]["id"] + schema = keymaster.create_schema(None, test_options) + + bc = keymaster.bind_credential(schema, bob, test_options) + assert_equal(bc["credentialSubject"]["id"], bob_id) + + did = keymaster.issue_credential(bc, test_options) + vc = keymaster.get_credential(did) + assert_equal(vc["issuer"], alice_id) + assert_equal(vc["credentialSubject"]["id"], bob_id) + + +def test_publish_credentials(): + bob = generate_id() + keymaster.create_id(bob) + bob_schema = keymaster.create_schema(None, test_options) + bc = keymaster.bind_credential(bob_schema, bob, test_options) + did = keymaster.issue_credential(bc, test_options) + identifier = keymaster.resolve_id(bob)["didDocument"]["id"] + + response = keymaster.publish_credential(did) + assert_equal(response["signature"]["signer"], identifier) + + response = keymaster.unpublish_credential(did) + assert_equal(response, f"OK credential {did} removed from manifest") + + +def test_accept_remove_revoke_credential(): + bob = generate_id() + keymaster.create_id(bob) + bob_schema = keymaster.create_schema(None, test_options) + bc = keymaster.bind_credential(bob_schema, bob, test_options) + did = keymaster.issue_credential(bc, test_options) + + response = keymaster.accept_credential(did) + assert_equal(response, True) + + response = keymaster.remove_credential(did) + assert_equal(response, True) + + response = keymaster.revoke_credential(did) + assert_equal(response, True) + + +def test_wallet(): + wallet = keymaster.load_wallet() + assert "seed" in wallet, "seed not present in wallet" + assert "mnemonic" in wallet["seed"], "mnemonic not present in wallet" + assert "hdkey" in wallet["seed"], "hdkey not present in wallet" + assert "xpriv" in wallet["seed"]["hdkey"], "xpriv not present in wallet" + + response = keymaster.save_wallet(wallet) + assert_equal(response, True) + + did = keymaster.backup_wallet() + doc = keymaster.resolve_did(did) + assert_equal(doc["didDocument"]["id"], did) + + mnemonic = keymaster.decrypt_mnemonic() + assert_equal(len(mnemonic.split()), 12) + + new_wallet = keymaster.new_wallet(mnemonic, True) + assert_equal(wallet["seed"]["hdkey"]["xpriv"], new_wallet["seed"]["hdkey"]["xpriv"]) + + recovered = keymaster.recover_wallet() + assert_equal(recovered, wallet) + + response = keymaster.check_wallet() + assert "checked" in response, "checked not present in check_wallet response" + + response = keymaster.fix_wallet() + assert "idsRemoved" in response, "idsRemoved not present in fix_wallet response" + + +def test_registeries(): + response = keymaster.list_registries() + assert ( + "hyperswarm" in response + ), "hyperswarm not present in list_registries response" + + +def test_backup_recover_id(): + alice = generate_id() + did = keymaster.create_id(alice) + + response = keymaster.backup_id(alice) + assert_equal(response, True) + + doc = keymaster.resolve_did(did) + vault = keymaster.resolve_did(doc["didDocumentData"]["vault"]) + assert len(vault["didDocumentData"]["backup"]) > 0, "backup not present in vault" + + keymaster.remove_id(generated_ids.pop()) + assert_equal(response, True) + + response = keymaster.list_ids() + assert alice not in response, "unexpected ID found in list_ids response" + + response = keymaster.recover_id(did) + assert_equal(response, alice) + + response = keymaster.list_ids() + assert alice in response, "expected ID not found in list_ids response" + + +def test_encrypt_decrypt_message(): + alice = generate_id() + bob = generate_id() + keymaster.create_id(alice) + bob_id = keymaster.create_id(bob) + keymaster.set_current_id(alice) + + msg = "Hi Bob" + + did = keymaster.encrypt_message(msg, bob_id) + response = keymaster.decrypt_message(did) + assert_equal(response, msg) + + keymaster.set_current_id(bob) + response = keymaster.decrypt_message(did) + assert_equal(response, msg) + + +def test_names(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + + response = keymaster.remove_name("Bob") + + response = keymaster.add_name("Bob", alice_id) + assert_equal(response, True) + + response = keymaster.list_names() + assert "Bob" in response, "expected name not found in list_names response" + + response = keymaster.remove_name("Bob") + assert_equal(response, True) + + +def test_challenge_response(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + keymaster.set_current_id(alice) + + challenge_did = keymaster.create_challenge({}) + doc = keymaster.resolve_did(challenge_did) + assert_equal(doc["didDocument"]["id"], challenge_did) + assert_equal(doc["didDocument"]["controller"], alice_id) + assert_equal(doc["didDocumentData"], {"challenge": {}}) + + bob = generate_id() + bob_id = keymaster.create_id(bob) + keymaster.set_current_id(bob) + + response_did = keymaster.create_response(challenge_did) + response = keymaster.decrypt_json(response_did) + assert_equal(response["response"]["challenge"], challenge_did) + assert_equal(response["response"]["credentials"], []) + + response = keymaster.verify_response(response_did) + assert_equal(response["challenge"], challenge_did) + assert_equal(response["responder"], bob_id) + + +def test_groups(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + keymaster.set_current_id(alice) + + name = "test_group" + did = keymaster.create_group(name) + doc = keymaster.resolve_did(did) + assert_equal(doc["didDocument"]["id"], did) + assert_equal(doc["didDocument"]["controller"], alice_id) + + response = keymaster.list_groups() + assert did in response, "expected group not found in list_groups response" + + response = keymaster.add_group_member(did, alice_id) + assert_equal(response, True) + + response = keymaster.test_group(did, alice_id) + assert_equal(response, True) + + response = keymaster.get_group(did) + assert_equal(response["name"], name) + assert_equal(response["members"], [alice_id]) + + response = keymaster.remove_group_member(did, alice_id) + assert_equal(response, True) + + response = keymaster.get_group(did) + assert_equal(response["name"], name) + assert_equal(response["members"], []) + + +def test_rotate_keys(): + alice = generate_id() + keymaster.create_id(alice) + keymaster.set_current_id(alice) + + keymaster.rotate_keys() + wallet = keymaster.load_wallet() + assert_equal(wallet["ids"][alice]["index"], 1) + + +def test_signature(): + alice = generate_id() + keymaster.create_id(alice) + keymaster.set_current_id(alice) + + signed = keymaster.add_signature(str({})) + valid = keymaster.verify_signature(signed) + assert_equal(valid, True) + + +def test_polls(): + alice = generate_id() + alice_id = keymaster.create_id(alice) + keymaster.set_current_id(alice) + name = "test_group" + group = keymaster.create_group(name) + keymaster.add_group_member(group, alice_id) + + template = keymaster.poll_template() + template["roster"] = group + + poll = keymaster.create_poll(template) + ballot = keymaster.vote_poll(poll, 1) + response = keymaster.update_poll(ballot) + assert_equal(response, True) + response = keymaster.publish_poll(poll) + assert_equal(response, True) + response = keymaster.unpublish_poll(poll) + assert_equal(response, True) + response = keymaster.view_poll(poll) + assert_equal(response["results"]["ballots"][0]["voter"], alice_id) + + +def test_remove_ids(): + for identifier in generated_ids: + response = keymaster.remove_id(identifier) + assert_equal(response, True) + + +# Test and helper functions +def generate_id(): + + generated_ids.append( + "".join(random.choice(string.ascii_letters + string.digits) for _ in range(11)) + ) + return generated_ids[len(generated_ids) - 1] + + +def assert_equal(thing1, thing2): + if thing1 != thing2: + raise AssertionError(f"not({thing1} == {thing2})") diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index 2c24336e..00000000 --- a/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests==2.31.0