Skip to content

Commit

Permalink
Merge pull request #439 from tlsfuzzer/bleichenbacher-fixes-0.7
Browse files Browse the repository at this point in the history
Bleichenbacher fixes [0.7]
  • Loading branch information
tomato42 committed Dec 8, 2020
2 parents acdde31 + 2738f15 commit c28d6d3
Show file tree
Hide file tree
Showing 9 changed files with 1,621 additions and 129 deletions.
14 changes: 14 additions & 0 deletions tlslite/utils/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import sys
import os
import re
import platform
import math
import binascii
Expand Down Expand Up @@ -68,6 +69,10 @@ def formatExceptionTrace(e):
"""Return exception information formatted as string"""
return str(e)

def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text, flags=re.UNICODE)

else:
# Python 2.6 requires strings instead of bytearrays in a couple places,
# so we define this function so it does the conversion if needed.
Expand All @@ -76,9 +81,18 @@ def formatExceptionTrace(e):
if sys.version_info < (2, 7) or sys.version_info < (2, 7, 4) \
or platform.system() == 'Java':
def compat26Str(x): return str(x)

def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text)

else:
def compat26Str(x): return x

def remove_whitespace(text):
"""Removes all whitespace from passed in string"""
return re.sub(r"\s+", "", text, flags=re.UNICODE)

def compatAscii2Bytes(val):
"""Convert ASCII string to bytes."""
return val
Expand Down
17 changes: 16 additions & 1 deletion tlslite/utils/constanttime.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def ct_lt_u32(val_a, val_b):

return (val_a^((val_a^val_b)|(((val_a-val_b)&0xffffffff)^val_b)))>>31


def ct_gt_u32(val_a, val_b):
"""
Return 1 if val_a > val_b, 0 otherwise. Constant time.
Expand All @@ -35,6 +36,7 @@ def ct_gt_u32(val_a, val_b):
"""
return ct_lt_u32(val_b, val_a)


def ct_le_u32(val_a, val_b):
"""
Return 1 if val_a <= val_b, 0 otherwise. Constant time.
Expand All @@ -47,14 +49,26 @@ def ct_le_u32(val_a, val_b):
"""
return 1 ^ ct_gt_u32(val_a, val_b)


def ct_lsb_prop_u8(val):
"""Propagate LSB to all 8 bits of the returned byte. Constant time."""
"""Propagate LSB to all 8 bits of the returned int. Constant time."""
val &= 0x01
val |= val << 1
val |= val << 2
val |= val << 4
return val


def ct_lsb_prop_u16(val):
"""Propagate LSB to all 16 bits of the returned int. Constant time."""
val &= 0x01
val |= val << 1
val |= val << 2
val |= val << 4
val |= val << 8
return val


