refactor away from pycryptopp "helper" classes

This commit is contained in:
meejah 2019-06-12 15:44:35 -06:00
parent 78a13aad43
commit df4671f90e
8 changed files with 165 additions and 123 deletions

View File

@ -12,7 +12,7 @@ from twisted.python.failure import Failure
import allmydata import allmydata
from allmydata.crypto.ed25519 import SigningKey from allmydata.crypto.ed25519 import SigningKey
from allmydata.crypto.rsa import PrivateKey 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
@ -156,8 +156,7 @@ class KeyGenerator(object):
keysize = keysize or self.default_keysize keysize = keysize or self.default_keysize
# RSA key generation for a 2048 bit key takes between 0.8 and 3.2 # RSA key generation for a 2048 bit key takes between 0.8 and 3.2
# secs # secs
signer = PrivateKey.generate(keysize) signer, verifier = rsa.create_signing_keypair(keysize)
verifier = signer.public_key()
return defer.succeed( (verifier, signer) ) return defer.succeed( (verifier, signer) )
class Terminator(service.Service): class Terminator(service.Service):

View File

@ -8,110 +8,153 @@ from cryptography.hazmat.primitives.serialization import load_der_private_key, l
from allmydata.crypto import BadSignature from allmydata.crypto import BadSignature
class RsaMixin(object): """
'''
This is the value that was used by `pycryptopp`, and we must continue to use it for This is the value that was used by `pycryptopp`, and we must continue to use it for
both backwards compatibility and interoperability. both backwards compatibility and interoperability.
The docs for `cryptography` suggest to use the constant defined at The docs for `cryptography` suggest to use the constant defined at
`cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH`, but this causes old `cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH`, but this causes old
signatures to fail to validate. signatures to fail to validate.
''' """
RSA_PSS_SALT_LENGTH = 32 RSA_PSS_SALT_LENGTH = 32
class PrivateKey(RsaMixin): def create_verifying_key_from_string(public_key_der):
"""
Create an RSA verifying key from a previously serialized public key.
def __init__(self, priv_key): :returns: public key
self._priv_key = priv_key """
pub_key = load_der_public_key(
public_key_der,
backend=default_backend(),
)
return pub_key
@classmethod
def generate(cls, key_size): def create_signing_keypair(key_size):
"""
Create a new RSA signing keypair from scratch. Can be used with
`sign_data` function.
:param int key_size: length of key in bits
:returns: 2-tuple of (private_key, public_key)
"""
priv_key = rsa.generate_private_key( priv_key = rsa.generate_private_key(
public_exponent=65537, # serisously don't change this value public_exponent=65537, # serisously don't change this value
key_size=key_size, key_size=key_size,
backend=default_backend() backend=default_backend()
) )
return cls(priv_key) return priv_key, priv_key.public_key()
@classmethod
def parse_string(cls, priv_key_str): def create_signing_keypair_from_string(private_key_der):
"""
Create an RSA signing key from a previously serialized private key.
:returns: 2-tuple of (private_key, public_key)
"""
priv_key = load_der_private_key( priv_key = load_der_private_key(
priv_key_str, private_key_der,
password=None, password=None,
backend=default_backend(), backend=default_backend(),
) )
return cls(priv_key) return priv_key, priv_key.public_key()
def public_key(self):
return PublicKey(self._priv_key.public_key())
def serialize(self): def der_string_from_signing_key(private_key):
return self._priv_key.private_bytes( """
Serializes a given RSA private key to a DER string
"""
_validate_private_key(private_key)
return private_key.private_bytes(
encoding=Encoding.DER, encoding=Encoding.DER,
format=PrivateFormat.PKCS8, format=PrivateFormat.PKCS8,
encryption_algorithm=NoEncryption(), encryption_algorithm=NoEncryption(),
) )
def sign(self, data):
return self._priv_key.sign(
data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=self.RSA_PSS_SALT_LENGTH,
),
hashes.SHA256(),
)
def __eq__(self, other): def der_string_from_verifying_key(public_key):
if isinstance(other, type(self)): """
return self.serialize() == other.serialize() Serializes a given RSA private key to a DER string
else: """
return False _validate_public_key(public_key)
return public_key.public_bytes(
def __ne__(self, other):
return not self.__eq__(other)
class PublicKey(RsaMixin):
def __init__(self, pub_key):
self._pub_key = pub_key
@classmethod
def parse_string(cls, pub_key_str):
pub_key = load_der_public_key(
pub_key_str,
backend=default_backend(),
)
return cls(pub_key)
def serialize(self):
return self._pub_key.public_bytes(
encoding=Encoding.DER, encoding=Encoding.DER,
format=PublicFormat.SubjectPublicKeyInfo, format=PublicFormat.SubjectPublicKeyInfo,
) )
def verify(self, signature, data):
try: def create_verifying_key_from_string(public_key_der):
self._pub_key.verify( """
signature, Create an RSA signing key from a previously serialized public key
"""
pub_key = load_der_public_key(
public_key_der,
backend=default_backend(),
)
return pub_key
def sign_data(private_key, data):
"""
:param private_key: the private part of a keypair returned from
`create_signing_keypair_from_string` or `create_signing_keypair`
:param bytes data: the bytes to sign
:returns: bytes which are a signature of the bytes given as `data`.
"""
_validate_private_key(private_key)
return private_key.sign(
data, data,
padding.PSS( padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), mgf=padding.MGF1(hashes.SHA256()),
salt_length=self.RSA_PSS_SALT_LENGTH, salt_length=RSA_PSS_SALT_LENGTH,
),
hashes.SHA256(),
)
def verify_signature(public_key, alleged_signature, data):
"""
:param public_key: a verifying key, returned from `create_verifying_key_from_string` or `create_verifying_key_from_private_key`
:param bytes alleged_signature: the bytes of the alleged signature
:param bytes data: the data which was allegedly signed
"""
try:
public_key.verify(
alleged_signature,
data,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=RSA_PSS_SALT_LENGTH,
), ),
hashes.SHA256(), hashes.SHA256(),
) )
except InvalidSignature: except InvalidSignature:
raise BadSignature raise BadSignature
def __eq__(self, other):
if isinstance(other, type(self)):
return self.serialize() == other.serialize()
else:
return False
def __ne__(self, other): def _validate_public_key(public_key):
return not self.__eq__(other) """
Internal helper. Checks that `public_key` is a valid cryptography
object
"""
if not isinstance(public_key, rsa.RSAPublicKey):
raise ValueError(
"public_key not an RSAPublicKey"
)
def _validate_private_key(private_key):
"""
Internal helper. Checks that `public_key` is a valid cryptography
object
"""
if not isinstance(private_key, rsa.RSAPrivateKey):
raise ValueError(
"private_key not an RSAPrivateKey"
)

