188 lines
6.2 KiB
Python
188 lines
6.2 KiB
Python
|
from __future__ import absolute_import
|
||
|
|
||
|
import sys
|
||
|
import re
|
||
|
|
||
|
from .exceptions import SignatureError, RouteMissingError, RouteEndpointError
|
||
|
from .utils import to_bytes_2and3, constant_time_compare
|
||
|
|
||
|
class AlgorithmBase(object):
|
||
|
"""Base for algorithm support classes."""
|
||
|
pass
|
||
|
|
||
|
class HasherBase(AlgorithmBase):
|
||
|
"""
|
||
|
Base for algos which need a hash function. The ``bits`` param can be
|
||
|
passed in from the capturing group of the routing regexp
|
||
|
"""
|
||
|
supported_bits = (256, 384, 512)
|
||
|
def __init__(self, bits):
|
||
|
"""
|
||
|
Determine if the algorithm supports the requested bit depth and set up
|
||
|
matching hash method from ``hashlib`` if necessary.
|
||
|
"""
|
||
|
self.bits = int(bits)
|
||
|
if self.bits not in self.supported_bits:
|
||
|
raise NotImplementedError("%s implements %s bit algorithms (given %d)" %
|
||
|
(self.__class__, ', '.join(self.supported_bits), self.bits))
|
||
|
if not getattr(self, 'hasher', None):
|
||
|
import hashlib
|
||
|
self.hasher = getattr(hashlib, 'sha%d' % self.bits)
|
||
|
|
||
|
class HMAC(HasherBase):
|
||
|
"""
|
||
|
Support for HMAC signing.
|
||
|
"""
|
||
|
def sign(self, msg, key):
|
||
|
import hmac
|
||
|
if sys.version < '3':
|
||
|
utfkey = unicode(key).encode('utf8')
|
||
|
else:
|
||
|
utfkey = to_bytes_2and3(key)
|
||
|
msg = to_bytes_2and3(msg)
|
||
|
return hmac.new(utfkey, msg, self.hasher).digest()
|
||
|
|
||
|
def verify(self, msg, crypto, key):
|
||
|
if not constant_time_compare(self.sign(msg, key), crypto):
|
||
|
raise SignatureError("Could not validate signature")
|
||
|
return True
|
||
|
|
||
|
class RSABase(HasherBase):
|
||
|
"""
|
||
|
Support for RSA signing.
|
||
|
|
||
|
The ``Crypto`` package >= 2.5 is required.
|
||
|
|
||
|
"""
|
||
|
supported_bits = (256,384,512,) #:Seems to worka > 256
|
||
|
|
||
|
def __init__(self, padder, bits):
|
||
|
super(RSABase,self).__init__(bits)
|
||
|
self.padder = padder
|
||
|
from Crypto.Hash import SHA256,SHA384,SHA512
|
||
|
self.hashm = __import__('Crypto.Hash.SHA%d'%self.bits, globals(), locals(), ['*']).new()
|
||
|
|
||
|
def sign(self, msg, key):
|
||
|
"""
|
||
|
Signs a message with an RSA PrivateKey and hash method
|
||
|
"""
|
||
|
import Crypto.PublicKey.RSA as RSA
|
||
|
|
||
|
self.hashm.update(msg.encode('UTF-8'))
|
||
|
## assume we are dealing with a real key
|
||
|
# private_key = RSA.importKey(key)
|
||
|
return self.padder.new(key).sign(self.hashm) # pycrypto 2.5
|
||
|
|
||
|
def verify(self, msg, crypto, key):
|
||
|
"""
|
||
|
Verifies a message using RSA cryptographic signature and key.
|
||
|
|
||
|
``crypto`` is the cryptographic signature
|
||
|
``key`` is the verifying key. Can be a real key object or a string.
|
||
|
"""
|
||
|
import Crypto.PublicKey.RSA as RSA
|
||
|
|
||
|
self.hashm.update(msg.encode('UTF-8'))
|
||
|
private_key = key
|
||
|
if not isinstance(key, RSA._RSAobj):
|
||
|
private_key = RSA.importKey(key)
|
||
|
if not self.padder.new( private_key ).verify(self.hashm, crypto): #:pycrypto 2.5
|
||
|
raise SignatureError("Could not validate signature")
|
||
|
return True
|
||
|
|
||
|
class RSA_PKCS1_5(RSABase):
|
||
|
def __init__(self, bits):
|
||
|
import Crypto.Signature.PKCS1_v1_5 as PKCS
|
||
|
super(RSA_PKCS1_5,self).__init__(PKCS, bits)
|
||
|
|
||
|
class RSA_PSS(RSABase):
|
||
|
def __init__(self, bits):
|
||
|
import Crypto.Signature.PKCS1_PSS as PSS
|
||
|
super(RSA_PSS,self).__init__(PSS, bits)
|
||
|
|
||
|
class ECDSA(HasherBase):
|
||
|
"""
|
||
|
Support for ECDSA signing. This is the preferred algorithm for private/public key
|
||
|
verification.
|
||
|
|
||
|
The ``ecdsa`` package is required. ``pip install ecdsa``
|
||
|
"""
|
||
|
bits_to_curve = {
|
||
|
256: 'NIST256p',
|
||
|
384: 'NIST384p',
|
||
|
512: 'NIST521p',
|
||
|
}
|
||
|
def sign(self, msg, key):
|
||
|
"""
|
||
|
Signs a message with an ECDSA SigningKey and hash method matching the
|
||
|
bit depth of curve algorithm.
|
||
|
"""
|
||
|
import ecdsa
|
||
|
## assume the signing key is already a real key
|
||
|
# curve = getattr(ecdsa, self.bits_to_curve[self.bits])
|
||
|
# signing_key = ecdsa.SigningKey.from_string(key, curve=curve)
|
||
|
msg = to_bytes_2and3(msg)
|
||
|
return key.sign(msg, hashfunc=self.hasher)
|
||
|
|
||
|
def verify(self, msg, crypto, key):
|
||
|
"""
|
||
|
Verifies a message using ECDSA cryptographic signature and key.
|
||
|
|
||
|
``crypto`` is the cryptographic signature
|
||
|
``key`` is the verifying key. Can be a real key object or a string.
|
||
|
"""
|
||
|
import ecdsa
|
||
|
curve = getattr(ecdsa, self.bits_to_curve[self.bits])
|
||
|
vk = key
|
||
|
if not isinstance(vk, ecdsa.VerifyingKey):
|
||
|
vk = ecdsa.VerifyingKey.from_string(key, curve=curve)
|
||
|
try:
|
||
|
vk.verify(crypto, to_bytes_2and3(msg), hashfunc=self.hasher)
|
||
|
except ecdsa.BadSignatureError:
|
||
|
raise SignatureError("Could not validate signature")
|
||
|
except AssertionError:
|
||
|
raise SignatureError("Could not validate signature")
|
||
|
return True
|
||
|
|
||
|
# algorithm routing
|
||
|
def route(name):
|
||
|
return resolve(*find(name))
|
||
|
|
||
|
def find(name):
|
||
|
# TODO: more error checking around custom algorithms
|
||
|
algorithms = CUSTOM + list(DEFAULT)
|
||
|
for (route, endpoint) in algorithms:
|
||
|
match = re.match(route, name)
|
||
|
if match:
|
||
|
return (endpoint, match)
|
||
|
raise RouteMissingError('endpoint matching %s could not be found' % name)
|
||
|
|
||
|
def resolve(endpoint, match):
|
||
|
if callable(endpoint):
|
||
|
# send result back through
|
||
|
return resolve(endpoint(**match.groupdict()), match)
|
||
|
|
||
|
# get the sign and verify methods from dict or obj
|
||
|
try:
|
||
|
crypt = { 'sign': endpoint['sign'], 'verify': endpoint['verify'] }
|
||
|
except TypeError:
|
||
|
try:
|
||
|
crypt = { 'sign': endpoint.sign, 'verify': endpoint.verify }
|
||
|
except AttributeError as e:
|
||
|
raise RouteEndpointError('route enpoint must have sign, verify as attributes or items of dict')
|
||
|
# verify callability
|
||
|
try:
|
||
|
assert callable(crypt['sign'])
|
||
|
assert callable(crypt['verify'])
|
||
|
except AssertionError as e:
|
||
|
raise RouteEndpointError('sign, verify of endpoint must be callable')
|
||
|
return crypt
|
||
|
|
||
|
DEFAULT = (
|
||
|
(r'^HS(?P<bits>256|384|512)$', HMAC),
|
||
|
(r'^RS(?P<bits>256|384|512)$', RSA_PKCS1_5),
|
||
|
(r'^PS(?P<bits>256|384|512)$', RSA_PSS),
|
||
|
(r'^ES(?P<bits>256|384|512)$', ECDSA),
|
||
|
)
|
||
|
CUSTOM = []
|