// --- 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 com.netscape.cms.servlet.common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Locale;

import netscape.security.x509.X500Name;
import netscape.security.x509.X509CertImpl;

import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.asn1.INTEGER;
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
import org.mozilla.jss.asn1.OCTET_STRING;
import org.mozilla.jss.asn1.SEQUENCE;
import org.mozilla.jss.asn1.SET;
import org.mozilla.jss.crypto.DigestAlgorithm;
import org.mozilla.jss.crypto.SignatureAlgorithm;
import org.mozilla.jss.pkix.cmc.CMCStatusInfoV2;
import org.mozilla.jss.pkix.cmc.OtherInfo;
import org.mozilla.jss.pkix.cmc.PendInfo;
import org.mozilla.jss.pkix.cmc.ResponseBody;
import org.mozilla.jss.pkix.cmc.TaggedAttribute;
import org.mozilla.jss.pkix.cms.ContentInfo;
import org.mozilla.jss.pkix.cms.EncapsulatedContentInfo;
import org.mozilla.jss.pkix.cms.IssuerAndSerialNumber;
import org.mozilla.jss.pkix.cms.SignedData;
import org.mozilla.jss.pkix.cms.SignerIdentifier;
import org.mozilla.jss.pkix.cms.SignerInfo;
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
import org.mozilla.jss.pkix.primitive.Name;

import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.authority.IAuthority;
import com.netscape.certsrv.base.IArgBlock;
import com.netscape.certsrv.ca.ICertificateAuthority;
import com.netscape.certsrv.ra.IRegistrationAuthority;
import com.netscape.certsrv.request.IRequest;
import com.netscape.certsrv.request.RequestId;
import com.netscape.cmsutil.util.Utils;

/**
 * default Pending template filler
 *
 * @version $Revision$, $Date$
 */
public class GenPendingTemplateFiller implements ICMSTemplateFiller {
    public static String FULL_RESPONSE = "cmcFullEnrollmentResponse";

    public GenPendingTemplateFiller() {
    }

