001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2012-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util.ssl; 037 038 039 040import java.security.cert.CertificateException; 041import java.security.cert.X509Certificate; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.LinkedHashSet; 045import java.util.Set; 046import javax.net.ssl.X509TrustManager; 047 048import com.unboundid.util.NotMutable; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.StaticUtils; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.Validator; 054 055import static com.unboundid.util.ssl.SSLMessages.*; 056 057 058 059/** 060 * This class provides an SSL trust manager that will only accept certificates 061 * whose hostname (as contained in the CN subject attribute or a subjectAltName 062 * extension) matches an expected value. Only the dNSName, iPAddress, and 063 * uniformResourceIdentifier subjectAltName formats are supported. 064 * <BR><BR> 065 * This implementation optionally supports wildcard certificates, which have a 066 * hostname that starts with an asterisk followed by a period and domain or 067 * subdomain. For example, "*.example.com" could be considered a match for 068 * anything in the "example.com" domain. If wildcards are allowed, then only 069 * the CN subject attribute and dNSName subjectAltName extension will be 070 * examined, and only the leftmost element of a hostname may be a wildcard 071 * character. 072 * <BR><BR> 073 * Note that no other elements of the certificate are examined, so it is 074 * strongly recommended that this trust manager be used in an 075 * {@link AggregateTrustManager} in conjunction with other trust managers that 076 * perform other forms of validation. 077 */ 078@NotMutable() 079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 080public final class HostNameTrustManager 081 implements X509TrustManager 082{ 083 /** 084 * A pre-allocated empty certificate array. 085 */ 086 @NotNull private static final X509Certificate[] NO_CERTIFICATES = 087 new X509Certificate[0]; 088 089 090 091 // Indicates whether to allow wildcard certificates (which 092 private final boolean allowWildcards; 093 094 // Indicates whether to check the CN attribute in the peer certificate's 095 // subject DN if the certificate also contains a subject alternative name 096 // extension that contains at least dNSName, uniformResourceIdentifier, or 097 // iPAddress value. 098 private final boolean checkCNWhenSubjectAltNameIsPresent; 099 100 // The set of hostname values that will be considered acceptable. 101 @NotNull private final Set<String> acceptableHostNames; 102 103 104 105 /** 106 * Creates a new hostname trust manager with the provided information. 107 * 108 * @param allowWildcards Indicates whether to allow wildcard 109 * certificates which contain an asterisk as the 110 * first component of a CN subject attribute or 111 * dNSName subjectAltName extension. 112 * @param acceptableHostNames The set of hostnames and/or IP addresses that 113 * will be considered acceptable. Only 114 * certificates with a CN or subjectAltName value 115 * that exactly matches one of these names 116 * (ignoring differences in capitalization) will 117 * be considered acceptable. It must not be 118 * {@code null} or empty. 119 */ 120 public HostNameTrustManager(final boolean allowWildcards, 121 @NotNull final String... acceptableHostNames) 122 { 123 this(allowWildcards, StaticUtils.toList(acceptableHostNames)); 124 } 125 126 127 128 /** 129 * Creates a new hostname trust manager with the provided information. 130 * 131 * @param allowWildcards Indicates whether to allow wildcard 132 * certificates which contain an asterisk as the 133 * first component of a CN subject attribute or 134 * dNSName subjectAltName extension. 135 * @param acceptableHostNames The set of hostnames and/or IP addresses that 136 * will be considered acceptable. Only 137 * certificates with a CN or subjectAltName value 138 * that exactly matches one of these names 139 * (ignoring differences in capitalization) will 140 * be considered acceptable. It must not be 141 * {@code null} or empty. 142 */ 143 public HostNameTrustManager(final boolean allowWildcards, 144 @NotNull final Collection<String> acceptableHostNames) 145 { 146 this(allowWildcards, 147 HostNameSSLSocketVerifier. 148 DEFAULT_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT, 149 acceptableHostNames); 150 } 151 152 153 154 /** 155 * Creates a new hostname trust manager with the provided information. 156 * 157 * @param allowWildcards 158 * Indicates whether to allow wildcard certificates that contain 159 * an asterisk in the leftmost component of a hostname in the 160 * dNSName or uniformResourceIdentifier of the subject 161 * alternative name extension, or in the CN attribute of the 162 * subject DN. 163 * @param checkCNWhenSubjectAltNameIsPresent 164 * Indicates whether to check the CN attribute in the peer 165 * certificate's subject DN if the certificate also contains a 166 * subject alternative name extension that contains at least one 167 * dNSName, uniformResourceIdentifier, or iPAddress value. 168 * Although RFC 6125 section 6.4.4 indicates that the CN 169 * attribute should not be checked in certificates that have an 170 * appropriate subject alternative name extension, LDAP clients 171 * historically treat both sources as equally valid. 172 * @param acceptableHostNames 173 * The set of hostnames and/or IP addresses that will be 174 * considered acceptable. Only certificates with a CN or 175 * subjectAltName value that exactly matches one of these names 176 * (ignoring differences in capitalization) will be considered 177 * acceptable. It must not be {@code null} or empty. 178 */ 179 public HostNameTrustManager(final boolean allowWildcards, 180 final boolean checkCNWhenSubjectAltNameIsPresent, 181 @NotNull final Collection<String> acceptableHostNames) 182 { 183 Validator.ensureNotNull(acceptableHostNames); 184 Validator.ensureFalse(acceptableHostNames.isEmpty(), 185 "The set of acceptable host names must not be empty."); 186 187 this.allowWildcards = allowWildcards; 188 this.checkCNWhenSubjectAltNameIsPresent = 189 checkCNWhenSubjectAltNameIsPresent; 190 191 final LinkedHashSet<String> nameSet = new LinkedHashSet<>( 192 StaticUtils.computeMapCapacity(acceptableHostNames.size())); 193 for (final String s : acceptableHostNames) 194 { 195 nameSet.add(StaticUtils.toLowerCase(s)); 196 } 197 198 this.acceptableHostNames = Collections.unmodifiableSet(nameSet); 199 } 200 201 202 203 /** 204 * Indicates whether wildcard certificates should be allowed, which may 205 * match multiple hosts in a given domain or subdomain. 206 * 207 * @return {@code true} if wildcard certificates should be allowed, or 208 * {@code false} if not. 209 */ 210 public boolean allowWildcards() 211 { 212 return allowWildcards; 213 } 214 215 216 217 /** 218 * Retrieves the set of hostnames that will be considered acceptable. 219 * 220 * @return The set of hostnames that will be considered acceptable. 221 */ 222 @NotNull() 223 public Set<String> getAcceptableHostNames() 224 { 225 return acceptableHostNames; 226 } 227 228 229 230 /** 231 * Checks to determine whether the provided client certificate chain should be 232 * trusted. 233 * 234 * @param chain The client certificate chain for which to make the 235 * determination. 236 * @param authType The authentication type based on the client certificate. 237 * 238 * @throws CertificateException If the provided client certificate chain 239 * should not be trusted. 240 */ 241 @Override() 242 public void checkClientTrusted(@NotNull final X509Certificate[] chain, 243 @NotNull final String authType) 244 throws CertificateException 245 { 246 final StringBuilder buffer = new StringBuilder(); 247 for (final String s : acceptableHostNames) 248 { 249 buffer.setLength(0); 250 if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0], 251 allowWildcards, checkCNWhenSubjectAltNameIsPresent, buffer)) 252 { 253 return; 254 } 255 } 256 257 throw new CertificateException( 258 ERR_HOSTNAME_NOT_FOUND.get(buffer.toString())); 259 } 260 261 262 263 /** 264 * Checks to determine whether the provided server certificate chain should be 265 * trusted. 266 * 267 * @param chain The server certificate chain for which to make the 268 * determination. 269 * @param authType The key exchange algorithm used. 270 * 271 * @throws CertificateException If the provided server certificate chain 272 * should not be trusted. 273 */ 274 @Override() 275 public void checkServerTrusted(@NotNull final X509Certificate[] chain, 276 @NotNull final String authType) 277 throws CertificateException 278 { 279 final StringBuilder buffer = new StringBuilder(); 280 for (final String s : acceptableHostNames) 281 { 282 buffer.setLength(0); 283 if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0], 284 allowWildcards, checkCNWhenSubjectAltNameIsPresent, buffer)) 285 { 286 return; 287 } 288 } 289 290 throw new CertificateException( 291 ERR_HOSTNAME_NOT_FOUND.get(buffer.toString())); 292 } 293 294 295 296 /** 297 * Retrieves the accepted issuer certificates for this trust manager. This 298 * will always return an empty array. 299 * 300 * @return The accepted issuer certificates for this trust manager. 301 */ 302 @Override() 303 @NotNull() 304 public X509Certificate[] getAcceptedIssuers() 305 { 306 return NO_CERTIFICATES; 307 } 308}