// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package org.mozilla.jss.netscape.security.util;

import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.text.DateFormat;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;

import org.mozilla.jss.asn1.ASN1Util;
import org.mozilla.jss.asn1.SET;
import org.mozilla.jss.netscape.security.x509.CertificateExtensions;
import org.mozilla.jss.netscape.security.x509.CertificateX509Key;
import org.mozilla.jss.netscape.security.x509.Extension;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;
import org.mozilla.jss.netscape.security.x509.X509CertInfo;
import org.mozilla.jss.netscape.security.x509.X509Key;
import org.mozilla.jss.pkcs7.ContentInfo;
import org.mozilla.jss.pkcs7.SignedData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class will display the certificate content in predefined
 * format.
 *
 * @author Jack Pan-Chen
 * @version $Revision$, $Date$
 */
public class CertPrettyPrint {

    /*==========================================================
     * constants
     *==========================================================*/
    private static final String CUSTOM_LOCALE = "Custom";

    private static final Logger logger = LoggerFactory.getLogger(CertPrettyPrint.class);

    /*==========================================================
     * variables
     *==========================================================*/
    private X509CertImpl mX509Cert = null;
    private PrettyPrintFormat pp = null;
    private byte[] mCert_b = null;

    /*==========================================================
     * constructors
     *==========================================================*/

    public CertPrettyPrint(Certificate cert) {
        if (cert instanceof X509CertImpl)
            mX509Cert = (X509CertImpl) cert;

        pp = new PrettyPrintFormat(":");
    }

    public CertPrettyPrint(byte[] certb) {
        mCert_b = certb;
        pp = new PrettyPrintFormat(":");
    }

    /*==========================================================
     * public methods
     *==========================================================*/

    /**
     * This method return string representation of the certificate
     * in predefined format using specified client local. I18N Support.
     *
     * @param clientLocale Locale to be used for localization
     * @return string representation of the certificate
     */
    public String toString(Locale clientLocale) {

        if (mX509Cert != null)
            return X509toString(clientLocale);
        else if (mCert_b != null)
            return pkcs7toString(clientLocale);
        else
            return null;
    }

    public String pkcs7toString(Locale clientLocale) {
        StringBuffer content=new StringBuffer();

        try {
            mX509Cert = new X509CertImpl(mCert_b);
            return toString(clientLocale);
        } catch (Exception e) {
        }

        ContentInfo ci = null;
        try {
            ci = (ContentInfo)
                    ASN1Util.decode(ContentInfo.getTemplate(), mCert_b);
        } catch (Exception e) {
            return "";
        }

        if (ci.getContentType().equals(ContentInfo.SIGNED_DATA)) {
            SignedData sd = null;
            try {
                sd = (SignedData) ci.getInterpretedContent();
            } catch (Exception e) {
                return "";
            }

            if (sd.hasCertificates()) {
                SET certs = sd.getCertificates();

                for (int i = 0; i < certs.size(); i++) {
                    org.mozilla.jss.pkix.cert.Certificate cert =
                            (org.mozilla.jss.pkix.cert.Certificate) certs.elementAt(i);
                    X509CertImpl certImpl = null;
                    try {
                        certImpl = new X509CertImpl(
                                ASN1Util.encode(cert));
                    } catch (Exception e) {
                    }

                    CertPrettyPrint print = new CertPrettyPrint(certImpl);
                    content.append(print.toString(Locale.getDefault()));
                    content.append("\n");
                }

                return content.toString();
            }
        }

        return content.toString();
    }

    public String stripCertBrackets(String s) {
        if (s == null) {
            return s;
        }

        if ((s.startsWith(Cert.HEADER)) &&
                (s.endsWith(Cert.FOOTER))) {
            return (s.substring(27, (s.length() - 25)));
        }

        // To support Thawte's header and footer
        if ((s.startsWith("-----BEGIN PKCS #7 SIGNED DATA-----")) &&
                (s.endsWith("-----END PKCS #7 SIGNED DATA-----"))) {
            return (s.substring(35, (s.length() - 33)));
        }

        return s;
    }

