bug 1360623 - add hash algorithm constants to pykey for easier consumer use r=jcj

For signing, pykey.py delegates to 3rd party libraries. One of these libraries
expects hash algorithms to be specified in the form "SHA-256" whereas the other
expects "sha256". Consumers of pykey shouldn't need to be aware of this detail.
This patch introduces constants HASH_SHA1, HASH_SHA256, etc. and changes pykey
to determine which string literals to use itself.

MozReview-Commit-ID: 27laM2uXMwJ

--HG--
extra : rebase_source : 9b74f486f7535671fd26c59e3e9cc3b4459f15e0
This commit is contained in:
David Keeler 2017-04-28 11:06:28 -07:00
Родитель 7d8e567c4c
Коммит 1ef3597000
2 изменённых файлов: 48 добавлений и 13 удалений

Просмотреть файл

@ -303,26 +303,26 @@ def stringToDN(string, tag=None):
def stringToAlgorithmIdentifiers(string):
"""Helper function that converts a description of an algorithm
to a representation usable by the pyasn1 package and a hash
algorithm name for use by pykey."""
algorithm constant for use by pykey."""
algorithmIdentifier = rfc2459.AlgorithmIdentifier()
algorithmName = None
algorithmType = None
algorithm = None
if string == 'sha1WithRSAEncryption':
algorithmName = 'SHA-1'
algorithmType = pykey.HASH_SHA1
algorithm = rfc2459.sha1WithRSAEncryption
elif string == 'sha256WithRSAEncryption':
algorithmName = 'SHA-256'
algorithmType = pykey.HASH_SHA256
algorithm = univ.ObjectIdentifier('1.2.840.113549.1.1.11')
elif string == 'md5WithRSAEncryption':
algorithmName = 'MD5'
algorithmType = pykey.HASH_MD5
algorithm = rfc2459.md5WithRSAEncryption
elif string == 'ecdsaWithSHA256':
algorithmName = 'sha256'
algorithmType = pykey.HASH_SHA256
algorithm = univ.ObjectIdentifier('1.2.840.10045.4.3.2')
else:
raise UnknownAlgorithmTypeError(string)
algorithmIdentifier.setComponentByName('algorithm', algorithm)
return (algorithmIdentifier, algorithmName)
return (algorithmIdentifier, algorithmType)
def datetimeToTime(dt):
"""Takes a datetime object and returns an rfc2459.Time object with
@ -640,7 +640,7 @@ class Certificate(object):
return stringToDN(self.subject)
def toDER(self):
(signatureOID, hashName) = stringToAlgorithmIdentifiers(self.signature)
(signatureOID, hashAlgorithm) = stringToAlgorithmIdentifiers(self.signature)
tbsCertificate = rfc2459.TBSCertificate()
tbsCertificate.setComponentByName('version', self.getVersion())
tbsCertificate.setComponentByName('serialNumber', self.getSerialNumber())
@ -660,7 +660,7 @@ class Certificate(object):
certificate.setComponentByName('tbsCertificate', tbsCertificate)
certificate.setComponentByName('signatureAlgorithm', signatureOID)
tbsDER = encoder.encode(tbsCertificate)
certificate.setComponentByName('signatureValue', self.issuerKey.sign(tbsDER, hashName))
certificate.setComponentByName('signatureValue', self.issuerKey.sign(tbsDER, hashAlgorithm))
return encoder.encode(certificate)
def toPEM(self):

Просмотреть файл

@ -41,6 +41,11 @@ import mock
import rsa
import sys
# "constants" to make it easier for consumers to specify hash algorithms
HASH_MD5 = 'hash:md5'
HASH_SHA1 = 'hash:sha1'
HASH_SHA256 = 'hash:sha256'
def byteStringToHexifiedBitString(string):
"""Takes a string of bytes and returns a hex string representing
those bytes for use with pyasn1.type.univ.BitString. It must be of
@ -66,6 +71,25 @@ class UnknownKeySpecificationError(UnknownBaseError):
UnknownBaseError.__init__(self, value)
self.category = 'key specification'
class UnknownHashAlgorithmError(UnknownBaseError):
"""Helper exception type to handle unknown key specifications."""
def __init__(self, value):
UnknownBaseError.__init__(self, value)
self.category = 'hash algorithm'
class UnsupportedHashAlgorithmError(Exception):
"""Helper exception type for unsupported hash algorithms."""
def __init__(self, value):
super(UnsupportedHashAlgorithmError, self).__init__()
self.value = value
def __str__(self):
return 'Unsupported hash algorithm "%s"' % repr(self.value)
class RSAPublicKey(univ.Sequence):
"""Helper type for encoding an RSA public key"""
componentType = namedtype.NamedTypes(
@ -553,10 +577,19 @@ class RSAKey(object):
spki.setComponentByName('subjectPublicKey', subjectPublicKey)
return spki
def sign(self, data, hashAlgorithmName):
def sign(self, data, hashAlgorithm):
"""Returns a hexified bit string representing a
signature by this key over the specified data.
Intended for use with pyasn1.type.univ.BitString"""
hashAlgorithmName = None
if hashAlgorithm == HASH_MD5:
hashAlgorithmName = "MD5"
elif hashAlgorithm == HASH_SHA1:
hashAlgorithmName = "SHA-1"
elif hashAlgorithm == HASH_SHA256:
hashAlgorithmName = "SHA-256"
else:
raise UnknownHashAlgorithmError(hashAlgorithm)
rsaPrivateKey = rsa.PrivateKey(self.RSA_N, self.RSA_E, self.RSA_D, self.RSA_P, self.RSA_Q)
signature = rsa.sign(data, rsaPrivateKey, hashAlgorithmName)
return byteStringToHexifiedBitString(signature)
@ -664,10 +697,12 @@ class ECCKey(object):
spki.setComponentByName('subjectPublicKey', subjectPublicKey)
return spki
def sign(self, data, hashAlgorithmName):
def sign(self, data, hashAlgorithm):
"""Returns a hexified bit string representing a
signature by this key over the specified data.
Intended for use with pyasn1.type.univ.BitString"""
if hashAlgorithm != HASH_SHA256:
raise UnsupportedHashAlgorithmError(hashAlgorithm)
# There is some non-determinism in ECDSA signatures. Work around
# this by patching ecc.ecdsa.urandom to not be random.
with mock.patch('ecc.ecdsa.urandom', side_effect=notRandom):
@ -677,9 +712,9 @@ class ECCKey(object):
# Also patch in secp256k1 if applicable.
if self.keyOID == secp256k1:
with mock.patch('ecc.curves.DOMAINS', {256: secp256k1Params}):
x, y = encoding.dec_point(self.key.sign(data, hashAlgorithmName))
x, y = encoding.dec_point(self.key.sign(data, 'sha256'))
else:
x, y = encoding.dec_point(self.key.sign(data, hashAlgorithmName))
x, y = encoding.dec_point(self.key.sign(data, 'sha256'))
point = ECPoint()
point.setComponentByName('x', x)
point.setComponentByName('y', y)