def ct_isnonzero_u32(val):
"""
Returns 1 if val is != 0, 0 otherwise. Constant time.
Expand All @@ -66,6 +80,7 @@ def ct_isnonzero_u32(val):
val &= 0xffffffff
return (val|(-val&0xffffffff)) >> 31


def ct_neq_u32(val_a, val_b):
"""
Return 1 if val_a != val_b, 0 otherwise. Constant time.
Expand Down
16 changes: 9 additions & 7 deletions tlslite/utils/keyfactory.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,23 @@ def parsePEMKey(s, private=False, public=False, passwordCallback=None,


def _parseKeyHelper(key, private, public):
if private:
if not key.hasPrivateKey():
raise SyntaxError("Not a private key!")
if private and not key.hasPrivateKey():
raise SyntaxError("Not a private key!")

if public:
return _createPublicKey(key)

if private:
if hasattr(key, "d"):
return _createPrivateKey(key)
else:
if cryptomath.m2cryptoLoaded:
if type(key) == Python_RSAKey:
return _createPrivateKey(key)
assert type(key) in (OpenSSL_RSAKey, ), type(key)
return key

elif hasattr(key, "d"):
return _createPrivateKey(key)
return key


def parseAsPublicKey(s):
"""Parse a PEM-formatted public key.
Expand Down
16 changes: 16 additions & 0 deletions tlslite/utils/openssl_rsakey.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .rsakey import *
from .python_rsakey import Python_RSAKey
from .compat import compatAscii2Bytes
import sys

#copied from M2Crypto.util.py, so when we load the local copy of m2
#we can still use it
Expand Down Expand Up @@ -65,12 +66,20 @@ def _rawPrivateKeyOp(self, m):
c = bytesToNumber(bytearray(s))
return c

def _raw_private_key_op_bytes(self, message):
return bytearray(m2.rsa_private_encrypt(self.rsa, bytes(message),
m2.no_padding))

def _rawPublicKeyOp(self, c):
b = numberToByteArray(c, numBytes(self.n))
s = m2.rsa_public_decrypt(self.rsa, bytes(b), m2.no_padding)
m = bytesToNumber(bytearray(s))
return m

def _raw_public_key_op_bytes(self, ciphertext):
return bytearray(m2.rsa_public_decrypt(self.rsa, bytes(ciphertext),
m2.no_padding))

def acceptsPassword(self): return True

def write(self, password=None):
Expand Down Expand Up @@ -146,6 +155,13 @@ def f():pass
key._hasPrivateKey = False
else:
raise SyntaxError()
if key._hasPrivateKey:
if sys.version_info < (3, 0):
b64_key = str(key.write())
else:
b64_key = str(key.write(), "ascii")
py_key = Python_RSAKey.parsePEM(b64_key)
key.d = py_key.d
return key
finally:
m2.bio_free(bio)
Expand Down
130 changes: 130 additions & 0 deletions tlslite/utils/python_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@


from .python_rsakey import Python_RSAKey
from .pem import dePem, pemSniff
from .asn1parser import ASN1Parser
from .cryptomath import bytesToNumber


class Python_Key(object):
"""
Generic methods for parsing private keys from files.
Handles both RSA and ECDSA keys, irrespective of file format.
"""

@staticmethod
def parsePEM(s, passwordCallback=None):
"""Parse a string containing a PEM-encoded <privateKey>."""

if pemSniff(s, "PRIVATE KEY"):
bytes = dePem(s, "PRIVATE KEY")
return Python_Key._parse_pkcs8(bytes)
elif pemSniff(s, "RSA PRIVATE KEY"):
bytes = dePem(s, "RSA PRIVATE KEY")
return Python_Key._parse_ssleay(bytes)
elif pemSniff(s, "DSA PRIVATE KEY"):
raise SyntaxError("DSA private key files unsupported")
elif pemSniff(s, "EC PRIVATE KEY"):
raise SyntaxError("ECDSA private key files unsupported")
elif pemSniff(s, "PUBLIC KEY"):
bytes = dePem(s, "PUBLIC KEY")
return Python_Key._parse_public_key(bytes)
else:
raise SyntaxError("Not a PEM private key file")

@staticmethod
def _parse_public_key(bytes):
# public keys are encoded as the subject_public_key_info objects
spk_info = ASN1Parser(bytes)

# first element of the SEQUENCE is the AlgorithmIdentifier
alg_id = spk_info.getChild(0)

# AlgId has two elements, the OID of the algorithm and parameters
# parameters generally have to be NULL, with exception of RSA-PSS

alg_oid = alg_id.getChild(0)

if list(alg_oid.value) != [42, 134, 72, 134, 247, 13, 1, 1, 1]:
raise SyntaxError("Only RSA Public keys supported")

subject_public_key = ASN1Parser(
ASN1Parser(spk_info.getChildBytes(1)).value[1:])

modulus = subject_public_key.getChild(0)
exponent = subject_public_key.getChild(1)

n = bytesToNumber(modulus.value)
e = bytesToNumber(exponent.value)

return Python_RSAKey(n, e)

@staticmethod
def _parse_pkcs8(bytes):
parser = ASN1Parser(bytes)

# first element in PrivateKeyInfo is an INTEGER
version = parser.getChild(0).value
if bytesToNumber(version) != 0:
raise SyntaxError("Unrecognized PKCS8 version")

# second element in PrivateKeyInfo is a SEQUENCE of type
# AlgorithmIdentifier
alg_ident = parser.getChild(1)
seq_len = alg_ident.getChildCount()
# first item of AlgorithmIdentifier is an OBJECT (OID)
oid = alg_ident.getChild(0)
if list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
key_type = "rsa"
elif list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 10]:
key_type = "rsa-pss"
else:
raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}"
.format(list(oid.value)))
# second item of AlgorithmIdentifier are parameters (defined by
# above algorithm)
if key_type == "rsa":
if seq_len != 2:
raise SyntaxError("Missing parameters for RSA algorithm ID")
parameters = alg_ident.getChild(1)
if parameters.value != bytearray(0):
raise SyntaxError("RSA parameters are not NULL")
else: # rsa-pss
pass # ignore parameters - don't apply restrictions

if seq_len > 2:
raise SyntaxError("Invalid encoding of AlgorithmIdentifier")

#Get the privateKey
private_key_parser = parser.getChild(2)

#Adjust for OCTET STRING encapsulation
private_key_parser = ASN1Parser(private_key_parser.value)

return Python_Key._parse_asn1_private_key(private_key_parser)

@staticmethod
def _parse_ssleay(data):
"""
Parse binary structure of the old SSLeay file format used by OpenSSL.
For RSA keys.
"""
private_key_parser = ASN1Parser(data)
return Python_Key._parse_asn1_private_key(private_key_parser)

@staticmethod
def _parse_asn1_private_key(private_key_parser):
version = private_key_parser.getChild(0).value[0]
if version != 0:
raise SyntaxError("Unrecognized RSAPrivateKey version")
n = bytesToNumber(private_key_parser.getChild(1).value)
e = bytesToNumber(private_key_parser.getChild(2).value)
d = bytesToNumber(private_key_parser.getChild(3).value)
p = bytesToNumber(private_key_parser.getChild(4).value)
q = bytesToNumber(private_key_parser.getChild(5).value)
dP = bytesToNumber(private_key_parser.getChild(6).value)
dQ = bytesToNumber(private_key_parser.getChild(7).value)
qInv = bytesToNumber(private_key_parser.getChild(8).value)
return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv)
77 changes: 3 additions & 74 deletions tlslite/utils/python_rsakey.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,79 +86,8 @@ def generate(bits):
return key
generate = staticmethod(generate)

@staticmethod
def parsePEM(s, passwordCallback=None):
"""Parse a string containing a PEM-encoded <privateKey>."""

if pemSniff(s, "PRIVATE KEY"):
bytes = dePem(s, "PRIVATE KEY")
return Python_RSAKey._parsePKCS8(bytes)
elif pemSniff(s, "RSA PRIVATE KEY"):
bytes = dePem(s, "RSA PRIVATE KEY")
return Python_RSAKey._parseSSLeay(bytes)
else:
raise SyntaxError("Not a PEM private key file")
parsePEM = staticmethod(parsePEM)

def _parsePKCS8(bytes):
p = ASN1Parser(bytes)

# first element in PrivateKeyInfo is an INTEGER
version = p.getChild(0).value
if bytesToNumber(version) != 0:
raise SyntaxError("Unrecognized PKCS8 version")

# second element in PrivateKeyInfo is a SEQUENCE of type
# AlgorithmIdentifier
algIdent = p.getChild(1)
seqLen = algIdent.getChildCount()
# first item of AlgorithmIdentifier is an OBJECT (OID)
oid = algIdent.getChild(0)
if list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
keyType = "rsa"
elif list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 10]:
keyType = "rsa-pss"
else:
raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}"
.format(list(oid.value)))
# second item of AlgorithmIdentifier are parameters (defined by
# above algorithm)
if keyType == "rsa":
if seqLen != 2:
raise SyntaxError("Missing parameters for RSA algorithm ID")
parameters = algIdent.getChild(1)
if parameters.value != bytearray(0):
raise SyntaxError("RSA parameters are not NULL")
else: # rsa-pss
pass # ignore parameters - don't apply restrictions

if seqLen > 2:
raise SyntaxError("Invalid encoding of AlgorithmIdentifier")

#Get the privateKey
privateKeyP = p.getChild(2)

#Adjust for OCTET STRING encapsulation
privateKeyP = ASN1Parser(privateKeyP.value)

return Python_RSAKey._parseASN1PrivateKey(privateKeyP)
_parsePKCS8 = staticmethod(_parsePKCS8)

def _parseSSLeay(bytes):
privateKeyP = ASN1Parser(bytes)
return Python_RSAKey._parseASN1PrivateKey(privateKeyP)
_parseSSLeay = staticmethod(_parseSSLeay)

def _parseASN1PrivateKey(privateKeyP):
version = privateKeyP.getChild(0).value[0]
if version != 0:
raise SyntaxError("Unrecognized RSAPrivateKey version")
n = bytesToNumber(privateKeyP.getChild(1).value)
e = bytesToNumber(privateKeyP.getChild(2).value)
d = bytesToNumber(privateKeyP.getChild(3).value)
p = bytesToNumber(privateKeyP.getChild(4).value)
q = bytesToNumber(privateKeyP.getChild(5).value)
dP = bytesToNumber(privateKeyP.getChild(6).value)
dQ = bytesToNumber(privateKeyP.getChild(7).value)
qInv = bytesToNumber(privateKeyP.getChild(8).value)
return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv)
_parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey)
from .python_key import Python_Key
return Python_Key.parsePEM(s, passwordCallback)
Loading

0 comments on commit c28d6d3

Please sign in to comment.