    /**
     * fill error details and description if any.
     *
     * @param cmsReq CMS Request
     * @param authority this authority
     * @param locale locale of template.
     * @param e unexpected exception e. ignored.
     */
    public CMSTemplateParams getTemplateParams(
            CMSRequest cmsReq, IAuthority authority, Locale locale, Exception e) {
        IArgBlock fixed = CMS.createArgBlock();
        CMSTemplateParams params = new CMSTemplateParams(null, fixed);

        if (cmsReq == null) {
            return null;
        }

        // request status if any.
        Integer sts = cmsReq.getStatus();

        if (sts != null)
            fixed.set(ICMSTemplateFiller.REQUEST_STATUS, sts.toString());

        // request id
        IRequest req = cmsReq.getIRequest();

        if (req != null) {
            RequestId reqId = req.getRequestId();

            fixed.set(ICMSTemplateFiller.REQUEST_ID, reqId);
            // set pendInfo, CMCStatusInfoV2
            IArgBlock httpParams = cmsReq.getHttpParams();

            if (doFullResponse(httpParams)) {
                SEQUENCE controlSeq = new SEQUENCE();
                int bpid = 1;
                PendInfo pendInfo = new PendInfo(reqId.toString(), new
                        Date());
                OtherInfo otherInfo = new
                        OtherInfo(OtherInfo.PEND, null, pendInfo);
                SEQUENCE bpids = new SEQUENCE();
                String[] reqIdArray =
                        req.getExtDataInStringArray(IRequest.CMC_REQIDS);

                for (int i = 0; i < reqIdArray.length; i++) {
                    bpids.addElement(new INTEGER(reqIdArray[i]));
                }
                CMCStatusInfoV2 cmcStatusInfo = new
                        CMCStatusInfoV2(CMCStatusInfoV2.PENDING, bpids,
                                (String) null, otherInfo);
                TaggedAttribute ta = new TaggedAttribute(new
                        INTEGER(bpid++),
                        OBJECT_IDENTIFIER.id_cmc_statusInfoV2,
                        cmcStatusInfo);

                controlSeq.addElement(ta);
                // copy transactionID, senderNonce,
                // create recipientNonce
                // create responseInfo if regInfo exist
                String[] transIds =
                        req.getExtDataInStringArray(IRequest.CMC_TRANSID);
                SET ids = new SET();

                for (int i = 0; i < transIds.length; i++) {
                    ids.addElement(new INTEGER(transIds[i]));
                }
                ta = new TaggedAttribute(new
                            INTEGER(bpid++),
                            OBJECT_IDENTIFIER.id_cmc_transactionId,
                            ids);
                controlSeq.addElement(ta);

                String[] senderNonce = req.getExtDataInStringArray(IRequest.CMC_SENDERNONCE);
                SET nonces = new SET();

                for (int i = 0; i < senderNonce.length; i++) {
                    nonces.addElement(new OCTET_STRING(senderNonce[i].getBytes()));
                }
                ta = new TaggedAttribute(new
                            INTEGER(bpid++),
                            OBJECT_IDENTIFIER.id_cmc_recipientNonce,
                            nonces);
                controlSeq.addElement(ta);
                req.setExtData(IRequest.CMC_RECIPIENTNONCE, senderNonce);

                Date date = CMS.getCurrentDate();
                String salt = "lala123" + date.toString();
                byte[] dig;

                try {
                    MessageDigest SHA2Digest = MessageDigest.getInstance("SHA256");

                    dig = SHA2Digest.digest(salt.getBytes());
                } catch (NoSuchAlgorithmException ex) {
                    dig = salt.getBytes();
                }
                String b64E = Utils.base64encode(dig, true);
                String[] newNonce = { b64E };

                ta = new TaggedAttribute(new
                            INTEGER(bpid++),
                            OBJECT_IDENTIFIER.id_cmc_senderNonce,
                            new OCTET_STRING(newNonce[0].getBytes()));
                controlSeq.addElement(ta);
                req.setExtData(IRequest.CMC_SENDERNONCE, newNonce);

                ResponseBody rb = new ResponseBody(controlSeq, new
                        SEQUENCE(), new
                        SEQUENCE());
                EncapsulatedContentInfo ci = new
                        EncapsulatedContentInfo(OBJECT_IDENTIFIER.id_cct_PKIResponse,
                                rb);
                org.mozilla.jss.crypto.X509Certificate x509cert = null;

                if (authority instanceof ICertificateAuthority) {
                    x509cert = ((ICertificateAuthority) authority).getCaX509Cert();
                } else if (authority instanceof IRegistrationAuthority) {
                    x509cert = ((IRegistrationAuthority) authority).getRACert();
                }
                if (x509cert == null)
                    return params;
                try {
                    X509CertImpl cert = new X509CertImpl(x509cert.getEncoded());
                    ByteArrayInputStream issuer1 = new
                            ByteArrayInputStream(((X500Name) cert.getIssuerDN()).getEncoded());
                    Name issuer = (Name) Name.getTemplate().decode(issuer1);
                    IssuerAndSerialNumber ias = new
                            IssuerAndSerialNumber(issuer, new INTEGER(cert.getSerialNumber().toString()));
                    SignerIdentifier si = new
                            SignerIdentifier(SignerIdentifier.ISSUER_AND_SERIALNUMBER, ias, null);

                    DigestAlgorithm digestAlg = null;
                    SignatureAlgorithm signAlg = null;
                    org.mozilla.jss.crypto.PrivateKey privKey = CryptoManager.getInstance().findPrivKeyByCert(x509cert);
                    org.mozilla.jss.crypto.PrivateKey.Type keyType = privKey.getType();

                    if (keyType.equals(org.mozilla.jss.crypto.PrivateKey.RSA)) {
                        signAlg = SignatureAlgorithm.RSASignatureWithSHA256Digest;
                    } else if (keyType.equals(org.mozilla.jss.crypto.PrivateKey.EC)) {
                        signAlg = SignatureAlgorithm.ECSignatureWithSHA256Digest;
                    } else {
                        CMS.debug("GenPendingTemplateFiller::getTemplateParams() - "
                                 + "keyType " + keyType.toString()
                                 + " is unsupported!");
                        return null;
                    }

                    MessageDigest SHADigest = null;
                    byte[] digest = null;

                    try {
                        SHADigest = MessageDigest.getInstance("SHA256");
                        digestAlg = DigestAlgorithm.SHA256;

                        ByteArrayOutputStream ostream = new ByteArrayOutputStream();

                        rb.encode(ostream);
                        digest = SHADigest.digest(ostream.toByteArray());
                    } catch (NoSuchAlgorithmException ex) {
                        //log("digest fail");
                    }

                    SignerInfo signInfo = new
                            SignerInfo(si, null, null,
                                    OBJECT_IDENTIFIER.id_cct_PKIResponse,
                                    digest, signAlg,
                                    privKey);
                    SET signInfos = new SET();

                    signInfos.addElement(signInfo);

                    SET digestAlgs = new SET();

                    if (digestAlg != null) {
                        AlgorithmIdentifier ai = new
                                AlgorithmIdentifier(digestAlg.toOID(),
                                        null);

                        digestAlgs.addElement(ai);
                    }

                    SignedData fResponse = new
                            SignedData(digestAlgs, ci,
                                    null, null, signInfos);
                    ContentInfo fullResponse = new
                            ContentInfo(ContentInfo.SIGNED_DATA, fResponse);
                    ByteArrayOutputStream ostream = new
                            ByteArrayOutputStream();

                    fullResponse.encode(ostream);
                    byte[] fr = ostream.toByteArray();

                    fixed.set(FULL_RESPONSE, Utils.base64encode(fr, true));
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        // this authority
        if (authority != null)
            fixed.set(ICMSTemplateFiller.AUTHORITY,
                    authority.getOfficialName());
        return params;
    }

    /**
     * handy routine to check if client want full enrollment response
     */
    public static boolean doFullResponse(IArgBlock httpParams) {
        if (httpParams.getValueAsBoolean("fullResponse", false))
            return true;
        else
            return false;
    }
}
