Skip to content

Commit

Permalink
parsing of DSA keys from X.509 certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
FrantisekKrenzelok committed Sep 7, 2020
1 parent 1c68f2e commit f223419
Show file tree
Hide file tree
Showing 11 changed files with 573 additions and 11 deletions.
16 changes: 16 additions & 0 deletions tlslite/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ class SignatureScheme(TLSEnum):
rsa_pss_sha384 = (8, 5)
rsa_pss_sha512 = (8, 6)

dsa_sha1 = (2, 2)
dsa_sha224 = (3, 2)
dsa_sha256 = (4, 2)
dsa_sha384 = (5, 2)
dsa_sha512 = (6, 2)

@classmethod
def toRepr(cls, value, blacklist=None):
"""Convert numeric type to name representation"""
Expand Down Expand Up @@ -337,6 +343,16 @@ class AlgorithmOID(TLSEnum):
SignatureScheme.rsa_pss_rsae_sha384
oid[bytes(a2b_hex('300b0609608648016503040203'))] = \
SignatureScheme.rsa_pss_rsae_sha512
oid[bytes(a2b_hex('06072A8648CE380403'))] = \
SignatureScheme.dsa_sha1
oid[bytes(a2b_hex('0609608648016503040301'))] = \
SignatureScheme.dsa_sha224
oid[bytes(a2b_hex('0609608648016503040302'))] = \
SignatureScheme.dsa_sha256
oid[bytes(a2b_hex('0609608648016503040303'))] = \
SignatureScheme.dsa_sha384
oid[bytes(a2b_hex('0609608648016503040304'))] = \
SignatureScheme.dsa_sha512


