com.zotoh.crypto.CryptoUte.java Source code

Java tutorial

Introduction

Here is the source code for com.zotoh.crypto.CryptoUte.java

Source

/*??
 * COPYRIGHT (C) 2008-2009 CHERIMOIA LLC. ALL RIGHTS RESERVED.
 *
 * THIS IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR
 * MODIFY IT UNDER THE TERMS OF THE APACHE LICENSE, 
 * VERSION 2.0 (THE "LICENSE").
 *
 * THIS LIBRARY 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 LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING PERMISSIONS 
 * AND LIMITATIONS UNDER THE LICENSE.
 *
 * You should have received a copy of the Apache License
 * along with this distribution; if not, you may obtain a copy of the 
 * License at 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 ??*/

package com.zotoh.crypto;

import static com.zotoh.core.io.StreamUte.asStream;
import static com.zotoh.core.io.StreamUte.readStream;
import static com.zotoh.core.io.StreamUte.safeReset;
import static com.zotoh.core.util.CoreUte.asList;
import static com.zotoh.core.util.CoreUte.safeGetClzname;
import static com.zotoh.core.util.CoreUte.tstArgIsType;
import static com.zotoh.core.util.CoreUte.tstEStrArg;
import static com.zotoh.core.util.CoreUte.tstObjArg;
import static com.zotoh.core.util.LangUte.LT;
import static com.zotoh.core.util.LoggerFactory.getLogger;
import static com.zotoh.core.util.StrUte.isEmpty;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.MailcapCommandMap;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.security.auth.x500.X500Principal;

import org.apache.commons.mail.DefaultAuthenticator;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
import org.bouncycastle.asn1.smime.SMIMECapability;
import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSCompressedDataParser;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSProcessableFile;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.CMSTypedStream;
import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
import org.bouncycastle.cms.Recipient;
import org.bouncycastle.cms.RecipientInfoGenerator;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
import org.bouncycastle.mail.smime.SMIMEEnveloped;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
import org.bouncycastle.mail.smime.SMIMEException;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.mail.smime.SMIMESignedParser;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

import com.zotoh.core.io.StreamData;
import com.zotoh.core.mime.MimeUte;
import com.zotoh.core.util.Logger;
import com.zotoh.core.util.Tuple;

/**
 * Helper functions related to Crypto. 
 *
 * @author kenl
 *
 */
public enum CryptoUte {
    ;

    private static Logger ilog() {
        return _log = getLogger(CryptoUte.class);
    }

    private static Logger _log = ilog();

    public static Logger tlog() {
        return _log == null ? ilog() : _log;
    }

    static {
        inizMapCap();
    }

    /**
     * @param inp
     * @return
     * @throws MessagingException
     */
    public static MimeMessage newMimeMsg(InputStream inp) throws MessagingException {
        tstObjArg("inp-stream", inp);
        return new MimeMessage(newSession(), inp);
    }

    /**
     * @param user
     * @param pwd
     * @return
     */
    public static MimeMessage newMimeMsg(String user, String pwd) {
        return new MimeMessage(newSession(user, pwd));
    }

    /**
     * @return
     */
    public static MimeMessage newMimeMsg() {
        return new MimeMessage(newSession());
    }

    /**
     * @param user
     * @param pwd
     * @return
     */
    public static Session newSession(String user, String pwd) {
        return Session.getInstance(System.getProperties(),
                isEmpty(user) ? null : new DefaultAuthenticator(user, pwd));
    }

    /**
     * @return
     */
    public static Session newSession() {
        return newSession("", "");
    }

    /**
     * @param obj
     * @return
     * @throws Exception
     */
    public static boolean isSigned(Object obj) throws Exception {
        InputStream inp = MimeUte.maybeAsStream(obj);

        if (inp == null) {
            if (obj instanceof Multipart) {
                return MimeUte.isSigned(((Multipart) obj).getContentType());
            }
            throw new Exception("Invalid content: " + safeGetClzname(obj));
        }
        // else
        try {
            return isSigned(newMimeMsg(inp).getContentType());
        } finally {
            safeReset(inp);
        }
    }

