/*
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * X509 support
 *
 *
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 *
 * Copyright (C) 2010-2016 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
 */
#include "globals.h"

#ifndef XMLSEC_NO_X509

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>

#include <libxml/tree.h>



#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/pkcs12.h>

#include <xmlsec/xmlsec.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/keys.h>
#include <xmlsec/keyinfo.h>
#include <xmlsec/keysmngr.h>
#include <xmlsec/x509.h>
#include <xmlsec/base64.h>
#include <xmlsec/errors.h>
#include <xmlsec/private.h>

#include <xmlsec/gnutls/crypto.h>
#include <xmlsec/gnutls/x509.h>

#include "x509utils.h"


/**************************************************************************
 *
 * X509 crt list
 *
 *****************************************************************************/
static xmlSecPtr        xmlSecGnuTLSX509CrtListDuplicateItem    (xmlSecPtr ptr);
static void             xmlSecGnuTLSX509CrtListDestroyItem      (xmlSecPtr ptr);
static void             xmlSecGnuTLSX509CrtListDebugDumpItem    (xmlSecPtr ptr,
                                                                 FILE* output);
static void             xmlSecGnuTLSX509CrtListDebugXmlDumpItem (xmlSecPtr ptr,
                                                                 FILE* output);

static xmlSecPtrListKlass xmlSecGnuTLSX509CrtListKlass = {
    BAD_CAST "gnutls-x509-crt-list",
    xmlSecGnuTLSX509CrtListDuplicateItem,       /* xmlSecPtrDuplicateItemMethod duplicateItem; */
    xmlSecGnuTLSX509CrtListDestroyItem,         /* xmlSecPtrDestroyItemMethod destroyItem; */
    xmlSecGnuTLSX509CrtListDebugDumpItem,       /* xmlSecPtrDebugDumpItemMethod debugDumpItem; */
    xmlSecGnuTLSX509CrtListDebugXmlDumpItem,    /* xmlSecPtrDebugDumpItemMethod debugXmlDumpItem; */
};

xmlSecPtrListId
xmlSecGnuTLSX509CrtListGetKlass(void) {
    return(&xmlSecGnuTLSX509CrtListKlass);
}

static xmlSecPtr
xmlSecGnuTLSX509CrtListDuplicateItem(xmlSecPtr ptr) {
    xmlSecAssert2(ptr != NULL, NULL);

    return xmlSecGnuTLSX509CertDup((gnutls_x509_crt_t)ptr);
}

static void
xmlSecGnuTLSX509CrtListDestroyItem(xmlSecPtr ptr) {
    xmlSecAssert(ptr != NULL);

    gnutls_x509_crt_deinit((gnutls_x509_crt_t)ptr);
}

static void
xmlSecGnuTLSX509CrtListDebugDumpItem(xmlSecPtr ptr, FILE* output) {
    xmlSecAssert(ptr != NULL);
    xmlSecAssert(output != NULL);

    xmlSecGnuTLSX509CertDebugDump((gnutls_x509_crt_t)ptr, output);
}


static void
xmlSecGnuTLSX509CrtListDebugXmlDumpItem(xmlSecPtr ptr, FILE* output) {
    xmlSecAssert(ptr != NULL);
    xmlSecAssert(output != NULL);

    xmlSecGnuTLSX509CertDebugXmlDump((gnutls_x509_crt_t)ptr, output);
}

/**************************************************************************
 *
 * X509 crl list
 *
 *****************************************************************************/
static xmlSecPtr        xmlSecGnuTLSX509CrlListDuplicateItem    (xmlSecPtr ptr);
static void             xmlSecGnuTLSX509CrlListDestroyItem      (xmlSecPtr ptr);
static void             xmlSecGnuTLSX509CrlListDebugDumpItem    (xmlSecPtr ptr,
                                                                 FILE* output);
static void             xmlSecGnuTLSX509CrlListDebugXmlDumpItem (xmlSecPtr ptr,
                                                                 FILE* output);

static xmlSecPtrListKlass xmlSecGnuTLSX509CrlListKlass = {
    BAD_CAST "gnutls-x509-crl-list",
    xmlSecGnuTLSX509CrlListDuplicateItem,       /* xmlSecPtrDuplicateItemMethod duplicateItem; */
    xmlSecGnuTLSX509CrlListDestroyItem,         /* xmlSecPtrDestroyItemMethod destroyItem; */
    xmlSecGnuTLSX509CrlListDebugDumpItem,       /* xmlSecPtrDebugDumpItemMethod debugDumpItem; */
    xmlSecGnuTLSX509CrlListDebugXmlDumpItem,    /* xmlSecPtrDebugDumpItemMethod debugXmlDumpItem; */
};

xmlSecPtrListId
xmlSecGnuTLSX509CrlListGetKlass(void) {
    return(&xmlSecGnuTLSX509CrlListKlass);
}

static xmlSecPtr
xmlSecGnuTLSX509CrlListDuplicateItem(xmlSecPtr ptr) {
    xmlSecAssert2(ptr != NULL, NULL);

    return xmlSecGnuTLSX509CrlDup((gnutls_x509_crl_t)ptr);
}

static void
xmlSecGnuTLSX509CrlListDestroyItem(xmlSecPtr ptr) {
    xmlSecAssert(ptr != NULL);

    gnutls_x509_crl_deinit((gnutls_x509_crl_t)ptr);
}

