From 0399f51229cc15a0eb43341b68564e8cf72d08a6 Mon Sep 17 00:00:00 2001 From: Stefan Kaestle Date: Fri, 4 Mar 2022 11:46:06 +0100 Subject: [PATCH 1/4] Support for signing request with delegations --- ic/agent.py | 12 ++++++++++++ ic/identity.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ic/agent.py b/ic/agent.py index 5e61fd8..5ede520 100644 --- a/ic/agent.py +++ b/ic/agent.py @@ -17,6 +17,15 @@ def sign_request(req, iden): 'sender_pubkey': sig[0], 'sender_sig': sig[1] } + + if iden.delegation != None: + print(colored(f"Signing request with delegation", "blue")) + assert(envelop["sender_pubkey"] == iden.sender_pubkey) + envelop.update({ + 'sender_delegation': [iden.delegation], + }) + print(colored(envelop, "blue")) + return req_id, cbor2.dumps(envelop) # According to did, get the method returned param type @@ -49,6 +58,7 @@ def get_expiry_date(self): def query_endpoint(self, canister_id, data): ret = self.client.query(canister_id, data) + print(ret) return cbor2.loads(ret) def call_endpoint(self, canister_id, request_id, data): @@ -71,6 +81,7 @@ def query_raw(self, canister_id, method_name, *arg): } _, data = sign_request(req, self.identity) result = self.query_endpoint(canister_id, data) + print(result) if result['status'] == 'replied': if len(arg) == 1: res = decode(result['reply']['arg']) @@ -114,6 +125,7 @@ def read_state_raw(self, canister_id, paths): _, data = sign_request(req, self.identity) ret = self.read_state_endpoint(canister_id, data) d = cbor2.loads(ret) + print(d) cert = cbor2.loads(d['certificate']) return cert diff --git a/ic/identity.py b/ic/identity.py index f71384f..ed096a8 100644 --- a/ic/identity.py +++ b/ic/identity.py @@ -4,6 +4,8 @@ from .principal import Principal import ecdsa +from termcolor import colored + class Identity: def __init__(self, privkey = "", type = "ed25519", anonymous = False): privkey = bytes(bytearray.fromhex(privkey)) @@ -32,6 +34,11 @@ def __init__(self, privkey = "", type = "ed25519", anonymous = False): else: raise 'unsupported identity type' + self.delegation = None + self.delegation_principal = None + self.session_key = None + self.sender_pubkey = None + @staticmethod def from_pem(pem: str): key = ecdsa.SigningKey.from_pem(pem) @@ -50,11 +57,25 @@ def to_pem(self): def sender(self): if self.anonymous: return Principal.anonymous() + elif self.delegation: + print(colored("Returning sender {self.delegation_principal} for delegation", "blue")) + return self.delegation_principal return Principal.self_authenticating(self._der_pubkey) + def add_delegation(self, delegation, delegation_principal, session_key, sender_pubkey): + self.delegation = delegation + self.delegation_principal = delegation_principal + self.session_key = session_key + self.sender_pubkey = sender_pubkey + def sign(self, msg: bytes): if self.anonymous: return (None, None) + if self.session_key: + print(colored(f"Using session key {self.session_key} to sign message", "blue")) + sig = self.session_key.sign(msg) + # First element of tuple is used as sender_pubkey + return (self.sender_pubkey, sig) if self.key_type == 'ed25519': sig = self.sk.sign(msg) return (self._der_pubkey, sig) From c6d609c8ad686482f0825f2cec063d942e0cff47 Mon Sep 17 00:00:00 2001 From: Stefan Kaestle Date: Fri, 4 Mar 2022 13:39:12 +0100 Subject: [PATCH 2/4] Minor --- ic/agent.py | 2 +- ic/identity.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ic/agent.py b/ic/agent.py index 5ede520..8290e0b 100644 --- a/ic/agent.py +++ b/ic/agent.py @@ -24,8 +24,8 @@ def sign_request(req, iden): envelop.update({ 'sender_delegation': [iden.delegation], }) - print(colored(envelop, "blue")) + print(colored(envelop, "yellow")) return req_id, cbor2.dumps(envelop) # According to did, get the method returned param type diff --git a/ic/identity.py b/ic/identity.py index ed096a8..7bef61d 100644 --- a/ic/identity.py +++ b/ic/identity.py @@ -37,7 +37,7 @@ def __init__(self, privkey = "", type = "ed25519", anonymous = False): self.delegation = None self.delegation_principal = None self.session_key = None - self.sender_pubkey = None + self.delegation_sender_pubkey = None @staticmethod def from_pem(pem: str): @@ -58,15 +58,16 @@ def sender(self): if self.anonymous: return Principal.anonymous() elif self.delegation: - print(colored("Returning sender {self.delegation_principal} for delegation", "blue")) - return self.delegation_principal + principal = self.delegation_principal + print(colored(f"Returning sender {principal} for delegation", "blue")) + return principal return Principal.self_authenticating(self._der_pubkey) - def add_delegation(self, delegation, delegation_principal, session_key, sender_pubkey): + def add_delegation(self, delegation, delegation_principal, session_key, delegation_sender_pubkey): self.delegation = delegation self.delegation_principal = delegation_principal self.session_key = session_key - self.sender_pubkey = sender_pubkey + self.delegation_sender_pubkey = delegation_sender_pubkey def sign(self, msg: bytes): if self.anonymous: @@ -74,8 +75,7 @@ def sign(self, msg: bytes): if self.session_key: print(colored(f"Using session key {self.session_key} to sign message", "blue")) sig = self.session_key.sign(msg) - # First element of tuple is used as sender_pubkey - return (self.sender_pubkey, sig) + return (self.delegation_sender_pubkey, sig) if self.key_type == 'ed25519': sig = self.sk.sign(msg) return (self._der_pubkey, sig) From bc5ce112e249efb495d2763f986fa5957ebaaa34 Mon Sep 17 00:00:00 2001 From: Stefan Kaestle Date: Fri, 4 Mar 2022 17:33:23 +0100 Subject: [PATCH 3/4] Lots of smaller little improvements and fixes --- ic/agent.py | 15 ++++++++++++--- ic/identity.py | 2 +- ic/utils.py | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ic/agent.py b/ic/agent.py index 8290e0b..c2ef4e3 100644 --- a/ic/agent.py +++ b/ic/agent.py @@ -6,6 +6,7 @@ from .constants import * from .utils import to_request_id from .certificate import lookup +import hashlib def sign_request(req, iden): @@ -17,16 +18,24 @@ def sign_request(req, iden): 'sender_pubkey': sig[0], 'sender_sig': sig[1] } - + if iden.delegation != None: + print("Delegation representation independent hash", + iden.delegation["delegation"], + hashlib.sha256(to_request_id(iden.delegation["delegation"])).digest().hex()) print(colored(f"Signing request with delegation", "blue")) - assert(envelop["sender_pubkey"] == iden.sender_pubkey) envelop.update({ 'sender_delegation': [iden.delegation], }) + envelop["sender_delegation"][0]["signature"] = bytes(envelop["sender_delegation"][0]["signature"]) + envelop["sender_delegation"][0]["delegation"]["pubkey"] = \ + bytes(envelop["sender_delegation"][0]["delegation"]["pubkey"]) + print(colored("Sender public key for delegation " + bytes(iden.delegation_sender_pubkey).hex(), "yellow")) print(colored(envelop, "yellow")) - return req_id, cbor2.dumps(envelop) + c = cbor2.dumps(envelop) + print("cbord encoded", c.hex()) + return req_id, c # According to did, get the method returned param type def getType(method:str): diff --git a/ic/identity.py b/ic/identity.py index 7bef61d..62dabb7 100644 --- a/ic/identity.py +++ b/ic/identity.py @@ -75,7 +75,7 @@ def sign(self, msg: bytes): if self.session_key: print(colored(f"Using session key {self.session_key} to sign message", "blue")) sig = self.session_key.sign(msg) - return (self.delegation_sender_pubkey, sig) + return (bytes(self.delegation_sender_pubkey), sig) if self.key_type == 'ed25519': sig = self.sk.sign(msg) return (self._der_pubkey, sig) diff --git a/ic/utils.py b/ic/utils.py index 66bce0c..7cd7b30 100755 --- a/ic/utils.py +++ b/ic/utils.py @@ -34,6 +34,8 @@ def to_request_id(d): pass vec = [] for k, v in d.items(): + if isinstance(v, dict): + v = to_request_id(v) if isinstance(v, list): v = encode_list(v) if isinstance(v, int): From e099e6071d6da8e1cf4e6dfd9858fadf1ff5f03c Mon Sep 17 00:00:00 2001 From: Stefan Kaestle Date: Thu, 10 Mar 2022 13:49:37 +0100 Subject: [PATCH 4/4] Lots of extra debug output --- ic/agent.py | 56 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/ic/agent.py b/ic/agent.py index c2ef4e3..c778752 100644 --- a/ic/agent.py +++ b/ic/agent.py @@ -7,7 +7,8 @@ from .utils import to_request_id from .certificate import lookup import hashlib - +import re +from termcolor import colored def sign_request(req, iden): req_id = to_request_id(req) @@ -27,14 +28,30 @@ def sign_request(req, iden): envelop.update({ 'sender_delegation': [iden.delegation], }) + + # Need to byte-encode various fields envelop["sender_delegation"][0]["signature"] = bytes(envelop["sender_delegation"][0]["signature"]) envelop["sender_delegation"][0]["delegation"]["pubkey"] = \ bytes(envelop["sender_delegation"][0]["delegation"]["pubkey"]) - print(colored("Sender public key for delegation " + bytes(iden.delegation_sender_pubkey).hex(), "yellow")) + + print(colored("Sender public key for delegation " + \ + bytes(iden.delegation_sender_pubkey).hex(), "yellow")) + print(colored("Sender delegation signature " \ + + bytes(envelop["sender_delegation"][0]["signature"]).hex(), "yellow")) + print(colored("Sender delegation signature, decoded cbor: ", "red"), + cbor2.loads(bytes(envelop["sender_delegation"][0]["signature"]))) + print(colored("Sender delegation delegation pubkey " \ + + bytes(envelop["sender_delegation"][0]["delegation"]["pubkey"]).hex(), "yellow")) + print(colored("sender_sig " \ + + bytes(sig[1]).hex(), "yellow")) + print(colored("sender_pubkey " \ + + bytes(sig[0]).hex(), "yellow")) print(colored(envelop, "yellow")) + print("cbor encoding", colored("evenlop", "red"), envelop) + c = cbor2.dumps(envelop) - print("cbord encoded", c.hex()) + print("cbord encoded", colored("evenlop", "red"), c.hex()) return req_id, c # According to did, get the method returned param type @@ -67,7 +84,27 @@ def get_expiry_date(self): def query_endpoint(self, canister_id, data): ret = self.client.query(canister_id, data) - print(ret) + print("query_endpoint ret", ret) + try: + ret_str = ret.decode('utf-8') + if 'Failed to authenticate' in ret_str: + print('Failed to authenticate .. analysing string', ret_str) + + m = re.search("signature d9d9[a-z0-9]*", ret_str) + if m: + cbor = m[0][len("signature")+1:] + cbor_decoded = cbor2.loads(bytes.fromhex(cbor)) + print("CBOR ", cbor_decoded) + print(bytes(cbor_decoded["certificate"]).hex()) + + # Certificate is naother CBOR encoded value + inner_cbor = cbor2.loads(bytes(cbor_decoded["certificate"])) + print("tree", inner_cbor["tree"][1]) + print("signature", bytes(inner_cbor["signature"]).hex()) + print(inner_cbor.keys()) + + except (UnicodeDecodeError, AttributeError): + pass return cbor2.loads(ret) def call_endpoint(self, canister_id, request_id, data): @@ -90,7 +127,10 @@ def query_raw(self, canister_id, method_name, *arg): } _, data = sign_request(req, self.identity) result = self.query_endpoint(canister_id, data) - print(result) + print("result in query_raw", result) + # result in query_raw {'status': 'replied', 'reply': {'arg': b'DIDL\ + if isinstance(result, dict) and "reply" in result: + print('candid encoded result, which we decode later', result["reply"]["arg"]) if result['status'] == 'replied': if len(arg) == 1: res = decode(result['reply']['arg']) @@ -111,8 +151,8 @@ def update_raw(self, canister_id, method_name, *arg): 'ingress_expiry': self.get_expiry_date() } req_id, data = sign_request(req, self.identity) - _ = self.call_endpoint(canister_id, req_id, data) - # print('update.req_id:', req_id.hex()) + req = self.call_endpoint(canister_id, req_id, data) + print(colored('update_raw - req', 'blue'), req) status, result = self.poll(canister_id, req_id) if status != 'replied': return status @@ -149,7 +189,7 @@ def request_status_raw(self, canister_id, req_id): else: return status.decode(), cert - def poll(self, canister_id, req_id, delay=1, timeout=10): + def poll(self, canister_id, req_id, delay=1, timeout=30): status = None for _ in wait(delay, timeout): status, cert = self.request_status_raw(canister_id, req_id)