Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Use a hostname verifier that does hostname verification, backport #510,
close #197
- Loading branch information
Stephane Landelle
committed
Jul 10, 2014
1 parent
899fd7a
commit a894583
Showing
6 changed files
with
255 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
src/main/java/com/ning/http/util/DefaultHostnameVerifier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* To the extent possible under law, Kevin Locke has waived all copyright and | ||
* related or neighboring rights to this work. | ||
* <p/> | ||
* A legal description of this waiver is available in <a href="https://gist.github.com/kevinoid/3829665">LICENSE.txt</a> | ||
*/ | ||
package com.ning.http.util; | ||
|
||
import javax.net.ssl.HostnameVerifier; | ||
import javax.net.ssl.SSLPeerUnverifiedException; | ||
import javax.net.ssl.SSLSession; | ||
import javax.security.auth.kerberos.KerberosPrincipal; | ||
import java.security.Principal; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Uses the internal HostnameChecker to verify the server's hostname matches with the | ||
* certificate. This is a requirement for HTTPS, but the raw SSLEngine does not have | ||
* this functionality. As such, it has to be added in manually. For a more complete | ||
* description of hostname verification and why it's important, | ||
* please read | ||
* <a href="http://tersesystems.com/2014/03/23/fixing-hostname-verification/">Fixing | ||
* Hostname Verification</a>. | ||
* <p/> | ||
* This code is based on Kevin Locke's <a href="http://kevinlocke.name/bits/2012/10/03/ssl-certificate-verification-in-dispatch-and-asynchttpclient/">guide</a> . | ||
* <p/> | ||
*/ | ||
public class DefaultHostnameVerifier implements HostnameVerifier { | ||
|
||
private HostnameChecker checker; | ||
|
||
private HostnameVerifier extraHostnameVerifier; | ||
|
||
// Logger to log exceptions. | ||
private static final Logger log = Logger.getLogger(DefaultHostnameVerifier.class.getName()); | ||
|
||
/** | ||
* A hostname verifier that uses the {{sun.security.util.HostnameChecker}} under the hood. | ||
*/ | ||
public DefaultHostnameVerifier() { | ||
this.checker = new ProxyHostnameChecker(); | ||
} | ||
|
||
/** | ||
* A hostname verifier that takes an external hostname checker. Useful for testing. | ||
* | ||
* @param checker a hostnamechecker. | ||
*/ | ||
public DefaultHostnameVerifier(HostnameChecker checker) { | ||
this.checker = checker; | ||
} | ||
|
||
/** | ||
* A hostname verifier that falls back to another hostname verifier if not found. | ||
* | ||
* @param extraHostnameVerifier another hostname verifier. | ||
*/ | ||
public DefaultHostnameVerifier(HostnameVerifier extraHostnameVerifier) { | ||
this.checker = new ProxyHostnameChecker(); | ||
this.extraHostnameVerifier = extraHostnameVerifier; | ||
} | ||
|
||
/** | ||
* A hostname verifier with a hostname checker, that falls back to another hostname verifier if not found. | ||
* | ||
* @param checker a custom HostnameChecker. | ||
* @param extraHostnameVerifier another hostname verifier. | ||
*/ | ||
public DefaultHostnameVerifier(HostnameChecker checker, HostnameVerifier extraHostnameVerifier) { | ||
this.checker = checker; | ||
this.extraHostnameVerifier = extraHostnameVerifier; | ||
} | ||
|
||
/** | ||
* Matches the hostname against the peer certificate in the session. | ||
* | ||
* @param hostname the IP address or hostname of the expected server. | ||
* @param session the SSL session containing the certificates with the ACTUAL hostname/ipaddress. | ||
* @return true if the hostname matches, false otherwise. | ||
*/ | ||
private boolean hostnameMatches(String hostname, SSLSession session) { | ||
log.log(Level.FINE, "hostname = {0}, session = {1}", new Object[] { hostname, Base64.encode(session.getId()) }); | ||
|
||
try { | ||
final Certificate[] peerCertificates = session.getPeerCertificates(); | ||
if (peerCertificates.length == 0) { | ||
log.log(Level.FINE, "No peer certificates"); | ||
return false; | ||
} | ||
|
||
if (peerCertificates[0] instanceof X509Certificate) { | ||
X509Certificate peerCertificate = (X509Certificate) peerCertificates[0]; | ||
log.log(Level.FINE, "peerCertificate = {0}", peerCertificate); | ||
try { | ||
checker.match(hostname, peerCertificate); | ||
// Certificate matches hostname if no exception is thrown. | ||
return true; | ||
} catch (CertificateException ex) { | ||
log.log(Level.FINE, "Certificate does not match hostname", ex); | ||
} | ||
} else { | ||
log.log(Level.FINE, "Peer does not have any certificates or they aren't X.509"); | ||
} | ||
return false; | ||
} catch (SSLPeerUnverifiedException ex) { | ||
log.log(Level.FINE, "Not using certificates for peers, try verifying the principal"); | ||
try { | ||
Principal peerPrincipal = session.getPeerPrincipal(); | ||
log.log(Level.FINE, "peerPrincipal = {0}", peerPrincipal); | ||
if (peerPrincipal instanceof KerberosPrincipal) { | ||
return checker.match(hostname, (KerberosPrincipal) peerPrincipal); | ||
} else { | ||
log.log(Level.FINE, "Can't verify principal, not Kerberos"); | ||
} | ||
} catch (SSLPeerUnverifiedException ex2) { | ||
// Can't verify principal, no principal | ||
log.log(Level.FINE, "Can't verify principal, no principal", ex2); | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* Verifies the hostname against the peer certificates in a session. Falls back to extraHostnameVerifier if | ||
* there is no match. | ||
* | ||
* @param hostname the IP address or hostname of the expected server. | ||
* @param session the SSL session containing the certificates with the ACTUAL hostname/ipaddress. | ||
* @return true if the hostname matches, false otherwise. | ||
*/ | ||
public boolean verify(String hostname, SSLSession session) { | ||
if (hostnameMatches(hostname, session)) { | ||
return true; | ||
} else { | ||
return extraHostnameVerifier != null && extraHostnameVerifier.verify(hostname, session); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright (c) Will Sargent. All rights reserved. | ||
* | ||
* This program is licensed to you under the Apache License Version 2.0, | ||
* and you may not use this file except in compliance with the Apache License Version 2.0. | ||
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the Apache License Version 2.0 is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. | ||
*/ | ||
package com.ning.http.util; | ||
|
||
import java.security.Principal; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
|
||
/** | ||
* Hostname checker interface. | ||
*/ | ||
public interface HostnameChecker { | ||
|
||
public void match(String hostname, X509Certificate peerCertificate) throws CertificateException; | ||
|
||
public boolean match(String hostname, Principal principal); | ||
} |
83 changes: 83 additions & 0 deletions
83
src/main/java/com/ning/http/util/ProxyHostnameChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright (c) Will Sargent. All rights reserved. | ||
* | ||
* This program is licensed to you under the Apache License Version 2.0, | ||
* and you may not use this file except in compliance with the Apache License Version 2.0. | ||
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the Apache License Version 2.0 is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. | ||
*/ | ||
package com.ning.http.util; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.security.Principal; | ||
import java.security.cert.CertificateException; | ||
import java.security.cert.X509Certificate; | ||
|
||
/** | ||
* A HostnameChecker proxy. | ||
*/ | ||
public class ProxyHostnameChecker implements HostnameChecker { | ||
|
||
public final static byte TYPE_TLS = 1; | ||
|
||
private final Object checker = getHostnameChecker(); | ||
|
||
public ProxyHostnameChecker() { | ||
} | ||
|
||
private Object getHostnameChecker() { | ||
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); | ||
try { | ||
final Class<Object> hostnameCheckerClass = (Class<Object>) classLoader.loadClass("sun.security.util.HostnameChecker"); | ||
final Method instanceMethod = hostnameCheckerClass.getMethod("getInstance", Byte.TYPE); | ||
return instanceMethod.invoke(null, TYPE_TLS); | ||
} catch (ClassNotFoundException e) { | ||
throw new IllegalStateException(e); | ||
} catch (NoSuchMethodException e) { | ||
throw new IllegalStateException(e); | ||
} catch (InvocationTargetException e) { | ||
throw new IllegalStateException(e); | ||
} catch (IllegalAccessException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
public void match(String hostname, X509Certificate peerCertificate) throws CertificateException { | ||
try { | ||
final Class<?> hostnameCheckerClass = checker.getClass(); | ||
final Method checkMethod = hostnameCheckerClass.getMethod("match", String.class, X509Certificate.class); | ||
checkMethod.invoke(checker, hostname, peerCertificate); | ||
} catch (NoSuchMethodException e) { | ||
throw new IllegalStateException(e); | ||
} catch (InvocationTargetException e) { | ||
Throwable t = e.getCause(); | ||
if (t instanceof CertificateException) { | ||
throw (CertificateException) t; | ||
} else { | ||
throw new IllegalStateException(e); | ||
} | ||
} catch (IllegalAccessException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
public boolean match(String hostname, Principal principal) { | ||
try { | ||
final Class<?> hostnameCheckerClass = checker.getClass(); | ||
final Method checkMethod = hostnameCheckerClass.getMethod("match", String.class, Principal.class); | ||
return (Boolean) checkMethod.invoke(null, hostname, principal); | ||
} catch (NoSuchMethodException e) { | ||
throw new IllegalStateException(e); | ||
} catch (InvocationTargetException e) { | ||
throw new IllegalStateException(e); | ||
} catch (IllegalAccessException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} | ||
|
||
} |
Binary file not shown.
Binary file not shown.