static void
xmlSecGnuTLSX509CrlListDebugDumpItem(xmlSecPtr ptr, FILE* output) {
    xmlSecAssert(ptr != NULL);
    xmlSecAssert(output != NULL);

    xmlSecGnuTLSX509CrlDebugDump((gnutls_x509_crl_t)ptr, output);
}


static void
xmlSecGnuTLSX509CrlListDebugXmlDumpItem(xmlSecPtr ptr, FILE* output) {
    xmlSecAssert(ptr != NULL);
    xmlSecAssert(output != NULL);

    xmlSecGnuTLSX509CrlDebugXmlDump((gnutls_x509_crl_t)ptr, output);
}

/*************************************************************************
 *
 * x509 certs utils/helpers
 *
 ************************************************************************/

/* HACK: gnutls doesn't have cert duplicate function, so we simply 
 write cert out and then read it back */
gnutls_x509_crt_t
xmlSecGnuTLSX509CertDup(gnutls_x509_crt_t src) {
    xmlChar * buf = NULL;
    gnutls_x509_crt_t res = NULL;

    xmlSecAssert2(src != NULL, NULL);

    buf = xmlSecGnuTLSX509CertBase64DerWrite(src, 0);
    if(buf == NULL) {
        xmlSecInternalError("xmlSecGnuTLSX509CertBase64DerWrite", NULL);
        return (NULL);
    }

    res = xmlSecGnuTLSX509CertBase64DerRead(buf);
    if(res == NULL) {
        xmlSecInternalError("xmlSecGnuTLSX509CertBase64DerRead", NULL);
        xmlFree(buf);
        return (NULL);
    }

    /* done */
    xmlFree(buf);
    return (res);
}

