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)