From 4ab4553af11fd90f5a06fe73249e520362bf2bed Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Mon, 9 Sep 2019 00:29:03 -0700 Subject: [PATCH] expand the pubkey to include the uuid from the identity... this is needd to be able to lookup the pubkey... In testing this, we verified the identity obj, and it exposed the issue that the ASN1DictCoder doesn't actually make canonical output (dict keys were not sorted), so fix this locally... --- ui/cli.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/ui/cli.py b/ui/cli.py index 41b441e..e472d5f 100644 --- a/ui/cli.py +++ b/ui/cli.py @@ -194,7 +194,18 @@ def _trytodict(o): except Exception: # pragma: no cover raise TypeError('unable to find __to_dict__ on %s: %s' % (type(o), `o`)) -_asn1coder = pasn1.ASN1DictCoder(coerce=_trytodict) +class CononicalCoder(pasn1.ASN1DictCoder): + def enc_dict(self, obj, **kwargs): + class FakeIter: + def iteritems(self): + itms = obj.items() + itms.sort() + + return iter(itms) + + return pasn1.ASN1DictCoder.enc_dict(self, FakeIter(), **kwargs) + +_asn1coder = CononicalCoder(coerce=_trytodict) class Persona(object): '''The object that represents a persona, or identity. It will @@ -216,6 +227,23 @@ class Persona(object): self._created_by_ref = self._identity.uuid + def __repr__(self): # pragma: no cover + r = '' % \ + (self._key is not None, self._pubkey is not None, + `self._identity`) + + return r + + @classmethod + def from_pubkey(cls, pubkeystr): + pubstr = base58.b58decode_check(pubkeystr) + + uuid, pubkey = _asn1coder.loads(pubstr) + + ident = Identity(uuid=uuid, pubkey=pubkey) + + return cls(ident) + def get_identity(self): '''Return the Identity object for this Persona.''' @@ -225,7 +253,10 @@ class Persona(object): '''Get a printable version of the public key. This is used for importing into different programs, or for shared.''' - return base58.b58encode_check(self._identity.pubkey) + idobj = self._identity + pubstr = _asn1coder.dumps([ idobj.uuid, idobj.pubkey ]) + + return base58.b58encode_check(pubstr) def new_version(self, *args): '''Update the Persona's Identity object.''' @@ -300,6 +331,8 @@ class Persona(object): def verify(self, obj): sigbytes = self._makesigbytes(obj) + pubkey = self._pubkey.public_bytes(Encoding.Raw, + PublicFormat.Raw) self._pubkey.verify(obj['sig'], sigbytes) return True @@ -551,6 +584,28 @@ def main(): if __name__ == '__main__': # pragma: no cover main() +class _TestCononicalCoder(unittest.TestCase): + def test_con(self): + obja = { 'foo': 23984732, 'a': 5, 'b': 6, 'something': '2398472398723498273dfasdfjlaksdfj' } + objaitems = obja.items() + objaitems.sort() + objb = dict(objaitems) + + self.assertEqual(obja, objb) + + # This is to make sure that item order changed + self.assertNotEqual(obja.items(), objb.items()) + + astr = pasn1.dumps(obja) + bstr = pasn1.dumps(objb) + + self.assertNotEqual(astr, bstr) + + astr = _asn1coder.dumps(obja) + bstr = _asn1coder.dumps(objb) + + self.assertEqual(astr, bstr) + class _TestCases(unittest.TestCase): def setUp(self): d = os.path.realpath(tempfile.mkdtemp()) @@ -724,11 +779,22 @@ class _TestCases(unittest.TestCase): self.assertIsInstance(idobj['pubkey'], str) # that get_pubkey returns the correct thing - self.assertEqual(persona.get_pubkey(), base58.b58encode_check(idobj['pubkey'])) + pubstr = _asn1coder.dumps([ idobj['uuid'], idobj['pubkey'] ]) + self.assertEqual(persona.get_pubkey(), + base58.b58encode_check(pubstr)) # and that there is a signature self.assertIsInstance(idobj['sig'], str) + # and that it can verify itself + persona.verify(idobj) + + # and that a new persona can be created from the pubkey + pkpersona = Persona.from_pubkey(persona.get_pubkey()) + + # and that it can verify the old identity + self.assertTrue(pkpersona.verify(idobj)) + # that a second time, it raises an exception self.assertRaises(RuntimeError, persona.generate_key)