refactor ed25519 helpers to functional style

eliminates the wrapper classes and uses some more-explicit
names throughout (e.g "sk" -> "signing_key")
This commit is contained in:
meejah 2019-06-13 22:17:58 -06:00
parent 49b7756a8b
commit 47ccdb0177
7 changed files with 300 additions and 186 deletions

View File

@ -11,8 +11,7 @@ from twisted.python.filepath import FilePath
from twisted.python.failure import Failure from twisted.python.failure import Failure
import allmydata import allmydata
from allmydata.crypto.ed25519 import SigningKey from allmydata.crypto import rsa, ed25519
from allmydata.crypto import rsa
from allmydata.storage.server import StorageServer from allmydata.storage.server import StorageServer
from allmydata import storage_client from allmydata import storage_client
from allmydata.immutable.upload import Uploader from allmydata.immutable.upload import Uploader
@ -479,19 +478,20 @@ class _Client(node.Node, pollmixin.PollMixin):
# we only create the key once. On all subsequent runs, we re-use the # we only create the key once. On all subsequent runs, we re-use the
# existing key # existing key
def _make_key(): def _make_key():
priv_key = SigningKey.generate() private_key, _ = ed25519.create_signing_keypair()
return priv_key.encoded_key() + "\n" return ed25519.string_from_signing_key(private_key) + "\n"
priv_key_str = self.config.get_or_create_private_config("node.privkey", _make_key) private_key_str = self.config.get_or_create_private_config("node.privkey", _make_key)
priv_key = SigningKey.parse_encoded_key(priv_key_str) private_key, public_key = ed25519.signing_keypair_from_string(private_key_str)
pub_key_str = priv_key.public_key().encoded_key() public_key_str = ed25519.string_from_verifying_key(public_key)
self.config.write_config_file("node.pubkey", pub_key_str + "\n") self.config.write_config_file("node.pubkey", public_key_str + "\n")
self._node_key = priv_key self._node_private_key = private_key
self._node_public_key = public_key
def get_long_nodeid(self): def get_long_nodeid(self):
# this matches what IServer.get_longname() says about us elsewhere # this matches what IServer.get_longname() says about us elsewhere
vk_bytes = self._node_key.public_key().public_bytes() vk_bytes = ed25519.bytes_from_verifying_key(self._node_public_key)
return "v0-"+base32.b2a(vk_bytes) return "v0-" + base32.b2a(vk_bytes)
def get_long_tubid(self): def get_long_tubid(self):
return idlib.nodeid_b2a(self.nodeid) return idlib.nodeid_b2a(self.nodeid)
@ -512,7 +512,7 @@ class _Client(node.Node, pollmixin.PollMixin):
else: else:
# otherwise, we're free to use the more natural seed of our # otherwise, we're free to use the more natural seed of our
# pubkey-based serverid # pubkey-based serverid
vk_bytes = self._node_key.public_key().public_bytes() vk_bytes = ed25519.bytes_from_verifying_key(self._node_public_key)
seed = base32.b2a(vk_bytes) seed = base32.b2a(vk_bytes)
self.config.write_config_file("permutation-seed", seed+"\n") self.config.write_config_file("permutation-seed", seed+"\n")
return seed.strip() return seed.strip()
@ -583,7 +583,7 @@ class _Client(node.Node, pollmixin.PollMixin):
"permutation-seed-base32": self._init_permutation_seed(ss), "permutation-seed-base32": self._init_permutation_seed(ss),
} }
for ic in self.introducer_clients: for ic in self.introducer_clients:
ic.publish("storage", ann, self._node_key) ic.publish("storage", ann, self._node_private_key)
def init_client(self): def init_client(self):
helper_furl = self.config.get_config("client", "helper.furl", None) helper_furl = self.config.get_config("client", "helper.furl", None)

View File