class GroupName(TLSEnum):
Expand Down
1 change: 1 addition & 0 deletions tlslite/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"python_aes",
"python_rc4",
"python_rsakey",
"python_dsakey",
"rc4",
"rijndael",
"rsakey",
Expand Down
108 changes: 108 additions & 0 deletions tlslite/utils/dsakey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Abstract class for DSA."""

class DSAKey(object):
"""This is an abstract base class for DSA keys.
Particular implementations of DSA keys, such as
:py:class:`~.python_dsakey.Python_DSAKey`
... more coming
inherit from this.
To create or parse an DSA key, don't use one of these classes
directly. Instead, use the factory functions in
:py:class:`~tlslite.utils.keyfactory`.
"""

def __init__(self, p, q, g, x, y):
"""Create a new DSA key.
:type p: int
:param p: domain parameter, prime num defining Gaolis Field
:type q: int
:param q: domain parameter, prime factor of p-1
:type g: int
:param g: domain parameter, generator of q-order cyclic group GP(p)
:type x: int
:param x: private key
:type y: int
:param y: public key
"""
raise NotImplementedError()

def __len__(self):
"""Return the size of the order of the curve of this key, in bits.
:rtype: int
"""
raise NotImplementedError()

def hasPrivateKey(self):
"""Return whether or not this key has a private component.
:rtype: bool
"""
raise NotImplementedError()

def hashAndSign(self, data, hAlg):
"""Hash and sign the passed-in bytes.
This requires the key to have a private component and
global parameters. It performs a signature on the passed-in data
with selected hash algorithm.
:type data: str
:param data: The data which will be hashed and signed.
:type hAlg: str
:param hAlg: The hash algorithm that will be used to hash data
:rtype: bytearray
:returns: An DSA signature on the passed-in data.
"""
raise NotImplementedError()

def hashAndVerify(self, signature, data, hAlg="sha1"):
"""Hash and verify the passed-in bytes with signature.
:type signature: ASN1 bytearray
:param signature: the r, s dsa signature
:type data: str
:param data: The data which will be hashed and verified.
:type hAlg: str
:param hAlg: The hash algorithm that will be used to hash data
:rtype: bool
:returns: return True if verification is OK.
"""
raise NotImplementedError()

@staticmethod
def generate(L, N):
"""Generate new key given by bit lengths L, N.
:type L: int
:param L: length of parameter p in bits
:type N: int
:param N: length of parameter q in bits
:rtype: DSAkey
:returns: DSAkey(domain parameters, private key, public key)
"""
raise NotImplementedError()

@staticmethod
def generate_qp(L, N):
"""Generate new (p, q) given by bit lengths L, N.
:type L: int
:param L: length of parameter p in bits
:type N: int
:param N: length of parameter q in bits
:rtype: (int, int)
:returns: new p and q key parameters
"""
raise NotImplementedError()
26 changes: 26 additions & 0 deletions tlslite/utils/keyfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .rsakey import RSAKey
from .python_rsakey import Python_RSAKey
from .python_ecdsakey import Python_ECDSAKey
from .python_dsakey import Python_DSAKey
from tlslite.utils import cryptomath

if cryptomath.m2cryptoLoaded:
Expand Down Expand Up @@ -233,3 +234,28 @@ def _create_public_ecdsa_key(point_x, point_y, curve_name,
if impl == "python":
return Python_ECDSAKey(point_x, point_y, curve_name)
raise ValueError("No acceptable implementation")

def _create_public_dsa_key(p, q, g, y,
implementations=("python",)):
"""
Convert public key parameters into concrete implementation of verifier.
The public key in DSA consists of four integers.
:type p: int
:param p: domain parameter, prime num defining Gaolis Field
:type q: int
:param q: domain parameter, prime factor of p-1
:type g: int
:param g: domain parameter, generator of q-order cyclic group GP(p)
:type y: int
:param y: public key
:type implementations: iterable of str
:param implementations: list of implementations that can be used as the
concrete implementation of the verifying key (only 'python' is
supported currently)
"""
for impl in implementations:
if impl == "python":
return Python_DSAKey(p=p, q=q, g=g, y=y)
raise ValueError("No acceptable implementation")
125 changes: 125 additions & 0 deletions tlslite/utils/python_dsakey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Author: Frantisek Krenzelok

"""Pure-Python RSA implementation."""
from ecdsa.der import encode_sequence, encode_integer, \
remove_sequence, remove_integer

from .cryptomath import getRandomNumber, getRandomPrime, \
powMod, numBits, bytesToNumber, invMod, secureHash, \
GMPY2_LOADED, gmpyLoaded

if GMPY2_LOADED:
from gmpy2 import mpz
elif gmpyLoaded:
from gmpy import mpz

from .dsakey import DSAKey

class Python_DSAKey(DSAKey):
"""
Concrete implementaion of DSA object.
for func docstring see tlslite/dsakey.py
"""
def __init__(self, p=0, q=0, g=0, x=0, y=0):
if gmpyLoaded or GMPY2_LOADED:
p = mpz(p)
q = mpz(q)
g = mpz(g)
x = mpz(x)
y = mpz(y)
self.p = p
self.q = q
self.g = g
self.private_key = x
self.public_key = y
self.key_type = "dsa"

if p and q and p < q:
raise ValueError("q is greater than p")

def __len__(self):
return numBits(self.p)

def hasPrivateKey(self):
return bool(self.private_key)

@staticmethod
def generate(L, N):
assert (L, N) in [(1024, 160), (2048, 224), (2048, 256), (3072, 256)]
key = Python_DSAKey()
(q, p) = Python_DSAKey.generate_qp(L, N)

index = getRandomNumber(1, (p-1))
g = powMod(index, int((p-1)/q), p)
x = getRandomNumber(1, q-1)
y = powMod(g, x, p)
if gmpyLoaded or GMPY2_LOADED:
p = mpz(p)
q = mpz(q)
g = mpz(g)
x = mpz(x)
y = mpz(y)
key.q = q
key.p = p
key.g = g
key.private_key = x
key.public_key = y
return key

@staticmethod
def generate_qp(L, N):
assert (L, N) in [(1024, 160), (2048, 224), (2048, 256), (3072, 256)]

q = int(getRandomPrime(N))
while True:
p = int(getRandomPrime(L))
if (p-1) % q:
break
return (q, p)

def hashAndSign(self, data, hAlg="sha1"):
digest = bytesToNumber(secureHash(bytearray(data), hAlg))
digest_size = numBits(digest)

# extract min(|hAlg|, N) left bits of digest
N = numBits(self.q)
if N < digest_size:
digest &= ~(~0 << (digest_size - N))

k = getRandomNumber(1, (self.q-1))
r = powMod(self.g, k, self.p) % self.q
s = invMod(k, self.q) * (digest + self.private_key * r) % self.q

return encode_sequence(encode_integer(r), encode_integer(s))

def hashAndVerify(self, signature, data, hAlg="sha1"):
# Get r, s components from signature
digest = bytesToNumber(secureHash(bytearray(data), hAlg))
digest_size = numBits(digest)

# extract min(|hAlg|, N) left bits of digest
N = numBits(self.q)
if N < digest_size:
digest &= ~(~0 << (digest_size - N))

# get r, s keys
if not signature:
return False
body, rest = remove_sequence(signature)
if rest:
return False
r, rest = remove_integer(body)
s, rest = remove_integer(rest)
if rest:
return False

# check the signature
if 0 < r < self.q and 0 < s < self.q:
w = invMod(s, self.q)
u1 = (digest * w) % self.q
u2 = (r * w) % self.q
v = ((powMod(self.g, u1, self.p) * \
powMod(self.public_key, u2, self.p)) % self.p) % self.q

return r == v
return False

0 comments on commit f223419

Please sign in to comment.