1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.http.conn.ssl;
29
30 import java.net.InetAddress;
31 import java.net.UnknownHostException;
32 import java.security.cert.Certificate;
33 import java.security.cert.CertificateParsingException;
34 import java.security.cert.X509Certificate;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.NoSuchElementException;
40
41 import javax.naming.InvalidNameException;
42 import javax.naming.NamingException;
43 import javax.naming.directory.Attribute;
44 import javax.naming.directory.Attributes;
45 import javax.naming.ldap.LdapName;
46 import javax.naming.ldap.Rdn;
47 import javax.net.ssl.HostnameVerifier;
48 import javax.net.ssl.SSLException;
49 import javax.net.ssl.SSLPeerUnverifiedException;
50 import javax.net.ssl.SSLSession;
51 import javax.security.auth.x500.X500Principal;
52
53 import org.apache.commons.logging.Log;
54 import org.apache.commons.logging.LogFactory;
55 import org.apache.http.annotation.Contract;
56 import org.apache.http.annotation.ThreadingBehavior;
57 import org.apache.http.conn.util.DnsUtils;
58 import org.apache.http.conn.util.DomainType;
59 import org.apache.http.conn.util.InetAddressUtils;
60 import org.apache.http.conn.util.PublicSuffixMatcher;
61
62
63
64
65
66
67 @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
68 public final class DefaultHostnameVerifier implements HostnameVerifier {
69
70 enum HostNameType {
71
72 IPv4(7), IPv6(7), DNS(2);
73
74 final int subjectType;
75
76 HostNameType(final int subjectType) {
77 this.subjectType = subjectType;
78 }
79
80 }
81
82 private final Log log = LogFactory.getLog(getClass());
83
84 private final PublicSuffixMatcher publicSuffixMatcher;
85
86 public DefaultHostnameVerifier(final PublicSuffixMatcher publicSuffixMatcher) {
87 this.publicSuffixMatcher = publicSuffixMatcher;
88 }
89
90 public DefaultHostnameVerifier() {
91 this(null);
92 }
93
94 @Override
95 public boolean verify(final String host, final SSLSession session) {
96 try {
97 final Certificate[] certs = session.getPeerCertificates();
98 final X509Certificate x509 = (X509Certificate) certs[0];
99 verify(host, x509);
100 return true;
101 } catch (final SSLException ex) {
102 if (log.isDebugEnabled()) {
103 log.debug(ex.getMessage(), ex);
104 }
105 return false;
106 }
107 }
108
109 public void verify(
110 final String host, final X509Certificate cert) throws SSLException {
111 final HostNameType hostType = determineHostFormat(host);
112 final List<SubjectName> subjectAlts = getSubjectAltNames(cert);
113 if (subjectAlts != null && !subjectAlts.isEmpty()) {
114 switch (hostType) {
115 case IPv4:
116 matchIPAddress(host, subjectAlts);
117 break;
118 case IPv6:
119 matchIPv6Address(host, subjectAlts);
120 break;
121 default:
122 matchDNSName(host, subjectAlts, this.publicSuffixMatcher);
123 }
124 } else {
125
126
127 final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
128 final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
129 if (cn == null) {
130 throw new SSLException("Certificate subject for <" + host + "> doesn't contain " +
131 "a common name and does not have alternative names");
132 }
133 matchCN(host, cn, this.publicSuffixMatcher);
134 }
135 }
136
137 static void matchIPAddress(final String host, final List<SubjectName> subjectAlts) throws SSLException {
138 for (int i = 0; i < subjectAlts.size(); i++) {
139 final SubjectName subjectAlt = subjectAlts.get(i);
140 if (subjectAlt.getType() == SubjectName.IP) {
141 if (host.equals(subjectAlt.getValue())) {
142 return;
143 }
144 }
145 }
146 throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
147 "of the subject alternative names: " + subjectAlts);
148 }
149
150 static void matchIPv6Address(final String host, final List<SubjectName> subjectAlts) throws SSLException {
151 final String normalisedHost = normaliseAddress(host);
152 for (int i = 0; i < subjectAlts.size(); i++) {
153 final SubjectName subjectAlt = subjectAlts.get(i);
154 if (subjectAlt.getType() == SubjectName.IP) {
155 final String normalizedSubjectAlt = normaliseAddress(subjectAlt.getValue());
156 if (normalisedHost.equals(normalizedSubjectAlt)) {
157 return;
158 }
159 }
160 }
161 throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
162 "of the subject alternative names: " + subjectAlts);
163 }
164
165 static void matchDNSName(final String host, final List<SubjectName> subjectAlts,
166 final PublicSuffixMatcher publicSuffixMatcher) throws SSLException {
167 final String normalizedHost = DnsUtils.normalize(host);
168 for (int i = 0; i < subjectAlts.size(); i++) {
169 final SubjectName subjectAlt = subjectAlts.get(i);
170 if (subjectAlt.getType() == SubjectName.DNS) {
171 final String normalizedSubjectAlt = DnsUtils.normalize(subjectAlt.getValue());
172 if (matchIdentityStrict(normalizedHost, normalizedSubjectAlt, publicSuffixMatcher)) {
173 return;
174 }
175 }
176 }
177 throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
178 "of the subject alternative names: " + subjectAlts);
179 }
180
181 static void matchCN(final String host, final String cn,
182 final PublicSuffixMatcher publicSuffixMatcher) throws SSLException {
183 final String normalizedHost = DnsUtils.normalize(host);
184 final String normalizedCn = DnsUtils.normalize(cn);
185 if (!matchIdentityStrict(normalizedHost, normalizedCn, publicSuffixMatcher)) {
186 throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match " +
187 "common name of the certificate subject: " + cn);
188 }
189 }
190
191 static boolean matchDomainRoot(final String host, final String domainRoot) {
192 if (domainRoot == null) {
193 return false;
194 }
195 return host.endsWith(domainRoot) && (host.length() == domainRoot.length()
196 || host.charAt(host.length() - domainRoot.length() - 1) == '.');
197 }
198
199 private static boolean matchIdentity(final String host, final String identity,
200 final PublicSuffixMatcher publicSuffixMatcher,
201 final DomainType domainType,
202 final boolean strict) {
203 if (publicSuffixMatcher != null && host.contains(".")) {
204 if (!matchDomainRoot(host, publicSuffixMatcher.getDomainRoot(identity, domainType))) {
205 return false;
206 }
207 }
208
209
210
211
212
213
214 final int asteriskIdx = identity.indexOf('*');
215 if (asteriskIdx != -1) {
216 final String prefix = identity.substring(0, asteriskIdx);
217 final String suffix = identity.substring(asteriskIdx + 1);
218 if (!prefix.isEmpty() && !host.startsWith(prefix)) {
219 return false;
220 }
221 if (!suffix.isEmpty() && !host.endsWith(suffix)) {
222 return false;
223 }
224
225 if (strict) {
226 final String remainder = host.substring(
227 prefix.length(), host.length() - suffix.length());
228 if (remainder.contains(".")) {
229 return false;
230 }
231 }
232 return true;
233 }
234 return host.equalsIgnoreCase(identity);
235 }
236
237 static boolean matchIdentity(final String host, final String identity,
238 final PublicSuffixMatcher publicSuffixMatcher) {
239 return matchIdentity(host, identity, publicSuffixMatcher, null, false);
240 }
241
242 static boolean matchIdentity(final String host, final String identity) {
243 return matchIdentity(host, identity, null, null, false);
244 }
245
246 static boolean matchIdentityStrict(final String host, final String identity,
247 final PublicSuffixMatcher publicSuffixMatcher) {
248 return matchIdentity(host, identity, publicSuffixMatcher, null, true);
249 }
250
251 static boolean matchIdentityStrict(final String host, final String identity) {
252 return matchIdentity(host, identity, null, null, true);
253 }
254
255 static boolean matchIdentity(final String host, final String identity,
256 final PublicSuffixMatcher publicSuffixMatcher,
257 final DomainType domainType) {
258 return matchIdentity(host, identity, publicSuffixMatcher, domainType, false);
259 }
260
261 static boolean matchIdentityStrict(final String host, final String identity,
262 final PublicSuffixMatcher publicSuffixMatcher,
263 final DomainType domainType) {
264 return matchIdentity(host, identity, publicSuffixMatcher, domainType, true);
265 }
266
267 static String extractCN(final String subjectPrincipal) throws SSLException {
268 if (subjectPrincipal == null) {
269 return null;
270 }
271 try {
272 final LdapName subjectDN = new LdapName(subjectPrincipal);
273 final List<Rdn> rdns = subjectDN.getRdns();
274 for (int i = rdns.size() - 1; i >= 0; i--) {
275 final Rdn rds = rdns.get(i);
276 final Attributes attributes = rds.toAttributes();
277 final Attribute cn = attributes.get("cn");
278 if (cn != null) {
279 try {
280 final Object value = cn.get();
281 if (value != null) {
282 return value.toString();
283 }
284 } catch (final NoSuchElementException ignore) {
285
286 } catch (final NamingException ignore) {
287
288 }
289 }
290 }
291 return null;
292 } catch (final InvalidNameException e) {
293 throw new SSLException(subjectPrincipal + " is not a valid X500 distinguished name");
294 }
295 }
296
297 static HostNameType determineHostFormat(final String host) {
298 if (InetAddressUtils.isIPv4Address(host)) {
299 return HostNameType.IPv4;
300 }
301 String s = host;
302 if (s.startsWith("[") && s.endsWith("]")) {
303 s = host.substring(1, host.length() - 1);
304 }
305 if (InetAddressUtils.isIPv6Address(s)) {
306 return HostNameType.IPv6;
307 }
308 return HostNameType.DNS;
309 }
310
311 static List<SubjectName> getSubjectAltNames(final X509Certificate cert) {
312 try {
313 final Collection<List<?>> entries = cert.getSubjectAlternativeNames();
314 if (entries == null) {
315 return Collections.emptyList();
316 }
317 final List<SubjectName> result = new ArrayList<SubjectName>();
318 for (final List<?> entry : entries) {
319 final Integer type = entry.size() >= 2 ? (Integer) entry.get(0) : null;
320 if (type != null) {
321 if (type == SubjectName.DNS || type == SubjectName.IP) {
322 final Object o = entry.get(1);
323 if (o instanceof String) {
324 result.add(new SubjectName((String) o, type));
325 } else if (o instanceof byte[]) {
326
327 }
328 }
329 }
330 }
331 return result;
332 } catch (final CertificateParsingException ignore) {
333 return Collections.emptyList();
334 }
335 }
336
337
338
339
340 static String normaliseAddress(final String hostname) {
341 if (hostname == null) {
342 return hostname;
343 }
344 try {
345 final InetAddress inetAddress = InetAddress.getByName(hostname);
346 return inetAddress.getHostAddress();
347 } catch (final UnknownHostException unexpected) {
348 return hostname;
349 }
350 }
351 }