    /**
     * @param obj
     * @return
     * @throws Exception
     */
    public static boolean isCompressed(Object obj) throws Exception {
        InputStream inp = MimeUte.maybeAsStream(obj);

        if (inp == null) {
            if (obj instanceof Multipart) {
                return MimeUte.isCompressed(((Multipart) obj).getContentType());
            }
            if (obj instanceof BodyPart) {
                return MimeUte.isCompressed(((BodyPart) obj).getContentType());
            }
            throw new Exception("Invalid content: " + safeGetClzname(obj));
        }
        // else
        try {
            return MimeUte.isCompressed(newMimeMsg(inp).getContentType());
        } finally {
            safeReset(inp);
        }
    }

    /**
     * @param obj
     * @return
     * @throws Exception
     */
    public static boolean isEncrypted(Object obj) throws Exception {
        InputStream inp = MimeUte.maybeAsStream(obj);

        if (inp == null) {
            if (obj instanceof Multipart) {
                return MimeUte.isEncrypted(((Multipart) obj).getContentType());
            }
            if (obj instanceof BodyPart) {
                return MimeUte.isEncrypted(((BodyPart) obj).getContentType());
            }
            throw new Exception("Invalid content: " + safeGetClzname(obj));
        }
        // else
        try {
            return MimeUte.isEncrypted(newMimeMsg(inp).getContentType());
        } finally {
            safeReset(inp);
        }
    }

    /**
     * @param cType
     * @param deFcs
     * @return
     */
    public static String getCharset(String cType, String deFcs) {
        String cs = getCharset(cType);
        return isEmpty(cs) ? MimeUtility.javaCharset(deFcs) : cs;
    }

    /**
     * @param cType
     * @return
     */
    public static String getCharset(String cType) {
        String rc = null;

        if (!isEmpty(cType))
            try {
                String charset = (new ContentType(cType)).getParameter("charset");
                rc = MimeUtility.javaCharset(charset);
            } catch (Exception e) {
                tlog().warn("", e);
            }

        return rc;
    }

    /**
     * @param key
     * @param certs
     * @param algo
     * @param mp
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertStoreException
     * @throws InvalidAlgorithmParameterException
     * @throws MessagingException
     * @throws CertificateEncodingException
     * @throws GeneralSecurityException
     */
    public static Multipart smimeDigSig(PrivateKey key, Certificate[] certs, SigningAlgo algo, Multipart mp)
            throws NoSuchAlgorithmException, CertStoreException, InvalidAlgorithmParameterException,
            MessagingException, CertificateEncodingException, GeneralSecurityException {

        tstObjArg("certificate(s)", certs);
        tstObjArg("private-key", key);
        tstObjArg("multipart", mp);
        tstObjArg("algo", algo);

        SMIMESignedGenerator gen = makeSignerGentor(key, certs, algo);
        MimeMessage mm = newMimeMsg();
        mm.setContent(mp);
        try {
            mp = gen.generate(mm, Crypto.getInstance().getProvider());
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }
        /*                
                MimeBodyPart dummy= new MimeBodyPart();
                dummy.setContent(mp);
                mp= gen.generate(dummy, PROV);
        */
        return mp;
    }

    /**
     * @param key
     * @param certs
     * @param algo
     * @param bp
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertStoreException
     * @throws InvalidAlgorithmParameterException
     * @throws CertificateEncodingException
     * @throws GeneralSecurityException
     */
    public static Multipart smimeDigSig(PrivateKey key, Certificate[] certs, SigningAlgo algo, BodyPart bp)
            throws NoSuchAlgorithmException, CertStoreException, InvalidAlgorithmParameterException,
            CertificateEncodingException, GeneralSecurityException {

        tstArgIsType("bodypart", bp, MimeBodyPart.class);
        tstObjArg("certificate(s)", certs);
        tstObjArg("private-key", key);
        tstObjArg("algo", algo);

        try {
            return makeSignerGentor(key, certs, algo).generate((MimeBodyPart) bp,
                    Crypto.getInstance().getProvider());
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }
    }