xmlChar *
xmlSecGnuTLSX509CertGetSubjectDN(gnutls_x509_crt_t cert) {
    char* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(cert != NULL, NULL);

    /* get subject size */
    err = gnutls_x509_crt_get_dn(cert, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_dn", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (char *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* finally write it out */
    err = gnutls_x509_crt_get_dn(cert, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_dn", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    return(BAD_CAST buf);
}

xmlChar *
xmlSecGnuTLSX509CertGetIssuerDN(gnutls_x509_crt_t cert) {
    char* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(cert != NULL, NULL);

    /* get issuer size */
    err = gnutls_x509_crt_get_issuer_dn(cert, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_issuer_dn", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (char *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* finally write it out */
    err = gnutls_x509_crt_get_issuer_dn(cert, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_issuer_dn", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    return(BAD_CAST buf);
}

xmlChar *
xmlSecGnuTLSX509CertGetIssuerSerial(gnutls_x509_crt_t cert) {
    xmlChar * res = NULL;
    unsigned char* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(cert != NULL, NULL);

    /* get issuer serial size */
    err = gnutls_x509_crt_get_serial(cert, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_serial", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (unsigned char *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* write it out */
    err = gnutls_x509_crt_get_serial(cert, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_serial", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* convert to string */
    res = xmlSecGnuTLSASN1IntegerWrite(buf, bufSize);
    if(res == NULL) {
        xmlSecInternalError("xmlSecGnuTLSASN1IntegerWrite", NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    xmlFree(buf);
    return(res);
}

xmlChar *
xmlSecGnuTLSX509CertGetSKI(gnutls_x509_crt_t cert) {
    xmlChar * res = NULL;
    xmlSecByte* buf = NULL;
    size_t bufSize = 0;
    unsigned int critical = 0;
    int err;

    xmlSecAssert2(cert != NULL, NULL);

    /* get ski size */
    err = gnutls_x509_crt_get_subject_key_id(cert, NULL, &bufSize, &critical);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_subject_key_id", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (xmlSecByte *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* write it out */
    err = gnutls_x509_crt_get_subject_key_id(cert, buf, &bufSize, &critical);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_get_subject_key_id", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* convert to string */
    res = xmlSecBase64Encode(buf, bufSize, 0);
    if(res == NULL) {
        xmlSecInternalError("xmlSecBase64Encode", NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    xmlFree(buf);
    return(res);
}


gnutls_x509_crt_t
xmlSecGnuTLSX509CertBase64DerRead(xmlChar* buf) {
    int ret;

    xmlSecAssert2(buf != NULL, NULL);

    /* usual trick with base64 decoding "in-place" */
    ret = xmlSecBase64Decode(buf, (xmlSecByte*)buf, xmlStrlen(buf));
    if(ret < 0) {
        xmlSecInternalError("xmlSecBase64Decode", NULL);
        return(NULL);
    }

    return(xmlSecGnuTLSX509CertRead((const xmlSecByte*)buf, ret, xmlSecKeyDataFormatCertDer));
}

gnutls_x509_crt_t
xmlSecGnuTLSX509CertRead(const xmlSecByte* buf, xmlSecSize size, xmlSecKeyDataFormat format) {
    gnutls_x509_crt_t cert = NULL;
    gnutls_x509_crt_fmt_t fmt;
    gnutls_datum_t data;
    int err;

    xmlSecAssert2(buf != NULL, NULL);
    xmlSecAssert2(size > 0, NULL);

    /* figure out format */
    switch(format) {
    case xmlSecKeyDataFormatPem:
    case xmlSecKeyDataFormatCertPem:
        fmt = GNUTLS_X509_FMT_PEM;
        break;
    case xmlSecKeyDataFormatDer:
    case xmlSecKeyDataFormatCertDer:
        fmt = GNUTLS_X509_FMT_DER;
        break;
    default:
        xmlSecOtherError2(XMLSEC_ERRORS_R_INVALID_FORMAT, NULL,
                         "format=%d", (int)format);
        return(NULL);
    }

    /* read cert */
    err = gnutls_x509_crt_init(&cert);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_init", err, NULL);
        return(NULL);
    }

    data.data = (unsigned char*)buf;
    data.size = size;
    err = gnutls_x509_crt_import(cert, &data, fmt);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_import", err, NULL);
        gnutls_x509_crt_deinit(cert);
        return(NULL);
    }

    return(cert);
}

xmlChar*
xmlSecGnuTLSX509CertBase64DerWrite(gnutls_x509_crt_t cert, int base64LineWrap) {
    xmlChar * res = NULL;
    xmlSecByte* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(cert != NULL, NULL);

    /* get size */
    err = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crt_export(GNUTLS_X509_FMT_DER)", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (xmlSecByte *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* write it out */
    err = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crt_export(GNUTLS_X509_FMT_DER)", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* convert to string */
    res = xmlSecBase64Encode(buf, bufSize, base64LineWrap);
    if(res == NULL) {
        xmlSecInternalError("xmlSecBase64Encode", NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    xmlFree(buf);
    return(res);
}

void
xmlSecGnuTLSX509CertDebugDump(gnutls_x509_crt_t cert, FILE* output) {
    xmlChar * buf;

    xmlSecAssert(cert != NULL);
    xmlSecAssert(output != NULL);

    buf = xmlSecGnuTLSX509CertGetSubjectDN(cert);
    if(buf != NULL) {
        fprintf(output, "==== Subject Name: %s\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "==== Subject Name: unknown\n");
    }

    buf = xmlSecGnuTLSX509CertGetIssuerDN(cert);
    if(buf != NULL) {
        fprintf(output, "==== Issuer Name: %s\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "==== Issuer Name: unknown\n");
    }

    buf = xmlSecGnuTLSX509CertGetIssuerSerial(cert);
    if(buf != NULL) {
        fprintf(output, "==== Issuer Serial: %s\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "==== Issuer Serial: unknown\n");
    }
}

void
xmlSecGnuTLSX509CertDebugXmlDump(gnutls_x509_crt_t cert, FILE* output) {
    xmlChar * buf;

    xmlSecAssert(cert != NULL);
    xmlSecAssert(output != NULL);

    buf = xmlSecGnuTLSX509CertGetSubjectDN(cert);
    if(buf != NULL) {
        fprintf(output, "<SubjectName>%s</SubjectName>\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "<SubjectName>unknown</SubjectName>\n");
    }

    buf = xmlSecGnuTLSX509CertGetIssuerDN(cert);
    if(buf != NULL) {
        fprintf(output, "<IssuerName>%s</IssuerName>\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "<IssuerName>unknown</IssuerName>\n");
    }

    buf = xmlSecGnuTLSX509CertGetIssuerSerial(cert);
    if(buf != NULL) {
        fprintf(output, "<SerialNumber>%s</SerialNumber>\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "<SerialNumber>unknown</SerialNumber>\n");
    }
}

/*************************************************************************
 *
 * x509 crls utils/helpers
 *
 ************************************************************************/

/* HACK: gnutls doesn't have crl duplicate function, so we simply 
 write crl out and then read it back */
gnutls_x509_crl_t
xmlSecGnuTLSX509CrlDup(gnutls_x509_crl_t src) {
    xmlChar * buf = NULL;
    gnutls_x509_crl_t res = NULL;

    xmlSecAssert2(src != NULL, NULL);

    buf = xmlSecGnuTLSX509CrlBase64DerWrite(src, 0);
    if(buf == NULL) {
        xmlSecInternalError("xmlSecGnuTLSX509CrlBase64DerWrite", NULL);
        return (NULL);
    }

    res = xmlSecGnuTLSX509CrlBase64DerRead(buf);
    if(res == NULL) {
        xmlSecInternalError("xmlSecGnuTLSX509CrlBase64DerRead", NULL);
        xmlFree(buf);
        return (NULL);
    }

    /* done */
    xmlFree(buf);
    return (res);
}

xmlChar *
xmlSecGnuTLSX509CrlGetIssuerDN(gnutls_x509_crl_t crl) {
    char* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(crl != NULL, NULL);

    /* get issuer size */
    err = gnutls_x509_crl_get_issuer_dn(crl, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crl_get_issuer_dn", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (char *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* finally write it out */
    err = gnutls_x509_crl_get_issuer_dn(crl, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crl_get_issuer_dn", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    return(BAD_CAST buf);
}

gnutls_x509_crl_t
xmlSecGnuTLSX509CrlBase64DerRead(xmlChar* buf) {
    int ret;

    xmlSecAssert2(buf != NULL, NULL);

    /* usual trick with base64 decoding "in-place" */
    ret = xmlSecBase64Decode(buf, (xmlSecByte*)buf, xmlStrlen(buf));
    if(ret < 0) {
        xmlSecInternalError("xmlSecBase64Decode", NULL);
        return(NULL);
    }

    return(xmlSecGnuTLSX509CrlRead((const xmlSecByte*)buf, ret, xmlSecKeyDataFormatCertDer));
}

gnutls_x509_crl_t
xmlSecGnuTLSX509CrlRead(const xmlSecByte* buf, xmlSecSize size, xmlSecKeyDataFormat format) {
    gnutls_x509_crl_t crl = NULL;
    gnutls_x509_crt_fmt_t fmt;
    gnutls_datum_t data;
    int err;

    xmlSecAssert2(buf != NULL, NULL);
    xmlSecAssert2(size > 0, NULL);

    /* figure out format */
    switch(format) {
    case xmlSecKeyDataFormatPem:
    case xmlSecKeyDataFormatCertPem:
        fmt = GNUTLS_X509_FMT_PEM;
        break;
    case xmlSecKeyDataFormatDer:
    case xmlSecKeyDataFormatCertDer:
        fmt = GNUTLS_X509_FMT_DER;
        break;
    default:
        xmlSecOtherError2(XMLSEC_ERRORS_R_INVALID_FORMAT, NULL,
                         "format=%d", (int)format);
        return(NULL);
    }

    /* read crl */
    err = gnutls_x509_crl_init(&crl);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crl_init", err, NULL);
        return(NULL);
    }

    data.data = (unsigned char*)buf;
    data.size = size;
    err = gnutls_x509_crl_import(crl, &data, fmt);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crl_import", err, NULL);
        gnutls_x509_crl_deinit(crl);
        return(NULL);
    }

    return(crl);
}

xmlChar*
xmlSecGnuTLSX509CrlBase64DerWrite(gnutls_x509_crl_t crl, int base64LineWrap) {
    xmlChar * res = NULL;
    xmlSecByte* buf = NULL;
    size_t bufSize = 0;
    int err;

    xmlSecAssert2(crl != NULL, NULL);

    /* get size */
    err = gnutls_x509_crl_export(crl, GNUTLS_X509_FMT_DER, NULL, &bufSize);
    if((err != GNUTLS_E_SHORT_MEMORY_BUFFER) || (bufSize <= 0)) {
        xmlSecGnuTLSError("gnutls_x509_crl_export(GNUTLS_X509_FMT_DER)", err, NULL);
        return(NULL);
    }

    /* allocate buffer */
    buf = (xmlSecByte *)xmlMalloc(bufSize + 1);
    if(buf == NULL) {
        xmlSecMallocError(bufSize + 1, NULL);
        return(NULL);
    }

    /* write it out */
    err = gnutls_x509_crl_export(crl, GNUTLS_X509_FMT_DER, buf, &bufSize);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_x509_crl_export(GNUTLS_X509_FMT_DER)", err, NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* convert to string */
    res = xmlSecBase64Encode(buf, bufSize, base64LineWrap);
    if(res == NULL) {
        xmlSecInternalError("xmlSecBase64Encode", NULL);
        xmlFree(buf);
        return(NULL);
    }

    /* done */
    xmlFree(buf);
    return(res);
}

void
xmlSecGnuTLSX509CrlDebugDump(gnutls_x509_crl_t crl, FILE* output) {
    xmlChar * buf;

    xmlSecAssert(crl != NULL);
    xmlSecAssert(output != NULL);

    buf = xmlSecGnuTLSX509CrlGetIssuerDN(crl);
    if(buf != NULL) {
        fprintf(output, "==== Issuer Name: %s\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "==== Issuer Name: unknown\n");
    }
}

void
xmlSecGnuTLSX509CrlDebugXmlDump(gnutls_x509_crl_t crl, FILE* output) {
    xmlChar * buf;

    xmlSecAssert(crl != NULL);
    xmlSecAssert(output != NULL);

    buf = xmlSecGnuTLSX509CrlGetIssuerDN(crl);
    if(buf != NULL) {
        fprintf(output, "<IssuerName>%s</IssuerName>\n", buf);
        xmlFree(buf);
    } else {
        fprintf(output, "<IssuerName>unknown</IssuerName>\n");
    }
}

/*************************************************************************
 *
 * Misc. utils/helpers
 *
 ************************************************************************/
xmlChar*
xmlSecGnuTLSASN1IntegerWrite(const unsigned char * data, size_t len) {
    xmlChar *res = NULL;
    int resLen = 64; /* not more than 64 chars */
    unsigned long long int val = 0;
    size_t ii = 0;
    int shift = 0;
    int ret;

    xmlSecAssert2(data != NULL, NULL);
    xmlSecAssert2(len <= 9, NULL);

    /* HACK : to be fixed after GnuTLS provides a way to read opaque ASN1 integer */
    for(ii = len; ii > 0; --ii, shift += 8) {
        val |= ((unsigned long long)data[ii - 1]) << shift;
    }

    res = (xmlChar*)xmlMalloc(resLen + 1);
    if(res == NULL) {
        xmlSecMallocError(resLen + 1, NULL);
        return(NULL);
    }

    ret = xmlStrPrintf(res, resLen, "%llu", val);
    if(ret < 0) {
        xmlSecXmlError("xmlStrPrintf", NULL);
        xmlFree(res);
        return(NULL);
    }

    return(res);
}

/*************************************************************************
 *
 * pkcs12 utils/helpers
 *
 ************************************************************************/
int
xmlSecGnuTLSPkcs12LoadMemory(const xmlSecByte* data, xmlSecSize dataSize,
                             const char *pwd,
                             gnutls_x509_privkey_t * priv_key,
                             gnutls_x509_crt_t * key_cert,
                             xmlSecPtrListPtr certsList)
{
    gnutls_pkcs12_t pkcs12 = NULL;
    gnutls_pkcs12_bag_t bag = NULL;
    gnutls_x509_crt_t cert = NULL;
    gnutls_datum_t datum;
    xmlSecSize certsSize;
    int res = -1;
    int idx;
    int err;
    int ret;

    xmlSecAssert2(data != NULL, -1);
    xmlSecAssert2(dataSize > 0, -1);
    xmlSecAssert2(priv_key != NULL, -1);
    xmlSecAssert2((*priv_key) == NULL, -1);
    xmlSecAssert2(key_cert!= NULL, -1);
    xmlSecAssert2((*key_cert) == NULL, -1);
    xmlSecAssert2(certsList != NULL, -1);

    /* read pkcs12 in internal structure */
    err = gnutls_pkcs12_init(&pkcs12);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_pkcs12_init", err, NULL);
        goto done;
    }

    datum.data = (unsigned char *)data;
    datum.size = dataSize;
    err = gnutls_pkcs12_import(pkcs12, &datum, GNUTLS_X509_FMT_DER, 0);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_pkcs12_import", err, NULL);
        goto done;
    }

    /* verify */
    err = gnutls_pkcs12_verify_mac(pkcs12, pwd);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_pkcs12_verify_mac", err, NULL);
        goto done;
    }

    /* scan the pkcs structure and find the first private key */
    for(idx = 0; ; ++idx) {
        int bag_type;
        int elements_in_bag;
        int ii;

        err = gnutls_pkcs12_bag_init(&bag);
        if(err != GNUTLS_E_SUCCESS) {
            xmlSecGnuTLSError("gnutls_pkcs12_bag_init", err, NULL);
            goto done;
        }

        err = gnutls_pkcs12_get_bag(pkcs12, idx, bag);
        if(err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
            /* scanned the whole pkcs12, stop */
            break;
        } else if(err != GNUTLS_E_SUCCESS) {
            xmlSecGnuTLSError("gnutls_pkcs12_get_bag", err, NULL);
            goto done;
        }

        /* check if we need to decrypt the bag */
        bag_type = gnutls_pkcs12_bag_get_type(bag, 0);
        if(bag_type < 0) {
            xmlSecGnuTLSError("gnutls_pkcs12_bag_get_type", bag_type, NULL);
            goto done;
        }
        if(bag_type == GNUTLS_BAG_ENCRYPTED) {
            err = gnutls_pkcs12_bag_decrypt(bag, pwd);
            if(err != GNUTLS_E_SUCCESS) {
                xmlSecGnuTLSError("gnutls_pkcs12_bag_decrypt", err, NULL);
                goto done;
            }
        }

        /* scan elements in bag */
        elements_in_bag = gnutls_pkcs12_bag_get_count(bag);
        if(elements_in_bag < 0) {
            xmlSecGnuTLSError("gnutls_pkcs12_bag_get_count", elements_in_bag, NULL);
            goto done;
        }
        for(ii = 0; ii < elements_in_bag; ++ii) {
            bag_type = gnutls_pkcs12_bag_get_type(bag, ii);
            if(bag_type < 0) {
                xmlSecGnuTLSError("gnutls_pkcs12_bag_get_type", bag_type, NULL);
                goto done;
            }

            err = gnutls_pkcs12_bag_get_data(bag, ii, &datum);
            if(err != GNUTLS_E_SUCCESS) {
                xmlSecGnuTLSError("gnutls_pkcs12_bag_get_data", err, NULL);
                goto done;
            }

            switch(bag_type) {
            case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
            case GNUTLS_BAG_PKCS8_KEY:
                /* we want only the first private key */
                if((*priv_key) == NULL) {
                    err = gnutls_x509_privkey_init(priv_key);
                    if(err != GNUTLS_E_SUCCESS) {
                        xmlSecGnuTLSError("gnutls_x509_privkey_init", err, NULL);
                        goto done;
                    }

                    err = gnutls_x509_privkey_import_pkcs8((*priv_key),
                                &datum, GNUTLS_X509_FMT_DER,
                                pwd,
                                (bag_type == GNUTLS_BAG_PKCS8_KEY) ? GNUTLS_PKCS_PLAIN : 0);
                    if(err != GNUTLS_E_SUCCESS) {
                        xmlSecGnuTLSError("gnutls_x509_privkey_import_pkcs8", err, NULL);
                        goto done;
                    }
                }
                break;
            case GNUTLS_BAG_CERTIFICATE:
                err = gnutls_x509_crt_init(&cert);
                if(err != GNUTLS_E_SUCCESS) {
                    xmlSecGnuTLSError("gnutls_x509_crt_init", err, NULL);
                    goto done;
                }

                err = gnutls_x509_crt_import(cert, &datum, GNUTLS_X509_FMT_DER);
                if(err != GNUTLS_E_SUCCESS) {
                    xmlSecGnuTLSError("gnutls_x509_crt_import", err, NULL);
                    goto done;
                }

                ret = xmlSecPtrListAdd(certsList, cert);
                if(ret < 0) {
                    xmlSecInternalError("xmlSecPtrListAdd(certsList)", NULL);
                    goto done;
                }
                cert = NULL; /* owned by certsList now */
                break;
            default:
                /* ignore unknown bag element */
                break;
            }
        }

        /* done with bag */
        gnutls_pkcs12_bag_deinit(bag);
        bag = NULL;
    }

    /* check we have private key */
    if((*priv_key) == NULL) {
        xmlSecOtherError(XMLSEC_ERRORS_R_KEY_NOT_FOUND, NULL,
                         "Private key was not found in pkcs12 object");
        goto done;
    }

    /* we will search for key cert using the key id */
    certsSize = xmlSecPtrListGetSize(certsList);
    if(certsSize > 0) {
        size_t cert_id_size = 0;
        size_t key_id_size = 0;
        xmlSecByte cert_id[100];
        xmlSecByte key_id[100];
        xmlSecSize ii;

        key_id_size = sizeof(key_id);
        err = gnutls_x509_privkey_get_key_id((*priv_key), 0, key_id, &key_id_size);
        if(err != GNUTLS_E_SUCCESS) {
            xmlSecGnuTLSError("gnutls_x509_privkey_get_key_id", err, NULL);
            goto done;
        }
        for(ii = 0; ii < certsSize; ++ii) {
            gnutls_x509_crt_t tmp;

            tmp = xmlSecPtrListGetItem(certsList, ii);
            if(tmp == NULL) {
                continue;
            }

            cert_id_size = sizeof(cert_id);
            err = gnutls_x509_crt_get_key_id(tmp, 0, cert_id, &cert_id_size);
            if(err != GNUTLS_E_SUCCESS) {
                xmlSecGnuTLSError("gnutls_x509_crt_get_key_id", err, NULL);
                goto done;
            }

            /* if key ids match, then this is THE key cert!!! */
            if((key_id_size == cert_id_size) && (memcmp(key_id, cert_id, key_id_size) == 0)) {
                (*key_cert) = xmlSecGnuTLSX509CertDup(tmp);
                if((*key_cert) == NULL) {
                    xmlSecInternalError("xmlSecGnuTLSX509CertDup", NULL);
                    goto done;
                }

                break;
            }
        }

        /* check we have key cert */
        if((*key_cert) == NULL) {
            xmlSecOtherError(XMLSEC_ERRORS_R_CERT_NOT_FOUND, NULL,
                             "Certificate for the private key was not found in pkcs12 object");
            goto done;
        }
    }


    /* success!!! */
    res = 0;

done:
    if(cert != NULL) {
        gnutls_x509_crt_deinit(cert);
    }
    if(bag != NULL) {
        gnutls_pkcs12_bag_deinit(bag);
    }
    if(pkcs12 != NULL) {
        gnutls_pkcs12_deinit(pkcs12);
    }
    return(res);
}

xmlSecKeyDataPtr
xmlSecGnuTLSCreateKeyDataAndAdoptPrivKey(gnutls_x509_privkey_t priv_key) {
    xmlSecKeyDataPtr res = NULL;
    int key_alg;
    int ret;

    xmlSecAssert2(priv_key != NULL, NULL);

    /* create key value data */
    key_alg = gnutls_x509_privkey_get_pk_algorithm(priv_key);
    if(key_alg < 0) {
        xmlSecGnuTLSError("gnutls_x509_privkey_get_pk_algorithm", key_alg < 0, NULL);
        return (NULL);
    }
    switch(key_alg) {
#ifndef XMLSEC_NO_RSA
    case GNUTLS_PK_RSA:
        res = xmlSecKeyDataCreate(xmlSecGnuTLSKeyDataRsaId);
        if(res == NULL) {
            xmlSecInternalError("xmlSecKeyDataCreate(KeyDataRsaId)", NULL);
            return(NULL);
        }

        ret = xmlSecGnuTLSKeyDataRsaAdoptPrivateKey(res, priv_key);
        if(ret < 0) {
            xmlSecInternalError("xmlSecGnuTLSKeyDataRsaAdoptPrivateKey(KeyDataRsaId)", NULL);
            xmlSecKeyDataDestroy(res);
            return(NULL);
        }
        break;
#endif /* XMLSEC_NO_RSA */

#ifndef XMLSEC_NO_DSA
    case GNUTLS_PK_DSA:
        res = xmlSecKeyDataCreate(xmlSecGnuTLSKeyDataDsaId);
        if(res == NULL) {
            xmlSecInternalError("xmlSecKeyDataCreate(KeyDataDsaId)", NULL);
            return(NULL);
        }

        ret = xmlSecGnuTLSKeyDataDsaAdoptPrivateKey(res, priv_key);
        if(ret < 0) {
            xmlSecInternalError("xmlSecGnuTLSKeyDataDsaAdoptPrivateKey(KeyDataDsaId)", NULL);
            xmlSecKeyDataDestroy(res);
            return(NULL);
        }
        break;
#endif /* XMLSEC_NO_DSA */
    default:
        xmlSecInvalidIntegerTypeError("key_alg", key_alg, "supported algorithm", NULL);
        return(NULL);
    }

    /* done */
    return(res);
}

/*************************************************************************
 *
 * LDAP DN parser
 *
 ************************************************************************/
void
xmlSecGnuTLSDnAttrsInitialize(xmlSecGnuTLSDnAttr * attrs, xmlSecSize attrsSize) {
    xmlSecAssert(attrs != NULL);
    xmlSecAssert(attrsSize > 0);

    memset(attrs, 0, attrsSize * sizeof(xmlSecGnuTLSDnAttr));
}

void
xmlSecGnuTLSDnAttrsDeinitialize(xmlSecGnuTLSDnAttr * attrs, xmlSecSize attrsSize) {
    xmlSecSize ii;

    xmlSecAssert(attrs != NULL);
    xmlSecAssert(attrsSize > 0);

    for(ii = 0; ii < attrsSize; ++ii) {
        if(attrs[ii].key != NULL) {
            xmlFree(attrs[ii].key);
        }
        if(attrs[ii].value != NULL) {
            xmlFree(attrs[ii].value);
        }
    }
    memset(attrs, 0, attrsSize * sizeof(xmlSecGnuTLSDnAttr));
}

const xmlSecGnuTLSDnAttr *
xmlSecGnuTLSDnAttrrsFind(const xmlSecGnuTLSDnAttr * attrs,
                         xmlSecSize attrsSize,
                         const xmlChar * key)
{
    xmlSecSize ii;

    xmlSecAssert2(attrs != NULL, NULL);
    xmlSecAssert2(attrsSize > 0, NULL);
    xmlSecAssert2(key != NULL, NULL);

    for(ii = 0; ii < attrsSize; ++ii) {
        /* simple case */
        if(xmlStrcasecmp(key, attrs[ii].key) == 0) {
            return(&(attrs[ii]));
        }

        /* special case for emailAddress (as usual) */
        if((xmlStrcasecmp(key, BAD_CAST "emailAddress") == 0) &&
           (xmlStrcasecmp(attrs[ii].key, BAD_CAST "email") == 0))
        {
            return(&(attrs[ii]));
        }
        if((xmlStrcasecmp(key, BAD_CAST "email") == 0) &&
           (xmlStrcasecmp(attrs[ii].key, BAD_CAST "emailAddress") == 0))
        {
            return(&(attrs[ii]));
        }
    }

    /* not found :( */
    return(NULL);
}

int
xmlSecGnuTLSDnAttrsEqual(const xmlSecGnuTLSDnAttr * ll, xmlSecSize llSize,
                         const xmlSecGnuTLSDnAttr * rr, xmlSecSize rrSize) 
{
    xmlSecSize llNum = 0;
    xmlSecSize rrNum = 0;
    const xmlSecGnuTLSDnAttr * tmp;
    xmlSecSize ii;

    xmlSecAssert2(ll != NULL, -1);
    xmlSecAssert2(llSize > 0, -1);
    xmlSecAssert2(rr != NULL, -1);
    xmlSecAssert2(rrSize > 0, -1);

    /* compare number of non-nullattributes */
    for(ii = 0; ii < llSize; ++ii) {
        if(ll[ii].key != NULL) {
            ++llNum;
        }
    }
    for(ii = 0; ii < rrSize; ++ii) {
        if(rr[ii].key != NULL) {
            ++rrNum;
        }
    }
    if(llNum != rrNum) {
        return(0);
    }

    /* make sure that all ll attrs are equal to rr attrs */
    for(ii = 0; ii < llSize; ++ii) {
        if(ll[ii].key == NULL) {
            continue;
        }

        tmp = xmlSecGnuTLSDnAttrrsFind(rr, rrSize, ll[ii].key);
        if(tmp == NULL) {
            return(0); /* attribute was not found */
        }

        if(!xmlStrEqual(ll[ii].value, tmp->value)) {
            return(0); /* different values */
        }
    }

    /* good!!! */
    return(1);
}

/*
Distinguished name syntax

The formal syntax for a Distinguished Name (DN) is based on RFC 2253. 
The Backus Naur Form (BNF) syntax is defined as follows:

    <name> ::= <name-component> ( <spaced-separator> )
          | <name-component> <spaced-separator> <name>

   <spaced-separator> ::= <optional-space>
                   <separator>
                   <optional-space>

   <separator> ::=  "," | ";"

   <optional-space> ::= ( <CR> ) *( " " )

   <name-component> ::= <attribute>
           | <attribute> <optional-space> "+"
             <optional-space> <name-component>

   <attribute> ::= <string>
           | <key> <optional-space> "=" <optional-space> <string>

   <key> ::= 1*( <keychar> ) | "OID." <oid> | "oid." <oid>
   <keychar> ::= letters, numbers, and space

   <oid> ::= <digitstring> | <digitstring> "." <oid>
   <digitstring> ::= 1*<digit>
   <digit> ::= digits 0-9

   <string> ::= *( <stringchar> | <pair> )
            | '"' *( <stringchar> | <special> | <pair> ) '"'
            | "#" <hex>


   <special> ::= "," | "=" | <CR> | "+" | "<" |  ">"
            | "#" | ";"

   <pair> ::= "\" ( <special> | "\" | '"')
   <stringchar> ::= any character except <special> or "\" or '"'


   <hex> ::= 2*<hexchar>
   <hexchar> ::= 0-9, a-f, A-F

A semicolon (;) character can be used to separate RDNs in a distinguished name,
although the comma (,) character is the typical notation.

White-space characters (spaces) might be present on either side of the comma or
semicolon. The white-space characters are ignored, and the semicolon is replaced
with a comma.

In addition, space (' ' ASCII 32) characters may be present either before or 
after a '+' or '='. These space characters are ignored when parsing.
*/
enum xmlSecGnuTLSDnParseState {
    xmlSecGnuTLSDnParseState_BeforeNameComponent = 0,
    xmlSecGnuTLSDnParseState_Key,
    xmlSecGnuTLSDnParseState_BeforeString,
    xmlSecGnuTLSDnParseState_String,
    xmlSecGnuTLSDnParseState_QuotedString,
    xmlSecGnuTLSDnParseState_AfterQuotedString
};

#define XMLSEC_GNUTLS_IS_SPACE(ch)      \
        (((ch) == ' ') || ((ch) == '\n') || ((ch) == '\r'))

int
xmlSecGnuTLSDnAttrsParse(const xmlChar * dn,
                         xmlSecGnuTLSDnAttr * attrs, xmlSecSize attrsSize)
{
    xmlChar * tmp = NULL;
    xmlChar * p;
    xmlChar ch;
    enum xmlSecGnuTLSDnParseState state;
    int slash;
    xmlSecSize pos;
    int res = -1;

    xmlSecAssert2(dn != NULL, -1);
    xmlSecAssert2(attrs != NULL, -1);
    xmlSecAssert2(attrsSize > 0, -1);

    /* allocate buffer, we don't need more than string */
    tmp = (xmlChar *)xmlMalloc(xmlStrlen(dn) + 1);
    if(tmp == NULL) {
        xmlSecMallocError(xmlStrlen(dn) + 1, NULL);
        goto done;
    }

    /* state machine */
    state = xmlSecGnuTLSDnParseState_BeforeNameComponent;
    slash = 0;
    pos = 0;
    p = tmp;
    for(ch = (*dn); ; ch = *(++dn)) {
        switch(state) {
        case xmlSecGnuTLSDnParseState_BeforeNameComponent:
            if(!XMLSEC_GNUTLS_IS_SPACE(ch)) {
                *(p++) = ch; /* we are sure we have enough buffer */
                state = xmlSecGnuTLSDnParseState_Key;
            } else {
                /* just skip space */
            }
            break;
        case xmlSecGnuTLSDnParseState_Key:
            /* we don't support
            1) <attribute><optional-space>"+"<optional-space><name-component>
            2) <attribute> ::= <string>
            */
            if(ch != '=') {
                *(p++) = ch; /* we are sure we have enough buffer */
            } else {
                *(p) = '\0';
                /* remove spaces back */
                while((p > tmp) && (XMLSEC_GNUTLS_IS_SPACE(*(p - 1)))) {
                    *(--p) = '\0';
                }

                /* insert into the attrs */
                if(pos >= attrsSize) {
                    xmlSecInvalidSizeLessThanError("Attributes",
                                                   attrsSize, pos, NULL);
                    goto done;
                }
                attrs[pos].key = xmlStrdup(tmp);
                if(attrs[pos].key == NULL) {
                    xmlSecStrdupError(tmp, NULL);
                    goto done;
                }

                state = xmlSecGnuTLSDnParseState_BeforeString;
                p = tmp;
            }
            break;
        case xmlSecGnuTLSDnParseState_BeforeString:
            if(!XMLSEC_GNUTLS_IS_SPACE(ch)) {
                if(ch != '\"') {
                    state = xmlSecGnuTLSDnParseState_String;
                    slash = 0;
                    --dn; /* small hack, so we can look at the same char 
                           again with the correct state */
                } else {
                    state = xmlSecGnuTLSDnParseState_QuotedString;
                    slash = 0;
                }
            } else {
                /* just skip space */
            }
            break;
        case xmlSecGnuTLSDnParseState_String:
            if(slash == 1) {
                *(p++) = ch; /* we are sure we have enough buffer */
                slash = 0;
            } else if(ch == '\\') {
                slash = 1;
            } else if((ch == ',') || (ch == ';') || (ch == '\0')) {
                *(p) = '\0';
                /* remove spaces back */
                while((p > tmp) && (XMLSEC_GNUTLS_IS_SPACE(*(p - 1)))) {
                    *(--p) = '\0';
                }

                attrs[pos].value = xmlStrdup(tmp);
                if(attrs[pos].value == NULL) {
                    xmlSecStrdupError(tmp, NULL);
                    goto done;
                }
                state = xmlSecGnuTLSDnParseState_BeforeNameComponent;
                ++pos;
                p = tmp;
            } else {
                *(p++) = ch; /* we are sure we have enough buffer */
            }
            break;
        case xmlSecGnuTLSDnParseState_QuotedString:
            if(slash == 1) {
                *(p++) = ch; /* we are sure we have enough buffer */
                slash = 0;
            } else if(ch == '\\') {
                slash = 1;
            } else if(ch == '\"') {
                *(p) = '\0'; 
                /* don't remove spaces for quoted string */

                attrs[pos].value = xmlStrdup(tmp);
                if(attrs[pos].value == NULL) {
                    xmlSecStrdupError(tmp, NULL);
                    goto done;
                }
                state = xmlSecGnuTLSDnParseState_AfterQuotedString;
                ++pos;
                p = tmp;
            } else {
                *(p++) = ch; /* we are sure we have enough buffer */
            }
            break;
        case xmlSecGnuTLSDnParseState_AfterQuotedString:
            if(!XMLSEC_GNUTLS_IS_SPACE(ch)) {
                if((ch == ',') || (ch == ';') || (ch == '\0')) {
                    state = xmlSecGnuTLSDnParseState_BeforeNameComponent;
                } else {
                    xmlSecInvalidIntegerDataError("ch", ch, "space,',',';','\\0'", NULL);
                    goto done;
                }
            } else {
                /* just skip space */
            }
            break;
        }

        if(ch == '\0') {
            /* done */
            break;
        }
    }

    /* check end state */
    if(state != xmlSecGnuTLSDnParseState_BeforeNameComponent) {
        xmlSecInvalidIntegerDataError("state", state, "xmlSecGnuTLSDnParseState_BeforeNameComponent", NULL);
        goto done;
    }

    /* debug
    {
        xmlSecSize ii;
        for(ii = 0; ii < attrsSize; ++ii) {
            if(attrs[ii].key != NULL) {
                printf("DEBUG: attrs - %s=>%s\n", attrs[ii].key, attrs[ii].value);
            }
        }
    }
    */

    /* done */
    res = 0;

done:
    if(tmp != NULL) {
        xmlFree(tmp);
    }
    return(res);
}


#endif /* XMLSEC_NO_X509 */



