#!/usr/bin/env python

"""Unit tests for M2Crypto.X509.

Contributed by Toby Allsopp <toby@MI6.GEN.NZ> under M2Crypto's license.

Portions created by Open Source Applications Foundation (OSAF) are
Copyright (C) 2004-2005 OSAF. All Rights Reserved.
Author: Heikki Toivonen
"""

import base64
import logging
import os
import time
import warnings

from M2Crypto import ASN1, BIO, EVP, RSA, Rand, X509, m2, six  # noqa
from tests import unittest

log = logging.getLogger(__name__)


class X509TestCase(unittest.TestCase):

    def callback(self, *args):
        pass

    def setUp(self):
        self.expected_hash = 'BA4212E8B55527570828E7F5A0005D17C64BDC4C'

    def mkreq(self, bits, ca=0):
        pk = EVP.PKey()
        x = X509.Request()
        rsa = RSA.gen_key(bits, 65537, self.callback)
        pk.assign_rsa(rsa)
        rsa = None  # should not be freed here
        x.set_pubkey(pk)
        name = x.get_subject()
        name.C = "UK"
        name.CN = "OpenSSL Group"
        if not ca:
            ext1 = X509.new_extension('subjectAltName',
                                      'DNS:foobar.example.com')
            ext2 = X509.new_extension('nsComment', 'Hello there')
            extstack = X509.X509_Extension_Stack()
            extstack.push(ext1)
            extstack.push(ext2)
            x.add_extensions(extstack)

        with self.assertRaises(ValueError):
            x.sign(pk, 'sha513')

        x.sign(pk, 'sha1')
        self.assertTrue(x.verify(pk))
        pk2 = x.get_pubkey()
        self.assertTrue(x.verify(pk2))
        return x, pk

    def test_ext(self):
        with self.assertRaises(ValueError):
            X509.new_extension('subjectKeyIdentifier', 'hash')

        ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
        self.assertEqual(ext.get_value(), 'DNS:foobar.example.com')
        self.assertEqual(ext.get_value(indent=2),
                         '  DNS:foobar.example.com')
        self.assertEqual(ext.get_value(flag=m2.X509V3_EXT_PARSE_UNKNOWN),
                         'DNS:foobar.example.com')

    def test_ext_error(self):
        with self.assertRaises(X509.X509Error):
            X509.new_extension('nonsensicalName', 'blabla')

    def test_extstack(self):
        # new
        ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
        ext2 = X509.new_extension('nsComment', 'Hello there')
        extstack = X509.X509_Extension_Stack()

        # push
        extstack.push(ext1)
        extstack.push(ext2)
        self.assertEqual(extstack[1].get_name(), 'nsComment')
        self.assertEqual(len(extstack), 2)

        # iterator
        i = 0
        for e in extstack:
            i += 1
            self.assertGreater(len(e.get_name()), 0)
        self.assertEqual(i, 2)

        # pop
        ext3 = extstack.pop()
        self.assertEqual(len(extstack), 1)
        self.assertEqual(extstack[0].get_name(), 'subjectAltName')
        extstack.push(ext3)
        self.assertEqual(len(extstack), 2)
        self.assertEqual(extstack[1].get_name(), 'nsComment')

        self.assertIsNotNone(extstack.pop())
        self.assertIsNotNone(extstack.pop())
        self.assertIsNone(extstack.pop())

    def test_x509_name(self):
        n = X509.X509_Name()
        # It seems this actually needs to be a real 2 letter country code
        n.C = 'US'
        self.assertEqual(n.C, 'US')
        n.SP = 'State or Province'
        self.assertEqual(n.SP, 'State or Province')
        n.L = 'locality name'
        self.assertEqual(n.L, 'locality name')
        # Yes, 'orhanization' is a typo, I know it and you're smart.
        # However, fixing this typo would break later hashes.
        # I don't think it is worthy of troubles.
        n.O = 'orhanization name'
        self.assertEqual(n.O, 'orhanization name')
        n.OU = 'org unit'
        self.assertEqual(n.OU, 'org unit')
        n.CN = 'common name'
        self.assertEqual(n.CN, 'common name')
        n.Email = 'bob@example.com'
        self.assertEqual(n.Email, 'bob@example.com')
        n.serialNumber = '1234'
        self.assertEqual(n.serialNumber, '1234')
        n.SN = 'surname'
        self.assertEqual(n.SN, 'surname')
        n.GN = 'given name'
        self.assertEqual(n.GN, 'given name')
        self.assertEqual(n.as_text(),
                         'C=US, ST=State or Province, ' +
                         'L=locality name, O=orhanization name, ' +
                         'OU=org unit, CN=common ' +
                         'name/emailAddress=bob@example.com' +
                         '/serialNumber=1234, ' +
                         'SN=surname, GN=given name')
        self.assertEqual(len(n), 10,
                         'X509_Name has inappropriate length %d ' % len(n))
        n.givenName = 'name given'
        self.assertEqual(n.GN, 'given name')  # Just gets the first
        self.assertEqual(n.as_text(), 'C=US, ST=State or Province, ' +
                         'L=locality name, O=orhanization name, ' +
                         'OU=org unit, ' +
                         'CN=common name/emailAddress=bob@example.com' +
                         '/serialNumber=1234, ' +
                         'SN=surname, GN=given name, GN=name given')
        self.assertEqual(len(n), 11,
                         'After adding one more attribute X509_Name should ' +
                         'have 11 and not %d attributes.' % len(n))
        n.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
                           entry="Proxy", len=-1, loc=-1, set=0)
        self.assertEqual(len(n), 12,
                         'After adding one more attribute X509_Name should ' +
                         'have 12 and not %d attributes.' % len(n))
        self.assertEqual(n.entry_count(), 12, n.entry_count())
        self.assertEqual(n.as_text(), 'C=US, ST=State or Province, ' +
                         'L=locality name, O=orhanization name, ' +
                         'OU=org unit, ' +
                         'CN=common name/emailAddress=bob@example.com' +
                         '/serialNumber=1234, ' +
                         'SN=surname, GN=given name, GN=name given, ' +
                         'CN=Proxy')

        with self.assertRaises(AttributeError):
            n.__getattr__('foobar')
        n.foobar = 1
        self.assertEqual(n.foobar, 1)

        # X509_Name_Entry tests
        l = 0
        for entry in n:
            self.assertIsInstance(entry, X509.X509_Name_Entry)
            self.assertIsInstance(entry.get_object(), ASN1.ASN1_Object)
            self.assertIsInstance(entry.get_data(), ASN1.ASN1_String)
            l += 1
        self.assertEqual(l, 12, l)

        l = 0
        for cn in n.get_entries_by_nid(m2.NID_commonName):
            self.assertIsInstance(cn, X509.X509_Name_Entry)
            self.assertIsInstance(cn.get_object(), ASN1.ASN1_Object)
            data = cn.get_data()
            self.assertIsInstance(data, ASN1.ASN1_String)
            t = data.as_text()
            self.assertIn(t, ("common name", "Proxy",))
            l += 1
        self.assertEqual(l, 2,
                         'X509_Name has %d commonName entries instead '
                         'of expected 2' % l)

        # The target list is not deleted when the loop is finished
        # https://docs.python.org/2.7/reference\
        #        /compound_stmts.html#the-for-statement
        # so this checks what are the attributes of the last value of
        # ``cn`` variable.
        cn.set_data(b"Hello There!")
        self.assertEqual(cn.get_data().as_text(), "Hello There!")

        # OpenSSL 1.0.1h switched from encoding strings as PRINTABLESTRING (the
        # first hash value) to UTF8STRING (the second one)
        self.assertIn(n.as_hash(), (1697185131, 1370641112),
                      'Unexpected value of the X509_Name hash %s' %
                      n.as_hash())

        self.assertRaises(IndexError, lambda: n[100])
        self.assertIsNotNone(n[10])

    def test_mkreq(self):
        (req, _) = self.mkreq(1024)
        req.save_pem('tests/tmp_request.pem')
        req2 = X509.load_request('tests/tmp_request.pem')
        os.remove('tests/tmp_request.pem')
        req.save('tests/tmp_request.pem')
        req3 = X509.load_request('tests/tmp_request.pem')
        os.remove('tests/tmp_request.pem')
        req.save('tests/tmp_request.der', format=X509.FORMAT_DER)
        req4 = X509.load_request('tests/tmp_request.der',
                                 format=X509.FORMAT_DER)
        os.remove('tests/tmp_request.der')
        self.assertEqual(req.as_pem(), req2.as_pem())
        self.assertEqual(req.as_text(), req2.as_text())
        self.assertEqual(req.as_der(), req2.as_der())
        self.assertEqual(req.as_pem(), req3.as_pem())
        self.assertEqual(req.as_text(), req3.as_text())
        self.assertEqual(req.as_der(), req3.as_der())
        self.assertEqual(req.as_pem(), req4.as_pem())
        self.assertEqual(req.as_text(), req4.as_text())
        self.assertEqual(req.as_der(), req4.as_der())
        self.assertEqual(req.get_version(), 0)
        req.set_version(1)
        self.assertEqual(req.get_version(), 1)
        req.set_version(0)
        self.assertEqual(req.get_version(), 0)

    def test_mkcert(self):
        for utc in (True, False):
            req, pk = self.mkreq(1024)
            pkey = req.get_pubkey()
            self.assertTrue(req.verify(pkey))
            sub = req.get_subject()
            self.assertEqual(len(sub), 2,
                             'Subject should be long 2 items not %d' % len(sub))

            cert = X509.X509()
            cert.set_serial_number(1)
            cert.set_version(2)
            cert.set_subject(sub)
            t = int(time.time()) + time.timezone
            if utc:
                now = ASN1.ASN1_UTCTIME()
            else:
                now = ASN1.ASN1_TIME()
            now.set_time(t)
            now_plus_year = ASN1.ASN1_TIME()
            now_plus_year.set_time(t + 60 * 60 * 24 * 365)
            cert.set_not_before(now)
            cert.set_not_after(now_plus_year)
            self.assertEqual(str(cert.get_not_before()), str(now))
            self.assertEqual(str(cert.get_not_after()), str(now_plus_year))

            issuer = X509.X509_Name()
            issuer.CN = 'The Issuer Monkey'
            issuer.O = 'The Organization Otherwise Known as My CA, Inc.'
            cert.set_issuer(issuer)
            cert.set_pubkey(pkey)
            cert.set_pubkey(cert.get_pubkey())  # Make sure get/set work

            ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
            ext.set_critical(0)
            self.assertEqual(ext.get_critical(), 0)
            cert.add_ext(ext)

            cert.sign(pk, 'sha1')
            with self.assertRaises(ValueError):
                cert.sign(pk, 'nosuchalgo')

            self.assertTrue(cert.get_ext('subjectAltName').get_name(),
                            'subjectAltName')
            self.assertTrue(cert.get_ext_at(0).get_name(),
                            'subjectAltName')
            self.assertTrue(cert.get_ext_at(0).get_value(),
                            'DNS:foobar.example.com')
            self.assertEqual(cert.get_ext_count(), 1,
                             'Certificate should have now 1 extension not %d' %
                             cert.get_ext_count())
            with self.assertRaises(IndexError):
                cert.get_ext_at(1)
            self.assertTrue(cert.verify())
            self.assertTrue(cert.verify(pkey))
            self.assertTrue(cert.verify(cert.get_pubkey()))
            self.assertEqual(cert.get_version(), 2)
            self.assertEqual(cert.get_serial_number(), 1)
            self.assertEqual(cert.get_issuer().CN, 'The Issuer Monkey')

            if m2.OPENSSL_VERSION_NUMBER >= 0x90800f:
                self.assertFalse(cert.check_ca())
                self.assertFalse(cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 1))
                self.assertFalse(cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER,
                                                    1))
                self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 0))
                self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER,
                                                   0))
                self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_ANY, 0))
            else:
                with self.assertRaises(AttributeError):
                    cert.check_ca()

    def mkcacert(self, utc):
        req, pk = self.mkreq(1024, ca=1)
        pkey = req.get_pubkey()
        sub = req.get_subject()
        cert = X509.X509()
        cert.set_serial_number(1)
        cert.set_version(2)
        cert.set_subject(sub)
        t = int(time.time()) + time.timezone
        if utc:
            now = ASN1.ASN1_UTCTIME()
        else:
            now = ASN1.ASN1_TIME()
        now.set_time(t)
        now_plus_year = ASN1.ASN1_TIME()
        now_plus_year.set_time(t + 60 * 60 * 24 * 365)
        cert.set_not_before(now)
        cert.set_not_after(now_plus_year)
        issuer = X509.X509_Name()
        issuer.C = "UK"
        issuer.CN = "OpenSSL Group"
        cert.set_issuer(issuer)
        cert.set_pubkey(pkey)
        ext = X509.new_extension('basicConstraints', 'CA:TRUE')
        cert.add_ext(ext)
        cert.sign(pk, 'sha1')

        if m2.OPENSSL_VERSION_NUMBER >= 0x0090800f:
            self.assertTrue(cert.check_ca())
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER,
                                               1))
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER,
                                               1))
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_ANY, 1))
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER,
                                               0))
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER,
                                               0))
            self.assertTrue(cert.check_purpose(m2.X509_PURPOSE_ANY, 0))
        else:
            with self.assertRaises(AttributeError):
                cert.check_ca()

        return cert, pk, pkey

    def test_mkcacert(self):
        for utc in (True, False):
            cacert, _, pkey = self.mkcacert(utc)
            self.assertTrue(cacert.verify(pkey))

    def test_mkproxycert(self):
        for utc in (True, False):
            cacert, pk1, _ = self.mkcacert(utc)
            end_entity_cert_req, pk2 = self.mkreq(1024)
            end_entity_cert = self.make_eecert(cacert, utc)
            end_entity_cert.set_subject(end_entity_cert_req.get_subject())
            end_entity_cert.set_pubkey(end_entity_cert_req.get_pubkey())
            end_entity_cert.sign(pk1, 'sha1')
            proxycert = self.make_proxycert(end_entity_cert, utc)
            proxycert.sign(pk2, 'sha1')
            self.assertTrue(proxycert.verify(pk2))
            self.assertEqual(proxycert.get_ext_at(0).get_name(),
                             'proxyCertInfo')
            self.assertEqual(proxycert.get_ext_at(0).get_value(),
                             'Path Length Constraint: infinite\n' +
                             'Policy Language: Inherit all\n')
            self.assertEqual(proxycert.get_ext_count(), 1,
                             proxycert.get_ext_count())
            self.assertEqual(proxycert.get_subject().as_text(),
                             'C=UK, CN=OpenSSL Group, CN=Proxy')
            self.assertEqual(
                proxycert.get_subject().as_text(indent=2,
                                                flags=m2.XN_FLAG_RFC2253),
                '  CN=Proxy,CN=OpenSSL Group,C=UK')

    @staticmethod
    def make_eecert(cacert, utc):
        eecert = X509.X509()
        eecert.set_serial_number(2)
        eecert.set_version(2)
        t = int(time.time()) + time.timezone
        if utc:
            now = ASN1.ASN1_UTCTIME()
        else:
            now = ASN1.ASN1_TIME()
        now.set_time(t)
        now_plus_year = ASN1.ASN1_TIME()
        now_plus_year.set_time(t + 60 * 60 * 24 * 365)
        eecert.set_not_before(now)
        eecert.set_not_after(now_plus_year)
        eecert.set_issuer(cacert.get_subject())
        return eecert

    def make_proxycert(self, eecert, utc):
        proxycert = X509.X509()
        pk2 = EVP.PKey()
        proxykey = RSA.gen_key(1024, 65537, self.callback)
        pk2.assign_rsa(proxykey)
        proxycert.set_pubkey(pk2)
        proxycert.set_version(2)
        if utc:
            not_before = ASN1.ASN1_UTCTIME()
            not_after = ASN1.ASN1_UTCTIME()
        else:
            not_before = ASN1.ASN1_TIME()
            not_after = ASN1.ASN1_TIME()
        not_before.set_time(int(time.time()))
        offset = 12 * 3600
        not_after.set_time(int(time.time()) + offset)
        proxycert.set_not_before(not_before)
        proxycert.set_not_after(not_after)
        proxycert.set_issuer_name(eecert.get_subject())
        proxycert.set_serial_number(12345678)
        issuer_name_string = eecert.get_subject().as_text()
        seq = issuer_name_string.split(",")

        subject_name = X509.X509_Name()
        for entry in seq:
            l = entry.split("=")
            subject_name.add_entry_by_txt(field=l[0].strip(),
                                          type=ASN1.MBSTRING_ASC,
                                          entry=l[1], len=-1, loc=-1, set=0)

        subject_name.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
                                      entry="Proxy", len=-1, loc=-1, set=0)

        proxycert.set_subject_name(subject_name)
        # XXX leaks 8 bytes
        pci_ext = X509.new_extension("proxyCertInfo",
                                     "critical,language:Inherit all", 1)
        proxycert.add_ext(pci_ext)
        return proxycert

    def test_fingerprint(self):
        x509 = X509.load_cert('tests/x509.pem')
        fp = x509.get_fingerprint('sha1')
        self.assertEqual(fp, self.expected_hash)

    def test_load_der_string(self):
        with open('tests/x509.der', 'rb') as f:
            x509 = X509.load_cert_der_string(f.read())

        fp = x509.get_fingerprint('sha1')
        self.assertEqual(fp, self.expected_hash)

    def test_save_der_string(self):
        x509 = X509.load_cert('tests/x509.pem')
        s = x509.as_der()
        with open('tests/x509.der', 'rb') as f:
            s2 = f.read()

        self.assertEqual(s, s2)

    def test_load(self):
        x509 = X509.load_cert('tests/x509.pem')
        x5092 = X509.load_cert('tests/x509.der', format=X509.FORMAT_DER)
        self.assertEqual(x509.as_text(), x5092.as_text())
        self.assertEqual(x509.as_pem(), x5092.as_pem())
        self.assertEqual(x509.as_der(), x5092.as_der())
        return

    def test_load_bio(self):
        with BIO.openfile('tests/x509.pem') as bio:
            with BIO.openfile('tests/x509.der') as bio2:
                x509 = X509.load_cert_bio(bio)
                x5092 = X509.load_cert_bio(bio2, format=X509.FORMAT_DER)

        with self.assertRaises(ValueError):
            X509.load_cert_bio(bio2, format=45678)

        self.assertEqual(x509.as_text(), x5092.as_text())
        self.assertEqual(x509.as_pem(), x5092.as_pem())
        self.assertEqual(x509.as_der(), x5092.as_der())

    def test_load_string(self):
        with open('tests/x509.pem') as f:
            s = f.read()

        with open('tests/x509.der', 'rb') as f2:
            s2 = f2.read()

        x509 = X509.load_cert_string(s)
        x5092 = X509.load_cert_string(s2, X509.FORMAT_DER)
        self.assertEqual(x509.as_text(), x5092.as_text())
        self.assertEqual(x509.as_pem(), x5092.as_pem())
        self.assertEqual(x509.as_der(), x5092.as_der())

    def test_load_request_bio(self):
        (req, _) = self.mkreq(1024)

        r1 = X509.load_request_der_string(req.as_der())
        r2 = X509.load_request_string(req.as_der(), X509.FORMAT_DER)
        r3 = X509.load_request_string(req.as_pem(), X509.FORMAT_PEM)

        r4 = X509.load_request_bio(BIO.MemoryBuffer(req.as_der()),
                                   X509.FORMAT_DER)
        r5 = X509.load_request_bio(BIO.MemoryBuffer(req.as_pem()),
                                   X509.FORMAT_PEM)

        for r in [r1, r2, r3, r4, r5]:
            self.assertEqual(req.as_der(), r.as_der())

        with self.assertRaises(ValueError):
            X509.load_request_bio(BIO.MemoryBuffer(req.as_pem()), 345678)

    def test_save(self):
        x509 = X509.load_cert('tests/x509.pem')
        with open('tests/x509.pem', 'r') as f:
            l_tmp = f.readlines()
            # -----BEGIN CERTIFICATE----- : -----END CERTIFICATE-----
            beg_idx = l_tmp.index('-----BEGIN CERTIFICATE-----\n')
            end_idx = l_tmp.index('-----END CERTIFICATE-----\n')
            x509_pem = ''.join(l_tmp[beg_idx:end_idx + 1])

        with open('tests/x509.der', 'rb') as f:
            x509_der = f.read()

        x509.save('tests/tmpcert.pem')
        with open('tests/tmpcert.pem') as f:
            s = f.read()

        self.assertEqual(s, x509_pem)
        os.remove('tests/tmpcert.pem')
        x509.save('tests/tmpcert.der', format=X509.FORMAT_DER)
        with open('tests/tmpcert.der', 'rb') as f:
            s = f.read()

        self.assertEqual(s, x509_der)
        os.remove('tests/tmpcert.der')

    def test_malformed_data(self):
        try:
            with self.assertRaises(X509.X509Error):
                X509.load_cert_string('Hello')
            with self.assertRaises(X509.X509Error):
                X509.load_cert_der_string('Hello')
            with self.assertRaises(X509.X509Error):
                X509.new_stack_from_der(b'Hello')
            with self.assertRaises(X509.X509Error):
                X509.load_cert('tests/alltests.py')
            with self.assertRaises(X509.X509Error):
                X509.load_request('tests/alltests.py')
            with self.assertRaises(X509.X509Error):
                X509.load_request_string('Hello')
            with self.assertRaises(X509.X509Error):
                X509.load_request_der_string('Hello')
            with self.assertRaises(X509.X509Error):
                X509.load_crl('tests/alltests.py')
        except SystemError:
            pass

    def test_long_serial(self):
        cert = X509.load_cert('tests/long_serial_cert.pem')
        self.assertEqual(cert.get_serial_number(), 17616841808974579194)

        cert = X509.load_cert('tests/thawte.pem')
        self.assertEqual(cert.get_serial_number(),
                         127614157056681299805556476275995414779)

    def test_set_long_serial(self):
        cert = X509.X509()
        cert.set_serial_number(127614157056681299805556476275995414779)
        self.assertEqual(cert.get_serial_number(),
                         127614157056681299805556476275995414779)

    def test_date_after_2050_working(self):
        cert = X509.load_cert('tests/bad_date_cert.crt')
        self.assertEqual(str(cert.get_not_after()), 'Feb  9 14:57:46 2116 GMT')

    def test_easy_rsa_generated(self):
        """ Test loading a cert generated by easy RSA.

        https://github.com/fedora-infra/fedmsg/pull/389
        """
        # Does this raise an exception?
        X509.load_cert('tests/easy_rsa.pem')


