Skip to content

TLS client auth interfaces and impl for Apache #1379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/feature-ApacheHTTPClient-d4cd12f.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"category": "Apache HTTP Client",
"type": "feature",
"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 changes: 5 additions & 0 deletions .changes/next-release/feature-HTTPClientSPI-9b3ab57.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"category": "HTTP Client SPI",
"type": "feature",
"description": "Add `TlsKeyManagersProvider` interface for supporting TLS client auth in HTTP client implementations."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http;

import java.nio.file.Path;
import javax.net.ssl.KeyManager;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.Validate;

/**
* Implementation of {@link FileStoreTlsKeyManagersProvider} that loads a the
* key store from a file.
*/
@SdkPublicApi
public final class FileStoreTlsKeyManagersProvider extends AbstractFileStoreTlsKeyManagersProvider {
private static final Logger log = Logger.loggerFor(FileStoreTlsKeyManagersProvider.class);

private final Path storePath;
private final String storeType;
private final char[] password;

private FileStoreTlsKeyManagersProvider(Path storePath, String storeType, char[] password) {
this.storePath = Validate.paramNotNull(storePath, "storePath");
this.storeType = Validate.paramNotBlank(storeType, "storeType");
this.password = password;
}

@Override
public KeyManager[] keyManagers() {
try {
return createKeyManagers(storePath, storeType, password);
} catch (Exception e) {
log.warn(() -> String.format("Unable to create KeyManagers from file %s", storePath), e);
return null;
}
}

public static FileStoreTlsKeyManagersProvider create(Path path, String type, String password) {
char[] passwordChars = password != null ? password.toCharArray() : null;
return new FileStoreTlsKeyManagersProvider(path, type, passwordChars);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http;

import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.Optional;
import javax.net.ssl.KeyManager;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.internal.http.AbstractFileStoreTlsKeyManagersProvider;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.internal.SystemSettingUtils;

/**
* Implementation of {@link TlsKeyManagersProvider} that gets the information
* about the KeyStore to load from the system properties.
* <p>
* This provider checks the standard {@code javax.net.ssl.keyStore},
* {@code javax.net.ssl.keyStorePassword}, and
* {@code javax.net.ssl.keyStoreType} properties defined by the
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html">JSSE</a>.
*/
@SdkPublicApi
public final class SystemPropertyTlsKeyManagersProvider extends AbstractFileStoreTlsKeyManagersProvider {
private static final Logger log = Logger.loggerFor(SystemPropertyTlsKeyManagersProvider.class);

private SystemPropertyTlsKeyManagersProvider() {
}

@Override
public KeyManager[] keyManagers() {
return getKeyStore().map(p -> {
Path path = Paths.get(p);
String type = getKeyStoreType();
char[] password = getKeyStorePassword().map(String::toCharArray).orElse(null);
try {
return createKeyManagers(path, type, password);
} catch (Exception e) {
log.warn(() -> String.format("Unable to create KeyManagers from %s property value '%s'",
SSL_KEY_STORE.property(), p), e);
return null;
}
}).orElse(null);
}

public static SystemPropertyTlsKeyManagersProvider create() {
return new SystemPropertyTlsKeyManagersProvider();
}

private static Optional<String> getKeyStore() {
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE);
}

private static String getKeyStoreType() {
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE_TYPE)
.orElse(KeyStore.getDefaultType());
}

private static Optional<String> getKeyStorePassword() {
return SystemSettingUtils.resolveSetting(SSL_KEY_STORE_PASSWORD);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http;

import javax.net.ssl.KeyManager;
import software.amazon.awssdk.annotations.SdkPublicApi;

/**
* Provider for the {@link KeyManager key managers} to be used by the SDK when
* creating the SSL context. Key managers are used when the client is required
* to authenticate with the remote TLS peer, such as an HTTP proxy.
*/
@SdkPublicApi
@FunctionalInterface
public interface TlsKeyManagersProvider {

/**
* @return The {@link KeyManager}s, or {@code null}.
*/
KeyManager[] keyManagers();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.internal.http;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.http.TlsKeyManagersProvider;

/**
* Abstract {@link TlsKeyManagersProvider} that loads the key store from a
* a given file path.
*/
@SdkInternalApi
public abstract class AbstractFileStoreTlsKeyManagersProvider implements TlsKeyManagersProvider {

protected final KeyManager[] createKeyManagers(Path storePath, String storeType, char[] password)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException {
KeyStore ks = createKeyStore(storePath, storeType, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
return kmf.getKeyManagers();
}

private KeyStore createKeyStore(Path storePath, String storeType, char[] password)
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
KeyStore ks = KeyStore.getInstance(storeType);
try (InputStream storeIs = Files.newInputStream(storePath)) {
ks.load(storeIs, password);
return ks;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.AfterClass;
import org.junit.BeforeClass;

abstract class ClientTlsAuthTestBase {
protected static final String STORE_PASSWORD = "password";
protected static final String CLIENT_STORE_TYPE = "pkcs12";
protected static final String TEST_KEY_STORE = "/software/amazon/awssdk/http/server-keystore";
protected static final String CLIENT_KEY_STORE = "/software/amazon/awssdk/http/client1.p12";

protected static Path tempDir;
protected static Path serverKeyStore;
protected static Path clientKeyStore;

@BeforeClass
public static void setUp() throws IOException {
tempDir = Files.createTempDirectory(ClientTlsAuthTestBase.class.getSimpleName());
copyCertsToTmpDir();
}

@AfterClass
public static void teardown() throws IOException {
Files.deleteIfExists(serverKeyStore);
Files.deleteIfExists(clientKeyStore);
Files.deleteIfExists(tempDir);
}

private static void copyCertsToTmpDir() throws IOException {
InputStream sksStream = ClientTlsAuthTestBase.class.getResourceAsStream(TEST_KEY_STORE);
Path sks = copyToTmpDir(sksStream, "server-keystore");

InputStream cksStream = ClientTlsAuthTestBase.class.getResourceAsStream(CLIENT_KEY_STORE);
Path cks = copyToTmpDir(cksStream, "client1.p12");

serverKeyStore = sks;
clientKeyStore = cks;
}

private static Path copyToTmpDir(InputStream srcStream, String name) throws IOException {
Path dst = tempDir.resolve(name);
Files.copy(srcStream, dst);
return dst;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.http;

import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.file.Paths;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class FileStoreTlsKeyManagersProviderTest extends ClientTlsAuthTestBase {

@BeforeClass
public static void setUp() throws IOException {
ClientTlsAuthTestBase.setUp();
}

@AfterClass
public static void teardown() throws IOException {
ClientTlsAuthTestBase.teardown();
}

@Test(expected = NullPointerException.class)
public void storePathNull_throwsValidationException() {
FileStoreTlsKeyManagersProvider.create(null, CLIENT_STORE_TYPE, STORE_PASSWORD);
}

@Test(expected = NullPointerException.class)
public void storeTypeNull_throwsValidationException() {
FileStoreTlsKeyManagersProvider.create(clientKeyStore, null, STORE_PASSWORD);
}

@Test(expected = IllegalArgumentException.class)
public void storeTypeEmpty_throwsValidationException() {
FileStoreTlsKeyManagersProvider.create(clientKeyStore, "", STORE_PASSWORD);
}

@Test
public void passwordNotGiven_doesNotThrowValidationException() {
FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, null);
}

@Test
public void paramsValid_createsKeyManager() {
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, STORE_PASSWORD);
assertThat(provider.keyManagers()).hasSize(1);
}

@Test
public void storeDoesNotExist_returnsNull() {
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(Paths.get("does", "not", "exist"), CLIENT_STORE_TYPE, STORE_PASSWORD);
assertThat(provider.keyManagers()).isNull();
}

@Test
public void invalidStoreType_returnsNull() {
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "invalid", STORE_PASSWORD);
assertThat(provider.keyManagers()).isNull();
}

@Test
public void passwordIncorrect_returnsNull() {
FileStoreTlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, CLIENT_STORE_TYPE, "not correct password");
assertThat(provider.keyManagers()).isNull();
}
}
Loading