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
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 import storage_client
from allmydata.immutable.upload import Uploader
@ -156,8 +156,7 @@ class KeyGenerator(object):
keysize = keysize or self.default_keysize
# RSA key generation for a 2048 bit key takes between 0.8 and 3.2
# secs
signer = PrivateKey.generate(keysize)
verifier = signer.public_key()
signer, verifier = rsa.create_signing_keypair(keysize)
return defer.succeed( (verifier, signer) )
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
class RsaMixin(object):
"""
This is the value that was used by `pycryptopp`, and we must continue to use it for
both backwards compatibility and interoperability.
'''
This is the value that was used by `pycryptopp`, and we must continue to use it for
both backwards compatibility and interoperability.
The docs for `cryptography` suggest to use the constant defined at
`cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH`, but this causes old
signatures to fail to validate.
'''
RSA_PSS_SALT_LENGTH = 32
The docs for `cryptography` suggest to use the constant defined at
`cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH`, but this causes old
signatures to fail to validate.
"""
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):
self._priv_key = priv_key
:returns: public 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(
public_exponent=65537, # serisously don't change this value
key_size=key_size,
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_str,
private_key_der,
password=None,
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):
return self._priv_key.private_bytes(
def der_string_from_signing_key(private_key):
"""
Serializes a given RSA private key to a DER string
"""
_validate_private_key(private_key)
return private_key.private_bytes(
encoding=Encoding.DER,
format=PrivateFormat.PKCS8,
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):
if isinstance(other, type(self)):
return self.serialize() == other.serialize()
else:
return False
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(
def der_string_from_verifying_key(public_key):
"""
Serializes a given RSA private key to a DER string
"""
_validate_public_key(public_key)
return public_key.public_bytes(
encoding=Encoding.DER,
format=PublicFormat.SubjectPublicKeyInfo,
)
def verify(self, signature, data):
try:
self._pub_key.verify(
signature,
def create_verifying_key_from_string(public_key_der):
"""
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,
padding.PSS(
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(),
)
except InvalidSignature:
raise BadSignature
def __eq__(self, other):
if isinstance(other, type(self)):
return self.serialize() == other.serialize()
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def _validate_public_key(public_key):
"""
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 allmydata.crypto.aes import AES
from allmydata.crypto import rsa
from allmydata.interfaces import IMutableFileNode, ICheckable, ICheckResults, \
NotEnoughSharesError, MDMF_VERSION, SDMF_VERSION, IMutableUploadable, \
IMutableFileVersion, IWriteable
@ -128,8 +129,8 @@ class MutableFileNode(object):
"""
(pubkey, privkey) = keypair
self._pubkey, self._privkey = pubkey, privkey
pubkey_s = self._pubkey.serialize()
privkey_s = self._privkey.serialize()
pubkey_s = rsa.der_string_from_verifying_key(self._pubkey)
privkey_s = rsa.der_string_from_signing_key(self._privkey)
self._writekey = hashutil.ssk_writekey_hash(privkey_s)
self._encprivkey = self._encrypt_privkey(self._writekey, privkey_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 allmydata.crypto.aes import AES
from allmydata.crypto import rsa
from allmydata.interfaces import IPublishStatus, SDMF_VERSION, MDMF_VERSION, \
IMutableUploadable
from allmydata.util import base32, hashutil, mathutil, log
@ -849,7 +850,7 @@ class Publish(object):
started = time.time()
self._status.set_status("Signing prefix")
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 writer in writers:
@ -864,7 +865,7 @@ class Publish(object):
self._status.set_status("Pushing shares")
self._started_pushing = started
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 writer in writers:

View File

@ -9,7 +9,7 @@ from foolscap.api import eventually, fireEventually, DeadReferenceError, \
RemoteException
from allmydata.crypto.aes import AES
from allmydata.crypto.rsa import PrivateKey
from allmydata.crypto import rsa
from allmydata.interfaces import IRetrieveStatus, NotEnoughSharesError, \
DownloadStopped, MDMF_VERSION, SDMF_VERSION
from allmydata.util.assertutil import _assert, precondition
@ -935,7 +935,7 @@ class Retrieve(object):
# it's good
self.log("got valid privkey from shnum %d on reader %s" %
(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_privkey(privkey)
self._need_privkey = False

View File

@ -9,7 +9,7 @@ from twisted.python import failure
from foolscap.api import DeadReferenceError, RemoteException, eventually, \
fireEventually
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.dictutil import DictOfSets
from allmydata.storage.server import si_b2a
@ -845,7 +845,7 @@ class ServermapUpdater(object):
# against the public key before keeping track of it.
assert self._node.get_pubkey()
try:
self._node.get_pubkey().verify(signature[1], prefix)
rsa.verify_signature(self._node.get_pubkey(), signature[1], prefix)
except BadSignature:
raise CorruptShareError(server, shnum,
"signature is invalid")
@ -916,7 +916,7 @@ class ServermapUpdater(object):
update_data)
def _deserialize_pubkey(self, pubkey_s):
verifier = PublicKey.parse_string(pubkey_s)
verifier = rsa.create_verifying_key_from_string(pubkey_s)
return verifier
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" %
(shnum, server.get_name()),
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_privkey(privkey)
self._need_privkey = False

View File

@ -5,6 +5,7 @@ from twisted.trial import unittest
from twisted.internet import defer
from foolscap.logging import log
from allmydata import uri
from allmydata.crypto import rsa
from allmydata.interfaces import NotEnoughSharesError, SDMF_VERSION, MDMF_VERSION
from allmydata.util import fileutil
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):
(pubkey, privkey) = keypair
nm.key_generator = SameKeyGenerator(pubkey, privkey)
pubkey_s = pubkey.serialize()
privkey_s = privkey.serialize()
pubkey_s = rsa.der_string_from_verifying_key(pubkey)
privkey_s = rsa.der_string_from_signing_key(privkey)
u = uri.WriteableSSKFileURI(ssk_writekey_hash(privkey_s),
ssk_pubkey_fingerprint_hash(pubkey_s))
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
using the new code.
'''
priv_key = rsa.PrivateKey.parse_string(self.RSA_2048_PRIV_KEY)
pub_key = priv_key.public_key()
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')
priv_key, pub_key = rsa.create_signing_keypair_from_string(self.RSA_2048_PRIV_KEY)
rsa.verify_signature(pub_key, self.RSA_2048_SIG, b'test')
class TestEd25519(unittest.TestCase):
@ -249,24 +244,26 @@ class TestEd25519(unittest.TestCase):
class TestRsa(unittest.TestCase):
def test_keys(self):
priv_key = rsa.PrivateKey.generate(2048)
priv_key_str = priv_key.serialize()
priv_key, pub_key = rsa.create_signing_keypair(2048)
priv_key_str = rsa.der_string_from_signing_key(priv_key)
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()
pub_key2 = priv_key2.public_key()
data_to_sign = b"test data"
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()
self.assertIsInstance(pub_key_str, six.string_types)
pub_key2 = rsa.PublicKey.parse_string(pub_key_str)
self.failUnlessEqual(pub_key, pub_key2)
# ..and a failed way
with self.assertRaises(rsa.BadSignature):
rsa.verify_signature(pub_key, sig1, data_to_sign + b"more")