    /**
     * @param key
     * @param part
     * @return
     * @throws MessagingException
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static StreamData smimeDecrypt(PrivateKey key, BodyPart part)
            throws MessagingException, GeneralSecurityException, IOException {

        tstArgIsType("bodypart", part, MimeBodyPart.class);
        tstObjArg("private-key", key);
        CMSTypedStream cms = null;
        try {
            SMIMEEnveloped env = new SMIMEEnveloped((MimeBodyPart) part);
            cms = smime_decrypt(key, env);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }
        if (cms == null) {
            throw new GeneralSecurityException("Failed to decrypt: no matching decryption key");
        }
        //else
        return readStream(cms.getContentStream());
    }

    private static CMSTypedStream smime_decrypt(PrivateKey key, SMIMEEnveloped env)
            throws MessagingException, GeneralSecurityException, IOException {

        Recipient r = new JceKeyTransEnvelopedRecipient(key).setProvider(Crypto.getInstance().getProvider());

        for (Object obj : env.getRecipientInfos().getRecipients()) {
            try {
                return ((RecipientInformation) obj).getContentStream(r);
            } catch (CMSException e) {
            }
        }

        return null;
    }

    /**
     * @param keys
     * @param msg
     * @return
     * @throws GeneralSecurityException
     * @throws MessagingException
     * @throws IOException
     */
    public static StreamData smimeDecryptAsStream(PrivateKey[] keys, MimeMessage msg)
            throws GeneralSecurityException, MessagingException, IOException {

        tstObjArg("mime-message", msg);
        tstObjArg("private-key(s)", keys);

        CMSTypedStream cms = null;
        SMIMEEnveloped env;
        try {
            env = new SMIMEEnveloped(msg);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }

        for (int n = 0; n < keys.length; ++n) {
            cms = smime_decrypt(keys[n], env);
            if (cms != null) {
                break;
            }
            cms = null;
        }

        if (cms == null) {
            throw new GeneralSecurityException("Failed to decrypt: no matching decryption key");
        }
        //else
        return readStream(cms.getContentStream());
    }

    /**
     * @param mp
     * @return
     * @throws IOException
     * @throws MessagingException
     * @throws GeneralSecurityException
     */
    public static Object peekSmimeSignedContent(Multipart mp)
            throws IOException, MessagingException, GeneralSecurityException {

        tstArgIsType("mulitpart", mp, MimeMultipart.class);
        try {
            return new SMIMESignedParser((MimeMultipart) mp, getCharset(mp.getContentType(), "binary")).getContent()
                    .getContent();
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }
    }

    /**
     * @param mp
     * @param certs
     * @param cte
     * @return
     * @throws MessagingException
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws CertificateEncodingException
     */
    public static Tuple verifySmimeDigSig(Multipart mp, Certificate[] certs, String cte)
            throws MessagingException, GeneralSecurityException, IOException, CertificateEncodingException {

        tstArgIsType("multipart", mp, MimeMultipart.class);
        tstObjArg("certs", certs);

        MimeMultipart mmp = (MimeMultipart) mp;
        SMIMESigned sc;
        SignerInformation si;
        byte[] digest = null;

        try {
            sc = isEmpty(cte) ? new SMIMESigned(mmp) : new SMIMESigned(mmp, cte);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }

        Provider prov = Crypto.getInstance().getProvider();
        Store s = new JcaCertStore(asList(true, certs));
        Collection<?> c;
        JcaSimpleSignerInfoVerifierBuilder bdr;
        for (Object obj : sc.getSignerInfos().getSigners())
            try {
                si = (SignerInformation) obj;
                c = s.getMatches(si.getSID());
                for (Iterator<?> it = c.iterator(); it.hasNext();) {
                    bdr = new JcaSimpleSignerInfoVerifierBuilder().setProvider(prov);
                    if (si.verify(bdr.build((X509CertificateHolder) it.next()))) {
                        digest = si.getContentDigest();
                        break;
                    }
                }
                if (digest != null) {
                    break;
                }
            } catch (Exception e) {
            }

        if (digest == null) {
            throw new GeneralSecurityException("Failed to verify signature: no matching certificate");
        }
        //else
        return new Tuple(sc.getContentAsMimeMessage(newSession()).getContent(), digest);
    }

