tomato42/tlslite-ng

View on GitHub
tlslite/utils/python_key.py

Summary

Maintainability
C
1 day
Test Coverage
A
90%
 
 
from .python_rsakey import Python_RSAKey
from .python_ecdsakey import Python_ECDSAKey
from .python_dsakey import Python_DSAKey
from .python_eddsakey import Python_EdDSAKey
from .pem import dePem, pemSniff
from .asn1parser import ASN1Parser
from .cryptomath import bytesToNumber
from .compat import compatHMAC
from ecdsa.curves import NIST256p, NIST384p, NIST521p
from ecdsa.keys import SigningKey, VerifyingKey
 
Expected 2 blank lines, found 1
class Python_Key(object):
"""
Generic methods for parsing private keys from files.
 
Handles both RSA and ECDSA keys, irrespective of file format.
"""
 
@staticmethod
Function `parsePEM` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
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, "rsa")
elif pemSniff(s, "DSA PRIVATE KEY"):
bytes = dePem(s, "DSA PRIVATE KEY")
return Python_Key._parse_dsa_ssleay(bytes)
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")
Avoid too many `return` statements within this function.
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]:
key_type = "rsa"
elif list(alg_oid.value) == [42, 134, 72, 206, 56, 4, 1]:
key_type = "dsa"
else:
raise SyntaxError("Only RSA or DSA Public keys supported")
 
if key_type == "rsa":
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")
 
elif key_type == "dsa":
# public key
subject_public_key = ASN1Parser(
ASN1Parser(spk_info.getChildBytes(1)).value[1:])
 
public_key = bytesToNumber(subject_public_key.value)
 
# domain parameters
domain = alg_id.getChild(1)
 
p = bytesToNumber(domain.getChild(0).value)
q = bytesToNumber(domain.getChild(1).value)
g = bytesToNumber(domain.getChild(2).value)
 
return Python_DSAKey(p, q, g, y=public_key)
 
Cyclomatic complexity is too high in method _parse_pkcs8. (23)
@staticmethod
Function `_parse_pkcs8` has a Cognitive Complexity of 32 (exceeds 5 allowed). Consider refactoring.
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"
elif list(oid.value) == [42, 134, 72, 206, 56, 4, 1]:
key_type = "dsa"
elif list(oid.value) == [42, 134, 72, 206, 61, 2, 1]:
key_type = "ecdsa"
elif list(oid.value) == [43, 101, 112]:
key_type = "Ed25519"
elif list(oid.value) == [43, 101, 113]:
key_type = "Ed448"
else:
raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}"
.format(list(oid.value)))
# second item of AlgorithmIdentifier are parameters (defined by
# above algorithm)
Similar blocks of code found in 2 locations. Consider refactoring.
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")
if key_type == "dsa":
if seq_len != 2:
raise SyntaxError("Invalid encoding of algorithm identifier")
parameters = alg_ident.getChild(1)
if parameters.value == bytearray(0):
parameters = None
elif key_type == "ecdsa":
if seq_len != 2:
raise SyntaxError("Invalid encoding of algorithm identifier")
curveID = alg_ident.getChild(1)
if list(curveID.value) == [42, 134, 72, 206, 61, 3, 1, 7]:
curve = NIST256p
elif list(curveID.value) == [43, 129, 4, 0, 34]:
curve = NIST384p
elif list(curveID.value) == [43, 129, 4, 0, 35]:
curve = NIST521p
else:
raise SyntaxError("Unknown curve")
else: # rsa-pss
pass # ignore parameters - don't apply restrictions
 
if seq_len > 2:
raise SyntaxError("Invalid encoding of AlgorithmIdentifier")
 
Block comment should start with '# '
#Get the privateKey
private_key_parser = parser.getChild(2)
 
Block comment should start with '# '
#Adjust for OCTET STRING encapsulation
private_key_parser = ASN1Parser(private_key_parser.value)
 