@ -44,95 +44,185 @@ PRIVATE_KEY_PREFIX = 'priv-v0-'
PUBLIC_KEY_PREFIX = 'pub-v0-' PUBLIC_KEY_PREFIX = 'pub-v0-'
class SigningKey: def create_signing_keypair():
"""
Creates a new ed25519 keypair.
def __init__(self, priv_key): :returns: 2-tuple of (private_key, public_key)
if not isinstance(priv_key, Ed25519PrivateKey): """
raise ValueError('priv_key must be an Ed25519PrivateKey') private_key = Ed25519PrivateKey.generate()
self._priv_key = priv_key return private_key, private_key.public_key()
@classmethod
def generate(cls):
return cls(Ed25519PrivateKey.generate())
@classmethod
def from_private_bytes(cls, priv_bytes):
if not isinstance(priv_bytes, six.binary_type):
raise ValueError('priv_bytes must be bytes')
return SigningKey(Ed25519PrivateKey.from_private_bytes(priv_bytes))
def private_bytes(self):
return self._priv_key.private_bytes(
Encoding.Raw,
PrivateFormat.Raw,
NoEncryption(),
)
def public_key(self):
return VerifyingKey(self._priv_key.public_key())
def sign(self, data):
if not isinstance(data, six.binary_type):
raise ValueError('data must be bytes')
return self._priv_key.sign(data)
@classmethod
def parse_encoded_key(cls, priv_str):
return cls.from_private_bytes(a2b(remove_prefix(priv_str, PRIVATE_KEY_PREFIX)))
def encoded_key(self):
return PRIVATE_KEY_PREFIX + b2a(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: def signing_keypair_from_bytes(private_bytes):
"""
Creates a ed25519 keypair from serialized bytes
def __init__(self, pub_key): :returns: 2-tuple of (private_key, public_key)
if not isinstance(pub_key, Ed25519PublicKey): """
raise ValueError('pub_key must be an Ed25519PublicKey')
self._pub_key = pub_key
@classmethod if not isinstance(private_bytes, six.binary_type):
def from_public_bytes(cls, pub_bytes): raise ValueError('private_bytes must be bytes')
if not isinstance(pub_bytes, six.binary_type): private_key = Ed25519PrivateKey.from_private_bytes(private_bytes)
raise ValueError('pub_bytes must be bytes') return private_key, private_key.public_key()
return cls(Ed25519PublicKey.from_public_bytes(pub_bytes))
def public_bytes(self):
return self._pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
def verify(self, signature, data): def bytes_from_signing_key(private_key):
if not isinstance(signature, six.binary_type): """
raise ValueError('signature must be bytes') Turn a private signing key into serialized bytes
"""
_validate_private_key(private_key)
return private_key.private_bytes(
Encoding.Raw,
PrivateFormat.Raw,
NoEncryption(),
)
if not isinstance(data, six.binary_type):
raise ValueError('data must be bytes')
try: def verifying_key_from_signing_key(private_key):
self._pub_key.verify(signature, data) """
except InvalidSignature: :returns: the public key associated to the given `private_key`
raise BadSignature """
_validate_private_key(private_key)
return private_key.public_key()
@classmethod
def parse_encoded_key(cls, pub_str):
return cls.from_public_bytes(a2b(remove_prefix(pub_str, PUBLIC_KEY_PREFIX)))
def encoded_key(self): def sign_data(private_key, data):
return PUBLIC_KEY_PREFIX + b2a(self.public_bytes()) """
Sign the given data using the given private key
def __eq__(self, other): :param private_key: the private part returned from
if isinstance(other, type(self)): `create_signing_keypair` or from
return self.public_bytes() == other.public_bytes() `signing_keypair_from_bytes`
else:
return False
def __ne__(self, other): :param bytes data: the data to sign
return not self.__eq__(other)
:returns: bytes representing the signature
"""
_validate_private_key(private_key)
if not isinstance(data, six.binary_type):
raise ValueError('data must be bytes')
return private_key.sign(data)
def string_from_signing_key(private_key):
"""
Encode a private key to a string
:param private_key: the private part returned from
`create_signing_keypair` or from
`signing_keypair_from_bytes`
:returns: string representing this key
"""
_validate_private_key(private_key)
return PRIVATE_KEY_PREFIX + b2a(bytes_from_signing_key(private_key))
def signing_keypair_from_string(private_key_str):
"""
Load a signing keypair from a string
:returns: a 2-tuple of (private_key, public_key)
"""
return signing_keypair_from_bytes(
a2b(remove_prefix(private_key_str, PRIVATE_KEY_PREFIX))
)
def verifying_key_from_string(public_key_str):
"""
Load a verifying key from a string
:returns: a public_key
"""
def verifying_key_from_bytes(public_key_bytes):
"""
Load a verifying key from bytes
:returns: a public_key
"""
if not isinstance(public_key_bytes, six.binary_type):
raise ValueError('public_key_bytes must be bytes')
return Ed25519PublicKey.from_public_bytes(public_key_bytes)
def bytes_from_verifying_key(public_key):
"""
Encode a verifying key to bytes.
:param public_key: the public part of a key returned from
`create_signing_keypair` or from
`signing_keypair_from_bytes`
:returns: bytes representing this key
"""
_validate_public_key(public_key)
return public_key.public_bytes(
Encoding.Raw,
PublicFormat.Raw,
)
def verify_signature(public_key, alleged_signature, data):
"""
:param public_key: a verifying key
:param bytes alleged_signature: the bytes of the alleged signature
:param bytes data: the data which was allegedly signed
:returns: None, or raises an exception if the signature is bad.
"""
if not isinstance(alleged_signature, six.binary_type):
raise ValueError('alleged_signature must be bytes')
if not isinstance(data, six.binary_type):
raise ValueError('data must be bytes')
_validate_public_key(public_key)
try:
public_key.verify(alleged_signature, data)
except InvalidSignature:
raise BadSignature
def verifying_key_from_string(public_key_str):
return verifying_key_from_bytes(
a2b(remove_prefix(public_key_str, PUBLIC_KEY_PREFIX))
)
def string_from_verifying_key(public_key):
"""
Encode a public key to a string
:param public_key: the public part of a keypair
:returns: string representing this key
"""
_validate_public_key(public_key)
return PUBLIC_KEY_PREFIX + b2a(bytes_from_verifying_key(public_key))
def _validate_public_key(public_key):
"""
Internal helper. Verify that `public_key` is an appropriate object
"""
if not isinstance(public_key, Ed25519PublicKey):
raise ValueError('public_key must be an Ed25519PublicKey')
return None
def _validate_private_key(private_key):
"""
Internal helper. Verify that `private_key` is an appropriate object
"""
if not isinstance(private_key, Ed25519PrivateKey):
raise ValueError('private_key must be an Ed25519PrivateKey')
return None

View File

@ -1,7 +1,7 @@
import re import re
import json import json
from allmydata import crypto from allmydata import crypto
from allmydata.crypto.ed25519 import VerifyingKey from allmydata.crypto import ed25519
from allmydata.util import base32, rrefutil from allmydata.util import base32, rrefutil
@ -15,14 +15,26 @@ def get_tubid_string(furl):
return m.group(1).lower() return m.group(1).lower()
def sign_to_foolscap(ann, sk): def sign_to_foolscap(announcement, signing_key):
"""
:param signing_key: a (private) signing key, as returned from
e.g. :func:`allmydata.crypto.ed25519.signing_keypair_from_string`
:returns: 3-tuple of (msg, sig, vk) where msg is a UTF8 JSON
serialization of the `announcement`, sig is bytes (a signature of
msg) and vk is the verifying key
"""
# return (bytes, sig-str, pubkey-str). A future HTTP-based serialization # return (bytes, sig-str, pubkey-str). A future HTTP-based serialization
# will use JSON({msg:b64(JSON(msg).utf8), sig:v0-b64(sig), # will use JSON({msg:b64(JSON(msg).utf8), sig:v0-b64(sig),
# pubkey:v0-b64(pubkey)}) . # pubkey:v0-b64(pubkey)}) .
msg = json.dumps(ann).encode("utf-8") msg = json.dumps(announcement).encode("utf-8")
sig = "v0-"+base32.b2a(sk.sign(msg)) sig = "v0-" + base32.b2a(
vk_bytes = sk.public_key().public_bytes() ed25519.sign_data(signing_key, msg)
ann_t = (msg, sig, "v0-"+base32.b2a(vk_bytes)) )
verifying_key_bytes = ed25519.bytes_from_verifying_key(
ed25519.verifying_key_from_signing_key(signing_key)
)
ann_t = (msg, sig, "v0-" + base32.b2a(verifying_key_bytes))
return ann_t return ann_t
@ -39,9 +51,9 @@ def unsign_from_foolscap(ann_t):
if not claimed_key_vs.startswith("v0-"): if not claimed_key_vs.startswith("v0-"):
raise UnknownKeyError("only v0- keys recognized") raise UnknownKeyError("only v0- keys recognized")
claimed_key = VerifyingKey.parse_encoded_key("pub-" + claimed_key_vs) claimed_key = ed25519.verifying_key_from_string("pub-" + claimed_key_vs)
sig_bytes = base32.a2b(crypto.remove_prefix(sig_vs, "v0-")) sig_bytes = base32.a2b(crypto.remove_prefix(sig_vs, "v0-"))
claimed_key.verify(sig_bytes, msg) ed25519.verify_signature(claimed_key, sig_bytes, msg)
key_vs = claimed_key_vs key_vs = claimed_key_vs
ann = json.loads(msg.decode("utf-8")) ann = json.loads(msg.decode("utf-8"))
return (ann, key_vs) return (ann, key_vs)

View File

@ -14,11 +14,11 @@ Generate a public/private keypair, dumped to stdout as two lines of ASCII..
return t return t
def print_keypair(options): def print_keypair(options):
from allmydata.crypto.ed25519 import SigningKey from allmydata.crypto import ed25519
out = options.stdout out = options.stdout
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
print("private:", priv_key.encoded_key(), file=out) print("private:", ed25519.string_from_signing_key(private_key), file=out)
print("public:", priv_key.public_key().encoded_key(), file=out) print("public:", ed25519.string_from_verifying_key(public_key), file=out)
class DerivePubkeyOptions(BaseOptions): class DerivePubkeyOptions(BaseOptions):
def parseArgs(self, privkey): def parseArgs(self, privkey):
@ -38,11 +38,11 @@ generate-keypair, derive the public key and print it to stdout.
def derive_pubkey(options): def derive_pubkey(options):
out = options.stdout out = options.stdout
from allmydata.crypto.ed25519 import SigningKey from allmydata.crypto import ed25519
privkey_vs = options.privkey privkey_vs = options.privkey
priv_key = SigningKey.parse_encoded_key(privkey_vs) private_key, public_key = ed25519.signing_keypair_from_string(privkey_vs)
print("private:", priv_key.encoded_key(), file=out) print("private:", ed25519.string_from_signing_key(private_key), file=out)
print("public:", priv_key.public_key().encoded_key(), file=out) print("public:", ed25519.string_from_verifying_key(public_key), file=out)
return 0 return 0
class AdminCommand(BaseOptions): class AdminCommand(BaseOptions):

View File

@ -735,16 +735,19 @@ class Admin(unittest.TestCase):
self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0]) self.failUnless(privkey_bits[1].startswith("priv-v0-"), lines[0])
self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1]) self.failUnless(pubkey_bits[1].startswith("pub-v0-"), lines[1])
sk_bytes = base32.a2b(crypto.remove_prefix(privkey_bits[1], "priv-v0-")) sk_bytes = base32.a2b(crypto.remove_prefix(privkey_bits[1], "priv-v0-"))
sk = ed25519.SigningKey.from_private_bytes(sk_bytes) sk, pk = ed25519.signing_keypair_from_bytes(sk_bytes)
vk_bytes = base32.a2b(crypto.remove_prefix(pubkey_bits[1], "pub-v0-")) vk_bytes = base32.a2b(crypto.remove_prefix(pubkey_bits[1], "pub-v0-"))
self.failUnlessEqual(sk.public_key().public_bytes(), vk_bytes) self.failUnlessEqual(
ed25519.bytes_from_verifying_key(pk),
vk_bytes,
)
d.addCallback(_done) d.addCallback(_done)
return d return d
def test_derive_pubkey(self): def test_derive_pubkey(self):
priv_key = ed25519.SigningKey.generate() priv_key, pub_key = ed25519.create_signing_keypair()
priv_key_str = priv_key.encoded_key() priv_key_str = ed25519.string_from_signing_key(priv_key)
pub_key_str = priv_key.public_key().encoded_key() pub_key_str = ed25519.string_from_verifying_key(pub_key)
d = run_cli("admin", "derive-pubkey", priv_key_str) d = run_cli("admin", "derive-pubkey", priv_key_str)
def _done(args): def _done(args):
(rc, stdout, stderr) = args (rc, stdout, stderr) = args

