287 lines
10 KiB
Python
287 lines
10 KiB
Python
# Authors:
|
|
# Trevor Perrin
|
|
# Google - parsing subject field
|
|
#
|
|
# See the LICENSE file for legal information regarding use of this file.
|
|
|
|
"""Class representing an X.509 certificate."""
|
|
|
|
from ecdsa.keys import VerifyingKey
|
|
|
|
from .utils.asn1parser import ASN1Parser
|
|
from .utils.cryptomath import *
|
|
from .utils.keyfactory import _createPublicRSAKey, _create_public_ecdsa_key, \
|
|
_create_public_dsa_key, _create_public_eddsa_key
|
|
from .utils.pem import *
|
|
from .utils.compat import compatHMAC, b2a_hex
|
|
from .constants import AlgorithmOID, RSA_PSS_OID
|
|
|
|
|
|
class X509(object):
|
|
"""
|
|
This class represents an X.509 certificate.
|
|
|
|
:vartype bytes: bytearray
|
|
:ivar bytes: The DER-encoded ASN.1 certificate
|
|
|
|
:vartype publicKey: ~tlslite.utils.rsakey.RSAKey
|
|
:ivar publicKey: The subject public key from the certificate.
|
|
|
|
:vartype subject: bytearray
|
|
:ivar subject: The DER-encoded ASN.1 subject distinguished name.
|
|
|
|
:vartype certAlg: str
|
|
:ivar certAlg: algorithm of the public key, "rsa" for RSASSA-PKCS#1 v1.5,
|
|
"rsa-pss" for RSASSA-PSS, "ecdsa" for ECDSA
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Create empty certificate object."""
|
|
self.bytes = bytearray(0)
|
|
self.serial_number = None
|
|
self.subject_public_key = None
|
|
self.publicKey = None
|
|
self.subject = None
|
|
self.certAlg = None
|
|
self.sigalg = None
|
|
self.issuer = None
|
|
|
|
def __hash__(self):
|
|
"""Calculate hash of object."""
|
|
return hash(bytes(self.bytes))
|
|
|
|
def __eq__(self, other):
|
|
"""Compare other object for equality."""
|
|
if not hasattr(other, "bytes"):
|
|
return NotImplemented
|
|
return self.bytes == other.bytes
|
|
|
|
def __ne__(self, other):
|
|
"""Compare with other object for inequality."""
|
|
if not hasattr(other, "bytes"):
|
|
return NotImplemented
|
|
return not self == other
|
|
|
|
def parse(self, s):
|
|
"""
|
|
Parse a PEM-encoded X.509 certificate.
|
|
|
|
:type s: str
|
|
:param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded
|
|
certificate wrapped with "-----BEGIN CERTIFICATE-----" and
|
|
"-----END CERTIFICATE-----" tags).
|
|
"""
|
|
bytes = dePem(s, "CERTIFICATE")
|
|
self.parseBinary(bytes)
|
|
return self
|
|
|
|
def parseBinary(self, cert_bytes):
|
|
"""
|
|
Parse a DER-encoded X.509 certificate.
|
|
|
|
:type bytes: L{str} (in python2) or L{bytearray} of unsigned bytes
|
|
:param bytes: A DER-encoded X.509 certificate.
|
|
"""
|
|
self.bytes = bytearray(cert_bytes)
|
|
parser = ASN1Parser(self.bytes)
|
|
|
|
# Get the SignatureAlgorithm
|
|
signature_algorithm_identifier = parser.getChild(1)
|
|
self.sigalg = bytes(signature_algorithm_identifier.getChildBytes(0))
|
|
|
|
# Finally get the (hash, signature) pair coresponding to it
|
|
# If it is rsa-pss we need to check the aditional parameters field
|
|
# to extract the hash algorithm
|
|
if self.sigalg == RSA_PSS_OID:
|
|
sigalg_hash = signature_algorithm_identifier.getChild(1)
|
|
sigalg_hash = bytes(sigalg_hash.getChild(0).value)
|
|
self.sigalg = AlgorithmOID.oid[sigalg_hash]
|
|
else:
|
|
self.sigalg = AlgorithmOID.oid[self.sigalg]
|
|
|
|
# Get the tbsCertificate
|
|
tbs_certificate = parser.getChild(0)
|
|
# Is the optional version field present?
|
|
# This determines which index the key is at.
|
|
if tbs_certificate.value[0] == 0xA0:
|
|
serial_number_index = 1
|
|
subject_public_key_info_index = 6
|
|
else:
|
|
serial_number_index = 0
|
|
subject_public_key_info_index = 5
|
|
|
|
# Get serial number
|
|
self.serial_number = bytesToNumber(tbs_certificate.getChild(serial_number_index).value)
|
|
|
|
# Get the issuer
|
|
self.issuer = tbs_certificate.getChildBytes(
|
|
subject_public_key_info_index - 3)
|
|
|
|
# Get the subject
|
|
self.subject = tbs_certificate.getChildBytes(
|
|
subject_public_key_info_index - 1)
|
|
|
|
# Get the subjectPublicKeyInfo
|
|
subject_public_key_info = tbs_certificate.getChild(
|
|
subject_public_key_info_index)
|
|
|
|
# Get the AlgorithmIdentifier
|
|
alg_identifier = subject_public_key_info.getChild(0)
|
|
alg_identifier_len = alg_identifier.getChildCount()
|
|
|
|
# first item of AlgorithmIdentifier is the algorithm
|
|
alg = alg_identifier.getChild(0)
|
|
alg_oid = alg.value
|
|
if list(alg_oid) == [42, 134, 72, 134, 247, 13, 1, 1, 1]:
|
|
self.certAlg = "rsa"
|
|
elif list(alg_oid) == [42, 134, 72, 134, 247, 13, 1, 1, 10]:
|
|
self.certAlg = "rsa-pss"
|
|
elif list(alg_oid) == [42, 134, 72, 206, 56, 4, 1]:
|
|
self.certAlg = "dsa"
|
|
elif list(alg_oid) == [42, 134, 72, 206, 61, 2, 1]:
|
|
self.certAlg = "ecdsa"
|
|
elif list(alg_oid) == [43, 101, 112]:
|
|
self.certAlg = "Ed25519"
|
|
elif list(alg_oid) == [43, 101, 113]:
|
|
self.certAlg = "Ed448"
|
|
else:
|
|
raise SyntaxError("Unrecognized AlgorithmIdentifier")
|
|
|
|
# for RSA the parameters of AlgorithmIdentifier shuld be a NULL
|
|
if self.certAlg == "rsa":
|
|
if alg_identifier_len != 2:
|
|
raise SyntaxError("Missing parameters in AlgorithmIdentifier")
|
|
params = alg_identifier.getChild(1)
|
|
if params.value != bytearray(0):
|
|
raise SyntaxError("Unexpected non-NULL parameters in "
|
|
"AlgorithmIdentifier")
|
|
elif self.certAlg == "ecdsa":
|
|
self._ecdsa_pubkey_parsing(
|
|
tbs_certificate.getChildBytes(subject_public_key_info_index))
|
|
return
|
|
elif self.certAlg == "dsa":
|
|
self._dsa_pubkey_parsing(subject_public_key_info)
|
|
return
|
|
elif self.certAlg == "Ed25519" or self.certAlg == "Ed448":
|
|
self._eddsa_pubkey_parsing(
|
|
tbs_certificate.getChildBytes(subject_public_key_info_index))
|
|
return
|
|
else: # rsa-pss
|
|
pass # ignore parameters, if any - don't apply key restrictions
|
|
|
|
self._rsa_pubkey_parsing(subject_public_key_info)
|
|
|
|
def _eddsa_pubkey_parsing(self, subject_public_key_info):
|
|
"""
|
|
Convert the raw DER encoded EdDSA parameters into public key object.
|
|
|
|
:param subject_public_key_info: bytes like object with the DER encoded
|
|
public key in it
|
|
"""
|
|
try:
|
|
# python ecdsa knows how to parse curve OIDs so re-use that
|
|
# code
|
|
public_key = VerifyingKey.from_der(compatHMAC(
|
|
subject_public_key_info))
|
|
except Exception:
|
|
raise SyntaxError("Malformed or unsupported public key in "
|
|
"certificate")
|
|
self.publicKey = _create_public_eddsa_key(public_key)
|
|
|
|
def _rsa_pubkey_parsing(self, subject_public_key_info):
|
|
"""
|
|
Parse the RSA public key from the certificate.
|
|
|
|
:param subject_public_key_info: ASN1Parser object with subject
|
|
public key info of X.509 certificate
|
|
"""
|
|
|
|
# Get the subjectPublicKey
|
|
subject_public_key = subject_public_key_info.getChild(1)
|
|
self.subject_public_key = subject_public_key_info.getChildBytes(1)
|
|
self.subject_public_key = ASN1Parser(self.subject_public_key).value[1:]
|
|
|
|
# Adjust for BIT STRING encapsulation
|
|
if subject_public_key.value[0]:
|
|
raise SyntaxError()
|
|
subject_public_key = ASN1Parser(subject_public_key.value[1:])
|
|
|
|
# Get the modulus and exponent
|
|
modulus = subject_public_key.getChild(0)
|
|
public_exponent = subject_public_key.getChild(1)
|
|
|
|
# Decode them into numbers
|
|
# pylint: disable=invalid-name
|
|
# 'n' and 'e' are the universally used parameters in RSA algorithm
|
|
# definition
|
|
n = bytesToNumber(modulus.value)
|
|
e = bytesToNumber(public_exponent.value)
|
|
|
|
# Create a public key instance
|
|
self.publicKey = _createPublicRSAKey(n, e, self.certAlg)
|
|
# pylint: enable=invalid-name
|
|
|
|
def _ecdsa_pubkey_parsing(self, subject_public_key_info):
|
|
"""
|
|
Convert the raw DER encoded ECDSA parameters into public key object
|
|
|
|
:param subject_public_key_info: bytes like object with DER encoded
|
|
public key in it
|
|
"""
|
|
try:
|
|
# python ecdsa knows how to parse curve OIDs so re-use that
|
|
# code
|
|
public_key = VerifyingKey.from_der(compatHMAC(
|
|
subject_public_key_info))
|
|
except Exception:
|
|
raise SyntaxError("Malformed or unsupported public key in "
|
|
"certificate")
|
|
x = public_key.pubkey.point.x()
|
|
y = public_key.pubkey.point.y()
|
|
curve_name = public_key.curve.name
|
|
self.publicKey = _create_public_ecdsa_key(x, y, curve_name)
|
|
|
|
def _dsa_pubkey_parsing(self, subject_public_key_info):
|
|
"""
|
|
Convert the raw DER encoded DSA parameters into public key object
|
|
|
|
:param subject_public_key_info: bytes like object with DER encoded
|
|
global parameters and public key in it
|
|
"""
|
|
global_parameters = (subject_public_key_info.getChild(0)).getChild(1)
|
|
# Get the subjectPublicKey
|
|
public_key = subject_public_key_info.getChild(1)
|
|
|
|
# Adjust for BIT STRING encapsulation and get hex value
|
|
if public_key.value[0]:
|
|
raise SyntaxError()
|
|
y = ASN1Parser(public_key.value[1:])
|
|
|
|
# Get the {A, p, q}
|
|
p = global_parameters.getChild(0)
|
|
q = global_parameters.getChild(1)
|
|
g = global_parameters.getChild(2)
|
|
|
|
# Decode them into numbers
|
|
y = bytesToNumber(y.value)
|
|
p = bytesToNumber(p.value)
|
|
q = bytesToNumber(q.value)
|
|
g = bytesToNumber(g.value)
|
|
|
|
# Create a public key instance
|
|
self.publicKey = _create_public_dsa_key(p, q, g, y)
|
|
|
|
def getFingerprint(self):
|
|
"""
|
|
Get the hex-encoded fingerprint of this certificate.
|
|
|
|
:rtype: str
|
|
:returns: A hex-encoded fingerprint.
|
|
"""
|
|
return b2a_hex(SHA1(self.bytes))
|
|
|
|
def writeBytes(self):
|
|
"""Serialise object to a DER encoded string."""
|
|
return self.bytes
|
|
|
|
|