Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/sun/security/util/HostnameChecker.java
41159 views
1
/*
2
* Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.security.util;
27
28
import java.io.IOException;
29
import java.net.IDN;
30
import java.net.InetAddress;
31
import java.net.UnknownHostException;
32
import java.security.Principal;
33
import java.security.cert.*;
34
import java.text.Normalizer;
35
import java.util.*;
36
import javax.security.auth.x500.X500Principal;
37
import javax.net.ssl.SNIHostName;
38
39
import sun.net.util.IPAddressUtil;
40
import sun.security.x509.X500Name;
41
import sun.security.ssl.SSLLogger;
42
43
/**
44
* Class to check hostnames against the names specified in a certificate as
45
* required for TLS and LDAP.
46
*
47
*/
48
public class HostnameChecker {
49
50
// Constant for a HostnameChecker for TLS
51
public static final byte TYPE_TLS = 1;
52
private static final HostnameChecker INSTANCE_TLS =
53
new HostnameChecker(TYPE_TLS);
54
55
// Constant for a HostnameChecker for LDAP
56
public static final byte TYPE_LDAP = 2;
57
private static final HostnameChecker INSTANCE_LDAP =
58
new HostnameChecker(TYPE_LDAP);
59
60
// constants for subject alt names of type DNS and IP
61
private static final int ALTNAME_DNS = 2;
62
private static final int ALTNAME_IP = 7;
63
64
// the algorithm to follow to perform the check. Currently unused.
65
private final byte checkType;
66
67
private HostnameChecker(byte checkType) {
68
this.checkType = checkType;
69
}
70
71
/**
72
* Get a HostnameChecker instance. checkType should be one of the
73
* TYPE_* constants defined in this class.
74
*/
75
public static HostnameChecker getInstance(byte checkType) {
76
if (checkType == TYPE_TLS) {
77
return INSTANCE_TLS;
78
} else if (checkType == TYPE_LDAP) {
79
return INSTANCE_LDAP;
80
}
81
throw new IllegalArgumentException("Unknown check type: " + checkType);
82
}
83
84
/**
85
* Perform the check.
86
*
87
* @param expectedName the expected host name or ip address
88
* @param cert the certificate to check against
89
* @param chainsToPublicCA true if the certificate chains to a public
90
* root CA (as pre-installed in the cacerts file)
91
* @throws CertificateException if the name does not match any of
92
* the names specified in the certificate
93
*/
94
public void match(String expectedName, X509Certificate cert,
95
boolean chainsToPublicCA) throws CertificateException {
96
if (expectedName == null) {
97
throw new CertificateException("Hostname or IP address is " +
98
"undefined.");
99
}
100
if (isIpAddress(expectedName)) {
101
matchIP(expectedName, cert);
102
} else {
103
matchDNS(expectedName, cert, chainsToPublicCA);
104
}
105
}
106
107
public void match(String expectedName, X509Certificate cert)
108
throws CertificateException {
109
match(expectedName, cert, false);
110
}
111
112
/**
113
* Test whether the given hostname looks like a literal IPv4 or IPv6
114
* address. The hostname does not need to be a fully qualified name.
115
*
116
* This is not a strict check that performs full input validation.
117
* That means if the method returns true, name need not be a correct
118
* IP address, rather that it does not represent a valid DNS hostname.
119
* Likewise for IP addresses when it returns false.
120
*/
121
private static boolean isIpAddress(String name) {
122
if (IPAddressUtil.isIPv4LiteralAddress(name) ||
123
IPAddressUtil.isIPv6LiteralAddress(name)) {
124
return true;
125
} else {
126
return false;
127
}
128
}
129
130
/**
131
* Check if the certificate allows use of the given IP address.
132
*
133
* From RFC2818:
134
* In some cases, the URI is specified as an IP address rather than a
135
* hostname. In this case, the iPAddress subjectAltName must be present
136
* in the certificate and must exactly match the IP in the URI.
137
*/
138
private static void matchIP(String expectedIP, X509Certificate cert)
139
throws CertificateException {
140
Collection<List<?>> subjAltNames = cert.getSubjectAlternativeNames();
141
if (subjAltNames == null) {
142
throw new CertificateException
143
("No subject alternative names present");
144
}
145
for (List<?> next : subjAltNames) {
146
// For IP address, it needs to be exact match
147
if (((Integer)next.get(0)).intValue() == ALTNAME_IP) {
148
String ipAddress = (String)next.get(1);
149
if (expectedIP.equalsIgnoreCase(ipAddress)) {
150
return;
151
} else {
152
// compare InetAddress objects in order to ensure
153
// equality between a long IPv6 address and its
154
// abbreviated form.
155
try {
156
if (InetAddress.getByName(expectedIP).equals(
157
InetAddress.getByName(ipAddress))) {
158
return;
159
}
160
} catch (UnknownHostException e) {
161
} catch (SecurityException e) {}
162
}
163
}
164
}
165
throw new CertificateException("No subject alternative " +
166
"names matching " + "IP address " +
167
expectedIP + " found");
168
}
169
170
/**
171
* Check if the certificate allows use of the given DNS name.
172
*
173
* From RFC2818:
174
* If a subjectAltName extension of type dNSName is present, that MUST
175
* be used as the identity. Otherwise, the (most specific) Common Name
176
* field in the Subject field of the certificate MUST be used. Although
177
* the use of the Common Name is existing practice, it is deprecated and
178
* Certification Authorities are encouraged to use the dNSName instead.
179
*
180
* Matching is performed using the matching rules specified by
181
* [RFC5280]. If more than one identity of a given type is present in
182
* the certificate (e.g., more than one dNSName name, a match in any one
183
* of the set is considered acceptable.)
184
*/
185
private void matchDNS(String expectedName, X509Certificate cert,
186
boolean chainsToPublicCA)
187
throws CertificateException {
188
// Check that the expected name is a valid domain name.
189
try {
190
// Using the checking implemented in SNIHostName
191
SNIHostName sni = new SNIHostName(expectedName);
192
} catch (IllegalArgumentException iae) {
193
throw new CertificateException(
194
"Illegal given domain name: " + expectedName, iae);
195
}
196
197
Collection<List<?>> subjAltNames = cert.getSubjectAlternativeNames();
198
if (subjAltNames != null) {
199
boolean foundDNS = false;
200
for (List<?> next : subjAltNames) {
201
if (((Integer)next.get(0)).intValue() == ALTNAME_DNS) {
202
foundDNS = true;
203
String dnsName = (String)next.get(1);
204
if (isMatched(expectedName, dnsName, chainsToPublicCA)) {
205
return;
206
}
207
}
208
}
209
if (foundDNS) {
210
// if certificate contains any subject alt names of type DNS
211
// but none match, reject
212
throw new CertificateException("No subject alternative DNS "
213
+ "name matching " + expectedName + " found.");
214
}
215
}
216
X500Name subjectName = getSubjectX500Name(cert);
217
DerValue derValue = subjectName.findMostSpecificAttribute
218
(X500Name.commonName_oid);
219
if (derValue != null) {
220
try {
221
String cname = derValue.getAsString();
222
if (!Normalizer.isNormalized(cname, Normalizer.Form.NFKC)) {
223
throw new CertificateException("Not a formal name "
224
+ cname);
225
}
226
if (isMatched(expectedName, cname, chainsToPublicCA)) {
227
return;
228
}
229
} catch (IOException e) {
230
// ignore
231
}
232
}
233
String msg = "No name matching " + expectedName + " found";
234
throw new CertificateException(msg);
235
}
236
237
238
/**
239
* Return the subject of a certificate as X500Name, by reparsing if
240
* necessary. X500Name should only be used if access to name components
241
* is required, in other cases X500Principal is to be preferred.
242
*
243
* This method is currently used from within JSSE, do not remove.
244
*/
245
@SuppressWarnings("deprecation")
246
public static X500Name getSubjectX500Name(X509Certificate cert)
247
throws CertificateParsingException {
248
try {
249
Principal subjectDN = cert.getSubjectDN();
250
if (subjectDN instanceof X500Name) {
251
return (X500Name)subjectDN;
252
} else {
253
X500Principal subjectX500 = cert.getSubjectX500Principal();
254
return new X500Name(subjectX500.getEncoded());
255
}
256
} catch (IOException e) {
257
throw(CertificateParsingException)
258
new CertificateParsingException().initCause(e);
259
}
260
}
261
262
263
/**
264
* Returns true if name matches against template.<p>
265
*
266
* The matching is performed as per RFC 2818 rules for TLS and
267
* RFC 2830 rules for LDAP.<p>
268
*
269
* The <code>name</code> parameter should represent a DNS name. The
270
* <code>template</code> parameter may contain the wildcard character '*'.
271
*/
272
private boolean isMatched(String name, String template,
273
boolean chainsToPublicCA) {
274
275
// Normalize to Unicode, because PSL is in Unicode.
276
try {
277
name = IDN.toUnicode(IDN.toASCII(name));
278
template = IDN.toUnicode(IDN.toASCII(template));
279
} catch (RuntimeException re) {
280
if (SSLLogger.isOn) {
281
SSLLogger.fine("Failed to normalize to Unicode: " + re);
282
}
283
284
return false;
285
}
286
287
if (hasIllegalWildcard(template, chainsToPublicCA)) {
288
return false;
289
}
290
291
// check the validity of the domain name template.
292
try {
293
// Replacing wildcard character '*' with 'z' so as to check
294
// the domain name template validity.
295
//
296
// Using the checking implemented in SNIHostName
297
new SNIHostName(template.replace('*', 'z'));
298
} catch (IllegalArgumentException iae) {
299
// It would be nice to add debug log if not matching.
300
return false;
301
}
302
303
if (checkType == TYPE_TLS) {
304
return matchAllWildcards(name, template);
305
} else if (checkType == TYPE_LDAP) {
306
return matchLeftmostWildcard(name, template);
307
} else {
308
return false;
309
}
310
}
311
312
/**
313
* Returns true if the template contains an illegal wildcard character.
314
*/
315
private static boolean hasIllegalWildcard(
316
String template, boolean chainsToPublicCA) {
317
// not ok if it is a single wildcard character or "*."
318
if (template.equals("*") || template.equals("*.")) {
319
if (SSLLogger.isOn) {
320
SSLLogger.fine(
321
"Certificate domain name has illegal single " +
322
"wildcard character: " + template);
323
}
324
return true;
325
}
326
327
int lastWildcardIndex = template.lastIndexOf("*");
328
329
// ok if it has no wildcard character
330
if (lastWildcardIndex == -1) {
331
return false;
332
}
333
334
String afterWildcard = template.substring(lastWildcardIndex);
335
int firstDotIndex = afterWildcard.indexOf(".");
336
337
// not ok if there is no dot after wildcard (ex: "*com")
338
if (firstDotIndex == -1) {
339
if (SSLLogger.isOn) {
340
SSLLogger.fine(
341
"Certificate domain name has illegal wildcard, " +
342
"no dot after wildcard character: " + template);
343
}
344
return true;
345
}
346
347
if (!chainsToPublicCA) {
348
return false; // skip check for non-public certificates
349
}
350
351
// If the wildcarded domain is a top-level domain under which names
352
// can be registered, then a wildcard is not allowed.
353
String wildcardedDomain = afterWildcard.substring(firstDotIndex + 1);
354
String templateDomainSuffix =
355
RegisteredDomain.from("z." + wildcardedDomain)
356
.filter(d -> d.type() == RegisteredDomain.Type.ICANN)
357
.map(RegisteredDomain::publicSuffix).orElse(null);
358
if (templateDomainSuffix == null) {
359
return false; // skip check if not known public suffix
360
}
361
362
// Is it a top-level domain?
363
if (wildcardedDomain.equalsIgnoreCase(templateDomainSuffix)) {
364
if (SSLLogger.isOn) {
365
SSLLogger.fine(
366
"Certificate domain name has illegal " +
367
"wildcard for top-level public suffix: " + template);
368
}
369
return true;
370
}
371
372
return false;
373
}
374
375
/**
376
* Returns true if name matches against template.<p>
377
*
378
* According to RFC 2818, section 3.1 -
379
* Names may contain the wildcard character * which is
380
* considered to match any single domain name component
381
* or component fragment.
382
* E.g., *.a.com matches foo.a.com but not
383
* bar.foo.a.com. f*.com matches foo.com but not bar.com.
384
*/
385
private static boolean matchAllWildcards(String name,
386
String template) {
387
name = name.toLowerCase(Locale.ENGLISH);
388
template = template.toLowerCase(Locale.ENGLISH);
389
StringTokenizer nameSt = new StringTokenizer(name, ".");
390
StringTokenizer templateSt = new StringTokenizer(template, ".");
391
392
if (nameSt.countTokens() != templateSt.countTokens()) {
393
return false;
394
}
395
396
while (nameSt.hasMoreTokens()) {
397
if (!matchWildCards(nameSt.nextToken(),
398
templateSt.nextToken())) {
399
return false;
400
}
401
}
402
return true;
403
}
404
405
406
/**
407
* Returns true if name matches against template.<p>
408
*
409
* As per RFC 2830, section 3.6 -
410
* The "*" wildcard character is allowed. If present, it applies only
411
* to the left-most name component.
412
* E.g. *.bar.com would match a.bar.com, b.bar.com, etc. but not
413
* bar.com.
414
*/
415
private static boolean matchLeftmostWildcard(String name,
416
String template) {
417
name = name.toLowerCase(Locale.ENGLISH);
418
template = template.toLowerCase(Locale.ENGLISH);
419
420
// Retrieve leftmost component
421
int templateIdx = template.indexOf(".");
422
int nameIdx = name.indexOf(".");
423
424
if (templateIdx == -1)
425
templateIdx = template.length();
426
if (nameIdx == -1)
427
nameIdx = name.length();
428
429
if (matchWildCards(name.substring(0, nameIdx),
430
template.substring(0, templateIdx))) {
431
432
// match rest of the name
433
return template.substring(templateIdx).equals(
434
name.substring(nameIdx));
435
} else {
436
return false;
437
}
438
}
439
440
441
/**
442
* Returns true if the name matches against the template that may
443
* contain wildcard char * <p>
444
*/
445
private static boolean matchWildCards(String name, String template) {
446
447
int wildcardIdx = template.indexOf("*");
448
if (wildcardIdx == -1)
449
return name.equals(template);
450
451
boolean isBeginning = true;
452
String beforeWildcard = "";
453
String afterWildcard = template;
454
455
while (wildcardIdx != -1) {
456
457
// match in sequence the non-wildcard chars in the template.
458
beforeWildcard = afterWildcard.substring(0, wildcardIdx);
459
afterWildcard = afterWildcard.substring(wildcardIdx + 1);
460
461
int beforeStartIdx = name.indexOf(beforeWildcard);
462
if ((beforeStartIdx == -1) ||
463
(isBeginning && beforeStartIdx != 0)) {
464
return false;
465
}
466
isBeginning = false;
467
468
// update the match scope
469
name = name.substring(beforeStartIdx + beforeWildcard.length());
470
wildcardIdx = afterWildcard.indexOf("*");
471
}
472
return name.endsWith(afterWildcard);
473
}
474
}
475
476