Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt side-channel free RSA decryption #438

Merged
merged 5 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -121,21 +121,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, Python_ECDSAKey), type(key)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the syntax. Why is type(key) at the end of the line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert will print the second element in the tuple on error, it's so that if the assert fails, it will print result of 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 @@ -67,13 +68,21 @@ def _rawPrivateKeyOp(self, message):
ciphertext = bytesToNumber(bytearray(string))
return ciphertext

def _raw_private_key_op_bytes(self, message):
return bytearray(m2.rsa_private_encrypt(self.rsa, bytes(message),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using _rawPrivateKeyOp instead of m2 internally? It would be nice if the pattern is the same as in tlslite/utils/rsakey.py. (same for public key operations)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. this is the class for wrapping m2crypto specifically
  2. we do direct call to m2crypto here instead of reusing the code from rsakey because we don't want to do the int() -> bytes() conversion in python, as it can't be done in side-channel safe manner

m2.no_padding))

def _rawPublicKeyOp(self, ciphertext):
data = numberToByteArray(ciphertext, numBytes(self.n))
string = m2.rsa_public_decrypt(self.rsa, bytes(data),
m2.no_padding)
message = bytesToNumber(bytearray(string))
return message

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 @@ -151,6 +160,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
30 changes: 30 additions & 0 deletions tlslite/utils/python_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,39 @@ def parsePEM(s, passwordCallback=None):
elif pemSniff(s, "EC PRIVATE KEY"):
bytes = dePem(s, "EC PRIVATE KEY")
return Python_Key._parse_ecc_ssleay(bytes)
elif pemSniff(s, "PUBLIC KEY"):
bytes = dePem(s, "PUBLIC KEY")
return Python_Key._parse_public_key(bytes)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this function.

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, key_type="rsa")

@staticmethod
def _parse_pkcs8(bytes):
parser = ASN1Parser(bytes)
Expand Down
Loading