Skip to content

Commit 4d688f5

Browse files
committed
TLS client auth interfaces and impl for Apache
1 parent 039d98f commit 4d688f5

File tree

17 files changed

+739
-6
lines changed

17 files changed

+739
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Apache HTTP Client",
3+
"type": "feature",
4+
"description": "Enable TLS client authentication support for the Apache HTTP Client by allowing customers to specify a `TlsKeyManagersProvider` on the builder. The `KeyManger`s provided will be used when the remote server wants to authenticate the client."
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "HTTP Client SPI",
3+
"type": "feature",
4+
"description": "Add `TlsKeyManagersProvider` interface for supporting TLS client auth in HTTP client implementations."
5+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import java.nio.file.Path;
19+
import javax.net.ssl.KeyManager;
20+
import software.amazon.awssdk.annotations.SdkPublicApi;
21+
import software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider;
22+
import software.amazon.awssdk.utils.Logger;
23+
import software.amazon.awssdk.utils.Validate;
24+
25+
/**
26+
* Implementation of {@link FileStoreTlsKeyManagersProvider} that loads a the
27+
* key store from a file.
28+
*/
29+
@SdkPublicApi
30+
public final class FileStoreTlsKeyManagersProvider extends AbstractFileStoreTlsKeyManagersProvider {
31+
private static final Logger log = Logger.loggerFor(FileStoreTlsKeyManagersProvider.class);
32+
33+
private final Path storePath;
34+
private final String storeType;
35+
private final char[] password;
36+
37+
private FileStoreTlsKeyManagersProvider(Path storePath, String storeType, char[] password) {
38+
this.storePath = Validate.paramNotNull(storePath, "storePath");
39+
this.storeType = Validate.paramNotBlank(storeType, "storeType");
40+
this.password = password;
41+
}
42+
43+
@Override
44+
public KeyManager[] keyManagers() {
45+
try {
46+
return createKeyManagers(storePath, storeType, password);
47+
} catch (Exception e) {
48+
log.warn(() -> String.format("Unable to create KeyManagers from file %s", storePath), e);
49+
return null;
50+
}
51+
}
52+
53+
public static FileStoreTlsKeyManagersProvider create(Path path, String type, String password) {
54+
char[] passwordChars = password != null ? password.toCharArray() : null;
55+
return new FileStoreTlsKeyManagersProvider(path, type, passwordChars);
56+
}
57+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
19+
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
20+
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;
21+
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
24+
import java.security.KeyStore;
25+
import java.util.Optional;
26+
import javax.net.ssl.KeyManager;
27+
import software.amazon.awssdk.annotations.SdkPublicApi;
28+
import software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider;
29+
import software.amazon.awssdk.utils.Logger;
30+
import software.amazon.awssdk.utils.internal.SystemSettingUtils;
31+
32+
/**
33+
* Implementation of {@link TlsKeyManagersProvider} that gets the information
34+
* about the KeyStore to load from the system properties.
35+
* <p>
36+
* This provider checks the standard {@code javax.net.ssl.keyStore},
37+
* {@code javax.net.ssl.keyStorePassword}, and
38+
* {@code javax.net.ssl.keyStoreType} properties defined by the
39+
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html">JSSE</a>.
40+
*/
41+
@SdkPublicApi
42+
public final class SystemPropertyTlsKeyManagersProvider extends AbstractFileStoreTlsKeyManagersProvider {
43+
private static final Logger log = Logger.loggerFor(SystemPropertyTlsKeyManagersProvider.class);
44+
45+
private SystemPropertyTlsKeyManagersProvider() {
46+
}
47+
48+
@Override
49+
public KeyManager[] keyManagers() {
50+
return getKeyStore().map(p -> {
51+
Path path = Paths.get(p);
52+
String type = getKeyStoreType();
53+
char[] password = getKeyStorePassword().map(String::toCharArray).orElse(null);
54+
try {
55+
return createKeyManagers(path, type, password);
56+
} catch (Exception e) {
57+
log.warn(() -> String.format("Unable to create KeyManagers from %s property value '%s'",
58+
SSL_KEY_STORE.property(), p), e);
59+
return null;
60+
}
61+
}).orElse(null);
62+
}
63+
64+
public static SystemPropertyTlsKeyManagersProvider create() {
65+
return new SystemPropertyTlsKeyManagersProvider();
66+
}
67+
68+
private static Optional<String> getKeyStore() {
69+
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE);
70+
}
71+
72+
private static String getKeyStoreType() {
73+
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE_TYPE)
74+
.orElse(KeyStore.getDefaultType());
75+
}
76+
77+
private static Optional<String> getKeyStorePassword() {
78+
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE_PASSWORD);
79+
}
80+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import javax.net.ssl.KeyManager;
19+
import software.amazon.awssdk.annotations.SdkPublicApi;
20+
21+
/**
22+
* Provider for the {@link KeyManager key managers} to be used by the SDK when
23+
* creating the SSL context. Key managers are used when the client is required
24+
* to authenticate with the remote TLS peer, such as an HTTP proxy.
25+
*/
26+
@SdkPublicApi
27+
@FunctionalInterface
28+
public interface TlsKeyManagersProvider {
29+
30+
/**
31+
* @return The {@link KeyManager}s, or {@code null}.
32+
*/
33+
KeyManager[] keyManagers();
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.internal.http;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.security.KeyStore;
23+
import java.security.KeyStoreException;
24+
import java.security.NoSuchAlgorithmException;
25+
import java.security.UnrecoverableKeyException;
26+
import java.security.cert.CertificateException;
27+
import javax.net.ssl.KeyManager;
28+
import javax.net.ssl.KeyManagerFactory;
29+
import software.amazon.awssdk.annotations.SdkInternalApi;
30+
import software.amazon.awssdk.http.TlsKeyManagersProvider;
31+
32+
/**
33+
* Abstract {@link TlsKeyManagersProvider} that loads the key store from a
34+
* a given file path.
35+
*/
36+
@SdkInternalApi
37+
public abstract class AbstractFileStoreTlsKeyManagersProvider implements TlsKeyManagersProvider {
38+
39+
protected final KeyManager[] createKeyManagers(Path storePath, String storeType, char[] password)
40+
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
41+
KeyStore ks = createKeyStore(storePath, storeType, password);
42+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
43+
kmf.init(ks, password);
44+
return kmf.getKeyManagers();
45+
}
46+
47+
private KeyStore createKeyStore(Path storePath, String storeType, char[] password)
48+
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
49+
KeyStore ks = KeyStore.getInstance(storeType);
50+
try (InputStream storeIs = Files.newInputStream(storePath)) {
51+
ks.load(storeIs, password);
52+
return ks;
53+
}
54+
}
55+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import org.junit.AfterClass;
23+
import org.junit.BeforeClass;
24+
25+
abstract class ClientTlsAuthTestBase {
26+
protected static final String STORE_PASSWORD = "password";
27+
protected static final String CLIENT_STORE_TYPE = "pkcs12";
28+
protected static final String TEST_KEY_STORE = "/software/amazon/awssdk/http/server-keystore";
29+
protected static final String CLIENT_KEY_STORE = "/software/amazon/awssdk/http/client1.p12";
30+
31+
protected static Path tempDir;
32+
protected static Path serverKeyStore;
33+
protected static Path clientKeyStore;
34+
35+
@BeforeClass
36+
public static void setUp() throws IOException {
37+
tempDir = Files.createTempDirectory(ClientTlsAuthTestBase.class.getSimpleName());
38+
copyCertsToTmpDir();
39+
}
40+
41+
@AfterClass
42+
public static void teardown() throws IOException {
43+
Files.deleteIfExists(serverKeyStore);
44+
Files.deleteIfExists(clientKeyStore);
45+
Files.deleteIfExists(tempDir);
46+
}
47+
48+
private static void copyCertsToTmpDir() throws IOException {
49+
InputStream sksStream = ClientTlsAuthTestBase.class.getResourceAsStream(TEST_KEY_STORE);
50+
Path sks = copyToTmpDir(sksStream, "server-keystore");
51+
52+
InputStream cksStream = ClientTlsAuthTestBase.class.getResourceAsStream(CLIENT_KEY_STORE);
53+
Path cks = copyToTmpDir(cksStream, "client1.p12");
54+
55+
serverKeyStore = sks;
56+
clientKeyStore = cks;
57+
}
58+
59+
private static Path copyToTmpDir(InputStream srcStream, String name) throws IOException {
60+
Path dst = tempDir.resolve(name);
61+
Files.copy(srcStream, dst);
62+
return dst;
63+
}
64+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import java.io.IOException;
20+
import java.nio.file.Paths;
21+
import org.junit.AfterClass;
22+
import org.junit.BeforeClass;
23+
import org.junit.Test;
24+
25+
public class FileStoreTlsKeyManagersProviderTest extends ClientTlsAuthTestBase {
26+
27+
@BeforeClass
28+
public static void setUp() throws IOException {
29+
ClientTlsAuthTestBase.setUp();
30+
}
31+
32+
@AfterClass
33+
public static void teardown() throws IOException {
34+
ClientTlsAuthTestBase.teardown();
35+
}
36+
37+
@Test(expected = NullPointerException.class)
38+
public void storePathNull_throwsValidationException() {
39+
FileStoreTlsKeyManagersProvider.create(null, CLIENT_STORE_TYPE, STORE_PASSWORD);
40+
}
41+
42+
@Test(expected = NullPointerException.class)
43+
public void storeTypeNull_throwsValidationException() {
44+
FileStoreTlsKeyManagersProvider.create(clientKeyStore, null, STORE_PASSWORD);
45+
}
46+
47+
@Test(expected = IllegalArgumentException.class)
48+
public void storeTypeEmpty_throwsValidationException() {
49+
FileStoreTlsKeyManagersProvider.create(clientKeyStore, "", STORE_PASSWORD);
50+
}
51+
52+
@Test
53+
public void passwordNotGiven_doesNotThrowValidationException() {
54+
FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, null);
55+
}
56+
57+
@Test
58+
public void paramsValid_createsKeyManager() {
59+
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, STORE_PASSWORD);
60+
assertThat(provider.keyManagers()).hasSize(1);
61+
}
62+
63+
@Test
64+
public void storeDoesNotExist_returnsNull() {
65+
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(Paths.get("does", "not", "exist"), CLIENT_STORE_TYPE, STORE_PASSWORD);
66+
assertThat(provider.keyManagers()).isNull();
67+
}
68+
69+
@Test
70+
public void invalidStoreType_returnsNull() {
71+
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "invalid", STORE_PASSWORD);
72+
assertThat(provider.keyManagers()).isNull();
73+
}
74+
75+
@Test
76+
public void passwordIncorrect_returnsNull() {
77+
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, "not correct password");
78+
assertThat(provider.keyManagers()).isNull();
79+
}
80+
}

0 commit comments

Comments
 (0)