cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cohei...@apache.org
Subject [1/2] cxf git commit: Updating HostnameVerifier as per recent changes in httpclient
Date Fri, 08 Jan 2016 16:53:49 GMT
Repository: cxf
Updated Branches:
  refs/heads/3.0.x-fixes 5c8e6c86e -> 6cdfe4bab


Updating HostnameVerifier as per recent changes in httpclient


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/99276baf
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/99276baf
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/99276baf

Branch: refs/heads/3.0.x-fixes
Commit: 99276baf0a2e6f8aaa08586d21ed905c5cce574e
Parents: 5c8e6c8
Author: Colm O hEigeartaigh <coheigea@apache.org>
Authored: Fri Jan 8 16:48:43 2016 +0000
Committer: Colm O hEigeartaigh <coheigea@apache.org>
Committed: Fri Jan 8 16:50:15 2016 +0000

----------------------------------------------------------------------
 .../httpclient/DefaultHostnameVerifier.java     |  71 ++++++-------
 .../transport/https/httpclient/DomainType.java  |  37 +++++++
 .../https/httpclient/PublicSuffixList.java      |  11 +-
 .../httpclient/PublicSuffixListParser.java      | 105 ++++++++++++++-----
 .../https/httpclient/PublicSuffixMatcher.java   |  99 ++++++++++++++---
 .../httpclient/DefaultHostnameVerifierTest.java |  14 ++-
 6 files changed, 254 insertions(+), 83 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