if key_type in ("Ed25519", "Ed448"):
return Python_Key._parse_eddsa_private_key(bytes)
if key_type == "ecdsa":
return Python_Key._parse_ecdsa_private_key(private_key_parser,
curve)
elif key_type == "dsa":
Line too long (84 > 79 characters)
return Python_Key._parse_dsa_private_key(private_key_parser, parameters)
else:
return Python_Key._parse_asn1_private_key(private_key_parser,
key_type)
 
@staticmethod
def _parse_ssleay(data, key_type="rsa"):
"""
Parse binary structure of the old SSLeay file format used by OpenSSL.
 
For RSA keys.
"""
private_key_parser = ASN1Parser(data)
# "rsa" type as old format doesn't support rsa-pss parameters
return Python_Key._parse_asn1_private_key(private_key_parser, key_type)
 
@staticmethod
def _parse_dsa_ssleay(data):
"""
Parse binary structure of the old SSLeay file format used by OpenSSL.
 
For DSA keys.
"""
private_key_parser = ASN1Parser(data)
return Python_Key._parse_dsa_private_key(private_key_parser)
 
@staticmethod
def _parse_ecc_ssleay(data):
"""
Parse binary structure of the old SSLeay file format used by OpenSSL.
 
For ECDSA keys.
"""
private_key = SigningKey.from_der(compatHMAC(data))
secret_mult = private_key.privkey.secret_multiplier
return Python_ECDSAKey(None, None, private_key.curve.name,
secret_mult)
 
@staticmethod
Function `_parse_ecdsa_private_key` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
def _parse_ecdsa_private_key(private, curve):
ver = private.getChild(0)
if ver.value != b'\x01':
raise SyntaxError("Unexpected EC key version")
private_key = private.getChild(1)
public_key = private.getChild(2)
# first two bytes are the ASN.1 custom type and the length of payload
# while the latter two bytes are just specification of the public
# key encoding (uncompressed)
TODO found
# TODO: update ecdsa lib to be able to parse PKCS#8 files
if curve is not NIST521p:
if list(public_key.value[:1]) != [3] or \
list(public_key.value[2:4]) != [0, 4]:
Line too long (82 > 79 characters)
raise SyntaxError("Invalid or unsupported encoding of public key")
 
pub_key = VerifyingKey.from_string(
compatHMAC(public_key.value[4:]),
curve)
else:
if list(public_key.value[:3]) != [3, 129, 134] or \
list(public_key.value[3:5]) != [0, 4]:
Line too long (82 > 79 characters)
raise SyntaxError("Invalid or unsupported encoding of public key")
 
pub_key = VerifyingKey.from_string(
compatHMAC(public_key.value[5:]),
curve)
pub_x = pub_key.pubkey.point.x()
pub_y = pub_key.pubkey.point.y()
priv_key = SigningKey.from_string(compatHMAC(private_key.value),
curve)
mult = priv_key.privkey.secret_multiplier
return Python_ECDSAKey(pub_x, pub_y, curve.name, mult)
 
@staticmethod
def _parse_eddsa_private_key(data):
"""Parse a DER encoded EdDSA key."""
priv_key = SigningKey.from_der(data)
return Python_EdDSAKey(priv_key.verifying_key, private_key=priv_key)
 
@staticmethod
def _parse_asn1_private_key(private_key_parser, key_type):
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, key_type)
 
 
Too many blank lines (2)
@staticmethod
def _parse_dsa_private_key(private_key_parser, domain_parameters=None):
if domain_parameters:
p = bytesToNumber(domain_parameters.getChild(0).value)
q = bytesToNumber(domain_parameters.getChild(1).value)
g = bytesToNumber(domain_parameters.getChild(2).value)
x = bytesToNumber(private_key_parser.value)
return Python_DSAKey(p, q, g, x)
p = bytesToNumber(private_key_parser.getChild(1).value)
q = bytesToNumber(private_key_parser.getChild(2).value)
g = bytesToNumber(private_key_parser.getChild(3).value)
y = bytesToNumber(private_key_parser.getChild(4).value)
x = bytesToNumber(private_key_parser.getChild(5).value)
return Python_DSAKey(p, q, g, x, y)