Skip to content

Commit

Permalink
[AD-1006] Add System and JDK trusted certificates (#441)
Browse files Browse the repository at this point in the history
* [AD-1006] Add System and JDK trusted certificates

* [AD-1006] Correct handling of AutoCloseable InputStreams.

* Commit Code Coverage Badge

Co-authored-by: birschick-bq <birschick-bq@users.noreply.github.com>
  • Loading branch information
Bruce Irschick and birschick-bq authored Nov 16, 2022
1 parent 5ec825c commit e4ee519
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/badges/branches.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ test {
}
}
jacoco {
toolVersion = "0.8.5"
toolVersion = "0.8.8"
}
jacocoTestReport {
reports {
Expand Down Expand Up @@ -335,8 +335,8 @@ dependencies {

testCompileOnly group: 'com.google.code.findbugs', name: 'annotations', version: '3.0.1'
testCompileOnly group: 'com.google.code.findbugs', name: 'annotations', version: '3.0.1'
testCompileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.16'
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.16'
testCompileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.24'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.9.1'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.9.1'
testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.8.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
import java.util.Arrays;
import java.util.concurrent.Executor;

import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getDocumentDbSearchPaths;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getPath;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getSshPrivateKeyFileSearchPaths;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.isNullOrWhitespace;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperty.REFRESH_SCHEMA;
import static software.amazon.documentdb.jdbc.metadata.DocumentDbDatabaseSchemaMetadata.VERSION_LATEST_OR_NEW;
Expand Down Expand Up @@ -389,7 +389,7 @@ private static void addIdentity(
final DocumentDbConnectionProperties connectionProperties,
final JSch jSch) throws JSchException {
final String privateKeyFileName = getPath(connectionProperties.getSshPrivateKeyFile(),
getSshPrivateKeyFileSearchPaths()).toString();
getDocumentDbSearchPaths()).toString();
LOGGER.debug("SSH private key file resolved to '{}'.", privateKeyFileName);
// If passPhrase protected, will need to provide this, too.
final String passPhrase = !isNullOrWhitespace(connectionProperties.getSshPrivateKeyPassphrase())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package software.amazon.documentdb.jdbc;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
Expand All @@ -34,8 +35,7 @@
import software.amazon.documentdb.jdbc.common.utilities.SqlState;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
Expand All @@ -45,8 +45,11 @@
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.Certificate;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
Expand All @@ -67,7 +70,7 @@ public class DocumentDbConnectionProperties extends Properties {
public static final int FETCH_SIZE_DEFAULT = 2000;
public static final String DOCUMENTDB_CUSTOM_OPTIONS = "DOCUMENTDB_CUSTOM_OPTIONS";
private static String classPathLocationName = null;
private static String[] sshPrivateKeyFileSearchPaths = null;
private static String[] documentDbSearchPaths = null;
private static final String DEFAULT_APPLICATION_NAME_KEY = "default.application.name";
private static final String PROPERTIES_FILE_PATH = "/documentdb-jdbc.properties";
static final String DEFAULT_APPLICATION_NAME;
Expand Down Expand Up @@ -106,15 +109,15 @@ public DocumentDbConnectionProperties() {
*
* @return an array of search paths.
*/
static String[] getSshPrivateKeyFileSearchPaths() {
if (sshPrivateKeyFileSearchPaths == null) {
sshPrivateKeyFileSearchPaths = new String[]{
public static String[] getDocumentDbSearchPaths() {
if (documentDbSearchPaths == null) {
documentDbSearchPaths = new String[]{
USER_HOME_PATH_NAME,
DOCUMENTDB_HOME_PATH_NAME,
getClassPathLocation(),
};
}
return sshPrivateKeyFileSearchPaths;
return documentDbSearchPaths.clone();
}

/**
Expand Down Expand Up @@ -1138,7 +1141,7 @@ public boolean enableSshTunnel() {
* @return returns {@code true} if the file exists, {@code false}, otherwise.
*/
public boolean isSshPrivateKeyFileExists() {
return Files.exists(getPath(getSshPrivateKeyFile(), getSshPrivateKeyFileSearchPaths()));
return Files.exists(getPath(getSshPrivateKeyFile(), getDocumentDbSearchPaths()));
}

/**
Expand Down Expand Up @@ -1178,26 +1181,36 @@ private void applyToSslSettings(final SslSettings.Builder builder) {
return;
}

// Handle the tlsCAFile option.
try (InputStream inputStream = getTlsCAFileInputStream()) {
final SSLContext sslContext = SSLFactory.builder()
.withTrustMaterial(CertificateUtils.loadCertificate(inputStream))
.build()
.getSslContext();
builder.context(sslContext);
}
applyCertificateAuthorities(builder);
}

private @Nullable InputStream getTlsCAFileInputStream() throws FileNotFoundException, SQLException {
final InputStream inputStream;
if (Strings.isNullOrEmpty(getTlsCAFilePath())) {
inputStream = getClass().getResourceAsStream(ROOT_PEM_RESOURCE_FILE_NAME);
} else {
private void applyCertificateAuthorities(final SslSettings.Builder builder) throws IOException, SQLException {
final List<Certificate> caCertificates = new ArrayList<>();
// Append embedded CA root certificate(s), and optionally including the tlsCAFile option, if provided.
appendEmbeddedAndOptionalCaCertificates(caCertificates);
// Add the system and JDK trusted certificates.
caCertificates.addAll(CertificateUtils.getSystemTrustedCertificates());
caCertificates.addAll(CertificateUtils.getJdkTrustedCertificates());
// Create the SSL context and apply to the builder.
final SSLContext sslContext = SSLFactory.builder()
.withTrustMaterial(caCertificates)
.build()
.getSslContext();
builder.context(sslContext);
}

@VisibleForTesting
void appendEmbeddedAndOptionalCaCertificates(final List<Certificate> caCertificates) throws IOException, SQLException {
// If provided, add user-specified CA root certificate file.
if (!Strings.isNullOrEmpty(getTlsCAFilePath())) {
final String tlsCAFileName = getTlsCAFilePath();
final Path tlsCAFileNamePath;
tlsCAFileNamePath = getPath(tlsCAFileName);
// Allow certificate file to be found under one the trusted DocumentDB folders
tlsCAFileNamePath = getPath(tlsCAFileName, getDocumentDbSearchPaths());
if (tlsCAFileNamePath.toFile().exists()) {
inputStream = new FileInputStream(tlsCAFileNamePath.toFile());
try (InputStream inputStream = Files.newInputStream(tlsCAFileNamePath)) {
caCertificates.addAll(CertificateUtils.loadCertificate(inputStream));
}
} else {
throw SqlError.createSQLException(
LOGGER,
Expand All @@ -1206,7 +1219,10 @@ private void applyToSslSettings(final SslSettings.Builder builder) {
tlsCAFileNamePath);
}
}
return inputStream;
// Load embedded CA root certificate.
try (InputStream resourceAsStream = getClass().getResourceAsStream(ROOT_PEM_RESOURCE_FILE_NAME)) {
caCertificates.addAll(CertificateUtils.loadCertificate(resourceAsStream));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.Certificate;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
Expand All @@ -41,9 +44,9 @@
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.DOCUMENTDB_CUSTOM_OPTIONS;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.DOCUMENT_DB_SCHEME;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getClassPathLocationName;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getDocumentDbSearchPaths;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getDocumentdbHomePathName;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getPath;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getSshPrivateKeyFileSearchPaths;
import static software.amazon.documentdb.jdbc.DocumentDbConnectionProperties.getUserHomePathName;

public class DocumentDbConnectionPropertiesTest {
Expand Down Expand Up @@ -390,7 +393,7 @@ void testGetPath() throws IOException {
try {
homeTempFilePath = Paths.get(getUserHomePathName(), tempFilename1);
Assertions.assertTrue(homeTempFilePath.toFile().createNewFile());
final Path path3 = getPath(tempFilename1, getSshPrivateKeyFileSearchPaths());
final Path path3 = getPath(tempFilename1, getDocumentDbSearchPaths());
Assertions.assertEquals(Paths.get(getUserHomePathName(), tempFilename1), path3);
} finally {
Assertions.assertTrue(homeTempFilePath != null && homeTempFilePath.toFile().delete());
Expand All @@ -405,7 +408,7 @@ void testGetPath() throws IOException {
Assertions.assertTrue(documentDbDirectory.mkdir());
}
Assertions.assertTrue(documentDbTempFilePath.toFile().createNewFile());
final Path path4 = getPath(tempFilename1, getSshPrivateKeyFileSearchPaths());
final Path path4 = getPath(tempFilename1, getDocumentDbSearchPaths());
Assertions.assertEquals(Paths.get(getDocumentdbHomePathName(), tempFilename1), path4);
} finally {
Assertions.assertTrue(documentDbTempFilePath != null && documentDbTempFilePath.toFile().delete());
Expand All @@ -416,7 +419,7 @@ void testGetPath() throws IOException {
try {
classPathParentTempFilePath = Paths.get(getClassPathLocationName(), tempFilename1);
Assertions.assertTrue(classPathParentTempFilePath.toFile().createNewFile());
final Path path5 = getPath(tempFilename1, getSshPrivateKeyFileSearchPaths());
final Path path5 = getPath(tempFilename1, getDocumentDbSearchPaths());
Assertions.assertEquals(Paths.get(getClassPathLocationName(), tempFilename1), path5);
} finally {
Assertions.assertTrue(classPathParentTempFilePath != null && classPathParentTempFilePath.toFile().delete());
Expand Down Expand Up @@ -473,4 +476,25 @@ public void testDefaultAuthenticationDatabase() throws SQLException {
final MongoClientSettings settings = properties.buildMongoClientSettings();
Assertions.assertEquals("test", settings.getCredential().getSource());
}

@Test
@DisplayName("Tests the appendEmbeddedAndOptionalCaCertificates method")
void testAppendEmbeddedAndOptionalCaCertificates() throws SQLException, IOException {
final Properties info = new Properties();
final String connectionString = "jdbc:documentdb://username:password@localhost/database";
final DocumentDbConnectionProperties properties = DocumentDbConnectionProperties
.getPropertiesFromConnectionString(info, connectionString, DOCUMENT_DB_SCHEME);
final List<Certificate> caCertificates = new ArrayList<>();
properties.appendEmbeddedAndOptionalCaCertificates(caCertificates);
Assertions.assertEquals(1, caCertificates.size());
caCertificates.clear();
properties.setTlsCAFilePath("src/main/resources/rds-ca-2019-root.pem");
properties.appendEmbeddedAndOptionalCaCertificates(caCertificates);
Assertions.assertEquals(2, caCertificates.size());
caCertificates.clear();
properties.setTlsCAFilePath("invalid-path.pem");
Assertions.assertThrows(SQLException.class,
() -> properties.appendEmbeddedAndOptionalCaCertificates(caCertificates));
Assertions.assertEquals(0, caCertificates.size());
}
}

0 comments on commit e4ee519

Please sign in to comment.