class X509StackTestCase(unittest.TestCase):
    def test_make_stack_from_der(self):
        with open("tests/der_encoded_seq.b64", 'rb') as f:
            b64 = f.read()

        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            if six.PY3:
                seq = base64.decodebytes(b64)
            else:
                seq = base64.decodestring(b64)

        stack = X509.new_stack_from_der(seq)
        cert = stack.pop()
        self.assertIsNone(stack.pop())

        cert.foobar = 1
        self.assertEqual(cert.foobar, 1)

        subject = cert.get_subject()
        self.assertEqual(
            str(subject),
            "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov")

    def test_make_stack_check_num(self):
        with open("tests/der_encoded_seq.b64", 'rb') as f:
            b64 = f.read()

        with warnings.catch_warnings():
            warnings.simplefilter('ignore', DeprecationWarning)
            if six.PY3:
                seq = base64.decodebytes(b64)
            else:
                seq = base64.decodestring(b64)

        stack = X509.new_stack_from_der(seq)
        num = len(stack)
        self.assertEqual(num, 1)
        cert = stack.pop()
        num = len(stack)
        self.assertEqual(num, 0)
        subject = cert.get_subject()
        self.assertEqual(
            str(subject),
            "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov")

    def test_make_stack(self):
        stack = X509.X509_Stack()
        cert = X509.load_cert("tests/x509.pem")
        issuer = X509.load_cert("tests/ca.pem")
        cert_subject1 = cert.get_subject()
        issuer_subject1 = issuer.get_subject()
        stack.push(cert)
        stack.push(issuer)

        # Test stack iterator
        i = 0
        for c in stack:
            i += 1
            self.assertGreater(len(c.get_subject().CN), 0)
        self.assertEqual(i, 2)

        stack.pop()
        cert_pop = stack.pop()
        cert_subject2 = cert_pop.get_subject()
        issuer_subject2 = issuer.get_subject()
        self.assertEqual(str(cert_subject1), str(cert_subject2))
        self.assertEqual(str(issuer_subject1), str(issuer_subject2))

    def test_as_der(self):
        stack = X509.X509_Stack()
        cert = X509.load_cert("tests/x509.pem")
        issuer = X509.load_cert("tests/ca.pem")
        cert_subject1 = cert.get_subject()
        issuer_subject1 = issuer.get_subject()
        stack.push(cert)
        stack.push(issuer)
        der_seq = stack.as_der()
        stack2 = X509.new_stack_from_der(der_seq)
        stack2.pop()
        cert_pop = stack2.pop()
        cert_subject2 = cert_pop.get_subject()
        issuer_subject2 = issuer.get_subject()
        self.assertEqual(str(cert_subject1), str(cert_subject2))
        self.assertEqual(str(issuer_subject1), str(issuer_subject2))


