diff --git a/docs/HTTP-batchsource.md b/docs/HTTP-batchsource.md index 37ed1e2d..1e227869 100644 --- a/docs/HTTP-batchsource.md +++ b/docs/HTTP-batchsource.md @@ -403,6 +403,20 @@ error. Do not disable this in production environment on a network you do not ent **Keystore Key Algorithm:** An algorithm used for keystore. +**Keystore Cert Alias** + + Alias of the key in the keystore to be used for communication. This options is supported only by X.509 keys or keystores. + +Below is an example how the store need to be prepared: + ``` + cat client.crt client.key > client-bundle.pem + + openssl pkcs12 -export -in client-bundle.pem -out full-chain.keycert.p12 -name ${CERT_ALIAS} + + keytool -importkeystore -srckeystore full-chain.keycert.p12 -srcstoretype pkcs12 -srcalias ${CERT_ALIAS} \ + -destkeystore identity.jks -deststoretype jks -destalias ${CERT_ALIAS} + ``` + **TrustStore File:** A path to a file which contains truststore. **TrustStore Type:** Format of a truststore. diff --git a/docs/HTTP-streamingsource.md b/docs/HTTP-streamingsource.md index 4cad538e..eafe35f6 100644 --- a/docs/HTTP-streamingsource.md +++ b/docs/HTTP-streamingsource.md @@ -410,6 +410,20 @@ error. Do not disable this in production environment on a network you do not ent **Keystore Key Algorithm:** An algorithm used for keystore. +**Keystore Cert Alias** + +Alias of the key in the keystore to be used for communication. This options is supported only by X.509 keys or keystores. + +Below is an example how the store need to be prepared: + ``` + cat client.crt client.key > client-bundle.pem + + openssl pkcs12 -export -in client-bundle.pem -out full-chain.keycert.p12 -name ${CERT_ALIAS} + + keytool -importkeystore -srckeystore full-chain.keycert.p12 -srcstoretype pkcs12 -srcalias ${CERT_ALIAS} \ + -destkeystore identity.jks -deststoretype jks -destalias ${CERT_ALIAS} + ``` + **TrustStore File:** A path to a file which contains truststore. **TrustStore Type:** Format of a truststore. diff --git a/pom.xml b/pom.xml index d49ef2e5..80bf3875 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ HTTP Plugins io.cdap http-plugins - 1.4.0-SNAPSHOT + 1.4.1-SNAPSHOT @@ -79,10 +79,12 @@ 6.1.1 3.9 1.12 + 1.2 + 1.2.17 2.8.5 2.3.0 4.5.9 - 2.4.0-SNAPSHOT + 2.4.0 2.9.9 4.11 2.7.1 @@ -93,6 +95,20 @@ + + + commons-logging + commons-logging + ${common.logging.version} + compile + + + log4j + log4j + ${log4j.version} + compile + + io.cdap.cdap cdap-api diff --git a/src/main/java/io/cdap/plugin/http/source/common/BaseHttpSourceConfig.java b/src/main/java/io/cdap/plugin/http/source/common/BaseHttpSourceConfig.java index a554dd6c..9c21627a 100644 --- a/src/main/java/io/cdap/plugin/http/source/common/BaseHttpSourceConfig.java +++ b/src/main/java/io/cdap/plugin/http/source/common/BaseHttpSourceConfig.java @@ -100,6 +100,8 @@ public abstract class BaseHttpSourceConfig extends ReferencePluginConfig { public static final String PROPERTY_CIPHER_SUITES = "cipherSuites"; public static final String PROPERTY_SCHEMA = "schema"; + public static final String PROPERTY_KEYSTORE_CERT_ALIAS = "keystoreCertAlias"; + public static final String PAGINATION_INDEX_PLACEHOLDER_REGEX = "\\{pagination.index\\}"; public static final String PAGINATION_INDEX_PLACEHOLDER = "{pagination.index}"; @@ -390,6 +392,12 @@ public abstract class BaseHttpSourceConfig extends ReferencePluginConfig { @Description("Output schema. Is required to be set.") protected String schema; + @Name(PROPERTY_KEYSTORE_CERT_ALIAS) + @Macro + @Nullable + @Description("Alias of the key in the keystore to be used for communication") + protected String keystoreCertAliasName; + protected BaseHttpSourceConfig(String referenceName) { super(referenceName); } @@ -627,6 +635,11 @@ public Schema getSchema() { } } + @Nullable + public String getKeystoreCertAliasName() { + return keystoreCertAliasName; + } + @Nullable public Map getHeadersMap() { return getMapFromKeyValueString(headers); diff --git a/src/main/java/io/cdap/plugin/http/source/common/http/SSLConnectionSocketFactoryCreator.java b/src/main/java/io/cdap/plugin/http/source/common/http/SSLConnectionSocketFactoryCreator.java index 1883f9ba..3cae7de4 100644 --- a/src/main/java/io/cdap/plugin/http/source/common/http/SSLConnectionSocketFactoryCreator.java +++ b/src/main/java/io/cdap/plugin/http/source/common/http/SSLConnectionSocketFactoryCreator.java @@ -59,6 +59,7 @@ public SSLConnectionSocketFactory create() { SSLContext sslContext = SSLContext.getInstance("TLS"); // "TLS" means rely system properties sslContext.init(getKeyManagers(), getTrustManagers(), null); + return new SSLConnectionSocketFactory(sslContext, config.getTransportProtocolsList().toArray(new String[0]), cipherSuites, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } catch (KeyManagementException | CertificateException | NoSuchAlgorithmException | KeyStoreException @@ -70,27 +71,30 @@ public SSLConnectionSocketFactory create() { private KeyManager[] getKeyManagers() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException { - KeyStore keystore = loadKeystore(config.getKeystoreFile(), config.getKeystoreType().name(), - config.getKeystorePassword()); - String keyStorePassword = config.getKeystorePassword(); + KeyStore keystore = loadKeystore(config.getKeystoreFile(), config.getKeystoreType().name(), keyStorePassword); // we have to manually fall back to default keystore. SSLContext won't provide such a functionality. if (keystore == null) { String keyStore = System.getProperty("javax.net.ssl.keyStore"); String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", ""); - keystore = loadKeystore(keyStore, keyStoreType, keyStorePassword); } - String keystoreAlgorithm = - (Strings.isNullOrEmpty(config.getKeystoreKeyAlgorithm())) ? KeyManagerFactory.getDefaultAlgorithm() + String keystoreAlgorithm = (Strings.isNullOrEmpty(config.getKeystoreKeyAlgorithm())) + ? KeyManagerFactory.getDefaultAlgorithm() : config.getKeystoreKeyAlgorithm(); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keystoreAlgorithm); - char[] passwordArr = (keyStorePassword == null) ? null : keyStorePassword.toCharArray(); - keyManagerFactory.init(keystore, passwordArr); - return keyManagerFactory.getKeyManagers(); + keyManagerFactory.init( + keystore, + (keyStorePassword == null) ? null : keyStorePassword.toCharArray() + ); + + return (Strings.isNullOrEmpty(config.getKeystoreCertAliasName())) + ? keyManagerFactory.getKeyManagers() + : X509KeyManagerAliasWrapper.getKeyManagers(keyManagerFactory, config.getKeystoreCertAliasName()); } private TrustManager[] getTrustManagers() @@ -100,13 +104,17 @@ private TrustManager[] getTrustManagers() return new TrustManager[] { new TrustAllTrustManager() }; } - KeyStore trustStore = loadKeystore(config.getTrustStoreFile(), config.getTrustStoreType().name(), - config.getTrustStorePassword()); + KeyStore trustStore = loadKeystore( + config.getTrustStoreFile(), + config.getTrustStoreType().name(), + config.getTrustStorePassword() + ); + TrustManager[] trustManagers = null; if (trustStore != null) { - String trustStoreAlgorithm = - (Strings.isNullOrEmpty(config.getTrustStoreKeyAlgorithm())) ? TrustManagerFactory.getDefaultAlgorithm() - : config.getTrustStoreKeyAlgorithm(); + String trustStoreAlgorithm = (Strings.isNullOrEmpty(config.getTrustStoreKeyAlgorithm())) + ? TrustManagerFactory.getDefaultAlgorithm() + : config.getTrustStoreKeyAlgorithm(); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm); trustManagerFactory.init(trustStore); trustManagers = trustManagerFactory.getTrustManagers(); @@ -117,13 +125,15 @@ private TrustManager[] getTrustManagers() private static KeyStore loadKeystore(String keystoreFile, String type, String password) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException { - KeyStore keystore = null; - if (keystoreFile != null) { - keystore = KeyStore.getInstance(type); - char[] passwordArr = (password == null) ? null : password.toCharArray(); - try (InputStream is = Files.newInputStream(Paths.get(keystoreFile))) { - keystore.load(is, passwordArr); - } + if (keystoreFile == null) { + return null; + } + + KeyStore keystore = KeyStore.getInstance(type); + char[] passwordArr = (password == null) ? null : password.toCharArray(); + + try (InputStream is = Files.newInputStream(Paths.get(keystoreFile))) { + keystore.load(is, passwordArr); } return keystore; } diff --git a/src/main/java/io/cdap/plugin/http/source/common/http/X509KeyManagerAliasWrapper.java b/src/main/java/io/cdap/plugin/http/source/common/http/X509KeyManagerAliasWrapper.java new file mode 100644 index 00000000..16dc553a --- /dev/null +++ b/src/main/java/io/cdap/plugin/http/source/common/http/X509KeyManagerAliasWrapper.java @@ -0,0 +1,87 @@ +/* + * Copyright © 2021 Cask Data, 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 io.cdap.plugin.http.source.common.http; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509KeyManager; + + +/** + * This is just wrapper over SunX509KeyManagerImpl with possibility to provide specific alias + * + * Usage example: + * X509KeyManagerAliasWrapper.getKeyManagers(keyManagerFactory, CERT_ALIAS); + */ +public class X509KeyManagerAliasWrapper implements X509KeyManager { + + private final X509KeyManager originalKeyManager; + private final String certAlias; + + public X509KeyManagerAliasWrapper(X509KeyManager originalKeyManager, String certAlias) { + this.originalKeyManager = originalKeyManager; + this.certAlias = certAlias; + } + + public static KeyManager[] getKeyManagers(KeyManagerFactory keyManagerFactory, String certAlias) { + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + + // Current implementation only support X509 Certificates + if (keyManagers.length != 1) { + return keyManagers; + } + if (!(keyManagers[0] instanceof X509KeyManager)) { + return keyManagers; + } + + return new KeyManager[]{ new X509KeyManagerAliasWrapper((X509KeyManager) keyManagers[0], certAlias) }; + }; + + @Override + public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) { + return certAlias; + } + + @Override + public String[] getClientAliases(String s, Principal[] principals) { + return originalKeyManager.getClientAliases(s, principals); + } + + @Override + public String[] getServerAliases(String s, Principal[] principals) { + return originalKeyManager.getServerAliases(s, principals); + } + + @Override + public String chooseServerAlias(String s, Principal[] principals, Socket socket) { + return originalKeyManager.chooseServerAlias(s, principals, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String s) { + return originalKeyManager.getCertificateChain(s); + } + + @Override + public PrivateKey getPrivateKey(String s) { + return originalKeyManager.getPrivateKey(s); + } +} diff --git a/widgets/HTTP-batchsource.json b/widgets/HTTP-batchsource.json index 6c053781..fd612166 100644 --- a/widgets/HTTP-batchsource.json +++ b/widgets/HTTP-batchsource.json @@ -426,6 +426,11 @@ "default": "SunX509" } }, + { + "widget-type": "textbox", + "label": "Keystore Cert Alias", + "name": "keystoreCertAlias" + }, { "widget-type": "textbox", "label": "TrustStore File", diff --git a/widgets/HTTP-streamingsource.json b/widgets/HTTP-streamingsource.json index e7abdb68..798a6fed 100644 --- a/widgets/HTTP-streamingsource.json +++ b/widgets/HTTP-streamingsource.json @@ -425,6 +425,11 @@ "default": "SunX509" } }, + { + "widget-type": "textbox", + "label": "Keystore Cert Alias", + "name": "keystoreCertAlias" + }, { "widget-type": "textbox", "label": "TrustStore File",