"""Class representing an X.509 certificate."""

from utils.ASN1Parser import ASN1Parser
from utils.cryptomath import *
from utils.keyfactory import _createPublicRSAKey


class X509:
    """This class represents an X.509 certificate.

    @type bytes: L{array.array} of unsigned bytes
    @ivar bytes: The DER-encoded ASN.1 certificate

    @type publicKey: L{tlslite.utils.RSAKey.RSAKey}
    @ivar publicKey: The subject public key from the certificate.

    @type subject: L{array.array} of unsigned bytes
    @ivar subject: The DER-encoded ASN.1 subject distinguished name.
    """

    def __init__(self):
        self.bytes = createByteArraySequence([])
        self.publicKey = None
        self.subject = None

    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).
        """

        start = s.find("-----BEGIN CERTIFICATE-----")
        end = s.find("-----END CERTIFICATE-----")
        if start == -1:
            raise SyntaxError("Missing PEM prefix")
        if end == -1:
            raise SyntaxError("Missing PEM postfix")
        s = s[start+len("-----BEGIN CERTIFICATE-----") : end]

        bytes = base64ToBytes(s)
        self.parseBinary(bytes)
        return self

    def parseBinary(self, bytes):
        """Parse a DER-encoded X.509 certificate.

        @type bytes: str or L{array.array} of unsigned bytes
        @param bytes: A DER-encoded X.509 certificate.
        """

        if isinstance(bytes, type("")):
            bytes = stringToBytes(bytes)

        self.bytes = bytes
        p = ASN1Parser(bytes)

        #Get the tbsCertificate
        tbsCertificateP = p.getChild(0)

        #Is the optional version field present?
        #This determines which index the key is at.
        if tbsCertificateP.value[0]==0xA0:
            subjectPublicKeyInfoIndex = 6
        else:
            subjectPublicKeyInfoIndex = 5

        #Get the subject
        self.subject = tbsCertificateP.getChildBytes(\
                           subjectPublicKeyInfoIndex - 1)

        #Get the subjectPublicKeyInfo
        subjectPublicKeyInfoP = tbsCertificateP.getChild(\
                                    subjectPublicKeyInfoIndex)

        #Get the algorithm
        algorithmP = subjectPublicKeyInfoP.getChild(0)
        rsaOID = algorithmP.value
        if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]:
            raise SyntaxError("Unrecognized AlgorithmIdentifier")

        #Get the subjectPublicKey
        subjectPublicKeyP = subjectPublicKeyInfoP.getChild(1)

        #Adjust for BIT STRING encapsulation
        if (subjectPublicKeyP.value[0] !=0):
            raise SyntaxError()
        subjectPublicKeyP = ASN1Parser(subjectPublicKeyP.value[1:])

        #Get the modulus and exponent
        modulusP = subjectPublicKeyP.getChild(0)
        publicExponentP = subjectPublicKeyP.getChild(1)

        #Decode them into numbers
        n = bytesToNumber(modulusP.value)
        e = bytesToNumber(publicExponentP.value)

        #Create a public key instance
        self.publicKey = _createPublicRSAKey(n, e)
        return self

    def getFingerprint(self):
        """Get the hex-encoded fingerprint of this certificate.

        @rtype: str
        @return: A hex-encoded fingerprint.
        """
        return sha.sha(self.bytes).hexdigest()

    def getCommonName(self):
        """Get the Subject's Common Name from the certificate.

        The cryptlib_py module must be installed in order to use this
        function.

        @rtype: str or None
        @return: The CN component of the certificate's subject DN, if
        present.
        """
        import cryptlib_py
        import array
        c = cryptlib_py.cryptImportCert(self.bytes, cryptlib_py.CRYPT_UNUSED)
        name = cryptlib_py.CRYPT_CERTINFO_COMMONNAME
        try:
            try:
                length = cryptlib_py.cryptGetAttributeString(c, name, None)
                returnVal = array.array('B', [0] * length)
                cryptlib_py.cryptGetAttributeString(c, name, returnVal)
                returnVal = returnVal.tostring()
            except cryptlib_py.CryptException, e:
                if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
                    returnVal = None
            return returnVal
        finally:
            cryptlib_py.cryptDestroyCert(c)

    def writeBytes(self):
        return self.bytes


