Skip to content

Commit

Permalink
factor padding scheme out of RSAKey class.
Browse files Browse the repository at this point in the history
The scheme is now explicitly incded in the signature string.
  • Loading branch information
rfk committed Aug 3, 2010
1 parent 02dfd06 commit cec32b5
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 101 deletions.
7 changes: 7 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@

v0.2.0

* Factor padding algorithm out from the RSA key class, so that it's easier
to upgrade to a new scheme:
* RSAKeyWithPSS is no more, just use RSAKey
* the signature produced by RSAKey.sign() is prefixed with the name
of the padding scheme used.
* "pss-sha1" is the default padding scheme; change it by setting
the attribute "default_padding_scheme" on the key.
* output hash data in sorted order, for easier debugging by hand.
* moved signedimp.pkres to signedimp.compat.pkgres.
* added signedimp.compat.esky, providing code to add signedimp support when
Expand Down
12 changes: 6 additions & 6 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Enabling Signed Imports
To enable signed imports, you need to create a SignedImportManager with the
appropriate cryptographic keys and install it into the import machinery::

from signedimp import SignedImportManager, RSAKeyWithPSS
from signedimp import SignedImportManager, RSAKey

key = RSAKeyWithPSS(modulus,pub_exponent)
key = RSAKey(modulus,pub_exponent)
mgr = SignedImportManager([key])
mgr.install()

Expand All @@ -60,8 +60,8 @@ Currently signedimp uses RSA keys for its digital signatures, along with the
"Probabilistic Signature Scheme" padding mechanism. To generate a new key
you will need PyCrypto installed, and to do the following::

from signedimp.crypto.rsa import RSAKeyWithPSS
key = RSAKeyWithPSS.generate()
from signedimp.crypto.rsa import RSAKey
key = RSAKey.generate()
pubkey = key.get_public_key()

Store this key somewhere safe, you'll need it to sign files. The simplest way
Expand All @@ -73,7 +73,7 @@ is using the "save_to_file" method::
To retreive the key in e.g. your build scripts, do something like this::

with open("mykeyfile","rb") as f:
key = RSAKeyWithPSS.load_from_file(f,getpass())
key = RSAKey.load_from_file(f,getpass())

You'll also need to embed the public key somewhere in your final executable
so it's available for verifying imports. The functions in signedimp.tools will
Expand Down Expand Up @@ -114,7 +114,7 @@ ASCII blobs.
To create a manifest file you will need a key object that includes the private
key data. You can then use the functions in the "tools" submodule::

key = RSAKeyWithPSS(modulus,pub_exponent,priv_exponent)
key = RSAKey(modulus,pub_exponent,priv_exponent)

signedimp.tools.sign_directory("some/dir/on/sys/path",key)
signedimp.tools.sign_zipfile("some/zipfile/on/sys/path.zip",key)
Expand Down
12 changes: 6 additions & 6 deletions signedimp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
To enable signed imports, you need to create a SignedImportManager with the
appropriate cryptographic keys and install it into the import machinery::
from signedimp import SignedImportManager, RSAKeyWithPSS
from signedimp import SignedImportManager, RSAKey
key = RSAKeyWithPSS(modulus,pub_exponent)
key = RSAKey(modulus,pub_exponent)
mgr = SignedImportManager([key])
mgr.install()
Expand All @@ -62,8 +62,8 @@
"Probabilistic Signature Scheme" padding mechanism. To generate a new key
you will need PyCrypto installed, and to do the following::
from signedimp.crypto.rsa import RSAKeyWithPSS
key = RSAKeyWithPSS.generate()
from signedimp.crypto.rsa import RSAKey
key = RSAKey.generate()
pubkey = key.get_public_key()
Store this key somewhere safe, you'll need it to sign files. The simplest way
Expand All @@ -75,7 +75,7 @@
To retreive the key in e.g. your build scripts, do something like this::
with open("mykeyfile","rb") as f:
key = RSAKeyWithPSS.load_from_file(f,getpass())
key = RSAKey.load_from_file(f,getpass())
You'll also need to embed the public key somewhere in your final executable
so it's available for verifying imports. The functions in signedimp.tools will
Expand Down Expand Up @@ -116,7 +116,7 @@
To create a manifest file you will need a key object that includes the private
key data. You can then use the functions in the "tools" submodule::
key = RSAKeyWithPSS(modulus,pub_exponent,priv_exponent)
key = RSAKey(modulus,pub_exponent,priv_exponent)
signedimp.tools.sign_directory("some/dir/on/sys/path",key)
signedimp.tools.sign_zipfile("some/zipfile/on/sys/path.zip",key)
Expand Down
24 changes: 12 additions & 12 deletions signedimp/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

