Skip to content

Commit

Permalink
Merge pull request #4489 from square/jwilson.1229.no_server_certs
Browse files Browse the repository at this point in the history
Exercise TLS with no server certificates
  • Loading branch information
swankjesse authored Dec 30, 2018
2 parents 2c3eb51 + c682317 commit 275d71b
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 5 deletions.
101 changes: 101 additions & 0 deletions okhttp-tests/src/test/java/okhttp3/CallTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.junit.rules.Timeout;

import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
import static okhttp3.CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256;
import static okhttp3.TestUtil.awaitGarbageCollection;
import static okhttp3.TestUtil.defaultClient;
import static okhttp3.tls.internal.TlsUtil.localhost;
Expand Down Expand Up @@ -1180,6 +1181,96 @@ private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exc
}
}

/**
* When the server doesn't present any certificates we fail the TLS handshake. This test requires
* that the client and server are each configured with a cipher suite that permits the server to
* be unauthenticated.
*/
@Test public void tlsSuccessWithNoPeerCertificates() throws Exception {
server.enqueue(new MockResponse()
.setBody("abc"));

// The _anon_ cipher suites don't require server certificates.
CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256;

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.build();
client = client.newBuilder()
.sslSocketFactory(
socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite),
clientCertificates.trustManager())
.connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuite)
.build()))
.hostnameVerifier(new RecordingHostnameVerifier())
.build();

HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
.build();
server.useHttps(socketFactoryWithCipherSuite(
serverCertificates.sslSocketFactory(), cipherSuite), false);

Call call = client.newCall(new Request.Builder()
.url(server.url("/"))
.build());
Response response = call.execute();
assertEquals("abc", response.body().string());
assertNull(response.handshake().peerPrincipal());
assertEquals(Collections.emptyList(), response.handshake().peerCertificates());
assertEquals(cipherSuite, response.handshake().cipherSuite());
}

@Test public void tlsHostnameVerificationFailure() throws Exception {
server.enqueue(new MockResponse());

HeldCertificate serverCertificate = new HeldCertificate.Builder()
.commonName("localhost") // Unusued for hostname verification.
.addSubjectAlternativeName("wronghostname")
.build();

HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
.heldCertificate(serverCertificate)
.build();

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(serverCertificate.certificate())
.build();

client = client.newBuilder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
.build();
server.useHttps(serverCertificates.sslSocketFactory(), false);

executeSynchronously("/")
.assertFailureMatches("(?s)Hostname localhost not verified.*");
}

@Test public void tlsHostnameVerificationFailureNoPeerCertificates() throws Exception {
server.enqueue(new MockResponse());

// The _anon_ cipher suites don't require server certificates.
CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256;

HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
.build();
client = client.newBuilder()
.sslSocketFactory(
socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite),
clientCertificates.trustManager())
.connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuite)
.build()))
.build();

HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
.build();
server.useHttps(socketFactoryWithCipherSuite(
serverCertificates.sslSocketFactory(), cipherSuite), false);

executeSynchronously("/")
.assertFailure("Hostname localhost not verified (no certificates)");
}

@Test public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception {
// Configure the client with only TLS configurations. No cleartext!
client = client.newBuilder()
Expand Down Expand Up @@ -3564,6 +3655,16 @@ private Thread cancelLater(final Call call, final long delay) {
return thread;
}

private SSLSocketFactory socketFactoryWithCipherSuite(
final SSLSocketFactory sslSocketFactory, final CipherSuite cipherSuite) {
return new DelegatingSSLSocketFactory(sslSocketFactory) {
@Override protected SSLSocket configureSocket(SSLSocket sslSocket) throws IOException {
sslSocket.setEnabledCipherSuites(new String[] { cipherSuite.javaName() });
return super.configureSocket(sslSocket);
}
};
}

private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory {

private List<SSLSocket> socketsCreated = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownServiceException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -323,11 +324,18 @@ private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IO

// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
if (!peerCertificates.isEmpty()) {
X509Certificate cert = (X509Certificate) peerCertificates.get(0);
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
} else {
throw new SSLPeerUnverifiedException(
"Hostname " + address.url().host() + " not verified (no certificates)");
}
}

// Check that the certificate pinner is satisfied by the certificates presented.
Expand Down

0 comments on commit 275d71b

Please sign in to comment.