org.apache.nifi.toolkit.tls.util.TlsHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.toolkit.tls.util.TlsHelper.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nifi.toolkit.tls.util;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TlsHelper {
    private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class);
    private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
    public static final String JCE_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html";
    public static final String ILLEGAL_KEY_SIZE = "illegal key size";
    private static boolean isUnlimitedStrengthCryptographyEnabled;

    // Evaluate an unlimited strength algorithm to determine if we support the capability we have on the system
    static {
        try {
            isUnlimitedStrengthCryptographyEnabled = (Cipher
                    .getMaxAllowedKeyLength("AES") > DEFAULT_MAX_ALLOWED_KEY_LENGTH);
        } catch (NoSuchAlgorithmException e) {
            // if there are issues with this, we default back to the value established
            isUnlimitedStrengthCryptographyEnabled = false;
        }
    }

    private static void logTruncationWarning(File file) {
        String fileToString = file.toString();
        String fileName = file.getName();
        logger.warn("**********************************************************************************");
        logger.warn("                                    WARNING!!!!");
        logger.warn("**********************************************************************************");
        logger.warn("Unlimited JCE Policy is not installed which means we cannot utilize a");
        logger.warn("PKCS12 password longer than 7 characters.");
        logger.warn("Autogenerated password has been reduced to 7 characters.");
        logger.warn("");
        logger.warn("Please strongly consider installing Unlimited JCE Policy at");
        logger.warn(JCE_URL);
        logger.warn("");
        logger.warn("Another alternative is to add a stronger password with the openssl tool to the");
        logger.warn("resulting client certificate: " + fileToString);
        logger.warn("");
        logger.warn("openssl pkcs12 -in '" + fileToString + "' -out '/tmp/" + fileName + "'");
        logger.warn("openssl pkcs12 -export -in '/tmp/" + fileName + "' -out '" + fileToString + "'");
        logger.warn("rm -f '/tmp/" + fileName + "'");
        logger.warn("");
        logger.warn("**********************************************************************************");

    }

    private TlsHelper() {

    }

    public static boolean isUnlimitedStrengthCryptographyEnabled() {
        return isUnlimitedStrengthCryptographyEnabled;
    }

    public static String writeKeyStore(KeyStore keyStore, OutputStreamFactory outputStreamFactory, File file,
            String password, boolean generatedPassword) throws IOException, GeneralSecurityException {
        try (OutputStream fileOutputStream = outputStreamFactory.create(file)) {
            keyStore.store(fileOutputStream, password.toCharArray());
        } catch (IOException e) {
            if (e.getMessage().toLowerCase().contains(ILLEGAL_KEY_SIZE)
                    && !isUnlimitedStrengthCryptographyEnabled()) {
                if (generatedPassword) {
                    file.delete();
                    String truncatedPassword = password.substring(0, 7);
                    try (OutputStream fileOutputStream = outputStreamFactory.create(file)) {
                        keyStore.store(fileOutputStream, truncatedPassword.toCharArray());
                    }
                    logTruncationWarning(file);
                    return truncatedPassword;
                } else {
                    throw new GeneralSecurityException("Specified password for " + file
                            + " too long to work without unlimited JCE policy installed." + System.lineSeparator()
                            + "Please see " + JCE_URL);
                }
            } else {
                throw e;
            }
        }
        return password;
    }

    private static KeyPairGenerator createKeyPairGenerator(String algorithm, int keySize)
            throws NoSuchAlgorithmException {
        KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm);
        instance.initialize(keySize);
        return instance;
    }

    public static byte[] calculateHMac(String token, PublicKey publicKey) throws GeneralSecurityException {
        SecretKeySpec keySpec = new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "RAW");
        Mac mac = Mac.getInstance("Hmac-SHA256", BouncyCastleProvider.PROVIDER_NAME);
        mac.init(keySpec);
        return mac.doFinal(getKeyIdentifier(publicKey));
    }

    public static byte[] getKeyIdentifier(PublicKey publicKey) throws NoSuchAlgorithmException {
        return new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey).getKeyIdentifier();
    }

    public static String pemEncodeJcaObject(Object object) throws IOException {
        StringWriter writer = new StringWriter();
        try (PemWriter pemWriter = new PemWriter(writer)) {
            pemWriter.writeObject(new JcaMiscPEMGenerator(object));
        }
        return writer.toString();
    }

    public static JcaPKCS10CertificationRequest parseCsr(String pemEncodedCsr) throws IOException {
        try (PEMParser pemParser = new PEMParser(new StringReader(pemEncodedCsr))) {
            Object o = pemParser.readObject();
            if (!PKCS10CertificationRequest.class.isInstance(o)) {
                throw new IOException(
                        "Expecting instance of " + PKCS10CertificationRequest.class + " but got " + o);
            }
            return new JcaPKCS10CertificationRequest((PKCS10CertificationRequest) o);
        }
    }

    public static X509Certificate parseCertificate(Reader pemEncodedCertificate)
            throws IOException, CertificateException {
        return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .getCertificate(parsePem(X509CertificateHolder.class, pemEncodedCertificate));
    }

    public static KeyPair parseKeyPair(Reader pemEncodedKeyPair) throws IOException {
        return new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .getKeyPair(parsePem(PEMKeyPair.class, pemEncodedKeyPair));
    }

    public static <T> T parsePem(Class<T> clazz, Reader pemReader) throws IOException {
        try (PEMParser pemParser = new PEMParser(pemReader)) {
            Object object = pemParser.readObject();
            if (!clazz.isInstance(object)) {
                throw new IOException("Expected " + clazz);
            }
            return (T) object;
        }
    }

    public static KeyPair generateKeyPair(String algorithm, int keySize) throws NoSuchAlgorithmException {
        return createKeyPairGenerator(algorithm, keySize).generateKeyPair();
    }

    public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn,
            String domainAlternativeNames, KeyPair keyPair, String signingAlgorithm)
            throws OperatorCreationException {
        JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(
                new X500Name(requestedDn), keyPair.getPublic());

        // add Subject Alternative Name(s)
        if (StringUtils.isNotBlank(domainAlternativeNames)) {
            try {
                jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,
                        createDomainAlternativeNamesExtensions(domainAlternativeNames));
            } catch (IOException e) {
                throw new OperatorCreationException(
                        "Error while adding " + domainAlternativeNames + " as Subject Alternative Name.", e);
            }
        }

        JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm);
        return new JcaPKCS10CertificationRequest(
                jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
    }

    public static Extensions createDomainAlternativeNamesExtensions(String domainAlternativeNames)
            throws IOException {
        List<GeneralName> namesList = new ArrayList<>();
        for (String alternativeName : domainAlternativeNames.split(",")) {
            namesList.add(new GeneralName(GeneralName.dNSName, alternativeName));
        }

        GeneralNames subjectAltNames = new GeneralNames(namesList.toArray(new GeneralName[] {}));
        ExtensionsGenerator extGen = new ExtensionsGenerator();
        extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
        return extGen.generate();
    }

}