__all__ = ["HASHFILE_NAME","IntegrityCheckError",
"IntegrityCheckFailed","IntegrityCheckMissing",
"SignedImportManager","SignedLoader","RSAKeyWithPSS",
"SignedImportManager","SignedLoader","RSAKey",
"DefaultImporter"]


Expand Down Expand Up @@ -161,20 +161,20 @@ def sha1(data):
_signedimp_util._sha1type = sha1
return _signedimp_util._sha1type(data)

_RSAKeyWithPSS = None
_RSAKey = None
@staticmethod
def RSAKeyWithPSS(modulus,pubexp,privexp=None):
def RSAKey(modulus,pubexp,privexp=None):
"""Pure-python implementation of RSA-PSS verification.
This is horribly slow and probably broken, but it's the best we can do
if PyCrypto is not available safely.
"""
# The signedimp.tools.get_bootstrap_code() function will inline the
# raw code from signedimp.cryptobase.rsa
if not _signedimp_util._RSAKeyWithPSS:
from signedimp.cryptobase.rsa import RSAKeyWithPSS
_signedimp_util._RSAKeyWithPSS = RSAKeyWithPSS
return _signedimp_util._RSAKeyWithPSS(modulus,pubexp,privexp)
if not _signedimp_util._RSAKey:
from signedimp.cryptobase.rsa import RSAKey
_signedimp_util._RSAKey = RSAKey
return _signedimp_util._RSAKey(modulus,pubexp,privexp)

@staticmethod
def _b64unquad(quad):
Expand Down Expand Up @@ -253,7 +253,7 @@ def recreate():
from signedimp.crypto import rsa
_signedimp_util.md5 = md5.md5
_signedimp_util.sha1 = sha1.sha1
_signedimp_util.RSAKeyWithPSS = rsa.RSAKeyWithPSS
_signedimp_util.RSAKey = rsa.RSAKey
except (ImportError,IntegrityCheckMissing):
# Try to use hashlib
try:
Expand Down Expand Up @@ -344,7 +344,7 @@ def parse_hash_data(self,hashdata):
try:
fingerprint,signature = ln.split()
signature = _signedimp_util.b64decode(signature)
except ValueError, e:
except (ValueError,TypeError):
return
for k in self.valid_keys:
if k.fingerprint() == fingerprint:
Expand Down Expand Up @@ -1087,9 +1087,9 @@ def get_datafilepath(self,path):



def RSAKeyWithPSS(*args,**kwds):
"""Wrapper to expose RSAKeyWithPSS at the top-level of the module."""
return _signedimp_util.RSAKeyWithPSS(*args,**kwds)
def RSAKey(*args,**kwds):
"""Wrapper to expose RSAKey at the top-level of the module."""
return _signedimp_util.RSAKey(*args,**kwds)


# Try to speed up initial imports by loading builtin or frozen utility mods.
Expand Down
18 changes: 8 additions & 10 deletions signedimp/crypto/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from Crypto.PublicKey import RSA as _RSA
from Crypto.Cipher import AES

from signedimp.cryptobase.rsa import RSAKey, RSAKeyWithPSS, math
from signedimp.cryptobase.rsa import RSAKey, math
from signedimp.crypto.pss import PSS, strxor


Expand Down Expand Up @@ -58,6 +58,8 @@ class RSAKey(RSAKey):
"""Public key using RSS with no padding."""

_math = math
_PSS = PSS


