com.infinities.keystone4j.utils.Cms.java Source code

Java tutorial

Introduction

Here is the source code for com.infinities.keystone4j.utils.Cms.java

Source

/*******************************************************************************
 * # Copyright 2015 InfinitiesSoft Solutions Inc.
 * #
 * # Licensed under the Apache License, Version 2.0 (the "License"); you may
 * # not use this file except in compliance with the License. You may obtain
 * # a copy of the License at
 * #
 * #      http://www.apache.org/licenses/LICENSE-2.0
 * #
 * # Unless required by applicable law or agreed to in writing, software
 * # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * # License for the specific language governing permissions and limitations
 * # under the License.
 *******************************************************************************/
package com.infinities.keystone4j.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.codec.DecoderException;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
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 org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.infinities.keystone4j.ssl.Base64Verifier;
import com.infinities.keystone4j.ssl.CertificateVerificationException;
import com.infinities.keystone4j.ssl.CertificateVerifier;

public enum Cms {
    Instance;

    public enum Algorithm {
        md5, sha1, sha256, sha512;
    }

    private final static Logger logger = LoggerFactory.getLogger(Cms.class);
    // private final static String ALGORITHM = "RSA/ECB/PKCS1Padding";
    public final static String PKI_ASN1_PREFIX = "MII";
    public final static String PKIZ_PREFIX = "PKIZ";
    public final static String PKIZ_CMS_FORM = "DER";
    public final static String PKI_ASN1_FORM = "PEM";
    private final static String BEGIN_CMS = "-----BEGIN CMS-----";
    private final static String END_CMS = "-----END CMS-----";
    // private Certificate cert;
    // private final Signature rsaSigner;
    private final static ConcurrentHashMap<String, X509Certificate> certMap = new ConcurrentHashMap<String, X509Certificate>();
    private final static ConcurrentHashMap<String, PrivateKey> keyMap = new ConcurrentHashMap<String, PrivateKey>();

    private Cms() {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // readPrivateKey();
        // readPublicKey();
    }

    // certFile public_key & keyFile private_key
    public String signToken(String text, String signingCertFileName, String signingKeyFile)
            throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException,
            OperatorCreationException, IOException, CMSException, CertStoreException {
        String output = cmsSignData(text, signingCertFileName, signingKeyFile, null);
        return toToken(output);
    }

    private String toToken(String output) {
        output = output.replace('/', '-');
        output = output.replace(BEGIN_CMS, "");
        output = output.replace(END_CMS, "");
        output = output.replace("\n", "");
        return output;
    }

    public String signText(String text, String signingCertFileName, String signingKeyFile)
            throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException,
            OperatorCreationException, IOException, CMSException, CertStoreException {
        return cmsSignData(text, signingCertFileName, signingKeyFile, null);
    }