    /**
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static SecureRandom newRandom() throws NoSuchAlgorithmException {
        return Crypto.getInstance().getSecureRandom();
    }

    /**
     * @param mp
     * @param certs
     * @return
     * @throws MessagingException
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws CertificateEncodingException
     */
    public static Tuple verifySmimeDigSig(Multipart mp, Certificate[] certs)
            throws MessagingException, GeneralSecurityException, IOException, CertificateEncodingException {
        return verifySmimeDigSig(mp, certs, "");
    }

    /**
     * @param part
     * @return
     * @throws IOException
     * @throws MessagingException
     * @throws GeneralSecurityException
     */
    public static StreamData decompress(BodyPart part)
            throws IOException, MessagingException, GeneralSecurityException {
        return decompressAsStream(part == null ? null : part.getInputStream());
    }

    /**
     * @param inp
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static StreamData decompressAsStream(InputStream inp) throws GeneralSecurityException, IOException {
        CMSTypedStream cms = null;
        StreamData r = null;

        if (inp != null)
            try {
                cms = new CMSCompressedDataParser(inp).getContent(new ZlibExpanderProvider());
                if (cms == null) {
                    throw new GeneralSecurityException("Failed to decompress stream: corrupted content");
                }
                r = readStream(cms.getContentStream());
            } catch (CMSException e) {
                throw new GeneralSecurityException(e);
            }

        return r != null ? r : new StreamData();
    }

    /**
     * @param cert
     * @param algo
     * @param bp
     * @return
     * @throws NoSuchAlgorithmException
     * @throws CertificateEncodingException
     * @throws GeneralSecurityException
     */
    public static MimeBodyPart smimeEncrypt(Certificate cert, EncryptionAlgo algo, BodyPart bp)
            throws NoSuchAlgorithmException, CertificateEncodingException, GeneralSecurityException {

        tstArgIsType("body-part", bp, MimeBodyPart.class);
        tstObjArg("cert", cert);
        tstObjArg("algo", algo);

        SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
        Provider prov = Crypto.getInstance().getProvider();
        RecipientInfoGenerator g;
        try {
            g = new JceKeyTransRecipientInfoGenerator((X509Certificate) cert).setProvider(prov);
            gen.addRecipientInfoGenerator(g);
            return gen.generate((MimeBodyPart) bp,
                    new JceCMSContentEncryptorBuilder(algo.getOID()).setProvider(prov).build());
        } catch (OperatorCreationException e) {
            throw new GeneralSecurityException(e);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }

    }

    /**
     * @param cert
     * @param algo
     * @param msg
     * @return
     * @throws Exception
     */
    public static MimeBodyPart smimeEncrypt(Certificate cert, EncryptionAlgo algo, MimeMessage msg)
            throws Exception {

        tstObjArg("mime-message", msg);
        tstObjArg("cert", cert);
        tstObjArg("algo", algo);

        SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
        Provider prov = Crypto.getInstance().getProvider();
        RecipientInfoGenerator g = new JceKeyTransRecipientInfoGenerator((X509Certificate) cert).setProvider(prov);
        gen.addRecipientInfoGenerator(g);

        return gen.generate(msg, new JceCMSContentEncryptorBuilder(algo.getOID()).setProvider(prov).build());
    }