class X509ExtTestCase(unittest.TestCase):
    def test_ext(self):
        if 0:  # XXX
            # With this leaks 8 bytes:
            name = "proxyCertInfo"
            value = "critical,language:Inherit all"
        else:
            # With this there are no leaks:
            name = "nsComment"
            value = "Hello"

        ctx = m2.x509v3_set_nconf()
        x509_ext_ptr = m2.x509v3_ext_conf(None, ctx, name, value)
        X509.X509_Extension(x509_ext_ptr, 1)


class CRLTestCase(unittest.TestCase):
    def test_new(self):
        crl = X509.CRL()
        self.assertEqual(crl.as_text()[:34],
                         'Certificate Revocation List (CRL):')


def suite():
    st = unittest.TestSuite()
    st.addTest(unittest.TestLoader().loadTestsFromTestCase(X509TestCase))
    st.addTest(unittest.TestLoader().loadTestsFromTestCase(X509StackTestCase))
    st.addTest(unittest.TestLoader().loadTestsFromTestCase(X509ExtTestCase))
    st.addTest(unittest.TestLoader().loadTestsFromTestCase(CRLTestCase))
    return st


if __name__ == '__main__':
    Rand.load_file('randpool.dat', -1)
    unittest.TextTestRunner().run(suite())
    Rand.save_file('randpool.dat')