def __init__(self,modulus,pub_exponent,priv_exponent=None,**kwds):
super(RSAKey,self).__init__(modulus,pub_exponent,priv_exponent,**kwds)
Expand Down Expand Up @@ -99,7 +101,11 @@ def verify(self,message,signature):
return False

def save_to_file(self,f,password):
"""Save to given filelike object, encrypted with given password."""
"""Save to given filelike object, encrypted with given password.
This simple stores a pickle of the key into the given file, encrypted
with a key derived from the password.
"""
# To save difficulty in unpadding, we add pickle.STOP until we get
# to the block size. This is ignored by the unpickler.
assert len(pickle.STOP) == 1
Expand Down Expand Up @@ -147,12 +153,4 @@ def load_from_file(cls,f,password):
# Finally we can unpickle the key
return pickle.loads(data)



class RSAKeyWithPSS(RSAKey,RSAKeyWithPSS):
"""Public key using RSS with PSS signature padding scheme."""

_math = math
_PSS = PSS


113 changes: 67 additions & 46 deletions signedimp/cryptobase/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ def num_bytes(n):


class RSAKey(object):
"""Public key using RSS with no padding."""
"""Public key using RSS with optional padding."""

_math = math
_PSS = PSS

def __init__(self,modulus,pub_exponent,priv_exponent=None):
def __init__(self,modulus,pub_exponent,priv_exponent=None,**kwds):
self.modulus = modulus
self.pub_exponent = pub_exponent
self.priv_exponent = priv_exponent
self.size = self._math.num_bytes(modulus) * 8
self.randbytes = kwds.pop("randbytes",None)
self.default_padding_scheme = kwds.pop("default_padding_scheme",None)
self.allowed_padding_schemes = kwds.pop("allowed_padding_schemes",None)
self._padders = {}

def fingerprint(self):
hash = sha1("RSAKey %s %s" % (self.modulus,self.pub_exponent,))
Expand All @@ -60,19 +65,31 @@ def fingerprint(self):
def get_public_key(self):
return self.__class__(self.modulus,self.pub_exponent)

def __getstate__(self):
state = self.__dict__.copy()
state.pop("_padders",None)
return state

def __setstate__(self,state):
state["_padders"] = {}
self.__dict__.update(state)

def __eq__(self,other):
return self.__dict__ == other.__dict__

def __ne__(self,other):
return self.__dict__ != other.__dict__

def __repr__(self):
s = "%s(%r,%r"%(self.__class__.__name__,self.modulus,self.pub_exponent)
if self.priv_exponent is not None:
return "%s(%s,%s,%s)" % (self.__class__.__name__,self.modulus,
self.pub_exponent,self.priv_exponent,)
else:
return "%s(%s,%s)" % (self.__class__.__name__,self.modulus,
self.pub_exponent,)
s += ",%r" % (self.priv_exponent,)
if self.default_padding_scheme is not None:
s += ",default_padding_scheme=%r" % (self.default_padding_scheme,)
if self.allowed_padding_schemes is not None:
s += ",allowed_padding_schemes=%r" % (self.allowed_padding_schemes,)
s += ")"
return s

def __getstate__(self):
return self.__dict__.copy()
Expand All @@ -88,50 +105,54 @@ def decrypt(self,message):
m = self._math.bytes_to_long(message)
return self._math.long_to_bytes(pow(m,self.priv_exponent,self.modulus))

def sign(self,message):
return self.decrypt(message)

def verify(self,message,signature):
while message.startswith("\x00"):
message = message[1:]
if not message:
message = "\x00"
return (message == self.encrypt(signature))


class RSAKeyWithPSS(RSAKey):
"""Public key using RSS with PSS signature padding scheme."""

_PSS = PSS

def __init__(self,modulus,pub_exponent,priv_exponent=None,randbytes=None):
super(RSAKeyWithPSS,self).__init__(modulus,pub_exponent,priv_exponent)
self._pss = self._PSS(self.size/8,randbytes=randbytes)

def __getstate__(self):
state = super(RSAKeyWithPSS,self).__getstate__()
state.pop("_pss",None)
state["randbytes"] = self._pss.randbytes
return state