    /**
     * @param cert
     * @param algo
     * @param mp
     * @return
     * @throws MessagingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws GeneralSecurityException
     * @throws CertificateEncodingException
     */
    public static MimeBodyPart smimeEncrypt(Certificate cert, EncryptionAlgo algo, Multipart mp)
            throws MessagingException, NoSuchAlgorithmException, NoSuchProviderException, GeneralSecurityException,
            CertificateEncodingException {

        tstObjArg("multi-part", mp);
        tstObjArg("cert", cert);
        tstObjArg("algo", algo);

        try {

            SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
            Provider prov = Crypto.getInstance().getProvider();
            RecipientInfoGenerator g = new JceKeyTransRecipientInfoGenerator((X509Certificate) cert)
                    .setProvider(prov);
            gen.addRecipientInfoGenerator(g);
            MimeMessage mm = newMimeMsg();
            mm.setContent(mp);

            return gen.generate(mm, new JceCMSContentEncryptorBuilder(algo.getOID()).setProvider(prov).build());
        } catch (OperatorCreationException e) {
            throw new GeneralSecurityException(e);
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }
    }

    /**
     * @param contentType
     * @param msg
     * @return
     * @throws IOException
     * @throws MessagingException
     * @throws GeneralSecurityException
     */
    public static MimeBodyPart compressContent(String contentType, StreamData msg)
            throws IOException, MessagingException, GeneralSecurityException {

        tstEStrArg("content-type", contentType);
        tstObjArg("input-content", msg);

        SMIMECompressedGenerator gen = new SMIMECompressedGenerator();
        MimeBodyPart bp = new MimeBodyPart();
        SmDataSource ds;

        if (msg.isDiskFile()) {
            ds = new SmDataSource(msg.getFileRef(), contentType);
        } else {
            ds = new SmDataSource(msg.getBytes(), contentType);
        }

        bp.setDataHandler(new DataHandler(ds));
        try {
            return gen.generate(bp, SMIMECompressedGenerator.ZLIB);
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }
    }

    /**
     * @param msg
     * @return
     * @throws GeneralSecurityException
     */
    public static MimeBodyPart compressContent(MimeMessage msg) throws GeneralSecurityException {
        tstObjArg("mime-message", msg);
        try {
            return new SMIMECompressedGenerator().generate(msg, SMIMECompressedGenerator.ZLIB);
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }
    }

    /**
     * @param cType
     * @param cte
     * @param contentLoc
     * @param cid
     * @param msg
     * @return
     * @throws MessagingException
     * @throws IOException
     * @throws GeneralSecurityException
     */
    public static MimeBodyPart compressContent(String cType, String cte, String contentLoc, String cid,
            StreamData msg) throws MessagingException, IOException, GeneralSecurityException {

        tstEStrArg("content-type", cType);
        tstEStrArg("content-id", cid);
        tstObjArg("input-content", msg);

        SMIMECompressedGenerator gen = new SMIMECompressedGenerator();
        MimeBodyPart bp = new MimeBodyPart();
        SmDataSource ds;

        if (msg.isDiskFile()) {
            ds = new SmDataSource(msg.getFileRef(), cType);
        } else {
            ds = new SmDataSource(msg.getBytes(), cType);
        }

        if (!isEmpty(contentLoc)) {
            bp.setHeader("content-location", contentLoc);
        }

        try {
            bp.setHeader("content-id", cid);
            bp.setDataHandler(new DataHandler(ds));
            bp = gen.generate(bp, SMIMECompressedGenerator.ZLIB);
        } catch (SMIMEException e) {
            throw new GeneralSecurityException(e);
        }

        if (true) {
            int pos = cid.lastIndexOf(">");
            if (pos >= 0) {
                cid = cid.substring(0, pos) + "--z>";
            } else {
                cid = cid + "--z";
            }
        }

        if (!isEmpty(contentLoc)) {
            bp.setHeader("content-location", contentLoc);
        }
        bp.setHeader("content-id", cid);

        // always base64
        cte = "base64";

        if (!isEmpty(cte)) {
            bp.setHeader("content-transfer-encoding", cte);
        }

        return bp;
    }