View File

@ -5,6 +5,7 @@ from twisted.internet import defer, reactor
from foolscap.api import eventually from foolscap.api import eventually
from allmydata.crypto.aes import AES from allmydata.crypto.aes import AES
from allmydata.crypto import rsa
from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \ from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \ NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
IMutableFileVersion, IWriteable IMutableFileVersion, IWriteable
@ -128,8 +129,8 @@ class MutableFileNode(object):
""" """
(pubkey, privkey) = keypair (pubkey, privkey) = keypair
self._pubkey, self._privkey = pubkey, privkey self._pubkey, self._privkey = pubkey, privkey
pubkey_s = self._pubkey.serialize() pubkey_s = rsa.der_string_from_verifying_key(self._pubkey)
privkey_s = self._privkey.serialize() privkey_s = rsa.der_string_from_signing_key(self._privkey)
self._writekey = hashutil.ssk_writekey_hash(privkey_s) self._writekey = hashutil.ssk_writekey_hash(privkey_s)
self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s) self._encprivkey = self._encrypt_privkey(self._writekey, privkey_s)
self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s) self._fingerprint = hashutil.ssk_pubkey_fingerprint_hash(pubkey_s)

View File

@ -6,6 +6,7 @@ from twisted.internet import defer
from twisted.python import failure from twisted.python import failure
from allmydata.crypto.aes import AES from allmydata.crypto.aes import AES
from allmydata.crypto import rsa
from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \ from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
IMutableUploadable IMutableUploadable
from allmydata.util import base32, hashutil, mathutil, log from allmydata.util import base32, hashutil, mathutil, log
@ -849,7 +850,7 @@ class Publish(object):
started = time.time() started = time.time()
self._status.set_status("Signing prefix") self._status.set_status("Signing prefix")
signable = self._get_some_writer().get_signable() signable = self._get_some_writer().get_signable()
self.signature = self._privkey.sign(signable) self.signature = rsa.sign_data(self._privkey, signable)
for (shnum, writers) in self.writers.iteritems(): for (shnum, writers) in self.writers.iteritems():
for writer in writers: for writer in writers:
@ -864,7 +865,7 @@ class Publish(object):
self._status.set_status("Pushing shares") self._status.set_status("Pushing shares")
self._started_pushing = started self._started_pushing = started
ds = [] ds = []
verification_key = self._pubkey.serialize() verification_key = rsa.der_string_from_verifying_key(self._pubkey)
for (shnum, writers) in self.writers.copy().iteritems(): for (shnum, writers) in self.writers.copy().iteritems():
for writer in writers: for writer in writers:

View File

@ -9,7 +9,7 @@ from foolscap.api import eventually, fireEventually, DeadReferenceError, \
RemoteException RemoteException
from allmydata.crypto.aes import AES from allmydata.crypto.aes import AES
from allmydata.crypto.rsa import PrivateKey from allmydata.crypto import rsa
from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \ from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
DownloadStopped, MDMF_VERSION, SDMF_VERSION DownloadStopped, MDMF_VERSION, SDMF_VERSION
from allmydata.util.assertutil import _assert, precondition from allmydata.util.assertutil import _assert, precondition
@ -935,7 +935,7 @@ class Retrieve(object):
# it's good # it's good
self.log("got valid privkey from shnum %d on reader %s" % self.log("got valid privkey from shnum %d on reader %s" %
(reader.shnum, reader)) (reader.shnum, reader))
privkey = PrivateKey.parse_string(alleged_privkey_s) privkey, _ = rsa.create_signing_keypair_from_string(alleged_privkey_s)
self._node._populate_encprivkey(enc_privkey) self._node._populate_encprivkey(enc_privkey)
self._node._populate_privkey(privkey) self._node._populate_privkey(privkey)
self._need_privkey = False self._need_privkey = False

View File

@ -9,7 +9,7 @@ from twisted.python import failure
from foolscap.api import DeadReferenceError, RemoteException, eventually, \ from foolscap.api import DeadReferenceError, RemoteException, eventually, \
fireEventually fireEventually
from allmydata.crypto import BadSignature from allmydata.crypto import BadSignature
from allmydata.crypto.rsa import PublicKey, PrivateKey from allmydata.crypto import rsa
from allmydata.util import base32, hashutil, log, deferredutil from allmydata.util import base32, hashutil, log, deferredutil
from allmydata.util.dictutil import DictOfSets from allmydata.util.dictutil import DictOfSets
from allmydata.storage.server import si_b2a from allmydata.storage.server import si_b2a
@ -845,7 +845,7 @@ class ServermapUpdater(object):
# against the public key before keeping track of it. # against the public key before keeping track of it.
assert self._node.get_pubkey() assert self._node.get_pubkey()
try: try:
self._node.get_pubkey().verify(signature[1], prefix) rsa.verify_signature(self._node.get_pubkey(), signature[1], prefix)
except BadSignature: except BadSignature:
raise CorruptShareError(server, shnum, raise CorruptShareError(server, shnum,
"signature is invalid") "signature is invalid")
@ -916,7 +916,7 @@ class ServermapUpdater(object):
update_data) update_data)
def _deserialize_pubkey(self, pubkey_s): def _deserialize_pubkey(self, pubkey_s):
verifier = PublicKey.parse_string(pubkey_s) verifier = rsa.create_verifying_key_from_string(pubkey_s)
return verifier return verifier
def _try_to_validate_privkey(self, enc_privkey, server, shnum, lp): def _try_to_validate_privkey(self, enc_privkey, server, shnum, lp):
@ -937,7 +937,7 @@ class ServermapUpdater(object):
self.log("got valid privkey from shnum %d on serverid %s" % self.log("got valid privkey from shnum %d on serverid %s" %
(shnum, server.get_name()), (shnum, server.get_name()),
parent=lp) parent=lp)
privkey = PrivateKey.parse_string(alleged_privkey_s) privkey, _ = rsa.create_signing_keypair_from_string(alleged_privkey_s)
self._node._populate_encprivkey(enc_privkey) self._node._populate_encprivkey(enc_privkey)
self._node._populate_privkey(privkey) self._node._populate_privkey(privkey)
self._need_privkey = False self._need_privkey = False

