Skip to content

Commit f795626

Browse files
author
Simca
committed
Add possibility to use particular certificate for client during 2 way ssl connection
1 parent 7eb1b8e commit f795626

File tree

8 files changed

+187
-23
lines changed

8 files changed

+187
-23
lines changed

docs/HTTP-batchsource.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,20 @@ error. Do not disable this in production environment on a network you do not ent
403403

404404
**Keystore Key Algorithm:** An algorithm used for keystore.
405405

406+
**Keystore Cert Alias**
407+
408+
Alias of the key in the keystore to be used for communication. This options is supported only by X.509 keys or keystores.
409+
410+
Below is an example how the store need to be prepared:
411+
```
412+
cat client.crt client.key > client-bundle.pem
413+
414+
openssl pkcs12 -export -in client-bundle.pem -out full-chain.keycert.p12 -name ${CERT_ALIAS}
415+
416+
keytool -importkeystore -srckeystore full-chain.keycert.p12 -srcstoretype pkcs12 -srcalias ${CERT_ALIAS} \
417+
-destkeystore identity.jks -deststoretype jks -destalias ${CERT_ALIAS}
418+
```
419+
406420
**TrustStore File:** A path to a file which contains truststore.
407421

408422
**TrustStore Type:** Format of a truststore.

docs/HTTP-streamingsource.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,20 @@ error. Do not disable this in production environment on a network you do not ent
410410

411411
**Keystore Key Algorithm:** An algorithm used for keystore.
412412

413+
**Keystore Cert Alias**
414+
415+
Alias of the key in the keystore to be used for communication. This options is supported only by X.509 keys or keystores.
416+
417+
Below is an example how the store need to be prepared:
418+
```
419+
cat client.crt client.key > client-bundle.pem
420+
421+
openssl pkcs12 -export -in client-bundle.pem -out full-chain.keycert.p12 -name ${CERT_ALIAS}
422+
423+
keytool -importkeystore -srckeystore full-chain.keycert.p12 -srcstoretype pkcs12 -srcalias ${CERT_ALIAS} \
424+
-destkeystore identity.jks -deststoretype jks -destalias ${CERT_ALIAS}
425+
```
426+
413427
**TrustStore File:** A path to a file which contains truststore.
414428

415429
**TrustStore Type:** Format of a truststore.

pom.xml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<name>HTTP Plugins</name>
2222
<groupId>io.cdap</groupId>
2323
<artifactId>http-plugins</artifactId>
24-
<version>1.4.0-SNAPSHOT</version>
24+
<version>1.4.1-SNAPSHOT</version>
2525

2626
<licenses>
2727
<license>
@@ -79,10 +79,12 @@
7979
<cdap.version>6.1.1</cdap.version>
8080
<commons.version>3.9</commons.version>
8181
<common.codec.version>1.12</common.codec.version>
82+
<common.logging.version>1.2</common.logging.version>
83+
<log4j.version>1.2.17</log4j.version>
8284
<gson.version>2.8.5</gson.version>
8385
<hadoop.version>2.3.0</hadoop.version>
8486
<httpcomponents.version>4.5.9</httpcomponents.version>
85-
<hydrator.version>2.4.0-SNAPSHOT</hydrator.version>
87+
<hydrator.version>2.4.0</hydrator.version>
8688
<jackson.version>2.9.9</jackson.version>
8789
<junit.version>4.11</junit.version>
8890
<jython.version>2.7.1</jython.version>
@@ -93,6 +95,20 @@
9395
</properties>
9496

9597
<dependencies>
98+
<!-- Required to work properly on external hadoop clusters-->
99+
<dependency>
100+
<groupId>commons-logging</groupId>
101+
<artifactId>commons-logging</artifactId>
102+
<version>${common.logging.version}</version>
103+
<scope>compile</scope>
104+
</dependency>
105+
<dependency>
106+
<groupId>log4j</groupId>
107+
<artifactId>log4j</artifactId>
108+
<version>${log4j.version}</version>
109+
<scope>compile</scope>
110+
</dependency>
111+
<!-- / end required -->
96112
<dependency>
97113
<groupId>io.cdap.cdap</groupId>
98114
<artifactId>cdap-api</artifactId>

