Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/util/HostnameChecker.java
38830 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.InetAddress;
30
import java.net.UnknownHostException;
31
import java.util.*;
32
33
import java.security.Principal;
34
import java.security.cert.*;
35
import java.text.Normalizer;
36
37
import javax.security.auth.x500.X500Principal;
38
import javax.net.ssl.SNIHostName;
39
40
import sun.security.ssl.Krb5Helper;
41
import sun.security.x509.X500Name;
42
43
import sun.net.util.IPAddressUtil;
44
import sun.security.ssl.SSLLogger;
45
46
/**
47
* Class to check hostnames against the names specified in a certificate as
48
* required for TLS and LDAP.
49
*
50
*/
51
public class HostnameChecker {
52
53
// Constant for a HostnameChecker for TLS
54
public final static byte TYPE_TLS = 1;
55
private final static HostnameChecker INSTANCE_TLS =
56
new HostnameChecker(TYPE_TLS);
57
58
// Constant for a HostnameChecker for LDAP
59
public final static byte TYPE_LDAP = 2;
60
private final static HostnameChecker INSTANCE_LDAP =
61
new HostnameChecker(TYPE_LDAP);
62
63
// constants for subject alt names of type DNS and IP
64
private final static int ALTNAME_DNS = 2;
65
private final static int ALTNAME_IP = 7;
66
67
// the algorithm to follow to perform the check. Currently unused.
68
private final byte checkType;
69
70
private HostnameChecker(byte checkType) {
71
this.checkType = checkType;
72
}
73
74
/**
75
* Get a HostnameChecker instance. checkType should be one of the
76
* TYPE_* constants defined in this class.
77
*/
78
public static HostnameChecker getInstance(byte checkType) {
79
if (checkType == TYPE_TLS) {
80
return INSTANCE_TLS;
81
} else if (checkType == TYPE_LDAP) {
82
return INSTANCE_LDAP;
83
}
84
throw new IllegalArgumentException("Unknown check type: " + checkType);
85
}
86
87
/**
88
* Perform the check.
89
*
90
* @param expectedName the expected host name or ip address
91
* @param cert the certificate to check against
92
* @param chainsToPublicCA true if the certificate chains to a public
93
* root CA (as pre-installed in the cacerts file)
94
* @throws CertificateException if the name does not match any of
95
* the names specified in the certificate
96
*/
97
public void match(String expectedName, X509Certificate cert,
98
boolean chainsToPublicCA) throws CertificateException {
99
if (expectedName == null) {
100
throw new CertificateException("Hostname or IP address is " +
101
"undefined.");
102
}
103
if (isIpAddress(expectedName)) {
104
matchIP(expectedName, cert);
105
} else {
106
matchDNS(expectedName, cert, chainsToPublicCA);
107
}
108
}
109
110
public void match(String expectedName, X509Certificate cert)
111
throws CertificateException {
112
match(expectedName, cert, false);
113
}
114
115
/**
116
* Perform the check for Kerberos.
117
*/
118
public static boolean match(String expectedName, Principal principal) {
119
String hostName = getServerName(principal);
120
return (expectedName.equalsIgnoreCase(hostName));
121
}
122
123
/**
124
* Return the Server name from Kerberos principal.
125
*/
126
public static String getServerName(Principal principal) {
127
return Krb5Helper.getPrincipalHostName(principal);
128
}
129
130
/**
131
* Test whether the given hostname looks like a literal IPv4 or IPv6
132
* address. The hostname does not need to be a fully qualified name.
133
*
134
* This is not a strict check that performs full input validation.
135
* That means if the method returns true, name need not be a correct
136
* IP address, rather that it does not represent a valid DNS hostname.
137
* Likewise for IP addresses when it returns false.
138
*/
139
private static boolean isIpAddress(String name) {
140
if (IPAddressUtil.isIPv4LiteralAddress(name) ||
141
IPAddressUtil.isIPv6LiteralAddress(name)) {
142
return true;
143
} else {
144
return false;
145
}
146
}
147
148
/**
149
* Check if the certificate allows use of the given IP address.
150
*
151
* From RFC2818:
152
* In some cases, the URI is specified as an IP address rather than a
153
* hostname. In this case, the iPAddress subjectAltName must be present
154
* in the certificate and must exactly match the IP in the URI.
155
*/
156
private static void matchIP(String expectedIP, X509Certificate cert)
157
throws CertificateException {
158
Collection<List<?>> subjAltNames = cert.getSubjectAlternativeNames();
159
if (subjAltNames == null) {
160
throw new CertificateException
161
("No subject alternative names present");
162
}
163
for (List<?> next : subjAltNames) {
164
// For IP address, it needs to be exact match
165
if (((Integer)next.get(0)).intValue() == ALTNAME_IP) {
166
String ipAddress = (String)next.get(1);
167
if (expectedIP.equalsIgnoreCase(ipAddress)) {
168
return;
169
} else {
170
// compare InetAddress objects in order to ensure
171
// equality between a long IPv6 address and its
172
// abbreviated form.
173
try {
174
if (InetAddress.getByName(expectedIP).equals(
175
InetAddress.getByName(ipAddress))) {
176
return;
177
}
178
} catch (UnknownHostException e) {
179
} catch (SecurityException e) {}
180
}
181
}
182
}
183
throw new CertificateException("No subject alternative " +
184
"names matching " + "IP address " +
185
expectedIP + " found");
186
}
187
188
/**
189
* Check if the certificate allows use of the given DNS name.
190
*
191
* From RFC2818:
192
* If a subjectAltName extension of type dNSName is present, that MUST
193
* be used as the identity. Otherwise, the (most specific) Common Name
194
* field in the Subject field of the certificate MUST be used. Although
195
* the use of the Common Name is existing practice, it is deprecated and
196
* Certification Authorities are encouraged to use the dNSName instead.
197
*
198
* Matching is performed using the matching rules specified by
199
* [RFC5280]. If more than one identity of a given type is present in
200
* the certificate (e.g., more than one dNSName name, a match in any one
201
* of the set is considered acceptable.)
202
*/
203
private void matchDNS(String expectedName, X509Certificate cert,
204
boolean chainsToPublicCA)
205
throws CertificateException {
206
// Check that the expected name is a valid domain name.
207
try {
208
// Using the checking implemented in SNIHostName
209
SNIHostName sni = new SNIHostName(expectedName);
210
} catch (IllegalArgumentException iae) {
211
throw new CertificateException(
212
"Illegal given domain name: " + expectedName, iae);
213
}
214
215
Collection<List<?>> subjAltNames = cert.getSubjectAlternativeNames();
216
if (subjAltNames != null) {
217
boolean foundDNS = false;
218
for ( List<?> next : subjAltNames) {
219
if (((Integer)next.get(0)).intValue() == ALTNAME_DNS) {
220
foundDNS = true;
221
String dnsName = (String)next.get(1);
222
if (isMatched(expectedName, dnsName, chainsToPublicCA)) {
223
return;
224
}
225
}
226
}
227
if (foundDNS) {
228
// if certificate contains any subject alt names of type DNS
229
// but none match, reject
230
throw new CertificateException("No subject alternative DNS "
231
+ "name matching " + expectedName + " found.");
232
}
233
}
234
X500Name subjectName = getSubjectX500Name(cert);
235
DerValue derValue = subjectName.findMostSpecificAttribute
236
(X500Name.commonName_oid);
237
if (derValue != null) {
238
try {
239
String cname = derValue.getAsString();
240
if (!Normalizer.isNormalized(cname, Normalizer.Form.NFKC)) {
241
throw new CertificateException("Not a formal name "
242
+ cname);
243
}
244
if (isMatched(expectedName, cname,
245
chainsToPublicCA)) {
246
return;
247
}
248
} catch (IOException e) {
249
// ignore
250
}
251
}
252
String msg = "No name matching " + expectedName + " found";
253
throw new CertificateException(msg);
254
}
255
256
257
/**
258
* Return the subject of a certificate as X500Name, by reparsing if
259
* necessary. X500Name should only be used if access to name components
260
* is required, in other cases X500Principal is to be preferred.
261
*
262
* This method is currently used from within JSSE, do not remove.
263
*/
264
public static X500Name getSubjectX500Name(X509Certificate cert)
265
throws CertificateParsingException {
266
try {
267
Principal subjectDN = cert.getSubjectDN();
268
if (subjectDN instanceof X500Name) {
269
return (X500Name)subjectDN;
270
} else {
271
X500Principal subjectX500 = cert.getSubjectX500Principal();
272
return new X500Name(subjectX500.getEncoded());
273
}
274
} catch (IOException e) {
275
throw(CertificateParsingException)
276
new CertificateParsingException().initCause(e);
277
}
278
}
279
280
281
/**
282
* Returns true if name matches against template.<p>
283
*
284
* The matching is performed as per RFC 2818 rules for TLS and
285
* RFC 2830 rules for LDAP.<p>
286
*
287
* The <code>name</code> parameter should represent a DNS name. The
288
* <code>template</code> parameter may contain the wildcard character '*'.
289
*/
290
private boolean isMatched(String name, String template,
291
boolean chainsToPublicCA) {
292
if (hasIllegalWildcard(name, template, chainsToPublicCA)) {
293
return false;
294
}
295
// check the validity of the domain name template.
296
try {
297
// Replacing wildcard character '*' with 'z' so as to check
298
// the domain name template validity.
299
//
300
// Using the checking implemented in SNIHostName
301
SNIHostName sni = new SNIHostName(template.replace('*', 'z'));
302
} catch (IllegalArgumentException iae) {
303
// It would be nice to add debug log if not matching.
304
return false;
305
}
306
307
if (checkType == TYPE_TLS) {
308
return matchAllWildcards(name, template);
309
} else if (checkType == TYPE_LDAP) {
310
return matchLeftmostWildcard(name, template);
311
} else {
312
return false;
313
}
314
}
315
316
/**
317
* Returns true if the template contains an illegal wildcard character.
318
*/
319
private static boolean hasIllegalWildcard(String domain, String template,
320
boolean chainsToPublicCA) {
321
// not ok if it is a single wildcard character or "*."
322
if (template.equals("*") || template.equals("*.")) {
323
if (SSLLogger.isOn) {
324
SSLLogger.fine(
325
"Certificate domain name has illegal single " +
326
"wildcard character: " + template);
327
}
328
return true;
329
}
330
331
int lastWildcardIndex = template.lastIndexOf("*");
332
333
// ok if it has no wildcard character
334
if (lastWildcardIndex == -1) {
335
return false;
336
}
337
338
String afterWildcard = template.substring(lastWildcardIndex);
339
int firstDotIndex = afterWildcard.indexOf(".");
340
341
// not ok if there is no dot after wildcard (ex: "*com")
342
if (firstDotIndex == -1) {
343
if (SSLLogger.isOn) {
344
SSLLogger.fine(
345
"Certificate domain name has illegal wildcard, " +
346
"no dot after wildcard character: " + template);
347
}
348
return true;
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
354
if (!chainsToPublicCA) {
355
return false; // skip check for non-public certificates
356
}
357
Optional<RegisteredDomain> rd = RegisteredDomain.from(domain)
358
.filter(d -> d.type() == RegisteredDomain.Type.ICANN);
359
360
if (rd.isPresent()) {
361
String wDomain = afterWildcard.substring(firstDotIndex + 1);
362
if (rd.get().publicSuffix().equalsIgnoreCase(wDomain)) {
363
if (SSLLogger.isOn) {
364
SSLLogger.fine(
365
"Certificate domain name has illegal " +
366
"wildcard for public suffix: " + template);
367
}
368
return true;
369
}
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
// Retreive 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