index 8fb067f..5d3287c 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifier.java
@@ -58,6 +58,8 @@ import org.apache.cxf.common.logging.LogUtils;
  */
 public final class DefaultHostnameVerifier implements HostnameVerifier {
     
+    enum TYPE { IPv4, IPv6, DNS };
+    
     static final int DNS_NAME_TYPE = 2;
     static final int IP_ADDRESS_TYPE = 7;
     
@@ -90,16 +92,29 @@ public final class DefaultHostnameVerifier implements HostnameVerifier
{
 
     public void verify(
             final String host, final X509Certificate cert) throws SSLException {
-        final boolean ipv4 = InetAddressUtils.isIPv4Address(host);
-        final boolean ipv6 = InetAddressUtils.isIPv6Address(host);
-        final int subjectType = ipv4 || ipv6 ? IP_ADDRESS_TYPE : DNS_NAME_TYPE;
+        TYPE hostFormat = TYPE.DNS;
+        if (InetAddressUtils.isIPv4Address(host)) {
+            hostFormat = TYPE.IPv4;
+        } else {
+            String s = host;
+            if (s.startsWith("[") && s.endsWith("]")) {
+                s = host.substring(1, host.length() - 1);
+            }
+            if (InetAddressUtils.isIPv6Address(s)) {
+                hostFormat = TYPE.IPv6;
+            }
+        }
+        final int subjectType = hostFormat == TYPE.IPv4 || hostFormat == TYPE.IPv6 ? IP_ADDRESS_TYPE
: DNS_NAME_TYPE;
         final List<String> subjectAlts = extractSubjectAlts(cert, subjectType);
         if (subjectAlts != null && !subjectAlts.isEmpty()) {
-            if (ipv4) {
+            switch (hostFormat) {
+            case IPv4:
                 matchIPAddress(host, subjectAlts);
-            } else if (ipv6) {
+                break;
+            case IPv6:
                 matchIPv6Address(host, subjectAlts);
-            } else {
+                break;
+            default:
                 matchDNSName(host, subjectAlts, this.publicSuffixMatcher);
             }
         } else {
@@ -108,7 +123,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier
{
             final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
             final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
             if (cn == null) {
-                throw new SSLException("Certificate subject for <" + host + "> doesn't
contain " 
+                throw new SSLException("Certificate subject for <" + host + "> doesn't
contain "
                     + "a common name and does not have alternative names");
             }
             matchCN(host, cn, this.publicSuffixMatcher);
@@ -160,35 +175,23 @@ public final class DefaultHostnameVerifier implements HostnameVerifier
{
                 + "common name of the certificate subject: " + cn);
         }
     }
+    
+    static boolean matchDomainRoot(final String host, final String domainRoot) {
+        if (domainRoot == null) {
+            return false;
+        }
+        return host.endsWith(domainRoot) && (host.length() == domainRoot.length()
+                || host.charAt(host.length() - domainRoot.length() - 1) == '.');
+    }
 
     private static boolean matchIdentity(final String host, final String identity,
                                          final PublicSuffixMatcher publicSuffixMatcher,
                                          final boolean strict) {
-        if (host == null) {
+        if (publicSuffixMatcher != null && host.contains(".")
+            && !matchDomainRoot(host, publicSuffixMatcher.getDomainRoot(identity,
DomainType.ICANN))) {
             return false;
         }
 
-        if (publicSuffixMatcher != null && host.contains(".")) {
-            String domainRoot = publicSuffixMatcher.getDomainRoot(identity);
-            if (domainRoot == null) {
-                // Public domain
-                return false;
-            }
-            domainRoot = "." + domainRoot;
-            if (!host.endsWith(domainRoot)) {
-                // Domain root mismatch
-                return false;
-            }
-            if (strict && countDots(identity) != countDots(domainRoot)) {
-                return false;
-            }
-        }
-        
-        return matchServerIdentity(host, identity, strict);
-    }
-
-    private static boolean matchServerIdentity(final String host, final String identity,
-                                               boolean strict) {
         // RFC 2818, 3.1. Server Identity
         // "...Names may contain the wildcard
         // character * which is considered to match any single domain name
@@ -217,16 +220,6 @@ public final class DefaultHostnameVerifier implements HostnameVerifier
{
         return host.equalsIgnoreCase(identity);
     }
 
-    static int countDots(final String s) {
-        int count = 0;
-        for (int i = 0; i < s.length(); i++) {
-            if (s.charAt(i) == '.') {
-                count++;
-            }
-        }
-        return count;
-    }
-
     static boolean matchIdentity(final String host, final String identity,
                                  final PublicSuffixMatcher publicSuffixMatcher) {
         return matchIdentity(host, identity, publicSuffixMatcher, false);

http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DomainType.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DomainType.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DomainType.java
new file mode 100644
index 0000000..41ec355
--- /dev/null
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/DomainType.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License 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.
+ */
+/*
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.cxf.transport.https.httpclient;
+
+/**
+ * Domain types differentiated by Mozilla Public Suffix List.
+ *
+ * Copied from httpclient
+ */
+public enum DomainType {
+
+    UNKNOWN, ICANN, PRIVATE
+
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixList.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixList.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixList.java
index b714a46..9bb9536 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixList.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixList.java
@@ -39,19 +39,28 @@ import java.util.List;
  */
 public final class PublicSuffixList {
 
+    private final DomainType type;
     private final List<String> rules;
     private final List<String> exceptions;
 
-    public PublicSuffixList(final List<String> rules, final List<String> exceptions)
{
+    public PublicSuffixList(final DomainType type, final List<String> rules, final
List<String> exceptions) {
+        if (type == null) {
+            throw new IllegalArgumentException("Domain type is null");
+        }
         if (rules == null) {
             throw new IllegalArgumentException("Domain suffix rules are null");
         }
         if (exceptions == null) {
             throw new IllegalArgumentException("Domain suffix exceptions are null");
         }
+        this.type = type;
         this.rules = Collections.unmodifiableList(rules);
         this.exceptions = Collections.unmodifiableList(exceptions);
     }
+    
+    public DomainType getType() {
+        return type;
+    }
 
     public List<String> getRules() {
         return rules;

http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixListParser.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixListParser.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixListParser.java
index f4b61d7..5c4df13 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixListParser.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixListParser.java
@@ -39,27 +39,25 @@ import java.util.List;
  */
 public final class PublicSuffixListParser {
 
-    private static final int MAX_LINE_LEN = 256;
-
     public PublicSuffixListParser() {
     }
 
     /**
-     * Parses the public suffix list format. When creating the reader from the file, make
sure to
-     * use the correct encoding (the original list is in UTF-8).
+     * Parses the public suffix list format.
+     * <p>
+     * When creating the reader from the file, make sure to use the correct encoding
+     * (the original list is in UTF-8).
      *
      * @param reader the data reader. The caller is responsible for closing the reader.
      * @throws java.io.IOException on error while reading from list
      */
     public PublicSuffixList parse(final Reader reader) throws IOException {
-        final List<String> rules = new ArrayList<String>();
-        final List<String> exceptions = new ArrayList<String>();
+        final List<String> rules = new ArrayList<>();
+        final List<String> exceptions = new ArrayList<>();
         final BufferedReader r = new BufferedReader(reader);
-        final StringBuilder sb = new StringBuilder(256);
-        boolean more = true;
-        while (more) {
-            more = readLine(r, sb);
-            String line = sb.toString();
+
+        String line;
+        while ((line = r.readLine()) != null) {
             if (line.isEmpty()) {
                 continue;
             }
@@ -81,30 +79,79 @@ public final class PublicSuffixListParser {
                 rules.add(line);
             }
         }
-        return new PublicSuffixList(rules, exceptions);
+        return new PublicSuffixList(DomainType.UNKNOWN, rules, exceptions);
     }
 
-    private boolean readLine(final Reader r, final StringBuilder sb) throws IOException {
-        sb.setLength(0);
-        int b;
-        boolean hitWhitespace = false;
-        while ((b = r.read()) != -1) {
-            final char c = (char) b;
-            if (c == '\n') {
-                break;
+    /**
+     * Parses the public suffix list format by domain type (currently supported ICANN and
PRIVATE).
+     * <p>
+     * When creating the reader from the file, make sure to use the correct encoding
+     * (the original list is in UTF-8).
+     *
+     * @param reader the data reader. The caller is responsible for closing the reader.
+     * @throws java.io.IOException on error while reading from list
+     *
+     * @since 4.5
+     */
+    public List<PublicSuffixList> parseByType(final Reader reader) throws IOException
{
+        final List<PublicSuffixList> result = new ArrayList<>(2);
+
+        final BufferedReader r = new BufferedReader(reader);
+
+        DomainType domainType = null;
+        List<String> rules = null;
+        List<String> exceptions = null;
+        String line;
+        while ((line = r.readLine()) != null) {
+            if (line.isEmpty()) {
+                continue;
             }
-            // Each line is only read up to the first whitespace
-            if (Character.isWhitespace(c)) {
-                hitWhitespace = true;
+            if (line.startsWith("//")) {
+
+                if (domainType == null) {
+                    if (line.contains("===BEGIN ICANN DOMAINS===")) {
+                        domainType = DomainType.ICANN;
+                    } else if (line.contains("===BEGIN PRIVATE DOMAINS===")) {
+                        domainType = DomainType.PRIVATE;
+                    }
+                } else {
+                    if (line.contains("===END ICANN DOMAINS===") || line.contains("===END
PRIVATE DOMAINS===")) {
+                        if (rules != null) {
+                            result.add(new PublicSuffixList(domainType, rules, exceptions));
+                        }
+                        domainType = null;
+                        rules = null;
+                        exceptions = null;
+                    }
+                }
+
+                continue; //entire lines can also be commented using //
             }
-            if (!hitWhitespace) {
-                sb.append(c);
+            if (domainType == null) {
+                continue;
+            }
+
+            if (line.startsWith(".")) {
+                line = line.substring(1); // A leading dot is optional
+            }
+            // An exclamation mark (!) at the start of a rule marks an exception to a previous
wildcard rule
+            final boolean isException = line.startsWith("!");
+            if (isException) {
+                line = line.substring(1);
             }
-            if (sb.length() > MAX_LINE_LEN) {
-                return false; // prevent excess memory usage
+
+            if (isException) {
+                if (exceptions == null) {
+                    exceptions = new ArrayList<>();
+                }
+                exceptions.add(line);
+            } else {
+                if (rules == null) {
+                    rules = new ArrayList<>();
+                }
+                rules.add(line);
             }
         }
-        return b != -1;
+        return result;
     }
-
 }

http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixMatcher.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixMatcher.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixMatcher.java
index fa2318f..b50b83f 100644
--- a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixMatcher.java
+++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/httpclient/PublicSuffixMatcher.java
@@ -27,6 +27,7 @@ package org.apache.cxf.transport.https.httpclient;
 
 import java.net.IDN;
 import java.util.Collection;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -41,35 +42,94 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public final class PublicSuffixMatcher {
 
-    private final Map<String, String> rules;
-    private final Map<String, String> exceptions;
+    private final Map<String, DomainType> rules;
+    private final Map<String, DomainType> exceptions;
 
     public PublicSuffixMatcher(final Collection<String> rules, final Collection<String>
exceptions) {
+        this(DomainType.UNKNOWN, rules, exceptions);
+    }
+    
+    public PublicSuffixMatcher(final DomainType domainType, 
+                               final Collection<String> rules, final Collection<String>
exceptions) {
+        if (domainType == null) {
+            throw new IllegalArgumentException("Domain type is null");
+        }
         if (rules == null) {
             throw new IllegalArgumentException("Domain suffix rules are null");
         }
-        this.rules = new ConcurrentHashMap<String, String>(rules.size());
+        this.rules = new ConcurrentHashMap<String, DomainType>(rules.size());
         for (String rule: rules) {
-            this.rules.put(rule, rule);
+            this.rules.put(rule, domainType);
         }
+        this.exceptions = new ConcurrentHashMap<String, DomainType>();
         if (exceptions != null) {
-            this.exceptions = new ConcurrentHashMap<String, String>(exceptions.size());
             for (String exception: exceptions) {
-                this.exceptions.put(exception, exception);
+                this.exceptions.put(exception, domainType);
             }
+        }
+    }
+
+    public PublicSuffixMatcher(final Collection<PublicSuffixList> lists) {
+        if (lists == null) {
+            throw new IllegalArgumentException("Domain suffix lists are null");
+        }
+        this.rules = new ConcurrentHashMap<String, DomainType>();
+        this.exceptions = new ConcurrentHashMap<String, DomainType>();
+        for (PublicSuffixList list: lists) {
+            final DomainType domainType = list.getType();
+            for (String rule: list.getRules()) {
+                this.rules.put(rule, domainType);
+            }
+            final List<String> listExceptions = list.getExceptions();
+            if (listExceptions != null) {
+                for (String exception: listExceptions) {
+                    this.exceptions.put(exception, domainType);
+                }
+            }
+        }
+    }
+
+    private static boolean hasEntry(final Map<String, DomainType> map, final String
rule, 
+                                    final DomainType expectedType) {
+        if (map == null) {
+            return false;
+        }
+        final DomainType domainType = map.get(rule);
+        if (domainType == null) {
+            return false;
         } else {
-            this.exceptions = null;
+            return expectedType == null || domainType.equals(expectedType);
         }
     }
 
+    private boolean hasRule(final String rule, final DomainType expectedType) {
+        return hasEntry(this.rules, rule, expectedType);
+    }
+
+    private boolean hasException(final String exception, final DomainType expectedType) {
+        return hasEntry(this.exceptions, exception, expectedType);
+    }
+
     /**
-     * Returns registrable part of the domain for the given domain name of {@code null}
+     * Returns registrable part of the domain for the given domain name or {@code null}
      * if given domain represents a public suffix.
      *
      * @param domain
      * @return domain root
      */
     public String getDomainRoot(final String domain) {
+        return getDomainRoot(domain, null);
+    }
+
+    /**
+     * Returns registrable part of the domain for the given domain name or {@code null}
+     * if given domain represents a public suffix.
+     *
+     * @param domain
+     * @param expectedType expected domain type or {@code null} if any.
+     * @return domain root
+     */
+    public String getDomainRoot(final String domain, final DomainType expectedType) {
         if (domain == null) {
             return null;
         }
@@ -81,11 +141,11 @@ public final class PublicSuffixMatcher {
         while (segment != null) {
 
             // An exception rule takes priority over any other matching rule.
-            if (this.exceptions != null && this.exceptions.containsKey(IDN.toUnicode(segment)))
{
+            if (hasException(IDN.toUnicode(segment), expectedType)) {
                 return segment;
             }
 
-            if (this.rules.containsKey(IDN.toUnicode(segment))) {
+            if (hasRule(IDN.toUnicode(segment), expectedType)) {
                 break;
             }
 
@@ -93,7 +153,7 @@ public final class PublicSuffixMatcher {
             final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;
 
             if (nextSegment != null
-                && this.rules.containsKey("*." + IDN.toUnicode(nextSegment))) {
+                && hasRule("*." + IDN.toUnicode(nextSegment), expectedType)) {
                 break;
             }
             if (nextdot != -1) {
@@ -104,11 +164,26 @@ public final class PublicSuffixMatcher {
         return domainName;
     }
 
+    /**
+     * Tests whether the given domain matches any of entry from the public suffix list.
+     */
     public boolean matches(final String domain) {
+        return matches(domain, null);
+    }
+
+    /**
+     * Tests whether the given domain matches any of entry from the public suffix list.
+     *
+     * @param domain
+     * @param expectedType expected domain type or {@code null} if any.
+     * @return {@code true} if the given domain matches any of the public suffixes.
+     */
+    public boolean matches(final String domain, final DomainType expectedType) {
         if (domain == null) {
             return false;
         }
-        final String domainRoot = getDomainRoot(domain.startsWith(".") ? domain.substring(1)
: domain);
+        final String domainRoot = getDomainRoot(
+                domain.startsWith(".") ? domain.substring(1) : domain, expectedType);
         return domainRoot == null;
     }
 

http://git-wip-us.apache.org/repos/asf/cxf/blob/99276baf/rt/transports/http/src/test/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifierTest.java
----------------------------------------------------------------------
diff --git a/rt/transports/http/src/test/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifierTest.java
b/rt/transports/http/src/test/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifierTest.java
index da284e8..3ec14d1 100644
--- a/rt/transports/http/src/test/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifierTest.java
+++ b/rt/transports/http/src/test/java/org/apache/cxf/transport/https/httpclient/DefaultHostnameVerifierTest.java
@@ -50,9 +50,9 @@ public class DefaultHostnameVerifierTest {
     private DefaultHostnameVerifier implWithPublicSuffixCheck;
 
     @Before
-    public void setUp() {
+    public void setup() {
         impl = new DefaultHostnameVerifier();
-        publicSuffixMatcher = new PublicSuffixMatcher(Arrays.asList("com", "co.jp", "gov.uk"),
null);
+        publicSuffixMatcher = new PublicSuffixMatcher(DomainType.ICANN, Arrays.asList("com",
"co.jp", "gov.uk"), null);
         implWithPublicSuffixCheck = new DefaultHostnameVerifier(publicSuffixMatcher);
     }
 
@@ -191,6 +191,16 @@ public class DefaultHostnameVerifierTest {
             // whew!  we're okay!
         }
     }
+    
+    @Test
+    public void testDomainRootMatching() {
+
+        Assert.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", null));
+        Assert.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "a.b.c"));
+        Assert.assertFalse(DefaultHostnameVerifier.matchDomainRoot("aa.b.c", "a.b.c"));
+        Assert.assertFalse(DefaultHostnameVerifier.matchDomainRoot("a.b.c", "aa.b.c"));
+        Assert.assertTrue(DefaultHostnameVerifier.matchDomainRoot("a.a.b.c", "a.b.c"));
+    }
 
     @Test
     public void testIdentityMatching() {


Mime
View raw message