    /**
     * @param key
     * @param certs
     * @param algo
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     * @throws CertStoreException
     * @throws IOException
     * @throws CertificateEncodingException
     * @throws GeneralSecurityException
     */
    public static byte[] pkcsDigSig(PrivateKey key, Certificate[] certs, SigningAlgo algo, StreamData data)
            throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, CertStoreException, IOException,
            CertificateEncodingException, GeneralSecurityException {

        tstObjArg("input-content", data);
        tstObjArg("private-key", key);

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        Provider prov = Crypto.getInstance().getProvider();
        List<Certificate> lst = asList(true, certs);
        CMSTypedData cms;
        X509Certificate cert = (X509Certificate) lst.get(0);

        try {
            ContentSigner cs = new JcaContentSignerBuilder(algo.toString()).setProvider(prov).build(key);

            JcaSignerInfoGeneratorBuilder bdr = new JcaSignerInfoGeneratorBuilder(
                    new JcaDigestCalculatorProviderBuilder().setProvider(prov).build());
            bdr.setDirectSignature(true);

            gen.addSignerInfoGenerator(bdr.build(cs, cert));
            gen.addCertificates(new JcaCertStore(lst));

            if (data.isDiskFile()) {
                cms = new CMSProcessableFile(data.getFileRef());
            } else {
                cms = new CMSProcessableByteArray(data.getBytes());
            }

            return gen.generate(cms, false).getEncoded();
        } catch (OperatorCreationException e) {
            throw new GeneralSecurityException(e);
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }

    }

    /**
     * @param cert
     * @param data
     * @param signature
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws CertificateEncodingException
     */
    public static byte[] verifyPkcsDigSig(Certificate cert, StreamData data, byte[] signature)
            throws GeneralSecurityException, IOException, CertificateEncodingException {

        tstObjArg("digital-signature", signature);
        tstObjArg("cert", cert);
        tstObjArg("input-content", data);

        Provider prov = Crypto.getInstance().getProvider();
        SignerInformation si;
        CMSProcessable cproc;
        CMSSignedData cms;
        byte[] digest;

        if (data.isDiskFile()) {
            cproc = new CMSProcessableFile(data.getFileRef());
        } else {
            cproc = new CMSProcessableByteArray(data.getBytes());
        }

        try {
            cms = new CMSSignedData(cproc, signature);
            digest = null;
        } catch (CMSException e) {
            throw new GeneralSecurityException(e);
        }

        List<Certificate> cl = LT();
        cl.add(cert);
        Store s = new JcaCertStore(cl);
        Collection<?> c;
        JcaSimpleSignerInfoVerifierBuilder bdr;

        for (Object obj : cms.getSignerInfos().getSigners())
            try {
                si = (SignerInformation) obj;
                c = s.getMatches(si.getSID());
                for (Iterator<?> it = c.iterator(); it.hasNext();) {
                    bdr = new JcaSimpleSignerInfoVerifierBuilder().setProvider(prov);
                    if (si.verify(bdr.build((X509CertificateHolder) it.next()))) {
                        digest = si.getContentDigest();
                        break;
                    }
                }
                if (digest != null) {
                    break;
                }
            } catch (Exception e) {
            }

        if (digest == null) {
            throw new GeneralSecurityException("Failed to decode signature: no matching certificate");
        }
        // else
        return digest;
    }

    /**
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String getSHA1FingerPrint(byte[] data) throws NoSuchAlgorithmException {
        return getFingerPrint(data, "SHA-1");
    }

    /**
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String getMD5FingerPrint(byte[] data) throws NoSuchAlgorithmException {
        return getFingerPrint(data, "MD5");
    }

    @SuppressWarnings("unused")
    private static String getSigningAlgoAsString(String algo) {
        if ("SHA-512".equals(algo))
            return SMIMESignedGenerator.DIGEST_SHA512;
        if ("SHA-1".equals(algo))
            return SMIMESignedGenerator.DIGEST_SHA1;
        if ("MD5".equals(algo))
            return SMIMESignedGenerator.DIGEST_MD5;
        throw new IllegalArgumentException("Unsupported signing algo:  " + algo);
    }

    /**
     * @param privateKeyBits
     * @param pwd
     * @return
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableEntryException
     * @throws KeyStoreException
     * @throws CertificateException
     * @throws IOException
     */
    public static Tuple getCertDesc(byte[] privateKeyBits, String pwd) throws NoSuchAlgorithmException,
            UnrecoverableEntryException, KeyStoreException, CertificateException, IOException {

        tstObjArg("private-key-bytes", privateKeyBits);
        tstEStrArg("password", pwd);

        return getCertDesc(bitsToKey(privateKeyBits, pwd).getCertificate());
    }

