Skip to content

Commit

Permalink
Add Java TLS Cipher API (#74)
Browse files Browse the repository at this point in the history
* Add TLS Cipher API

* Update Dependencies to use PostQuantum Compatible Versions
  • Loading branch information
alexw91 authored and Justin Boswell committed Aug 1, 2019
1 parent e3b644a commit 3f17283
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 23 deletions.
4 changes: 2 additions & 2 deletions aws-common-runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if (UNIX AND NOT APPLE)
endif()

set(AWS_C_IO_URL "https://github.com/awslabs/aws-c-io.git")
set(AWS_C_IO_SHA "v0.4.1")
set(AWS_C_IO_SHA "v0.4.2")
include(BuildAwsCIO)

set(AWS_C_COMPRESSION_URL "https://github.com/awslabs/aws-c-compression.git")
Expand All @@ -42,7 +42,7 @@ set(AWS_C_MQTT_SHA "v0.4.1")
include(BuildAwsCMqtt)

set(AWS_C_HTTP_URL "https://github.com/awslabs/aws-c-http.git")
set(AWS_C_HTTP_SHA "v0.3.2")
set(AWS_C_HTTP_SHA "v0.3.3")
include(BuildAwsCHttp)

add_dependencies(AwsCCompression AwsCCommon)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2018 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.crt.io;

public enum TlsCipherPreference {
/**
* Use whatever the System Default Preference is. This is usually the best option, as it will be automatically
* updated as the underlying OS or platform changes.
*/
TLS_CIPHER_SYSTEM_DEFAULT(0),

/**
* Contains Draft Hybrid TLS Ciphers: https://tools.ietf.org/html/draft-campagna-tls-bike-sike-hybrid
*
* These ciphers perform two Key Exchanges (1 ECDHE + 1 Post-Quantum) during the TLS Handshake in order to
* combine the security of Classical ECDHE Key Exchange with the conjectured quantum-resistance of newly
* proposed key exchanges.
*
* The algorithms these new Post-Quantum ciphers are based on have been submitted to NIST's Post-Quantum Crypto
* Standardization Process, and are still under review.
*
* This Cipher Preference may stop being supported at any time.
*/
TLS_CIPHER_KMS_PQ_TLSv1_0_2019_06(1);

private int val;

TlsCipherPreference(int val) {
this.val = val;
}

int getValue() { return val; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public enum TlsVersions {
int getValue() { return version; }
}

private TlsVersions tlsVersion = TlsVersions.TLS_VER_SYS_DEFAULTS;
private TlsCipherPreference tlsCipherPreference = TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT;

/**
* Creates a new set of options that can be used to create a {@link TlsContext}
* @throws CrtRuntimeException If the system is not able to allocate space for a native tls context options structure
Expand All @@ -75,11 +78,18 @@ public void close() {
}

/**
* Sets the minimum acceptable TLS version that the {@link TlsContext} will allow
* Sets the minimum acceptable TLS version that the {@link TlsContext} will allow. Not compatible with
* setCipherPreference() API.
*
* @param version Select from TlsVersions, a good default is TlsVersions.TLS_VER_SYS_DEFAULTS
* as this will update if the OS TLS is updated
*/
public void setMinimumTlsVersion(TlsVersions version) {
if (this.tlsCipherPreference != TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT && version != TlsVersions.TLS_VER_SYS_DEFAULTS) {
throw new IllegalArgumentException("Currently only setMinimumTlsVersion() or setCipherPreference() may be used, not both.");
}

this.tlsVersion = version;
tlsContextOptionsSetMinimumTlsVersion(native_ptr(), version.getValue());
}

Expand All @@ -92,6 +102,25 @@ public void setAlpnList(String alpn) {
tlsContextOptionsSetAlpn(native_ptr(), alpn);
}

/**
* Sets the TLS Cipher Preferences that can be negotiated and used during the TLS Connection. Not compatible with
* setMinimumTlsVersion() API.
*
* @param cipherPref The Cipher Preference to use
*/
public void setCipherPreference(TlsCipherPreference cipherPref) {
if(!isCipherPreferenceSupported(cipherPref)) {
throw new IllegalArgumentException("TlsCipherPreference is not supported on this platform: " + cipherPref.name());
}

if (this.tlsVersion != TlsVersions.TLS_VER_SYS_DEFAULTS && cipherPref != TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT) {
throw new IllegalArgumentException("Currently only setMinimumTlsVersion() or setCipherPreference() may be used, not both.");
}

this.tlsCipherPreference = cipherPref;
tlsContextOptionsSetCipherPreference(native_ptr(), cipherPref.getValue());
}

/**
* Sets the path to the certificate that identifies this TLS host. Must be in PEM format.
* @param certificatePath Path to PEM format certificate
Expand Down Expand Up @@ -129,6 +158,15 @@ public static boolean isAlpnSupported() {
return tlsContextOptionsIsAlpnAvailable();
}

/**
* Returns whether or not the current platform can be configured to a specific TlsCipherPreference.
* @param cipherPref The TlsCipherPreference to check
* @return True if the current platform does support this TlsCipherPreference, false otherwise
*/
public static boolean isCipherPreferenceSupported(TlsCipherPreference cipherPref) {
return tlsContextOptionsIsCipherPreferenceSupported(cipherPref.getValue());
}

/**
* Helper function to provide a TlsContext-local trust store
* @param caPath Path to the local trust store. Can be null.
Expand Down Expand Up @@ -173,6 +211,25 @@ public static TlsContextOptions createWithMTLSPkcs12(String pkcs12Path, String p
return options;
}

/*******************************************************************************
* .with() methods
******************************************************************************/

public TlsContextOptions withCipherPreference(TlsCipherPreference cipherPref) {
setCipherPreference(cipherPref);
return this;
}

public TlsContextOptions withMinimumTlsVersion(TlsVersions version) {
setMinimumTlsVersion(version);
return this;
}

public TlsContextOptions withAlpnList(String alpnList) {
setAlpnList(alpnList);
return this;
}

/*******************************************************************************
* native methods
******************************************************************************/
Expand All @@ -186,11 +243,16 @@ public static TlsContextOptions createWithMTLSPkcs12(String pkcs12Path, String p

private static native void tlsContextOptionsSetAlpn(long tls, String alpn);

private static native void tlsContextOptionsSetCipherPreference(long tls, int cipherPref);

private static native void tlsContextOptionsInitMTLSFromPath(long tls, String cert_path, String key_path);

private static native void tlsContextOptionsInitMTLSPkcs12FromPath(long tls, String pkcs12_path, String pkcs12_password);

private static native void tlsContextOptionsSetVerifyPeer(long tls, boolean verify);

private static native boolean tlsContextOptionsIsAlpnAvailable();

private static native boolean tlsContextOptionsIsCipherPreferenceSupported(int cipherPref);

};
45 changes: 45 additions & 0 deletions src/native/tls_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,31 @@ void JNICALL Java_software_amazon_awssdk_crt_io_TlsContextOptions_tlsContextOpti
aws_tls_ctx_options_set_alpn_list(&tls->options, (const char *)aws_string_bytes(tls->alpn_list));
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_io_TlsContextOptions_tlsContextOptionsSetCipherPreference(
JNIEnv *env,
jclass jni_class,
jlong jni_tls,
jint jni_cipher_pref) {

(void)jni_class;
struct jni_tls_ctx_options *tls = (struct jni_tls_ctx_options *)jni_tls;

if (!tls) {
return;
}

if (jni_cipher_pref < 0 || AWS_IO_TLS_CIPHER_PREF_END_RANGE <= jni_cipher_pref) {
aws_jni_throw_runtime_exception(
env,
"TlsContextOptions.tlsContextOptionsSetCipherPreference: TlsCipherPreference is out of range: %d",
(int)jni_cipher_pref);
return;
}

tls->options.cipher_pref = (enum aws_tls_cipher_pref)jni_cipher_pref;
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_io_TlsContextOptions_tlsContextOptionsInitMTLSFromPath(
JNIEnv *env,
Expand Down Expand Up @@ -243,6 +268,26 @@ jboolean JNICALL Java_software_amazon_awssdk_crt_io_TlsContextOptions_tlsContext
return aws_tls_is_alpn_available();
}

JNIEXPORT
jboolean JNICALL Java_software_amazon_awssdk_crt_io_TlsContextOptions_tlsContextOptionsIsCipherPreferenceSupported(
JNIEnv *env,
jclass jni_class,
jint jni_cipher_pref) {

(void)env;
(void)jni_class;

if (jni_cipher_pref < 0 || AWS_IO_TLS_CIPHER_PREF_END_RANGE <= jni_cipher_pref) {
aws_jni_throw_runtime_exception(
env,
"TlsContextOptions.tlsContextOptionsSetCipherPreference: TlsCipherPreference is out of range: %d",
(int)jni_cipher_pref);
return false;
}

return aws_tls_is_cipher_pref_supported((enum aws_tls_cipher_pref)jni_cipher_pref);
}

#if UINTPTR_MAX == 0xffffffff
# if defined(_MSC_VER)
# pragma warning(pop)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,79 @@
import software.amazon.awssdk.crt.http.HttpConnection;
import software.amazon.awssdk.crt.io.ClientBootstrap;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.io.TlsCipherPreference;
import software.amazon.awssdk.crt.io.TlsContext;

import java.net.URI;
import software.amazon.awssdk.crt.io.TlsContextOptions;

public class HttpConnectionTest {

protected void testConnection(URI uri, boolean expectConnected, String exceptionMsg) throws CrtRuntimeException {
private class HttpConnectionTestResponse {
boolean actuallyConnected = false;
boolean exceptionThrown = false;
Exception exception = null;
}

ClientBootstrap bootstrap = new ClientBootstrap(1);
SocketOptions sockOpts = new SocketOptions();
TlsContext tlsContext = new TlsContext();

private HttpConnectionTestResponse testConnection(URI uri, ClientBootstrap bootstrap, SocketOptions sockOpts, TlsContext tlsContext) {
HttpConnectionTestResponse resp = new HttpConnectionTestResponse();
try {
HttpConnection conn = HttpConnection.createConnection(uri, bootstrap, sockOpts, tlsContext).get();
actuallyConnected = true;
resp.actuallyConnected = true;
conn.shutdown().get();
conn.close();
} catch (Exception e) {
exceptionThrown = true;
Assert.assertTrue(e.getMessage(), e.getMessage().contains(exceptionMsg));
resp.exceptionThrown = true;
resp.exception = e;

} finally {
tlsContext.close();
sockOpts.close();
bootstrap.close();
}

Assert.assertEquals("URI: " + uri.toString(), expectConnected, actuallyConnected);
Assert.assertEquals("URI: " + uri.toString(), expectConnected, !exceptionThrown);
Assert.assertEquals(0, CrtResource.getAllocatedNativeResourceCount());
return resp;
}

private void testConnectionWithAllCiphers(URI uri, boolean expectConnected, String exceptionMsg) throws CrtRuntimeException {
for (TlsCipherPreference pref: TlsCipherPreference.values()) {
if (!TlsContextOptions.isCipherPreferenceSupported(pref)) {
continue;
}

TlsContextOptions tlsOpts = new TlsContextOptions().withCipherPreference(pref);
HttpConnectionTestResponse resp = testConnection(uri, new ClientBootstrap(1), new SocketOptions(), new TlsContext(tlsOpts));
tlsOpts.close();

Assert.assertEquals("URI: " + uri.toString(), expectConnected, resp.actuallyConnected);
Assert.assertEquals("URI: " + uri.toString(), expectConnected, !resp.exceptionThrown);
if (resp.exception != null) {
Assert.assertTrue(resp.exception.getMessage(), resp.exception.getMessage().contains(exceptionMsg));
}

Assert.assertEquals(0, CrtResource.getAllocatedNativeResourceCount());
}
}

@Test
public void testHttpConnection() throws Exception {
testConnection(new URI("https://aws-crt-test-stuff.s3.amazonaws.com"), true, null);
testConnection(new URI("http://aws-crt-test-stuff.s3.amazonaws.com"), true, null);
testConnection(new URI("http://aws-crt-test-stuff.s3.amazonaws.com:80"), true, null);
testConnection(new URI("http://aws-crt-test-stuff.s3.amazonaws.com:443"), true, null);
testConnection(new URI("https://aws-crt-test-stuff.s3.amazonaws.com:443"), true, null);
testConnection(new URI("https://rsa2048.badssl.com/"), true, null);
testConnection(new URI("http://http.badssl.com/"), true, null);
testConnection(new URI("https://expired.badssl.com/"), false, "TLS (SSL) negotiation failed");
testConnection(new URI("https://self-signed.badssl.com/"), false, "TLS (SSL) negotiation failed");
// S3
testConnectionWithAllCiphers(new URI("https://aws-crt-test-stuff.s3.amazonaws.com"), true, null);
testConnectionWithAllCiphers(new URI("http://aws-crt-test-stuff.s3.amazonaws.com"), true, null);
testConnectionWithAllCiphers(new URI("http://aws-crt-test-stuff.s3.amazonaws.com:80"), true, null);
testConnectionWithAllCiphers(new URI("http://aws-crt-test-stuff.s3.amazonaws.com:443"), true, null);
testConnectionWithAllCiphers(new URI("https://aws-crt-test-stuff.s3.amazonaws.com:443"), true, null);

// KMS
testConnectionWithAllCiphers(new URI("https://kms.us-east-1.amazonaws.com:443"), true, null);
testConnectionWithAllCiphers(new URI("https://kms-fips.us-east-1.amazonaws.com:443"), true, null);
testConnectionWithAllCiphers(new URI("https://kms.us-west-2.amazonaws.com:443"), true, null);
testConnectionWithAllCiphers(new URI("https://kms-fips.us-west-2.amazonaws.com:443"), true, null);

// BadSSL
testConnectionWithAllCiphers(new URI("https://rsa2048.badssl.com/"), true, null);
testConnectionWithAllCiphers(new URI("http://http.badssl.com/"), true, null);
testConnectionWithAllCiphers(new URI("https://expired.badssl.com/"), false, "TLS (SSL) negotiation failed");
testConnectionWithAllCiphers(new URI("https://self-signed.badssl.com/"), false, "TLS (SSL) negotiation failed");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package software.amazon.awssdk.crt.test;

import static software.amazon.awssdk.crt.io.TlsContextOptions.TlsVersions;

import org.junit.Assert;
import org.junit.Test;
import software.amazon.awssdk.crt.CrtResource;
import software.amazon.awssdk.crt.io.TlsCipherPreference;
import software.amazon.awssdk.crt.io.TlsContextOptions;

public class TlsContextOptionsTest {

@Test
public void testTlsContextOptionsAPI() {
Assert.assertEquals(0, CrtResource.getAllocatedNativeResourceCount());

TlsContextOptions options = new TlsContextOptions();

for (TlsVersions tlsVersion: TlsContextOptions.TlsVersions.values()) {
options.setMinimumTlsVersion(tlsVersion);
}

options.setMinimumTlsVersion(TlsVersions.TLS_VER_SYS_DEFAULTS);

for (TlsCipherPreference pref: TlsCipherPreference.values()) {
if (TlsContextOptions.isCipherPreferenceSupported(pref)) {
options.setCipherPreference(pref);
}
}

boolean exceptionThrown = false;

try {
options.setCipherPreference(TlsCipherPreference.TLS_CIPHER_KMS_PQ_TLSv1_0_2019_06);
options.setMinimumTlsVersion(TlsVersions.TLSv1_2);
Assert.fail();
} catch (IllegalArgumentException e) {
exceptionThrown = true;
}

Assert.assertTrue(exceptionThrown);

options.close();
Assert.assertEquals(0, CrtResource.getAllocatedNativeResourceCount());
}
}

0 comments on commit 3f17283

Please sign in to comment.