View File

@ -194,15 +194,21 @@ class TestRegression(unittest.TestCase):
b'\x86 ier!\xe8\xe5#*\x9d\x8c\x0bI\x02\xd90\x0e7\xbeW\xbf\xa3\xfe\xc1\x1c\xf5+\xe9)' b'\x86 ier!\xe8\xe5#*\x9d\x8c\x0bI\x02\xd90\x0e7\xbeW\xbf\xa3\xfe\xc1\x1c\xf5+\xe9)'
b'\xa3\xde\xc9\xc6s\xc9\x90\xf7x\x08') b'\xa3\xde\xc9\xc6s\xc9\x90\xf7x\x08')
priv_key = ed25519.SigningKey.parse_encoded_key(priv_str) private_key, derived_public_key = ed25519.signing_keypair_from_string(priv_str)
pub_key = ed25519.VerifyingKey.parse_encoded_key(pub_str) public_key = ed25519.verifying_key_from_string(pub_str)
self.failUnlessEqual(priv_key.public_key(), pub_key) self.failUnlessEqual(
ed25519.bytes_from_verifying_key(public_key),
ed25519.bytes_from_verifying_key(derived_public_key),
)
new_sig = priv_key.sign(test_data) new_sig = ed25519.sign_data(private_key, test_data)
self.failUnlessEqual(new_sig, sig) self.failUnlessEqual(new_sig, sig)
pub_key.verify(new_sig, test_data) ed25519.verify_signature(public_key, new_sig, test_data)
ed25519.verify_signature(derived_public_key, new_sig, test_data)
ed25519.verify_signature(public_key, sig, test_data)
ed25519.verify_signature(derived_public_key, sig, test_data)
def test_decode_rsa_keypair(self): def test_decode_rsa_keypair(self):
''' '''
@ -216,29 +222,34 @@ class TestRegression(unittest.TestCase):
class TestEd25519(unittest.TestCase): class TestEd25519(unittest.TestCase):
def test_keys(self): def test_keys(self):
priv_key = ed25519.SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
priv_key_str = priv_key.encoded_key() private_key_str = ed25519.string_from_signing_key(private_key)
self.assertIsInstance(priv_key_str, six.string_types) self.assertIsInstance(private_key_str, six.string_types)
self.assertIsInstance(priv_key.private_bytes(), six.binary_type)
priv_key2 = ed25519.SigningKey.parse_encoded_key(priv_key_str) private_key2, public_key2 = ed25519.signing_keypair_from_string(private_key_str)
self.failUnlessEqual(priv_key, priv_key2) self.failUnlessEqual(
ed25519.bytes_from_signing_key(private_key),
ed25519.bytes_from_signing_key(private_key2),
)
self.failUnlessEqual(
ed25519.bytes_from_verifying_key(public_key),
ed25519.bytes_from_verifying_key(public_key2),
)
pub_key = priv_key.public_key() public_key_str = ed25519.string_from_verifying_key(public_key)
pub_key2 = priv_key2.public_key() public_key_bytes = ed25519.bytes_from_verifying_key(public_key)
self.failUnlessEqual(pub_key, pub_key2) self.assertIsInstance(public_key_str, six.string_types)
self.assertIsInstance(public_key_bytes, six.binary_type)
pub_key_str = pub_key.encoded_key() public_key2 = ed25519.verifying_key_from_string(public_key_str)
self.assertIsInstance(pub_key_str, six.string_types) self.failUnlessEqual(
self.assertIsInstance(pub_key.public_bytes(), six.binary_type) ed25519.bytes_from_verifying_key(public_key),
ed25519.bytes_from_verifying_key(public_key2),
pub_key2 = ed25519.VerifyingKey.parse_encoded_key(pub_key_str) )
self.failUnlessEqual(pub_key, pub_key2)
class TestRsa(unittest.TestCase): class TestRsa(unittest.TestCase):

