| /* |
| * 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.ambari.logsearch.configurer; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.net.ssl.SSLContext; |
| |
| import org.apache.ambari.logsearch.conf.LogSearchSslConfig; |
| import org.apache.ambari.logsearch.util.FileUtil; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.bouncycastle.asn1.ASN1InputStream; |
| import org.bouncycastle.asn1.x500.X500Name; |
| import org.bouncycastle.asn1.x509.AlgorithmIdentifier; |
| import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| import org.bouncycastle.operator.ContentSigner; |
| import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; |
| import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; |
| import org.bouncycastle.operator.OperatorCreationException; |
| import org.bouncycastle.operator.bc.BcContentSignerBuilder; |
| import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; |
| import org.bouncycastle.cert.X509CertificateHolder; |
| import org.bouncycastle.cert.X509v3CertificateBuilder; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
| import org.bouncycastle.crypto.params.RSAKeyParameters; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.math.BigInteger; |
| import java.net.InetAddress; |
| import java.security.InvalidKeyException; |
| import java.security.KeyPair; |
| import java.security.KeyPairGenerator; |
| import java.security.KeyStore; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.SecureRandom; |
| import java.security.Security; |
| import java.security.SignatureException; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.security.interfaces.RSAPrivateKey; |
| import java.security.interfaces.RSAPublicKey; |
| import java.util.Date; |
| |
| import static org.apache.ambari.logsearch.conf.LogSearchSslConfig.CREDENTIAL_STORE_PROVIDER_PATH; |
| import static org.apache.ambari.logsearch.conf.LogSearchSslConfig.LOGSEARCH_CERT_DEFAULT_FOLDER; |
| |
| @Named |
| public class SslConfigurer { |
| private static final Logger logger = LogManager.getLogger(SslConfigurer.class); |
| |
| private static final String KEYSTORE_LOCATION_ARG = "javax.net.ssl.keyStore"; |
| private static final String KEYSTORE_PASSWORD_ARG = "javax.net.ssl.keyStorePassword"; |
| private static final String KEYSTORE_TYPE_ARG = "javax.net.ssl.keyStoreType"; |
| private static final String DEFAULT_KEYSTORE_TYPE = "JKS"; |
| private static final String TRUSTSTORE_LOCATION_ARG = "javax.net.ssl.trustStore"; |
| private static final String TRUSTSTORE_PASSWORD_ARG = "javax.net.ssl.trustStorePassword"; |
| private static final String TRUSTSTORE_TYPE_ARG = "javax.net.ssl.trustStoreType"; |
| private static final String DEFAULT_TRUSTSTORE_TYPE = "JKS"; |
| private static final String KEYSTORE_PASSWORD_PROPERTY_NAME = "logsearch_keystore_password"; |
| private static final String TRUSTSTORE_PASSWORD_PROPERTY_NAME = "logsearch_truststore_password"; |
| private static final String KEYSTORE_PASSWORD_FILE = "ks_pass.txt"; |
| private static final String TRUSTSTORE_PASSWORD_FILE = "ts_pass.txt"; |
| |
| private static final String LOGSEARCH_CERT_FILENAME = "logsearch.crt"; |
| private static final String LOGSEARCH_KEYSTORE_FILENAME = "logsearch.jks"; |
| private static final String LOGSEARCH_KEYSTORE_PRIVATE_KEY = "logsearch.private.key"; |
| private static final String LOGSEARCH_KEYSTORE_PUBLIC_KEY = "logsearch.public.key"; |
| |
| private static final String LOGSEARCH_KEYSTORE_DEFAULT_PASSWORD = "bigdata"; |
| |
| @Inject |
| private LogSearchSslConfig logSearchSslConfig; |
| |
| private String getKeyStoreLocation() { |
| return System.getProperty(KEYSTORE_LOCATION_ARG); |
| } |
| |
| private String getKeyStorePassword() { |
| return System.getProperty(KEYSTORE_PASSWORD_ARG); |
| } |
| |
| private String getKeyStoreType() { |
| return System.getProperty(KEYSTORE_TYPE_ARG, DEFAULT_KEYSTORE_TYPE); |
| } |
| |
| private String getTrustStoreLocation() { |
| return System.getProperty(TRUSTSTORE_LOCATION_ARG); |
| } |
| |
| private String getTrustStorePassword() { |
| return System.getProperty(TRUSTSTORE_PASSWORD_ARG); |
| } |
| |
| private String getTrustStoreType() { |
| return System.getProperty(TRUSTSTORE_TYPE_ARG, DEFAULT_TRUSTSTORE_TYPE); |
| } |
| |
| public boolean isKeyStoreSpecified() { |
| return StringUtils.isNotEmpty(getKeyStoreLocation()); |
| } |
| |
| private boolean isTrustStoreSpecified() { |
| return StringUtils.isNotEmpty(getTrustStoreLocation()); |
| } |
| |
| public SslContextFactory getSslContextFactory() { |
| SslContextFactory sslContextFactory = new SslContextFactory(); |
| sslContextFactory.setKeyStorePath(getKeyStoreLocation()); |
| sslContextFactory.setKeyStorePassword(getKeyStorePassword()); |
| sslContextFactory.setKeyStoreType(getKeyStoreType()); |
| if (isTrustStoreSpecified()) { |
| sslContextFactory.setTrustStorePath(getTrustStoreLocation()); |
| sslContextFactory.setTrustStorePassword(getTrustStorePassword()); |
| sslContextFactory.setTrustStoreType(getTrustStoreType()); |
| } |
| |
| return sslContextFactory; |
| } |
| |
| public SSLContext getSSLContext() { |
| SslContextFactory sslContextFactory = getSslContextFactory(); |
| |
| try { |
| sslContextFactory.start(); |
| return sslContextFactory.getSslContext(); |
| } catch (Exception e) { |
| logger.error("Could not create SSL Context", e); |
| return null; |
| } finally { |
| try { |
| sslContextFactory.stop(); |
| } catch (Exception e) { |
| logger.error("Could not stop sslContextFactory", e); |
| } |
| } |
| } |
| |
| private String getPasswordFromFile(String fileName) { |
| try { |
| File pwdFile = new File(LOGSEARCH_CERT_DEFAULT_FOLDER, fileName); |
| if (!pwdFile.exists()) { |
| FileUtils.writeStringToFile(pwdFile, LOGSEARCH_KEYSTORE_DEFAULT_PASSWORD); |
| return LOGSEARCH_KEYSTORE_DEFAULT_PASSWORD; |
| } else { |
| return FileUtils.readFileToString(pwdFile); |
| } |
| } catch (Exception e) { |
| logger.warn("Exception occurred during read/write password file for keystore/truststore.", e); |
| return null; |
| } |
| } |
| |
| private String getPasswordFromCredentialStore(String propertyName) { |
| try { |
| String providerPath = logSearchSslConfig.getCredentialStoreProviderPath(); |
| if (StringUtils.isEmpty(providerPath)) { |
| return null; |
| } |
| |
| Configuration config = new Configuration(); |
| config.set(CREDENTIAL_STORE_PROVIDER_PATH, providerPath); |
| char[] passwordChars = config.getPassword(propertyName); |
| return (ArrayUtils.isNotEmpty(passwordChars)) ? new String(passwordChars) : null; |
| } catch (Exception e) { |
| logger.warn(String.format("Could not load password %s from credential store, using default password", propertyName), e); |
| return null; |
| } |
| } |
| |
| private String getPassword(String propertyName, String fileName) { |
| String credentialStorePassword = getPasswordFromCredentialStore(propertyName); |
| if (credentialStorePassword != null) { |
| return credentialStorePassword; |
| } |
| |
| String filePassword = getPasswordFromFile(fileName); |
| if (filePassword != null) { |
| return filePassword; |
| } |
| |
| return LOGSEARCH_KEYSTORE_DEFAULT_PASSWORD; |
| } |
| |
| /** |
| * Put private key into in-memory keystore and write it to a file (JKS file) |
| */ |
| private void setKeyAndCertInKeystore(X509Certificate cert, KeyPair keyPair, KeyStore keyStore, String keyStoreLocation, char[] password) |
| throws Exception { |
| Certificate[] certChain = new Certificate[1]; |
| certChain[0] = cert; |
| try (FileOutputStream fos = new FileOutputStream(keyStoreLocation)) { |
| keyStore.setKeyEntry("logsearch.alias", keyPair.getPrivate(), password, certChain); |
| keyStore.store(fos, password); |
| } catch (Exception e) { |
| logger.error("Could not write certificate to Keystore", e); |
| throw e; |
| } |
| } |
| |
| /** |
| * Create in-memory keypair with bouncy castle |
| */ |
| private KeyPair createKeyPair(String encryptionType, int byteCount) |
| throws NoSuchProviderException, NoSuchAlgorithmException { |
| Security.addProvider(new BouncyCastleProvider()); |
| KeyPairGenerator keyPairGenerator = createKeyPairGenerator(encryptionType, byteCount); |
| return keyPairGenerator.genKeyPair(); |
| } |
| |
| /** |
| * Generate X509 certificate if it does not exist |
| */ |
| private X509Certificate generateCertificate(String certificateLocation, KeyPair keyPair, String algorithm) throws Exception { |
| try { |
| File certFile = new File(certificateLocation); |
| if (certFile.exists()) { |
| logger.info("Certificate file exists ({}), skip the generation.", certificateLocation); |
| return getCertFile(certificateLocation); |
| } else { |
| Security.addProvider(new BouncyCastleProvider()); |
| X509Certificate cert = createCert(keyPair, algorithm, InetAddress.getLocalHost().getCanonicalHostName()); |
| FileUtils.writeByteArrayToFile(certFile, cert.getEncoded()); |
| return cert; |
| } |
| } catch (Exception e) { |
| logger.error("Could not create certificate.", e); |
| throw e; |
| } |
| } |
| |
| private void ensureStorePassword(String locationArg, String pwdArg, String propertyName, String fileName) { |
| if (StringUtils.isNotEmpty(System.getProperty(locationArg)) && StringUtils.isEmpty(System.getProperty(pwdArg))) { |
| String password = getPassword(propertyName, fileName); |
| System.setProperty(pwdArg, password); |
| } |
| } |
| |
| public void ensureStorePasswords() { |
| ensureStorePassword(KEYSTORE_LOCATION_ARG, KEYSTORE_PASSWORD_ARG, KEYSTORE_PASSWORD_PROPERTY_NAME, KEYSTORE_PASSWORD_FILE); |
| ensureStorePassword(TRUSTSTORE_LOCATION_ARG, TRUSTSTORE_PASSWORD_ARG, TRUSTSTORE_PASSWORD_PROPERTY_NAME, TRUSTSTORE_PASSWORD_FILE); |
| } |
| |
| private X509Certificate getCertFile(String location) throws Exception { |
| try (FileInputStream fos = new FileInputStream(location)) { |
| CertificateFactory factory = CertificateFactory.getInstance("X.509"); |
| return (X509Certificate) factory.generateCertificate(fos); |
| } catch (Exception e) { |
| logger.error("Cannot read cert file. ('" + location + "')", e); |
| throw e; |
| } |
| } |
| |
| private X509Certificate createCert(KeyPair keyPair, String signatureAlgoritm, String domainName) |
| throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, OperatorCreationException, CertificateException, IOException { |
| |
| RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); |
| RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); |
| |
| AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(signatureAlgoritm); |
| AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); |
| BcContentSignerBuilder sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId); |
| |
| ASN1InputStream publicKeyStream = new ASN1InputStream(rsaPublicKey.getEncoded()); |
| SubjectPublicKeyInfo pubKey = SubjectPublicKeyInfo.getInstance(publicKeyStream.readObject()); |
| publicKeyStream.close(); |
| |
| X509v3CertificateBuilder v3CertBuilder = new X509v3CertificateBuilder( |
| new X500Name("CN=" + domainName + ", OU=None, O=None L=None, C=None"), |
| BigInteger.valueOf(Math.abs(new SecureRandom().nextInt())), |
| new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30), |
| new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365*10)), |
| new X500Name("CN=" + domainName + ", OU=None, O=None L=None, C=None"), |
| pubKey); |
| |
| RSAKeyParameters keyParams = new RSAKeyParameters(true, rsaPrivateKey.getPrivateExponent(), rsaPrivateKey.getModulus()); |
| ContentSigner contentSigner = sigGen.build(keyParams); |
| |
| X509CertificateHolder certificateHolder = v3CertBuilder.build(contentSigner); |
| |
| JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter().setProvider("BC"); |
| return certConverter.getCertificate(certificateHolder); |
| } |
| |
| private KeyPairGenerator createKeyPairGenerator(String algorithmIdentifier, int bitCount) |
| throws NoSuchProviderException, NoSuchAlgorithmException { |
| KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithmIdentifier, BouncyCastleProvider.PROVIDER_NAME); |
| kpg.initialize(bitCount); |
| return kpg; |
| } |
| |
| /** |
| * Create keystore with keys and certificate (only if the keystore does not exist or if you have no permissions on the keystore file) |
| */ |
| public void loadKeystore() { |
| try { |
| String certFolder = logSearchSslConfig.getCertFolder(); |
| String certAlgorithm = logSearchSslConfig.getCertAlgorithm(); |
| String certLocation = String.format("%s/%s", LOGSEARCH_CERT_DEFAULT_FOLDER, LOGSEARCH_CERT_FILENAME); |
| String keyStoreLocation = StringUtils.isNotEmpty(getKeyStoreLocation()) ? getKeyStoreLocation() |
| : String.format("%s/%s", LOGSEARCH_CERT_DEFAULT_FOLDER, LOGSEARCH_KEYSTORE_FILENAME); |
| char[] password = StringUtils.isNotEmpty(getKeyStorePassword()) ? |
| getKeyStorePassword().toCharArray() : LOGSEARCH_KEYSTORE_DEFAULT_PASSWORD.toCharArray(); |
| boolean keyStoreFileExists = new File(keyStoreLocation).exists(); |
| if (!keyStoreFileExists) { |
| FileUtil.createDirectory(certFolder); |
| logger.warn("Keystore file ('{}') does not exist, creating new one. " + |
| "If the file exists, make sure you have proper permissions on that.", keyStoreLocation); |
| if (isKeyStoreSpecified() && !"JKS".equalsIgnoreCase(getKeyStoreType())) { |
| throw new RuntimeException(String.format("Keystore does not exist. Only JKS keystore can be auto generated. (%s)", keyStoreLocation)); |
| } |
| logger.info("SSL keystore is not specified. Generating it with certificate ... (using default format: JKS)"); |
| Security.addProvider(new BouncyCastleProvider()); |
| KeyPair keyPair = createKeyPair("RSA", 2048); |
| File privateKeyFile = new File(String.format("%s/%s", certFolder, LOGSEARCH_KEYSTORE_PRIVATE_KEY)); |
| if (!privateKeyFile.exists()) { |
| FileUtils.writeByteArrayToFile(privateKeyFile, keyPair.getPrivate().getEncoded()); |
| } |
| File file = new File(String.format("%s/%s", certFolder, LOGSEARCH_KEYSTORE_PUBLIC_KEY)); |
| if (!file.exists()) { |
| FileUtils.writeByteArrayToFile(file, keyPair.getPublic().getEncoded()); |
| } |
| X509Certificate cert = generateCertificate(certLocation, keyPair, certAlgorithm); |
| KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
| keyStore.load(null, password); |
| setKeyAndCertInKeystore(cert, keyPair, keyStore, keyStoreLocation, password); |
| FileUtil.setPermissionOnDirectory(certFolder, "600"); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| } |