"""
Testing CryptoBackendXMLSecurity with SoftHSM as backend
"""

# Command to convert test.pem to PKCS#8 PEM format readable by softhsm :
#
#  openssl pkcs8 -topk8 -inform PEM -outform PEM -in test.key -out test.key.p8
# -nocrypt
#

__author__ = 'leifj'  # based on p11_test from pyXMLSecurity

import logging
import os
import traceback
import subprocess
import tempfile
import pytest
from pathutils import full_path

from saml2 import sigver
from saml2 import class_name
from saml2 import time_util
from saml2 import saml
from saml2.s_utils import factory, do_attribute_statement

#xmlsec = pytest.importorskip("xmlsec")

def _find_alts(alts):
    for a in alts:
        if os.path.exists(a):
            return a
    return None


PUB_KEY = full_path("test.pem")
PRIV_KEY = full_path("test.key.p8")

P11_MODULES = ['/usr/lib/libsofthsm.so', '/usr/lib/softhsm/libsofthsm.so']
P11_MODULE = _find_alts(P11_MODULES)
P11_ENGINE = '/usr/lib/engines/engine_pkcs11.so'


def _eq(l1, l2):
    return set(l1) == set(l2)


class FakeConfig():
    """
    Configuration parameters for signature validation test cases.
    """
    def __init__(self, pub_key = PUB_KEY):
        self.xmlsec_binary = None
        self.crypto_backend = 'XMLSecurity'
        self.only_use_keys_in_metadata = False
        self.metadata = None
        self.cert_file = pub_key
        self.key_file = "pkcs11://%s:0/test?pin=secret1" % P11_MODULE
        self.debug = False
        self.cert_handler_extra_class = None
        self.generate_cert_info = False
        self.generate_cert_info = False
        self.tmp_cert_file = None
        self.tmp_key_file = None
        self.validate_certificate = False
        self.delete_tmpfiles = True


class TestPKCS11():

    def __init__(self):
        self.private_keyspec = None
        self.public_keyspec = None
        self.p11_test_files = []
        self.softhsm_conf = None
        self.softhsm_db = None
        self.configured = False
        self.sec = None
        self._assertion = None

    def setup_class(self):
        logging.debug("Creating test pkcs11 token using softhsm")
        try:
            self.softhsm_db = self._tf()
            self.softhsm_conf = self._tf()
            self.signer_cert_pem = self._tf()
            self.openssl_conf = self._tf()
            self.signer_cert_der = self._tf()

            logging.debug("Generating softhsm.conf")
            with open(self.softhsm_conf, "w") as f:
                f.write("#Generated by pysaml2 cryptobackend test\n0:%s\n" % self.softhsm_db)
            logging.debug("Initializing the token")
            self._p(['softhsm',
                    '--slot', '0',
                    '--label', 'test',
                    '--init-token',
                    '--pin', 'secret1',
                    '--so-pin', 'secret2'])

            logging.debug("Importing test key {!r} into SoftHSM".format(PRIV_KEY))
            self._p(['softhsm',
                    '--slot', '0',
                    '--label', 'test',
                    '--import', PRIV_KEY,
                    '--id', 'a1b2',
                    '--pin', 'secret1',
                    '--so-pin', 'secret2'])

            logging.debug("Transforming PEM certificate to DER")
            self._p(['openssl', 'x509',
                    '-inform', 'PEM',
                    '-outform', 'DER',
                    '-in', PUB_KEY,
                    '-out', self.signer_cert_der])

            logging.debug("Importing certificate into token")

            self._p(['pkcs11-tool',
                    '--module', P11_MODULE,
                    '-l',
                    '--slot', '0',
                    '--id', 'a1b2',
                    '--label', 'test',
                    '-y', 'cert',
                    '-w', self.signer_cert_der,
                    '--pin', 'secret1'])

            # list contents of SoftHSM
            self._p(['pkcs11-tool',
                     '--module', P11_MODULE,
                     '-l',
                     '--pin', 'secret1', '-O'])
            self._p(['pkcs11-tool',
                    '--module', P11_MODULE,
                    '-l',
                    '--pin', 'secret1', '-T'])
            self._p(['pkcs11-tool',
                    '--module', P11_MODULE,
                    '-l',
                    '--pin', 'secret1', '-L'])
            self.sec = sigver.security_context(FakeConfig(pub_key = PUB_KEY))
            self._assertion = factory(saml.Assertion,
                                      version="2.0",
                                      id="11111",
                                      issue_instant="2009-10-30T13:20:28Z",
                                      signature=sigver.pre_signature_part("11111", self.sec.my_cert, 1),
                                      attribute_statement=do_attribute_statement(
                                          {("", "", "surName"): ("Foo", ""),
                                           ("", "", "givenName"): ("Bar", ""),
                                           })
                                      )
            self.configured = True
        except Exception as ex:
            print("-" * 64)
            traceback.print_exc()
            print("-" * 64)
            logging.warning("PKCS11 tests disabled: unable to initialize test token: %s" % ex)
            raise

    def teardown_class(self):
        """
        Remove temporary files created in setup_class.
        """
        for o in self.p11_test_files:
            if os.path.exists(o):
                os.unlink(o)
        self.configured = False
        self.p11_test_files = []

    def _tf(self):
        f = tempfile.NamedTemporaryFile(delete=False)
        self.p11_test_files.append(f.name)
        return f.name

    def _p(self, args):
        env = {}
        if self.softhsm_conf is not None:
            env['SOFTHSM_CONF'] = self.softhsm_conf
            #print("env SOFTHSM_CONF=%s " % softhsm_conf +" ".join(args))
        logging.debug("Environment {!r}".format(env))
        logging.debug("Executing {!r}".format(args))
        args = ['ls']
        proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
        out, err = proc.communicate()
        if err is not None and len(err) > 0:
            logging.error(err)
        if out is not None and len(out) > 0:
            logging.debug(out)
        rv = proc.wait()
        if rv:
            raise RuntimeError("command exited with code != 0: %d" % rv)

    def test_SAML_sign_with_pkcs11(self):
        """
        Test signing a SAML assertion using PKCS#11 and then verifying it.
        """
        os.environ['SOFTHSM_CONF'] = self.softhsm_conf

        ass = self._assertion
        print(ass)
        sign_ass = self.sec.sign_assertion("%s" % ass, node_id=ass.id)
        #print(sign_ass)
        sass = saml.assertion_from_string(sign_ass)
        #print(sass)
        assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
                                   'version', 'signature', 'id'])
        assert sass.version == "2.0"
        assert sass.id == "11111"
        assert time_util.str_to_time(sass.issue_instant)

        print("Crypto version : %s" % (self.sec.crypto.version()))

        item = self.sec.check_signature(sass, class_name(sass), sign_ass)

        assert isinstance(item, saml.Assertion)

        print("Test PASSED")


def test_xmlsec_cryptobackend():
    t = TestPKCS11()
    t.setup_class()
    t.test_SAML_sign_with_pkcs11()


if __name__ == "__main__":
    test_xmlsec_cryptobackend()
