From c06800042818573688778d77a5d42f636dfd6648fc2c8e0c8daa2fe73287caa2 Mon Sep 17 00:00:00 2001 From: Michal Vyskocil Date: Thu, 14 Feb 2013 09:26:33 +0000 Subject: [PATCH] - fix bnc#8033332: no ssl certificate hostname checking (CVE-2012-5783) * commons-httpclient-CVE-2012-5783.patch - add jakarta- compat symlinks OBS-URL: https://build.opensuse.org/package/show/Java:packages/apache-commons-httpclient?expand=0&rev=3 --- apache-commons-httpclient.changes | 7 + apache-commons-httpclient.spec | 8 + commons-httpclient-CVE-2012-5783.patch | 212 +++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 commons-httpclient-CVE-2012-5783.patch diff --git a/apache-commons-httpclient.changes b/apache-commons-httpclient.changes index ccf112b..60b3409 100644 --- a/apache-commons-httpclient.changes +++ b/apache-commons-httpclient.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Thu Feb 14 09:10:48 UTC 2013 - mvyskocil@suse.com + +- fix bnc#8033332: no ssl certificate hostname checking (CVE-2012-5783) + * commons-httpclient-CVE-2012-5783.patch +- add jakarta- compat symlinks + ------------------------------------------------------------------- Sun Feb 3 20:07:59 UTC 2013 - p.drouand@gmail.com diff --git a/apache-commons-httpclient.spec b/apache-commons-httpclient.spec index ed3a7f8..d747402 100644 --- a/apache-commons-httpclient.spec +++ b/apache-commons-httpclient.spec @@ -32,6 +32,9 @@ Patch0: %{name}-disablecryptotests.patch # Add OSGi MANIFEST.MF bits Patch1: %{name}-addosgimanifest.patch Patch2: %{name}-encoding.patch +#PATCH-FIX-UPSTREAM: bnc#803332 +#http://svn.apache.org/viewvc?view=revision&revision=483925 +Patch3: commons-httpclient-CVE-2012-5783.patch BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -148,6 +151,8 @@ pushd $RPM_BUILD_ROOT%{_javadir} ln -s apache-commons-httpclient.jar apache-commons-httpclient3.jar ln -s apache-commons-httpclient.jar commons-httpclient3.jar ln -s apache-commons-httpclient.jar commons-httpclient.jar +ln -s apache-commons-httpclient.jar jakarta-commons-httpclient.jar +ln -s apache-commons-httpclient.jar jakarta-commons-httpclient3.jar popd # javadoc @@ -162,6 +167,9 @@ cp -pr src/examples src/contrib $RPM_BUILD_ROOT%{_datadir}/%{name} rm -f dist/docs/{BUILDING,TESTING}.txt ln -s %{_javadocdir}/%{name} dist/docs/apidocs +%clean +rm -rf $RPM_BUILD_ROOT + %files %defattr(0644,root,root,0755) %doc LICENSE.txt README.txt RELEASE_NOTES.txt diff --git a/commons-httpclient-CVE-2012-5783.patch b/commons-httpclient-CVE-2012-5783.patch new file mode 100644 index 0000000..ddd1bb1 --- /dev/null +++ b/commons-httpclient-CVE-2012-5783.patch @@ -0,0 +1,212 @@ +Index: commons-httpclient-3.1/src/java/org/apache/commons/httpclient/protocol/SSLProtocolSocketFactory.java +=================================================================== +--- commons-httpclient-3.1.orig/src/java/org/apache/commons/httpclient/protocol/SSLProtocolSocketFactory.java ++++ commons-httpclient-3.1/src/java/org/apache/commons/httpclient/protocol/SSLProtocolSocketFactory.java +@@ -31,10 +31,17 @@ + package org.apache.commons.httpclient.protocol; + + import java.io.IOException; ++import java.io.InputStream; + import java.net.InetAddress; + import java.net.Socket; + import java.net.UnknownHostException; + ++import java.security.cert.Certificate; ++import java.security.cert.X509Certificate; ++ ++import javax.net.ssl.SSLException; ++import javax.net.ssl.SSLSession; ++import javax.net.ssl.SSLSocket; + import javax.net.ssl.SSLSocketFactory; + + import org.apache.commons.httpclient.ConnectTimeoutException; +@@ -79,12 +86,17 @@ public class SSLProtocolSocketFactory im + InetAddress clientHost, + int clientPort) + throws IOException, UnknownHostException { +- return SSLSocketFactory.getDefault().createSocket( ++ SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket( + host, + port, + clientHost, + clientPort + ); ++ ++ verifyHostName( host, (SSLSocket) socket ); ++ ++ // verifyHostName() didn't blowup - good! ++ return socket; + } + + /** +@@ -124,15 +136,18 @@ public class SSLProtocolSocketFactory im + } + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { +- return createSocket(host, port, localAddress, localPort); ++ SSLSocket socket = (SSLSocket) createSocket(host, port, localAddress, localPort); ++ verifyHostName(host, (SSLSocket) socket); ++ return socket; + } else { + // To be eventually deprecated when migrated to Java 1.4 or above +- Socket socket = ReflectionSocketFactory.createSocket( ++ SSLSocket socket =(SSLSocket) ReflectionSocketFactory.createSocket( + "javax.net.ssl.SSLSocketFactory", host, port, localAddress, localPort, timeout); + if (socket == null) { +- socket = ControllerThreadSocketFactory.createSocket( ++ socket = (SSLSocket) ControllerThreadSocketFactory.createSocket( + this, host, port, localAddress, localPort, timeout); + } ++ verifyHostName(host, (SSLSocket) socket); + return socket; + } + } +@@ -142,10 +157,12 @@ public class SSLProtocolSocketFactory im + */ + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException { +- return SSLSocketFactory.getDefault().createSocket( ++ SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket( + host, + port + ); ++ verifyHostName( host, (SSLSocket) socket ); ++ return socket; + } + + /** +@@ -157,14 +174,133 @@ public class SSLProtocolSocketFactory im + int port, + boolean autoClose) + throws IOException, UnknownHostException { +- return ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket( ++ SSLSocket s = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory.getDefault()).createSocket( + socket, + host, + port, + autoClose + ); ++ verifyHostName( host, (SSLSocket) socket ); ++ ++ // verifyHostName() didn't blowup - good! ++ return s; ++ } ++ ++ private static void verifyHostName( String host, SSLSocket ssl ) ++ throws IOException { ++ if ( host == null ) { ++ throw new NullPointerException( "host to verify was null" ); ++ } ++ ++ SSLSession session = ssl.getSession(); ++ if ( session == null ) { ++ // In our experience this only happens under IBM 1.4.x when ++ // spurious (unrelated) certificates show up in the server's chain. ++ // Hopefully this will unearth the real problem: ++ InputStream in = ssl.getInputStream(); ++ in.available(); ++ /* ++ If you're looking at the 2 lines of code above because you're ++ running into a problem, you probably have two options: ++ ++ #1. Clean up the certificate chain that your server ++ is presenting (e.g. edit "/etc/apache2/server.crt" or ++ wherever it is your server's certificate chain is ++ defined). ++ ++ OR ++ ++ #2. Upgrade to an IBM 1.5.x or greater JVM, or switch to a ++ non-IBM JVM. ++ */ ++ ++ // If ssl.getInputStream().available() didn't cause an exception, ++ // maybe at least now the session is available? ++ session = ssl.getSession(); ++ if ( session == null ) { ++ // If it's still null, probably a startHandshake() will ++ // unearth the real problem. ++ ssl.startHandshake(); ++ ++ // Okay, if we still haven't managed to cause an exception, ++ // might as well go for the NPE. Or maybe we're okay now? ++ session = ssl.getSession(); ++ } ++ } ++ ++ Certificate[] certs = session.getPeerCertificates(); ++ X509Certificate x509 = (X509Certificate) certs[ 0 ]; ++ String cn = getCN( x509 ); ++ if ( cn == null ) { ++ String subject = x509.getSubjectX500Principal().toString(); ++ String msg = "certificate doesn't contain CN: " + subject; ++ throw new SSLException( msg ); ++ } ++ // I'm okay with being case-insensitive when comparing the host we used ++ // to establish the socket to the hostname in the certificate. ++ // Don't trim the CN, though. ++ cn = cn.toLowerCase(); ++ host = host.trim().toLowerCase(); ++ boolean doWildcard = false; ++ if ( cn.startsWith( "*." ) ) { ++ // The CN better have at least two dots if it wants wildcard action, ++ // but can't be [*.co.uk] or [*.co.jp] or [*.org.uk], etc... ++ String withoutCountryCode = ""; ++ if ( cn.length() >= 7 && cn.length() <= 9 ) { ++ withoutCountryCode = cn.substring( 2, cn.length() - 2 ); ++ } ++ doWildcard = cn.lastIndexOf( '.' ) >= 0 && ++ !"ac.".equals( withoutCountryCode ) && ++ !"co.".equals( withoutCountryCode ) && ++ !"com.".equals( withoutCountryCode ) && ++ !"ed.".equals( withoutCountryCode ) && ++ !"edu.".equals( withoutCountryCode ) && ++ !"go.".equals( withoutCountryCode ) && ++ !"gouv.".equals( withoutCountryCode ) && ++ !"gov.".equals( withoutCountryCode ) && ++ !"info.".equals( withoutCountryCode ) && ++ !"lg.".equals( withoutCountryCode ) && ++ !"ne.".equals( withoutCountryCode ) && ++ !"net.".equals( withoutCountryCode ) && ++ !"or.".equals( withoutCountryCode ) && ++ !"org.".equals( withoutCountryCode ); ++ ++ // The [*.co.uk] problem is an interesting one. Should we just ++ // hope that CA's would never foolishly allow such a ++ // certificate to happen? ++ } ++ ++ boolean match; ++ if ( doWildcard ) { ++ match = host.endsWith( cn.substring( 1 ) ); ++ } else { ++ match = host.equals( cn ); ++ } ++ if ( !match ) { ++ throw new SSLException( "hostname in certificate didn't match: <" + host + "> != <" + cn + ">" ); ++ } + } + ++ private static String getCN( X509Certificate cert ) { ++ // Note: toString() seems to do a better job than getName() ++ // ++ // For example, getName() gives me this: ++ // 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d ++ // ++ // whereas toString() gives me this: ++ // EMAILADDRESS=juliusdavies@cucbc.com ++ String subjectPrincipal = cert.getSubjectX500Principal().toString(); ++ int x = subjectPrincipal.indexOf( "CN=" ); ++ if ( x >= 0 ) { ++ int y = subjectPrincipal.indexOf( ',', x ); ++ // If there are no more commas, then CN= is the last entry. ++ y = ( y >= 0 ) ? y : subjectPrincipal.length(); ++ return subjectPrincipal.substring( x + 3, y ); ++ } else { ++ return null; ++ } ++ } ++ + /** + * All instances of SSLProtocolSocketFactory are the same. + */