def __setstate__(self,state):
randbytes = state.pop("randbytes",None)
state["_pss"] = self._PSS(state["size"]/8,randbytes)
super(RSAKeyWithPSS,self).__setstate__(state)

def fingerprint(self):
hash = sha1("RSAKeyWithPSS %s %s" % (self.modulus,self.pub_exponent,))
return hash.hexdigest()

def sign(self,message):
encsig = self._pss.encode(message)
def sign(self,message,padding_scheme=None):
if padding_scheme is None:
padding_scheme = self.default_padding_scheme
if padding_scheme is None:
padding_scheme = "pss-sha1"
encsig = self._get_padder(padding_scheme).encode(message)
signature = self.decrypt(encsig)
return signature.rjust(self.size/8,"\x00")
return padding_scheme + ":" + signature.rjust(self.size/8,"\x00")

def verify(self,message,signature):
try:
padding_scheme,signature = signature.split(":",1)
except (ValueError,TypeError):
return False
signature = signature.rjust(self.size/8,"\x00")
encsig = self.encrypt(signature)
encsig = encsig.rjust(self.size/8,"\x00")
return self._pss.verify(message,encsig)
try:
padder = self._get_padder(padding_scheme)
except ValueError:
return False
return padder.verify(message,encsig)


def _get_padder(self,scheme):
if self.allowed_padding_schemes is not None:
if scheme not in self.allowed_padding_schemes:
msg = "padding scheme '%s' has been disallowed" % (scheme,)
raise ValueError(msg)
try:
padder = self._padders[scheme]
except KeyError:
if scheme == "pss-sha1":
padder = self._PSS(self.size/8,self.randbytes)
elif scheme == "raw":
padder = self._RawPadder(self.size)
else:
msg = "unrecognised padding scheme: %s" % (scheme,)
raise ValueError(msg)
self._padders[scheme] = padder
return padder

class _RawPadder:
def __init__(self,size):
self.size = size
def encode(self,message):
return message.rjust(self.size/8,"\x00")
def verify(self,message,signature):
return (message.rjust(self.size/8,"\x00") == signature)


2 changes: 1 addition & 1 deletion signedimp/tests/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TestCrypto(TestCryptoBase):


def test_load_and_save_keys(self):
cls = self.rsa.RSAKeyWithPSS
cls = self.rsa.RSAKey
k = cls.generate()
tf = tempfile.TemporaryFile()
try:
Expand Down
12 changes: 8 additions & 4 deletions signedimp/tests/test_cryptobase.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ def _corrupt(self,data,inv_probability=5):
newdata = "".join(newbytes)
return newdata

def test_rsa_verify(self):
def test_rsa_raw_verify(self):
k = RSA.generate(1024,os.urandom)
pubkey = self.rsa.RSAKey(k.n,k.e)
privkey = self.rsa.RSAKey(k.n,k.e,k.d)
pubkey.default_padding_scheme = "raw"
privkey.default_padding_scheme = "raw"
self.assertEquals(pubkey.size,1024)
self.assertEquals(privkey.size,1024)
self.assertTrue(pubkey.verify("",privkey.sign("")))
Expand All @@ -89,10 +91,12 @@ def test_rsa_verify(self):
self.assertFalse(pubkey.verify(bs,self._corrupt(sig,5)))
self.assertFalse(pubkey.verify(bs,self._corrupt(sig,1)))

def test_rsawithpss_verify(self):
def test_rsa_pss_verify(self):
k = RSA.generate(1024,os.urandom)
pubkey = self.rsa.RSAKeyWithPSS(k.n,k.e)
privkey = self.rsa.RSAKeyWithPSS(k.n,k.e,k.d,randbytes=os.urandom)
pubkey = self.rsa.RSAKey(k.n,k.e)
privkey = self.rsa.RSAKey(k.n,k.e,k.d,randbytes=os.urandom)
pubkey.default_padding_scheme = "pss-sha1"
privkey.default_padding_scheme = "pss-sha1"
self.assertEquals(pubkey.size,1024)
self.assertEquals(privkey.size,1024)
self.assertTrue(pubkey.verify("",privkey.sign("")))
Expand Down

0 comments on commit cec32b5

Please sign in to comment.