From 3a5a0fb5722ffef81ddbab086e30c808d463578b Mon Sep 17 00:00:00 2001 From: heartsucker Date: Thu, 16 May 2019 16:21:12 +0200 Subject: [PATCH] replaced uses of pycryptopp's ed25519 with our own --- src/allmydata/client.py | 4 +- src/allmydata/crypto/ed25519.py | 75 ++++++++++++++++++++++++++++++ src/allmydata/introducer/common.py | 2 +- src/allmydata/test/cli/test_cli.py | 7 ++- src/allmydata/util/keyutil.py | 39 ---------------- 5 files changed, 81 insertions(+), 46 deletions(-) delete mode 100644 src/allmydata/util/keyutil.py diff --git a/src/allmydata/client.py b/src/allmydata/client.py index 2333a35b7..8297675ee 100644 --- a/src/allmydata/client.py +++ b/src/allmydata/client.py @@ -489,7 +489,7 @@ class _Client(node.Node, pollmixin.PollMixin): def get_long_nodeid(self): # this matches what IServer.get_longname() says about us elsewhere - vk_bytes = self._node_key.get_verifying_key_bytes() + vk_bytes = self._node_key.public_key().public_bytes() return "v0-"+base32.b2a(vk_bytes) def get_long_tubid(self): @@ -511,7 +511,7 @@ class _Client(node.Node, pollmixin.PollMixin): else: # otherwise, we're free to use the more natural seed of our # pubkey-based serverid - vk_bytes = self._node_key.get_verifying_key_bytes() + vk_bytes = self._node_key.public_key().public_bytes() seed = base32.b2a(vk_bytes) self.config.write_config_file("permutation-seed", seed+"\n") return seed.strip() diff --git a/src/allmydata/crypto/ed25519.py b/src/allmydata/crypto/ed25519.py index 5edf72108..646a6393a 100644 --- a/src/allmydata/crypto/ed25519.py +++ b/src/allmydata/crypto/ed25519.py @@ -1,9 +1,48 @@ +''' +Ed25519 keys and helpers. + +Key Formatting +-------------- + +- in base32, keys are 52 chars long (both signing and verifying keys) +- in base62, keys is 43 chars long +- in base64, keys is 43 chars long + +We can't use base64 because we want to reserve punctuation and preserve +cut-and-pasteability. The base62 encoding is shorter than the base32 form, +but the minor usability improvement is not worth the documentation and +specification confusion of using a non-standard encoding. So we stick with +base32. +''' + import six from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, \ PublicFormat +# When we were still using `pycryptopp`, BadSignatureError was the name of the exception, and now +# that we've switched to `cryptography`, we are importing and "re-exporting" this to keep the name +# the same (for now). +from cryptography.exceptions import InvalidSignature +BadSignatureError = InvalidSignature +del InvalidSignature + +from allmydata.util.base32 import a2b, b2a + +_PRIV_PREFIX = 'priv-v0-' +_PUB_PREFIX = 'pub-v0-' + + +class BadPrefixError(Exception): + pass + + +def _remove_prefix(s_bytes, prefix): + if not s_bytes.startswith(prefix): + raise BadPrefixError("did not see expected '%s' prefix" % (prefix,)) + return s_bytes[len(prefix):] + class SigningKey: @@ -37,6 +76,24 @@ class SigningKey: raise ValueError('data must be bytes') return self._priv_key.sign(data) + @classmethod + def parse_encoded_key(cls, priv_str): + global _PRIV_PREFIX + return cls.from_private_bytes(a2b(_remove_prefix(priv_str, _PRIV_PREFIX))) + + def encoded_key(self): + global _PRIV_PREFIX + return _PRIV_PREFIX + a2b(self.private_bytes()) + + def __eq__(self, other): + if isinstance(other, type(self)): + return self.private_bytes() == other.private_bytes() + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + class VerifyingKey: @@ -62,3 +119,21 @@ class VerifyingKey: raise ValueError('data must be bytes') self._pub_key.verify(signature, data) + + @classmethod + def parse_encoded_key(cls, pub_str): + global _PUB_PREFIX + return cls.from_public_bytes(a2b(_remove_prefix(pub_str, _PUB_PREFIX))) + + def encoded_key(self): + global _PUB_PREFIX + return _PUB_PREFIX + a2b(self.public_bytes()) + + def __eq__(self, other): + if isinstance(other, type(self)): + return self.public_bytes() == other.public_bytes() + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/src/allmydata/introducer/common.py b/src/allmydata/introducer/common.py index 7ef67d652..f367ba829 100644 --- a/src/allmydata/introducer/common.py +++ b/src/allmydata/introducer/common.py @@ -19,7 +19,7 @@ def sign_to_foolscap(ann, sk): # pubkey:v0-b64(pubkey)}) . msg = json.dumps(ann).encode("utf-8") sig = "v0-"+base32.b2a(sk.sign(msg)) - vk_bytes = sk.get_verifying_key_bytes() + vk_bytes = sk.public_key().public_bytes() ann_t = (msg, sig, "v0-"+base32.b2a(vk_bytes)) return ann_t diff --git a/src/allmydata/test/cli/test_cli.py b/src/allmydata/test/cli/test_cli.py index 3f695f414..657a991b3 100644 --- a/src/allmydata/test/cli/test_cli.py +++ b/src/allmydata/test/cli/test_cli.py @@ -1,4 +1,3 @@ - import os.path from six.moves import cStringIO as StringIO import urllib, sys @@ -11,6 +10,7 @@ from twisted.internet import task from twisted.python.filepath import FilePath import allmydata +from allmydata.crypto import ed25519 from allmydata.util import fileutil, hashutil, base32, keyutil from allmydata.util.namespace import Namespace from allmydata import uri @@ -18,7 +18,6 @@ from allmydata.immutable import upload from allmydata.dirnode import normalize from allmydata.scripts.common_http import socket_error import allmydata.scripts.common_http -from pycryptopp.publickey import ed25519 # Test that the scripts can be imported. from allmydata.scripts import create_node, debug, tahoe_start, tahoe_restart, \ @@ -735,9 +734,9 @@ class Admin(unittest.TestCase): self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0]) self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1]) sk_bytes = base32.a2b(keyutil.remove_prefix(privkey_bits[1], "priv-v0-")) - sk = ed25519.SigningKey(sk_bytes) + sk = ed25519.SigningKey.from_private_bytes(sk_bytes) vk_bytes = base32.a2b(keyutil.remove_prefix(pubkey_bits[1], "pub-v0-")) - self.failUnlessEqual(sk.get_verifying_key_bytes(), vk_bytes) + self.failUnlessEqual(sk.public_key().public_bytes(), vk_bytes) d.addCallback(_done) return d diff --git a/src/allmydata/util/keyutil.py b/src/allmydata/util/keyutil.py deleted file mode 100644 index ee28bd746..000000000 --- a/src/allmydata/util/keyutil.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -from pycryptopp.publickey import ed25519 -from allmydata.util.base32 import a2b, b2a - -BadSignatureError = ed25519.BadSignatureError - -class BadPrefixError(Exception): - pass - -def remove_prefix(s_bytes, prefix): - if not s_bytes.startswith(prefix): - raise BadPrefixError("did not see expected '%s' prefix" % (prefix,)) - return s_bytes[len(prefix):] - -# in base32, keys are 52 chars long (both signing and verifying keys) -# in base62, keys is 43 chars long -# in base64, keys is 43 chars long -# -# We can't use base64 because we want to reserve punctuation and preserve -# cut-and-pasteability. The base62 encoding is shorter than the base32 form, -# but the minor usability improvement is not worth the documentation and -# specification confusion of using a non-standard encoding. So we stick with -# base32. - -def make_keypair(): - sk_bytes = os.urandom(32) - sk = ed25519.SigningKey(sk_bytes) - vk_bytes = sk.get_verifying_key_bytes() - return ("priv-v0-"+b2a(sk_bytes), "pub-v0-"+b2a(vk_bytes)) - -def parse_privkey(privkey_vs): - sk_bytes = a2b(remove_prefix(privkey_vs, "priv-v0-")) - sk = ed25519.SigningKey(sk_bytes) - vk_bytes = sk.get_verifying_key_bytes() - return (sk, "pub-v0-"+b2a(vk_bytes)) - -def parse_pubkey(pubkey_vs): - vk_bytes = a2b(remove_prefix(pubkey_vs, "pub-v0-")) - return ed25519.VerifyingKey(vk_bytes)