    /**
     * @param algo
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static MessageDigest newDigestInstance(String algo) throws NoSuchAlgorithmException {
        return MessageDigest.getInstance(algo);
    }

    /**
     * @param certBits
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws UnrecoverableEntryException
     * @throws IOException
     */
    public static Tuple getCertDesc(byte[] certBits) throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableEntryException, IOException {
        tstObjArg("cert-bytes", certBits);
        return getCertDesc(bitsToCert(certBits).getTrustedCertificate());
    }

    /**
     * @param cert
     * @return
     */
    public static Tuple getCertDesc(Certificate cert) {

        tstArgIsType("cert", cert, X509Certificate.class);

        X509Certificate x509 = (X509Certificate) cert;
        X500Principal issuer = x509.getIssuerX500Principal();
        X500Principal subj = x509.getSubjectX500Principal();
        Date vs = x509.getNotBefore();
        Date ve = x509.getNotAfter();

        return new Tuple(subj, issuer, vs, ve);
    }

    /**
     * @param os
     */
    public static void dbgProviderProps(PrintStream os) {
        try {
            Crypto.getInstance().getProvider().list(os);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    /**
     * @param privateKeyBits
     * @param pwd
     * @return
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableEntryException
     * @throws KeyStoreException
     * @throws CertificateException
     * @throws IOException
     */
    public static PrivateKeyEntry bitsToKey(byte[] privateKeyBits, String pwd) throws NoSuchAlgorithmException,
            UnrecoverableEntryException, KeyStoreException, CertificateException, IOException {
        tstObjArg("privatekey-bits", privateKeyBits);
        tstEStrArg("password", pwd);
        CryptoStore cs = new PKCSStore();
        cs.init("xxx");
        cs.addKeyEntity(asStream(privateKeyBits), pwd);
        return cs.getKeyEntity(cs.getKeyAliases().iterator().next(), pwd);
    }

    /**
     * @param certBits
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws IOException
     * @throws UnrecoverableEntryException
     */
    public static TrustedCertificateEntry bitsToCert(byte[] certBits) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableEntryException {
        tstObjArg("cert-bits", certBits);
        CryptoStore cs = new PKCSStore();
        cs.init("xxx");
        cs.addCertEntity(asStream(certBits));
        return cs.getCertEntity(cs.getCertAliases().iterator().next());
    }

    /**
     * @param bits
     * @param pwd
     * @return
     * @throws Exception
     */
    public static boolean tstPKeyValid(byte[] bits, String pwd) throws Exception {
        tstObjArg("privatekey-bits", bits);
        tstEStrArg("password", pwd);

        PrivateKeyEntry k = bitsToKey(bits, pwd);
        X509Certificate c = null;

        if (k != null) {
            c = (X509Certificate) k.getCertificate();
        }

        return tstCertValid(c);
    }

    /**
     * @param bits
     * @return
     * @throws Exception
     */
    public static boolean tstCertValid(byte[] bits) throws Exception {

        tstObjArg("cert-bits", bits);

        TrustedCertificateEntry t = bitsToCert(bits);
        X509Certificate c = null;

        if (t != null) {
            c = (X509Certificate) t.getTrustedCertificate();
        }

        return tstCertValid(c);
    }

    /**
     * @param x
     * @return
     */
    public static boolean tstCertValid(X509Certificate x) {

        tstObjArg("cert", x);

        boolean ok = false;
        try {
            x.checkValidity(new Date());
            ok = true;
        } catch (Exception e) {
        }

        return ok;
    }

    /**
     * @param certs
     * @return
     */
    public static Certificate[] toCerts(TrustedCertificateEntry[] certs) {
        Certificate[] cs = new Certificate[0];
        if (certs != null) {
            for (int i = 0; i < certs.length; ++i) {
                cs[i] = certs[i].getTrustedCertificate();
            }
        }
        return cs;
    }

    /**
     * @param keys
     * @return
     */
    public static PrivateKey[] toPKeys(PrivateKeyEntry[] keys) {
        PrivateKey[] ks = new PrivateKey[0];
        if (keys != null) {
            for (int i = 0; i < keys.length; ++i) {
                ks[i] = keys[i].getPrivateKey();
            }
        }
        return ks;
    }

    private static SMIMESignedGenerator makeSignerGentor(PrivateKey key, Certificate[] certs, SigningAlgo algo)
            throws CertStoreException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            GeneralSecurityException, CertificateEncodingException {

        SMIMESignedGenerator gen = new SMIMESignedGenerator("base64");
        List<Certificate> lst = asList(true, certs);

        ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
        SMIMECapabilityVector caps = new SMIMECapabilityVector();

        caps.addCapability(SMIMECapability.dES_EDE3_CBC);
        caps.addCapability(SMIMECapability.rC2_CBC, 128);
        caps.addCapability(SMIMECapability.dES_CBC);

        signedAttrs.add(new SMIMECapabilitiesAttribute(caps));

        X509Certificate x0 = (X509Certificate) certs[0];
        X509Certificate issuer = x0;
        X500Principal issuerDN;

        if (certs.length > 1) {
            issuer = (X509Certificate) certs[1];
        }

        issuerDN = issuer.getSubjectX500Principal();
        x0 = (X509Certificate) certs[0];

        //
        // add an encryption key preference for encrypted responses -
        // normally this would be different from the signing certificate...
        //

        IssuerAndSerialNumber issAndSer = new IssuerAndSerialNumber(X500Name.getInstance(issuerDN.getEncoded()),
                x0.getSerialNumber());
        Provider prov = Crypto.getInstance().getProvider();

        signedAttrs.add(new SMIMEEncryptionKeyPreferenceAttribute(issAndSer));

        try {
            JcaSignerInfoGeneratorBuilder bdr = new JcaSignerInfoGeneratorBuilder(
                    new JcaDigestCalculatorProviderBuilder().setProvider(prov).build());
            bdr.setDirectSignature(true);

            ContentSigner cs = new JcaContentSignerBuilder(algo.toString()).setProvider(prov).build(key);

            bdr.setSignedAttributeGenerator(
                    new DefaultSignedAttributeTableGenerator(new AttributeTable(signedAttrs)));

            gen.addSignerInfoGenerator(bdr.build(cs, x0));
            gen.addCertificates(new JcaCertStore(lst));

            return gen;
        } catch (OperatorCreationException e) {
            throw new GeneralSecurityException(e);
        }
    }

    private static String getFingerPrint(byte[] data, String algo) throws NoSuchAlgorithmException {

        MessageDigest md5 = MessageDigest.getInstance(algo);
        StringBuilder ret = new StringBuilder(256);
        byte[] hash = md5.digest(data);
        int tail = hash.length - 1;
        String n;

        for (int i = 0; i < hash.length; ++i) {
            n = Integer.toString((hash[i] & 0xff), 16).toUpperCase();
            ret.append(n.length() == 1 ? ("0" + n) : n).append(i != tail ? ":" : "");
        }

        return ret.toString();
    }

    private static void inizMapCap() {
        MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();

        mc.addMailcap("application/pkcs7-signature;; "
                + "x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");

        mc.addMailcap("application/pkcs7-mime;; "
                + "x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");

        mc.addMailcap("application/x-pkcs7-signature;; " + ""
                + "x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");

        mc.addMailcap("application/x-pkcs7-mime;; "
                + "x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");

        mc.addMailcap("multipart/signed;; "
                + "x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");

    }

}