    private String cmsSignData(String data, String signingCertFileName, String signingKeyFile, String outform)
            throws CertificateException, IOException, NoSuchAlgorithmException, NoSuchProviderException,
            CMSException, OperatorCreationException, CertStoreException {
        if (Strings.isNullOrEmpty(outform)) {
            outform = PKI_ASN1_FORM;
        }

        Security.addProvider(new BouncyCastleProvider());
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        logger.debug("signingCertFile: {}, caFile:{}", new Object[] { signingCertFileName, signingKeyFile });
        X509Certificate signercert = generateCertificate(signingCertFileName);
        // X509Certificate cacert = generateCertificate(caFileName);
        PrivateKey key = generatePrivateKey(signingKeyFile);
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(key);
        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, signercert));
        List<X509Certificate> certList = new ArrayList<X509Certificate>();
        certList.add(signercert);
        Store certs = new JcaCertStore(certList);
        gen.addCertificates(certs);

        CMSProcessableByteArray b = new CMSProcessableByteArray(data.getBytes());
        CMSSignedData signed = gen.generate(b, true);
        String signedContent = new String(DERtoPEM(signed.getContentInfo().getDEREncoded(), "CMS"));
        return signedContent;
    }

    @SuppressWarnings("rawtypes")
    public String verifySignature(byte[] sigbytes, String signingCertFileName, String caFileName)
            throws CMSException, CertificateException, OperatorCreationException, NoSuchAlgorithmException,
            NoSuchProviderException, CertPathBuilderException, InvalidAlgorithmParameterException, IOException,
            CertificateVerificationException {
        logger.debug("signingCertFile: {}, caFile:{}", new Object[] { signingCertFileName, caFileName });
        Security.addProvider(new BouncyCastleProvider());
        X509Certificate signercert = generateCertificate(signingCertFileName);
        X509Certificate cacert = generateCertificate(caFileName);
        Set<X509Certificate> additionalCerts = new HashSet<X509Certificate>();
        additionalCerts.add(cacert);

        CertificateVerifier.verifyCertificate(signercert, additionalCerts, true); // .validateKeyChain(signercert,
        // certs);
        if (Base64Verifier.isBase64(sigbytes)) {
            try {
                sigbytes = Base64.decode(sigbytes);
                logger.debug("Signature file is BASE64 encoded");
            } catch (Exception ioe) {
                logger.warn("Problem decoding from b64", ioe);
            }
        }

        // sigbytes = Base64.decode(sigbytes);

        // --- Use Bouncy Castle provider to verify included-content CSM/PKCS#7
        // signature ---
        ASN1InputStream in = null;
        try {
            logger.debug("sigbytes size: {}", sigbytes.length);
            in = new ASN1InputStream(new ByteArrayInputStream(sigbytes), Integer.MAX_VALUE);

            CMSSignedData s = new CMSSignedData(ContentInfo.getInstance(in.readObject()));
            Store store = s.getCertificates();
            SignerInformationStore signers = s.getSignerInfos();
            Collection c = signers.getSigners();
            Iterator it = c.iterator();
            int verified = 0;

            while (it.hasNext()) {
                X509Certificate cert = null;
                SignerInformation signer = (SignerInformation) it.next();
                Collection certCollection = store.getMatches(signer.getSID());
                if (certCollection.isEmpty() && signercert == null)
                    continue;
                else if (signercert != null) // use a signer cert file for
                    // verification, if it was
                    // provided
                    cert = signercert;
                else { // use the certificates included in the signature for
                       // verification
                    Iterator certIt = certCollection.iterator();
                    cert = (X509Certificate) certIt.next();
                }

                // if (signer.verify(new
                // JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
                // verified++;
            }

            if (verified == 0) {
                logger.warn(" No signers' signatures could be verified !");
            } else if (signercert != null)
                logger.info("Verified a signature using signer certificate file  {}", signingCertFileName);
            else
                logger.info("Verified a signature using a certificate in the signature data");

            CMSProcessableByteArray cpb = (CMSProcessableByteArray) s.getSignedContent();
            byte[] rawcontent = (byte[]) cpb.getContent();

            return new String(rawcontent);
        } catch (Exception ex) {
            logger.error("Couldn't verify included-content CMS signature", ex);
            throw new RuntimeException("Couldn't verify included-content CMS signature", ex);
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    public static byte[] DERtoPEM(byte[] bytes, String headfoot) {
        ByteArrayOutputStream pemStream = new ByteArrayOutputStream();
        PrintWriter writer = new PrintWriter(pemStream);

        byte[] stringBytes = BaseEncoding.base64().encode(bytes).getBytes();

        String encoded = new String(stringBytes);

        if (headfoot != null) {
            writer.print("-----BEGIN " + headfoot + "-----\n");
        }

        // write 64 chars per line till done
        int i = 0;
        while ((i + 1) * 64 < encoded.length()) {
            writer.print(encoded.substring(i * 64, (i + 1) * 64));
            writer.print("\n");
            i++;
        }
        if (encoded.length() % 64 != 0) {
            writer.print(encoded.substring(i * 64)); // write remainder
            writer.print("\n");
        }
        if (headfoot != null) {
            writer.print("-----END " + headfoot + "-----\n");
        }
        writer.flush();
        return pemStream.toByteArray();
    }

    private static X509Certificate generateCertificate(String certFilePath)
            throws CertificateException, IOException {
        X509Certificate cert = certMap.get(certFilePath);
        if (cert != null) {
            return cert;
        } else {
            synchronized (certMap) {
                cert = certMap.get(certFilePath);
                if (cert != null) {
                    return cert;
                }
                File f = new File(certFilePath);
                logger.debug("cert file {} exist? {}", new Object[] { certFilePath, f.exists() });
                cert = X509CertificateParser.parse(f);
                certMap.put(certFilePath, cert);
                return cert;
            }
        }
    }

    private static PrivateKey generatePrivateKey(String signingKeyFile) throws CertificateException, IOException {
        PrivateKey key = keyMap.get(signingKeyFile);
        if (key != null) {
            return key;
        } else {
            synchronized (keyMap) {
                key = keyMap.get(signingKeyFile);
                if (key != null) {
                    return key;
                }
                key = X509CertificateParser.parseKey(new File(signingKeyFile));
                keyMap.put(signingKeyFile, key);
                return key;
            }
        }
    }

    public static boolean isPkiz(String userToken) {
        return userToken.startsWith(PKIZ_PREFIX);
    }

    public static boolean isAsn1Token(String userToken) {
        return userToken.startsWith(PKI_ASN1_PREFIX);
    }

    public String hashToken(String tokenid, Algorithm mode)
            throws UnsupportedEncodingException, NoSuchAlgorithmException, DecoderException {
        if (mode == null) {
            mode = Algorithm.md5;
        }

        if (Strings.isNullOrEmpty(tokenid)) {
            throw new NullPointerException("invalid tokenid");
        }

        if (isAsn1Token(tokenid) || isPkiz(tokenid)) {
            HashFunction hf = Hashing.md5();
            if (mode == Algorithm.sha1) {
                hf = Hashing.sha1();
            } else if (mode == Algorithm.sha256) {
                hf = Hashing.sha256();
            } else if (mode == Algorithm.sha512) {
                hf = Hashing.sha512();
            }
            HashCode hc = hf.newHasher().putString(tokenid).hash();
            return toHex(hc.asBytes());

        } else {
            return tokenid;
        }
    }

    public static String toHex(byte[] digest) {
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%1$02X", b));
        }

        return sb.toString();
    }

    public String tokenToCms(String signedText) {
        String copyOfText = signedText.replace('-', '/');
        String formatted = "-----BEGIN CMS-----\n";
        int lineLength = 64;
        while (copyOfText.length() > 0) {
            if (copyOfText.length() > lineLength) {
                formatted += copyOfText.substring(0, lineLength);
                copyOfText = copyOfText.substring(lineLength);
            } else {
                formatted += copyOfText;
                copyOfText = "";
            }
            formatted += "\n";
        }
        formatted += "-----END CMS-----\n";

        return formatted;
    }

    public String pkizSign(String text, String certfile, String keyfile)
            throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException,
            OperatorCreationException, CertStoreException, IOException, CMSException {
        String signed = cmsSignData(text, certfile, keyfile, PKIZ_CMS_FORM);
        byte[] compressed = CompressionUtils.compress(signed.getBytes());
        String encoded = PKIZ_PREFIX + BaseEncoding.base64Url().encode(compressed);
        return encoded;
    }
}