View File

@ -5,6 +5,7 @@ from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from foolscap.logging import log from foolscap.logging import log
from allmydata import uri from allmydata import uri
from allmydata.crypto import rsa
from allmydata.interfaces import NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION from allmydata.interfaces import NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
from allmydata.util import fileutil from allmydata.util import fileutil
from allmydata.util.hashutil import ssk_writekey_hash, ssk_pubkey_fingerprint_hash from allmydata.util.hashutil import ssk_writekey_hash, ssk_pubkey_fingerprint_hash
@ -211,8 +212,8 @@ class Problems(GridTestMixin, unittest.TestCase, testutil.ShouldFailMixin):
def _got_key(keypair): def _got_key(keypair):
(pubkey, privkey) = keypair (pubkey, privkey) = keypair
nm.key_generator = SameKeyGenerator(pubkey, privkey) nm.key_generator = SameKeyGenerator(pubkey, privkey)
pubkey_s = pubkey.serialize() pubkey_s = rsa.der_string_from_verifying_key(pubkey)
privkey_s = privkey.serialize() privkey_s = rsa.der_string_from_signing_key(privkey)
u = uri.WriteableSSKFileURI(ssk_writekey_hash(privkey_s), u = uri.WriteableSSKFileURI(ssk_writekey_hash(privkey_s),
ssk_pubkey_fingerprint_hash(pubkey_s)) ssk_pubkey_fingerprint_hash(pubkey_s))
self._storage_index = u.get_storage_index() self._storage_index = u.get_storage_index()

View File

@ -209,13 +209,8 @@ class TestRegression(unittest.TestCase):
This simply checks that keys and signatures generated using the old code are still valid This simply checks that keys and signatures generated using the old code are still valid
using the new code. using the new code.
''' '''
priv_key = rsa.PrivateKey.parse_string(self.RSA_2048_PRIV_KEY) priv_key, pub_key = rsa.create_signing_keypair_from_string(self.RSA_2048_PRIV_KEY)
pub_key = priv_key.public_key() rsa.verify_signature(pub_key, self.RSA_2048_SIG, b'test')
parsed_pub_key = rsa.PublicKey.parse_string(self.RSA_2048_PUB_KEY)
self.failUnlessEqual(pub_key, parsed_pub_key)
pub_key.verify(self.RSA_2048_SIG, b'test')
class TestEd25519(unittest.TestCase): class TestEd25519(unittest.TestCase):
@ -249,24 +244,26 @@ class TestEd25519(unittest.TestCase):
class TestRsa(unittest.TestCase): class TestRsa(unittest.TestCase):
def test_keys(self): def test_keys(self):
priv_key = rsa.PrivateKey.generate(2048) priv_key, pub_key = rsa.create_signing_keypair(2048)
priv_key_str = priv_key.serialize() priv_key_str = rsa.der_string_from_signing_key(priv_key)
self.assertIsInstance(priv_key_str, six.string_types) self.assertIsInstance(priv_key_str, six.string_types)
priv_key2 = rsa.PrivateKey.parse_string(priv_key_str) priv_key2, pub_key2 = rsa.create_signing_keypair_from_string(priv_key_str)
self.failUnlessEqual(priv_key, priv_key2) # instead of asking "are these two keys equal", we can instead
# test their function: can the second key verify a signature
# produced by the first (and FAIL a signature with different
# data)
pub_key = priv_key.public_key() data_to_sign = b"test data"
pub_key2 = priv_key2.public_key() sig0 = rsa.sign_data(priv_key, data_to_sign)
rsa.verify_signature(pub_key2, sig0, data_to_sign)
self.failUnlessEqual(pub_key, pub_key2) # ..and the other way
sig1 = rsa.sign_data(priv_key2, data_to_sign)
rsa.verify_signature(pub_key, sig1, data_to_sign)
pub_key_str = pub_key.serialize() # ..and a failed way
with self.assertRaises(rsa.BadSignature):
self.assertIsInstance(pub_key_str, six.string_types) rsa.verify_signature(pub_key, sig1, data_to_sign + b"more")
pub_key2 = rsa.PublicKey.parse_string(pub_key_str)
self.failUnlessEqual(pub_key, pub_key2)