    public String normalizeCertStr(String s) {
        StringBuffer val = new StringBuffer();

        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '\n') {
                continue;
            } else if (s.charAt(i) == '\r') {
                continue;
            } else if (s.charAt(i) == '"') {
                continue;
            } else if (s.charAt(i) == ' ') {
                continue;
            }
            val.append(s.charAt(i));
        }
        return val.toString();
    }

    public String X509toString(Locale clientLocale) {

        //get I18N resources
        ResourceBundle resource = ResourceBundle.getBundle(
                PrettyPrintResources.class.getName());
        DateFormat dateFormater = DateFormat.getDateTimeInstance(
                DateFormat.FULL, DateFormat.FULL, clientLocale);
        //get timezone and timezone ID
        String tz = " ";
        String tzid = " ";

        StringBuffer sb = new StringBuffer();

        try {
            X509CertInfo info = (X509CertInfo) mX509Cert.get(
                    X509CertImpl.NAME + "." + X509CertImpl.INFO);
            String serial2 = mX509Cert.getSerialNumber().toString(16).toUpperCase();

            //get correct instance of key
            PublicKey pKey = mX509Cert.getPublicKey();
            X509Key key = null;

            if (pKey instanceof CertificateX509Key) {
                CertificateX509Key certKey = (CertificateX509Key) pKey;

                key = (X509Key) certKey.get(CertificateX509Key.KEY);
            }
            if (pKey instanceof X509Key) {
                key = (X509Key) pKey;
            }

            //take care of spki
            sb.append(pp.indent(4) + resource.getString(
                    PrettyPrintResources.TOKEN_CERTIFICATE) + "\n");
            sb.append(pp.indent(8) + resource.getString(
                    PrettyPrintResources.TOKEN_DATA) + "\n");
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_VERSION) + " v");
            sb.append((mX509Cert.getVersion() + 1) + "\n");
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_SERIAL) + "0x" + serial2 + "\n");
            //XXX I18N Algorithm Name ?
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_SIGALG) + mX509Cert.getSigAlgName() +
                    " - " + mX509Cert.getSigAlgOID() + "\n");
            //XXX I18N IssuerDN ?
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_ISSUER) +
                    mX509Cert.getIssuerX500Principal() + "\n");
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_VALIDITY) + "\n");
            String notBefore = dateFormater.format(mX509Cert.getNotBefore());
            String notAfter = dateFormater.format(mX509Cert.getNotAfter());

            //get timezone and timezone ID
            if (TimeZone.getDefault() != null) {
                tz = TimeZone.getDefault().getDisplayName(
                            TimeZone.getDefault().inDaylightTime(
                                    mX509Cert.getNotBefore()),
                            TimeZone.SHORT,
                            clientLocale);
                tzid = TimeZone.getDefault().getID();
            }
            // Specify notBefore
            if (tz.equals(tzid) || tzid.equals(CUSTOM_LOCALE)) {
                // Do NOT append timezone ID
                sb.append(pp.indent(16)
                        + resource.getString(
                                PrettyPrintResources.TOKEN_NOT_BEFORE)
                        + notBefore
                        + "\n");
            } else {
                // Append timezone ID
                sb.append(pp.indent(16)
                        + resource.getString(
                                PrettyPrintResources.TOKEN_NOT_BEFORE)
                        + notBefore
                        + " " + tzid + "\n");
            }
            // re-get timezone (just in case it is different . . .)
            if (TimeZone.getDefault() != null) {
                tz = TimeZone.getDefault().getDisplayName(
                            TimeZone.getDefault().inDaylightTime(
                                    mX509Cert.getNotAfter()),
                            TimeZone.SHORT,
                            clientLocale);
            }
            // Specify notAfter
            if (tz.equals(tzid) || tzid.equals(CUSTOM_LOCALE)) {
                // Do NOT append timezone ID
                sb.append(pp.indent(16)
                        + resource.getString(
                                PrettyPrintResources.TOKEN_NOT_AFTER)
                        + notAfter
                        + "\n");
            } else {
                // Append timezone ID
                sb.append(pp.indent(16)
                        + resource.getString(
                                PrettyPrintResources.TOKEN_NOT_AFTER)
                        + notAfter
                        + " " + tzid + "\n");
            }
            //XXX I18N SubjectDN ?
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_SUBJECT) +
                    mX509Cert.getSubjectX500Principal() + "\n");
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_SPKI) + "\n");

            PubKeyPrettyPrint pkpp = new PubKeyPrettyPrint(key);

            sb.append(pkpp.toString(clientLocale, 16, 16));

            //take care of extensions
            CertificateExtensions extensions = (CertificateExtensions)
                    info.get(X509CertInfo.EXTENSIONS);

            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_EXTENSIONS) + "\n");
            if (extensions != null)
                for (int i = 0; i < extensions.size(); i++) {
                    Extension ext = extensions.elementAt(i);
                    ExtPrettyPrint extpp = new ExtPrettyPrint(ext, 16);

                    sb.append(extpp.toString());
                }

            //take care of signature
            sb.append(pp.indent(8) + resource.getString(
                    PrettyPrintResources.TOKEN_SIGNATURE) + "\n");
            //XXX I18N Algorithm Name ?
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_ALGORITHM) +
                    mX509Cert.getSigAlgName() + " - " + mX509Cert.getSigAlgOID() + "\n");
            sb.append(pp.indent(12) + resource.getString(
                    PrettyPrintResources.TOKEN_SIGNATURE) + "\n");
            sb.append(pp.toHexString(mX509Cert.getSignature(), 16, 16));

            // fingerprints
            String[] hashes = new String[] { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-512" };
            StringBuffer certFingerprints = new StringBuffer();

            sb.append(pp.indent(8) + "FingerPrint\n");
            for (int i = 0; i < hashes.length; i++) {
                MessageDigest md = MessageDigest.getInstance(hashes[i]);

                md.update(mX509Cert.getEncoded());
                certFingerprints.append(pp.indent(12) + hashes[i] + ":\n" +
                        pp.toHexString(md.digest(), 16, 16));
            }

            sb.append(certFingerprints.toString());
        } catch (Exception e) {
            logger.error("Problem converting to string", e);
        }

        return sb.toString();
    }

}