View File

@ -15,7 +15,7 @@ from twisted.python.filepath import FilePath
from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue
from twisted.application import service from twisted.application import service
from allmydata import crypto from allmydata import crypto
from allmydata.crypto.ed25519 import SigningKey from allmydata.crypto import ed25519
from allmydata.interfaces import InsufficientVersionError from allmydata.interfaces import InsufficientVersionError
from allmydata.introducer.client import IntroducerClient from allmydata.introducer.client import IntroducerClient
from allmydata.introducer.server import IntroducerService, FurlFileConflictError from allmydata.introducer.server import IntroducerService, FurlFileConflictError
@ -202,21 +202,21 @@ class Client(AsyncTestCase):
furl1a = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:7777/gydnp" furl1a = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:7777/gydnp"
furl2 = "pb://ttwwooyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/ttwwoo" furl2 = "pb://ttwwooyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/ttwwoo"
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
pub_key_str = priv_key.public_key().encoded_key() public_key_str = ed25519.string_from_verifying_key(public_key)
pubkey_s = crypto.remove_prefix(pub_key_str, "pub-") pubkey_s = crypto.remove_prefix(public_key_str, "pub-")
# ann1: ic1, furl1 # ann1: ic1, furl1
# ann1a: ic1, furl1a (same SturdyRef, different connection hints) # ann1a: ic1, furl1a (same SturdyRef, different connection hints)
# ann1b: ic2, furl1 # ann1b: ic2, furl1
# ann2: ic2, furl2 # ann2: ic2, furl2
self.ann1 = make_ann_t(ic1, furl1, priv_key, seqnum=10) self.ann1 = make_ann_t(ic1, furl1, private_key, seqnum=10)
self.ann1old = make_ann_t(ic1, furl1, priv_key, seqnum=9) self.ann1old = make_ann_t(ic1, furl1, private_key, seqnum=9)
self.ann1noseqnum = make_ann_t(ic1, furl1, priv_key, seqnum=None) self.ann1noseqnum = make_ann_t(ic1, furl1, private_key, seqnum=None)
self.ann1b = make_ann_t(ic2, furl1, priv_key, seqnum=11) self.ann1b = make_ann_t(ic2, furl1, private_key, seqnum=11)
self.ann1a = make_ann_t(ic1, furl1a, priv_key, seqnum=12) self.ann1a = make_ann_t(ic1, furl1a, private_key, seqnum=12)
self.ann2 = make_ann_t(ic2, furl2, priv_key, seqnum=13) self.ann2 = make_ann_t(ic2, furl2, private_key, seqnum=13)
ic1.remote_announce_v2([self.ann1]) # queues eventual-send ic1.remote_announce_v2([self.ann1]) # queues eventual-send
d = fireEventually() d = fireEventually()
@ -300,13 +300,13 @@ class Server(AsyncTestCase):
FilePath(self.mktemp())) FilePath(self.mktemp()))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:36106/gydnp"
priv_key = SigningKey.generate() private_key, _ = ed25519.create_signing_keypair()
ann1 = make_ann_t(ic1, furl1, priv_key, seqnum=10) ann1 = make_ann_t(ic1, furl1, private_key, seqnum=10)
ann1_old = make_ann_t(ic1, furl1, priv_key, seqnum=9) ann1_old = make_ann_t(ic1, furl1, private_key, seqnum=9)
ann1_new = make_ann_t(ic1, furl1, priv_key, seqnum=11) ann1_new = make_ann_t(ic1, furl1, private_key, seqnum=11)
ann1_noseqnum = make_ann_t(ic1, furl1, priv_key, seqnum=None) ann1_noseqnum = make_ann_t(ic1, furl1, private_key, seqnum=None)
ann1_badseqnum = make_ann_t(ic1, furl1, priv_key, seqnum="not an int") ann1_badseqnum = make_ann_t(ic1, furl1, private_key, seqnum="not an int")
i.remote_publish_v2(ann1, None) i.remote_publish_v2(ann1, None)
all = i.get_announcements() all = i.get_announcements()
@ -397,7 +397,7 @@ class Queue(SystemTestMixin, AsyncTestCase):
u"nickname", "version", "oldest", {}, fakeseq, u"nickname", "version", "oldest", {}, fakeseq,
FilePath(self.mktemp())) FilePath(self.mktemp()))
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
priv_key = SigningKey.generate() private_key, _ = ed25519.create_signing_keypair()
d = introducer.disownServiceParent() d = introducer.disownServiceParent()
@ -405,7 +405,7 @@ class Queue(SystemTestMixin, AsyncTestCase):
# now that the introducer server is offline, create a client and # now that the introducer server is offline, create a client and
# publish some messages # publish some messages
c.setServiceParent(self.parent) # this starts the reconnector c.setServiceParent(self.parent) # this starts the reconnector
c.publish("storage", make_ann(furl1), priv_key) c.publish("storage", make_ann(furl1), private_key)
introducer.setServiceParent(self.parent) # restart the server introducer.setServiceParent(self.parent) # restart the server
# now wait for the messages to be delivered # now wait for the messages to be delivered
@ -486,16 +486,16 @@ class SystemTest(SystemTestMixin, AsyncTestCase):
expected_announcements[i] += 1 # all expect a 'storage' announcement expected_announcements[i] += 1 # all expect a 'storage' announcement
node_furl = tub.registerReference(Referenceable()) node_furl = tub.registerReference(Referenceable())
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
pub_key_str = priv_key.public_key().encoded_key() public_key_str = ed25519.string_from_verifying_key(public_key)
privkeys[i] = priv_key privkeys[i] = private_key
pubkeys[i] = pub_key_str pubkeys[i] = public_key_str
if i < NUM_STORAGE: if i < NUM_STORAGE:
# sign all announcements # sign all announcements
c.publish("storage", make_ann(node_furl), priv_key) c.publish("storage", make_ann(node_furl), private_key)
assert pub_key_str.startswith("pub-") assert public_key_str.startswith("pub-")
printable_serverids[i] = pub_key_str[len("pub-"):] printable_serverids[i] = public_key_str[len("pub-"):]
publishing_clients.append(c) publishing_clients.append(c)
else: else:
# the last one does not publish anything # the last one does not publish anything
@ -504,7 +504,7 @@ class SystemTest(SystemTestMixin, AsyncTestCase):
if i == 2: if i == 2:
# also publish something that nobody cares about # also publish something that nobody cares about
boring_furl = tub.registerReference(Referenceable()) boring_furl = tub.registerReference(Referenceable())
c.publish("boring", make_ann(boring_furl), priv_key) c.publish("boring", make_ann(boring_furl), private_key)
c.setServiceParent(self.parent) c.setServiceParent(self.parent)
clients.append(c) clients.append(c)
@ -760,17 +760,16 @@ class Announcements(AsyncTestCase):
fakeseq, FilePath(self.mktemp())) fakeseq, FilePath(self.mktemp()))
furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum" furl1 = "pb://62ubehyunnyhzs7r6vdonnm2hpi52w6y@127.0.0.1:0/swissnum"
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
pub_key = priv_key.public_key() public_key_str = crypto.remove_prefix(ed25519.string_from_verifying_key(public_key), "pub-")
pks = crypto.remove_prefix(pub_key.encoded_key(), "pub-")
ann_t0 = make_ann_t(client_v2, furl1, priv_key, 10) ann_t0 = make_ann_t(client_v2, furl1, private_key, 10)
canary0 = Referenceable() canary0 = Referenceable()
introducer.remote_publish_v2(ann_t0, canary0) introducer.remote_publish_v2(ann_t0, canary0)
a = introducer.get_announcements() a = introducer.get_announcements()
self.failUnlessEqual(len(a), 1) self.failUnlessEqual(len(a), 1)
self.assertThat(a[0].canary, Is(canary0)) self.assertThat(a[0].canary, Is(canary0))
self.failUnlessEqual(a[0].index, ("storage", pks)) self.failUnlessEqual(a[0].index, ("storage", public_key_str))
self.failUnlessEqual(a[0].announcement["app-versions"], app_versions) self.failUnlessEqual(a[0].announcement["app-versions"], app_versions)
self.failUnlessEqual(a[0].nickname, u"nick-v2") self.failUnlessEqual(a[0].nickname, u"nick-v2")
self.failUnlessEqual(a[0].service_name, "storage") self.failUnlessEqual(a[0].service_name, "storage")
@ -800,11 +799,10 @@ class Announcements(AsyncTestCase):
c = yield create_client(basedir) c = yield create_client(basedir)
ic = c.introducer_clients[0] ic = c.introducer_clients[0]
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
pub_key = priv_key.public_key() public_key_str = crypto.remove_prefix(ed25519.string_from_verifying_key(public_key), "pub-")
pub_key_str = crypto.remove_prefix(pub_key.encoded_key(), "pub-")
furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short") furl1 = "pb://onug64tu@127.0.0.1:123/short" # base32("short")
ann_t = make_ann_t(ic, furl1, priv_key, 1) ann_t = make_ann_t(ic, furl1, private_key, 1)
ic.got_announcements([ann_t]) ic.got_announcements([ann_t])
yield flushEventualQueue() yield flushEventualQueue()
@ -812,7 +810,7 @@ class Announcements(AsyncTestCase):
# check the cache for the announcement # check the cache for the announcement
announcements = self._load_cache(cache_filepath) announcements = self._load_cache(cache_filepath)
self.failUnlessEqual(len(announcements), 1) self.failUnlessEqual(len(announcements), 1)
self.failUnlessEqual(announcements[0]['key_s'], pub_key_str) self.failUnlessEqual(announcements[0]['key_s'], public_key_str)
ann = announcements[0]["ann"] ann = announcements[0]["ann"]
self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1) self.failUnlessEqual(ann["anonymous-storage-FURL"], furl1)
self.failUnlessEqual(ann["seqnum"], 1) self.failUnlessEqual(ann["seqnum"], 1)
@ -820,29 +818,28 @@ class Announcements(AsyncTestCase):
# a new announcement that replaces the first should replace the # a new announcement that replaces the first should replace the
# cached entry, not duplicate it # cached entry, not duplicate it
furl2 = furl1 + "er" furl2 = furl1 + "er"
ann_t2 = make_ann_t(ic, furl2, priv_key, 2) ann_t2 = make_ann_t(ic, furl2, private_key, 2)
ic.got_announcements([ann_t2]) ic.got_announcements([ann_t2])
yield flushEventualQueue() yield flushEventualQueue()
announcements = self._load_cache(cache_filepath) announcements = self._load_cache(cache_filepath)
self.failUnlessEqual(len(announcements), 1) self.failUnlessEqual(len(announcements), 1)
self.failUnlessEqual(announcements[0]['key_s'], pub_key_str) self.failUnlessEqual(announcements[0]['key_s'], public_key_str)
ann = announcements[0]["ann"] ann = announcements[0]["ann"]
self.failUnlessEqual(ann["anonymous-storage-FURL"], furl2) self.failUnlessEqual(ann["anonymous-storage-FURL"], furl2)
self.failUnlessEqual(ann["seqnum"], 2) self.failUnlessEqual(ann["seqnum"], 2)
# but a third announcement with a different key should add to the # but a third announcement with a different key should add to the
# cache # cache
priv_key2 = SigningKey.generate() private_key2, public_key2 = ed25519.create_signing_keypair()
pub_key2 = priv_key2.public_key() public_key_str2 = crypto.remove_prefix(ed25519.string_from_verifying_key(public_key2), "pub-")
pub_key_str2 = crypto.remove_prefix(pub_key2.encoded_key(), "pub-")
furl3 = "pb://onug64tu@127.0.0.1:456/short" furl3 = "pb://onug64tu@127.0.0.1:456/short"
ann_t3 = make_ann_t(ic, furl3, priv_key2, 1) ann_t3 = make_ann_t(ic, furl3, private_key2, 1)
ic.got_announcements([ann_t3]) ic.got_announcements([ann_t3])
yield flushEventualQueue() yield flushEventualQueue()
announcements = self._load_cache(cache_filepath) announcements = self._load_cache(cache_filepath)
self.failUnlessEqual(len(announcements), 2) self.failUnlessEqual(len(announcements), 2)
self.failUnlessEqual(set([pub_key_str, pub_key_str2]), self.failUnlessEqual(set([public_key_str, public_key_str2]),
set([a["key_s"] for a in announcements])) set([a["key_s"] for a in announcements]))
self.failUnlessEqual(set([furl2, furl3]), self.failUnlessEqual(set([furl2, furl3]),
set([a["ann"]["anonymous-storage-FURL"] set([a["ann"]["anonymous-storage-FURL"]
@ -860,17 +857,17 @@ class Announcements(AsyncTestCase):
ic2._load_announcements() # normally happens when connection fails ic2._load_announcements() # normally happens when connection fails
yield flushEventualQueue() yield flushEventualQueue()
self.failUnless(pub_key_str in announcements) self.failUnless(public_key_str in announcements)
self.failUnlessEqual(announcements[pub_key_str]["anonymous-storage-FURL"], self.failUnlessEqual(announcements[public_key_str]["anonymous-storage-FURL"],
furl2) furl2)
self.failUnlessEqual(announcements[pub_key_str2]["anonymous-storage-FURL"], self.failUnlessEqual(announcements[public_key_str2]["anonymous-storage-FURL"],
furl3) furl3)
c2 = yield create_client(basedir) c2 = yield create_client(basedir)
c2.introducer_clients[0]._load_announcements() c2.introducer_clients[0]._load_announcements()
yield flushEventualQueue() yield flushEventualQueue()
self.assertEqual(c2.storage_broker.get_all_serverids(), self.assertEqual(c2.storage_broker.get_all_serverids(),
frozenset([pub_key_str, pub_key_str2])) frozenset([public_key_str, public_key_str2]))
class ClientSeqnums(AsyncBrokenTestCase): class ClientSeqnums(AsyncBrokenTestCase):
@ -899,7 +896,7 @@ class ClientSeqnums(AsyncBrokenTestCase):
f.close() f.close()
return int(seqnum) return int(seqnum)
ic.publish("sA", {"key": "value1"}, c._node_key) ic.publish("sA", {"key": "value1"}, c._node_private_key)
self.failUnlessEqual(read_seqnum(), 1) self.failUnlessEqual(read_seqnum(), 1)
self.failUnless("sA" in outbound) self.failUnless("sA" in outbound)
self.failUnlessEqual(outbound["sA"]["seqnum"], 1) self.failUnlessEqual(outbound["sA"]["seqnum"], 1)
@ -911,7 +908,7 @@ class ClientSeqnums(AsyncBrokenTestCase):
# publishing a second service causes both services to be # publishing a second service causes both services to be
# re-published, with the next higher sequence number # re-published, with the next higher sequence number
ic.publish("sB", {"key": "value2"}, c._node_key) ic.publish("sB", {"key": "value2"}, c._node_private_key)
self.failUnlessEqual(read_seqnum(), 2) self.failUnlessEqual(read_seqnum(), 2)
self.failUnless("sB" in outbound) self.failUnless("sB" in outbound)
self.failUnlessEqual(outbound["sB"]["seqnum"], 2) self.failUnlessEqual(outbound["sB"]["seqnum"], 2)
@ -986,8 +983,9 @@ class Signatures(SyncTestCase):
def test_sign(self): def test_sign(self):
ann = {"key1": "value1"} ann = {"key1": "value1"}
priv_key = SigningKey.generate() private_key, public_key = ed25519.create_signing_keypair()
ann_t = sign_to_foolscap(ann, priv_key) public_key_str = ed25519.string_from_verifying_key(public_key)
ann_t = sign_to_foolscap(ann, private_key)
(msg, sig, key) = ann_t (msg, sig, key) = ann_t
self.failUnlessEqual(type(msg), type("".encode("utf-8"))) # bytes self.failUnlessEqual(type(msg), type("".encode("utf-8"))) # bytes
self.failUnlessEqual(json.loads(msg.decode("utf-8")), ann) self.failUnlessEqual(json.loads(msg.decode("utf-8")), ann)
@ -995,7 +993,7 @@ class Signatures(SyncTestCase):
self.failUnless(key.startswith("v0-")) self.failUnless(key.startswith("v0-"))
(ann2,key2) = unsign_from_foolscap(ann_t) (ann2,key2) = unsign_from_foolscap(ann_t)
self.failUnlessEqual(ann2, ann) self.failUnlessEqual(ann2, ann)
self.failUnlessEqual("pub-" + key2, priv_key.public_key().encoded_key()) self.failUnlessEqual("pub-" + key2, public_key_str)
# not signed # not signed
self.failUnlessRaises(UnknownKeyError, self.failUnlessRaises(UnknownKeyError,