001/* 002 * Copyright 2012-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2020 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-2020 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.StaticUtils; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.Validator; 053 054import static com.unboundid.util.ssl.SSLMessages.*; 055 056 057 058/** 059 * This class provides an SSL trust manager that will only accept certificates 060 * whose hostname (as contained in the CN subject attribute or a subjectAltName 061 * extension) matches an expected value. Only the dNSName, iPAddress, and 062 * uniformResourceIdentifier subjectAltName formats are supported. 063 * <BR><BR> 064 * This implementation optionally supports wildcard certificates, which have a 065 * hostname that starts with an asterisk followed by a period and domain or 066 * subdomain. For example, "*.example.com" could be considered a match for 067 * anything in the "example.com" domain. If wildcards are allowed, then only 068 * the CN subject attribute and dNSName subjectAltName extension will be 069 * examined, and only the leftmost element of a hostname may be a wildcard 070 * character. 071 * <BR><BR> 072 * Note that no other elements of the certificate are examined, so it is 073 * strongly recommended that this trust manager be used in an 074 * {@link AggregateTrustManager} in conjunction with other trust managers that 075 * perform other forms of validation. 076 */ 077@NotMutable() 078@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 079public final class HostNameTrustManager 080 implements X509TrustManager 081{ 082 /** 083 * A pre-allocated empty certificate array. 084 */ 085 private static final X509Certificate[] NO_CERTIFICATES = 086 new X509Certificate[0]; 087 088 089 090 // Indicates whether to allow wildcard certificates (which 091 private final boolean allowWildcards; 092 093 // The set of hostname values that will be considered acceptable. 094 private final Set<String> acceptableHostNames; 095 096 097 098 /** 099 * Creates a new hostname trust manager with the provided information. 100 * 101 * @param allowWildcards Indicates whether to allow wildcard 102 * certificates which contain an asterisk as the 103 * first component of a CN subject attribute or 104 * dNSName subjectAltName extension. 105 * @param acceptableHostNames The set of hostnames and/or IP addresses that 106 * will be considered acceptable. Only 107 * certificates with a CN or subjectAltName value 108 * that exactly matches one of these names 109 * (ignoring differences in capitalization) will 110 * be considered acceptable. It must not be 111 * {@code null} or empty. 112 */ 113 public HostNameTrustManager(final boolean allowWildcards, 114 final String... acceptableHostNames) 115 { 116 this(allowWildcards, StaticUtils.toList(acceptableHostNames)); 117 } 118 119 120 121 /** 122 * Creates a new hostname trust manager with the provided information. 123 * 124 * @param allowWildcards Indicates whether to allow wildcard 125 * certificates which contain an asterisk as the 126 * first component of a CN subject attribute or 127 * dNSName subjectAltName extension. 128 * @param acceptableHostNames The set of hostnames and/or IP addresses that 129 * will be considered acceptable. Only 130 * certificates with a CN or subjectAltName value 131 * that exactly matches one of these names 132 * (ignoring differences in capitalization) will 133 * be considered acceptable. It must not be 134 * {@code null} or empty. 135 */ 136 public HostNameTrustManager(final boolean allowWildcards, 137 final Collection<String> acceptableHostNames) 138 { 139 Validator.ensureNotNull(acceptableHostNames); 140 Validator.ensureFalse(acceptableHostNames.isEmpty(), 141 "The set of acceptable host names must not be empty."); 142 143 this.allowWildcards = allowWildcards; 144 145 final LinkedHashSet<String> nameSet = new LinkedHashSet<>( 146 StaticUtils.computeMapCapacity(acceptableHostNames.size())); 147 for (final String s : acceptableHostNames) 148 { 149 nameSet.add(StaticUtils.toLowerCase(s)); 150 } 151 152 this.acceptableHostNames = Collections.unmodifiableSet(nameSet); 153 } 154 155 156 157 /** 158 * Indicates whether wildcard certificates should be allowed, which may 159 * match multiple hosts in a given domain or subdomain. 160 * 161 * @return {@code true} if wildcard certificates should be allowed, or 162 * {@code false} if not. 163 */ 164 public boolean allowWildcards() 165 { 166 return allowWildcards; 167 } 168 169 170 171 /** 172 * Retrieves the set of hostnames that will be considered acceptable. 173 * 174 * @return The set of hostnames that will be considered acceptable. 175 */ 176 public Set<String> getAcceptableHostNames() 177 { 178 return acceptableHostNames; 179 } 180 181 182 183 /** 184 * Checks to determine whether the provided client certificate chain should be 185 * trusted. 186 * 187 * @param chain The client certificate chain for which to make the 188 * determination. 189 * @param authType The authentication type based on the client certificate. 190 * 191 * @throws CertificateException If the provided client certificate chain 192 * should not be trusted. 193 */ 194 @Override() 195 public void checkClientTrusted(final X509Certificate[] chain, 196 final String authType) 197 throws CertificateException 198 { 199 final StringBuilder buffer = new StringBuilder(); 200 for (final String s : acceptableHostNames) 201 { 202 buffer.setLength(0); 203 if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0], 204 allowWildcards, buffer)) 205 { 206 return; 207 } 208 } 209 210 throw new CertificateException( 211 ERR_HOSTNAME_NOT_FOUND.get(buffer.toString())); 212 } 213 214 215 216 /** 217 * Checks to determine whether the provided server certificate chain should be 218 * trusted. 219 * 220 * @param chain The server certificate chain for which to make the 221 * determination. 222 * @param authType The key exchange algorithm used. 223 * 224 * @throws CertificateException If the provided server certificate chain 225 * should not be trusted. 226 */ 227 @Override() 228 public void checkServerTrusted(final X509Certificate[] chain, 229 final String authType) 230 throws CertificateException 231 { 232 final StringBuilder buffer = new StringBuilder(); 233 for (final String s : acceptableHostNames) 234 { 235 buffer.setLength(0); 236 if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0], 237 allowWildcards, buffer)) 238 { 239 return; 240 } 241 } 242 243 throw new CertificateException( 244 ERR_HOSTNAME_NOT_FOUND.get(buffer.toString())); 245 } 246 247 248 249 /** 250 * Retrieves the accepted issuer certificates for this trust manager. This 251 * will always return an empty array. 252 * 253 * @return The accepted issuer certificates for this trust manager. 254 */ 255 @Override() 256 public X509Certificate[] getAcceptedIssuers() 257 { 258 return NO_CERTIFICATES; 259 } 260}