src/main/java/io/cdap/plugin/http/source/common/BaseHttpSourceConfig.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public abstract class BaseHttpSourceConfig extends ReferencePluginConfig {
100100
public static final String PROPERTY_CIPHER_SUITES = "cipherSuites";
101101
public static final String PROPERTY_SCHEMA = "schema";
102102

103+
public static final String PROPERTY_KEYSTORE_CERT_ALIAS = "keystoreCertAlias";
104+
103105
public static final String PAGINATION_INDEX_PLACEHOLDER_REGEX = "\\{pagination.index\\}";
104106
public static final String PAGINATION_INDEX_PLACEHOLDER = "{pagination.index}";
105107

@@ -390,6 +392,12 @@ public abstract class BaseHttpSourceConfig extends ReferencePluginConfig {
390392
@Description("Output schema. Is required to be set.")
391393
protected String schema;
392394

395+
@Name(PROPERTY_KEYSTORE_CERT_ALIAS)
396+
@Macro
397+
@Nullable
398+
@Description("Alias of the key in the keystore to be used for communication")
399+
protected String keystoreCertAliasName;
400+
393401
protected BaseHttpSourceConfig(String referenceName) {
394402
super(referenceName);
395403
}
@@ -627,6 +635,11 @@ public Schema getSchema() {
627635
}
628636
}
629637

638+
@Nullable
639+
public String getKeystoreCertAliasName() {
640+
return keystoreCertAliasName;
641+
}
642+
630643
@Nullable
631644
public Map<String, String> getHeadersMap() {
632645
return getMapFromKeyValueString(headers);

src/main/java/io/cdap/plugin/http/source/common/http/SSLConnectionSocketFactoryCreator.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public SSLConnectionSocketFactory create() {
5959
SSLContext sslContext = SSLContext.getInstance("TLS"); // "TLS" means rely system properties
6060
sslContext.init(getKeyManagers(), getTrustManagers(), null);
6161

62+
6263
return new SSLConnectionSocketFactory(sslContext, config.getTransportProtocolsList().toArray(new String[0]),
6364
cipherSuites, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
6465
} catch (KeyManagementException | CertificateException | NoSuchAlgorithmException | KeyStoreException
@@ -70,27 +71,30 @@ public SSLConnectionSocketFactory create() {
7071
private KeyManager[] getKeyManagers() throws CertificateException, NoSuchAlgorithmException,
7172
KeyStoreException, IOException, UnrecoverableKeyException {
7273

73-
KeyStore keystore = loadKeystore(config.getKeystoreFile(), config.getKeystoreType().name(),
74-
config.getKeystorePassword());
75-
7674
String keyStorePassword = config.getKeystorePassword();
75+
KeyStore keystore = loadKeystore(config.getKeystoreFile(), config.getKeystoreType().name(), keyStorePassword);
7776

7877
// we have to manually fall back to default keystore. SSLContext won't provide such a functionality.
7978
if (keystore == null) {
8079
String keyStore = System.getProperty("javax.net.ssl.keyStore");
8180
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
8281
keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
83-
8482
keystore = loadKeystore(keyStore, keyStoreType, keyStorePassword);
8583
}
8684

87-
String keystoreAlgorithm =
88-
(Strings.isNullOrEmpty(config.getKeystoreKeyAlgorithm())) ? KeyManagerFactory.getDefaultAlgorithm()
85+
String keystoreAlgorithm = (Strings.isNullOrEmpty(config.getKeystoreKeyAlgorithm()))
86+
? KeyManagerFactory.getDefaultAlgorithm()
8987
: config.getKeystoreKeyAlgorithm();
88+
9089
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keystoreAlgorithm);
91-
char[] passwordArr = (keyStorePassword == null) ? null : keyStorePassword.toCharArray();
92-
keyManagerFactory.init(keystore, passwordArr);
93-
return keyManagerFactory.getKeyManagers();
90+
keyManagerFactory.init(
91+
keystore,
92+
(keyStorePassword == null) ? null : keyStorePassword.toCharArray()
93+
);
94+
95+
return (Strings.isNullOrEmpty(config.getKeystoreCertAliasName()))
96+
? keyManagerFactory.getKeyManagers()
97+
: X509KeyManagerAliasWrapper.getKeyManagers(keyManagerFactory, config.getKeystoreCertAliasName());
9498
}
9599

96100
private TrustManager[] getTrustManagers()
@@ -100,13 +104,17 @@ private TrustManager[] getTrustManagers()
100104
return new TrustManager[] { new TrustAllTrustManager() };
101105
}
102106

103-
KeyStore trustStore = loadKeystore(config.getTrustStoreFile(), config.getTrustStoreType().name(),
104-
config.getTrustStorePassword());
107+
KeyStore trustStore = loadKeystore(
108+
config.getTrustStoreFile(),
109+
config.getTrustStoreType().name(),
110+
config.getTrustStorePassword()
111+
);
112+
105113
TrustManager[] trustManagers = null;
106114
if (trustStore != null) {
107-
String trustStoreAlgorithm =
108-
(Strings.isNullOrEmpty(config.getTrustStoreKeyAlgorithm())) ? TrustManagerFactory.getDefaultAlgorithm()
109-
: config.getTrustStoreKeyAlgorithm();
115+
String trustStoreAlgorithm = (Strings.isNullOrEmpty(config.getTrustStoreKeyAlgorithm()))
116+
? TrustManagerFactory.getDefaultAlgorithm()
117+
: config.getTrustStoreKeyAlgorithm();
110118
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
111119
trustManagerFactory.init(trustStore);
112120
trustManagers = trustManagerFactory.getTrustManagers();
@@ -117,13 +125,15 @@ private TrustManager[] getTrustManagers()
117125
private static KeyStore loadKeystore(String keystoreFile, String type, String password)
118126
throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
119127

120-
KeyStore keystore = null;
121-
if (keystoreFile != null) {
122-
keystore = KeyStore.getInstance(type);
123-
char[] passwordArr = (password == null) ? null : password.toCharArray();
124-
try (InputStream is = Files.newInputStream(Paths.get(keystoreFile))) {
125-
keystore.load(is, passwordArr);
126-
}
128+
if (keystoreFile == null) {
129+
return null;
130+
}
131+
132+
KeyStore keystore = KeyStore.getInstance(type);
133+
char[] passwordArr = (password == null) ? null : password.toCharArray();
134+
135+
try (InputStream is = Files.newInputStream(Paths.get(keystoreFile))) {
136+
keystore.load(is, passwordArr);
127137
}
128138
return keystore;
129139
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright © 2021 Cask Data, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package io.cdap.plugin.http.source.common.http;
17+
18+
import java.net.Socket;
19+
import java.security.Principal;
20+
import java.security.PrivateKey;
21+
import java.security.cert.X509Certificate;
22+
23+
import javax.net.ssl.KeyManager;
24+
import javax.net.ssl.KeyManagerFactory;
25+
import javax.net.ssl.X509KeyManager;
26+
27+
28+
/**
29+
* This is just wrapper over SunX509KeyManagerImpl with possibility to provide specific alias
30+
*
31+
* Usage example:
32+
* X509KeyManagerAliasWrapper.getKeyManagers(keyManagerFactory, CERT_ALIAS);
33+
*/
34+
public class X509KeyManagerAliasWrapper implements X509KeyManager {
35+
36+
private final X509KeyManager originalKeyManager;
37+
private final String certAlias;
38+
39+
public X509KeyManagerAliasWrapper(X509KeyManager originalKeyManager, String certAlias) {
40+
this.originalKeyManager = originalKeyManager;
41+
this.certAlias = certAlias;
42+
}
43+
44+
public static KeyManager[] getKeyManagers(KeyManagerFactory keyManagerFactory, String certAlias) {
45+
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
46+
47+
// Current implementation only support X509 Certificates
48+
if (keyManagers.length != 1) {
49+
return keyManagers;
50+
}
51+
if (!(keyManagers[0] instanceof X509KeyManager)) {
52+
return keyManagers;
53+
}
54+
55+
return new KeyManager[]{ new X509KeyManagerAliasWrapper((X509KeyManager) keyManagers[0], certAlias) };
56+
};
57+
58+
@Override
59+
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
60+
return certAlias;
61+
}
62+
63+
@Override
64+
public String[] getClientAliases(String s, Principal[] principals) {
65+
return originalKeyManager.getClientAliases(s, principals);
66+
}
67+
68+
@Override
69+
public String[] getServerAliases(String s, Principal[] principals) {
70+
return originalKeyManager.getServerAliases(s, principals);
71+
}
72+
73+
@Override
74+
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
75+
return originalKeyManager.chooseServerAlias(s, principals, socket);
76+
}
77+
78+
@Override
79+
public X509Certificate[] getCertificateChain(String s) {
80+
return originalKeyManager.getCertificateChain(s);
81+
}
82+
83+
@Override
84+
public PrivateKey getPrivateKey(String s) {
85+
return originalKeyManager.getPrivateKey(s);
86+
}
87+
}

widgets/HTTP-batchsource.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@
426426
"default": "SunX509"
427427
}
428428
},
429+
{
430+
"widget-type": "textbox",
431+
"label": "Keystore Cert Alias",
432+
"name": "keystoreCertAlias"
433+
},
429434
{
430435
"widget-type": "textbox",
431436
"label": "TrustStore File",

widgets/HTTP-streamingsource.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@
425425
"default": "SunX509"
426426
}
427427
},
428+
{
429+
"widget-type": "textbox",
430+
"label": "Keystore Cert Alias",
431+
"name": "keystoreCertAlias"
432+
},
428433
{
429434
"widget-type": "textbox",
430435
"label": "TrustStore File",

0 commit comments

Comments
 (0)