refactor away from pycryptopp "helper" classes
This commit is contained in:
parent
78a13aad43
commit
df4671f90e
|
@ -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):
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue