001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.io.File; 041import java.io.FileInputStream; 042import java.io.Serializable; 043import java.security.KeyStore; 044import java.security.cert.CertificateException; 045import java.security.cert.X509Certificate; 046import java.util.Date; 047import javax.net.ssl.TrustManager; 048import javax.net.ssl.TrustManagerFactory; 049import javax.net.ssl.X509TrustManager; 050 051import com.unboundid.util.CryptoHelper; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059import com.unboundid.util.Validator; 060 061import static com.unboundid.util.ssl.SSLMessages.*; 062 063 064 065/** 066 * This class provides an SSL trust manager that will consult a specified trust 067 * store file to determine whether to trust a certificate that is presented to 068 * it. By default, it will use the default trust store format for the JVM 069 * (e.g., "JKS" for Sun-provided Java implementations), but alternate formats 070 * like PKCS12 may be used. 071 */ 072@NotMutable() 073@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 074public final class TrustStoreTrustManager 075 implements X509TrustManager, Serializable 076{ 077 /** 078 * A pre-allocated empty certificate array. 079 */ 080 @NotNull private static final X509Certificate[] NO_CERTIFICATES = 081 new X509Certificate[0]; 082 083 084 085 /** 086 * The serial version UID for this serializable class. 087 */ 088 private static final long serialVersionUID = -4093869102727719415L; 089 090 091 092 // Indicates whether to automatically trust expired or not-yet-valid 093 // certificates. 094 private final boolean examineValidityDates; 095 096 // The PIN to use to access the trust store. 097 @Nullable private final char[] trustStorePIN; 098 099 // The path to the trust store file. 100 @NotNull private final String trustStoreFile; 101 102 // The format to use for the trust store file. 103 @NotNull private final String trustStoreFormat; 104 105 106 107 /** 108 * Creates a new instance of this trust store trust manager that will trust 109 * all certificates in the specified file within the validity window. It will 110 * use the default trust store format and will not provide a PIN when 111 * attempting to read the trust store. 112 * 113 * @param trustStoreFile The path to the trust store file to use. It must 114 * not be {@code null}. 115 */ 116 public TrustStoreTrustManager(@NotNull final File trustStoreFile) 117 { 118 this(trustStoreFile.getAbsolutePath(), null, null, true); 119 } 120 121 122 123 /** 124 * Creates a new instance of this trust store trust manager that will trust 125 * all certificates in the specified file within the validity window. It will 126 * use the default trust store format and will not provide a PIN when 127 * attempting to read the trust store. 128 * 129 * @param trustStoreFile The path to the trust store file to use. It must 130 * not be {@code null}. 131 */ 132 public TrustStoreTrustManager(@NotNull final String trustStoreFile) 133 { 134 this(trustStoreFile, null, null, true); 135 } 136 137 138 139 /** 140 * Creates a new instance of this trust store trust manager that will trust 141 * all certificates in the specified file with the specified constraints. 142 * 143 * @param trustStoreFile The path to the trust store file to use. It 144 * must not be {@code null}. 145 * @param trustStorePIN The PIN to use to access the contents of the 146 * trust store. It may be {@code null} if no 147 * PIN is required. 148 * @param trustStoreFormat The format to use for the trust store. It 149 * may be {@code null} if the default format 150 * should be used. 151 * @param examineValidityDates Indicates whether to reject certificates if 152 * the current time is outside the validity 153 * window for the certificate. 154 */ 155 public TrustStoreTrustManager(@NotNull final File trustStoreFile, 156 @Nullable final char[] trustStorePIN, 157 @Nullable final String trustStoreFormat, 158 final boolean examineValidityDates) 159 { 160 this(trustStoreFile.getAbsolutePath(), trustStorePIN, trustStoreFormat, 161 examineValidityDates); 162 } 163 164 165 166 /** 167 * Creates a new instance of this trust store trust manager that will trust 168 * all certificates in the specified file with the specified constraints. 169 * 170 * @param trustStoreFile The path to the trust store file to use. It 171 * must not be {@code null}. 172 * @param trustStorePIN The PIN to use to access the contents of the 173 * trust store. It may be {@code null} if no 174 * PIN is required. 175 * @param trustStoreFormat The format to use for the trust store. It 176 * may be {@code null} if the default format 177 * should be used. 178 * @param examineValidityDates Indicates whether to reject certificates if 179 * the current time is outside the validity 180 * window for the certificate. 181 */ 182 public TrustStoreTrustManager(@NotNull final String trustStoreFile, 183 @Nullable final char[] trustStorePIN, 184 @Nullable final String trustStoreFormat, 185 final boolean examineValidityDates) 186 { 187 Validator.ensureNotNull(trustStoreFile); 188 189 this.trustStoreFile = trustStoreFile; 190 this.trustStorePIN = trustStorePIN; 191 this.examineValidityDates = examineValidityDates; 192 193 if (trustStoreFormat == null) 194 { 195 this.trustStoreFormat = CryptoHelper.getDefaultKeyStoreType(); 196 } 197 else 198 { 199 this.trustStoreFormat = trustStoreFormat; 200 } 201 } 202 203 204 205 /** 206 * Retrieves the path to the trust store file to use. 207 * 208 * @return The path to the trust store file to use. 209 */ 210 @NotNull() 211 public String getTrustStoreFile() 212 { 213 return trustStoreFile; 214 } 215 216 217 218 /** 219 * Retrieves the name of the trust store file format. 220 * 221 * @return The name of the trust store file format. 222 */ 223 @NotNull() 224 public String getTrustStoreFormat() 225 { 226 return trustStoreFormat; 227 } 228 229 230 231 /** 232 * Indicate whether to reject certificates if the current time is outside the 233 * validity window for the certificate. 234 * 235 * @return {@code true} if the certificate validity time should be examined 236 * and certificates should be rejected if they are expired or not 237 * yet valid, or {@code false} if certificates should be accepted 238 * even outside of the validity window. 239 */ 240 public boolean examineValidityDates() 241 { 242 return examineValidityDates; 243 } 244 245 246 247 /** 248 * Retrieves a set of trust managers that may be used to determine whether the 249 * provided certificate chain should be trusted. It will also check the 250 * validity of the provided certificates. 251 * 252 * @param chain The certificate chain for which to make the determination. 253 * 254 * @return The set of trust managers that may be used to make the 255 * determination. 256 * 257 * @throws CertificateException If the provided client certificate chain 258 * should not be trusted. 259 */ 260 @NotNull() 261 private X509TrustManager[] getTrustManagers( 262 @NotNull final X509Certificate[] chain) 263 throws CertificateException 264 { 265 if (examineValidityDates) 266 { 267 final Date d = new Date(); 268 for (final X509Certificate c : chain) 269 { 270 c.checkValidity(d); 271 } 272 } 273 274 final File f = new File(trustStoreFile); 275 if (! f.exists()) 276 { 277 throw new CertificateException( 278 ERR_TRUSTSTORE_NO_SUCH_FILE.get(trustStoreFile)); 279 } 280 281 final KeyStore ks; 282 try 283 { 284 ks = CryptoHelper.getKeyStore(trustStoreFormat); 285 } 286 catch (final Exception e) 287 { 288 Debug.debugException(e); 289 290 throw new CertificateException( 291 ERR_TRUSTSTORE_UNSUPPORTED_FORMAT.get(trustStoreFormat), e); 292 } 293 294 try (FileInputStream inputStream = new FileInputStream(f)) 295 { 296 ks.load(inputStream, trustStorePIN); 297 } 298 catch (final Exception e) 299 { 300 Debug.debugException(e); 301 302 throw new CertificateException( 303 ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, trustStoreFormat, 304 StaticUtils.getExceptionMessage(e)), 305 e); 306 } 307 308 try 309 { 310 final TrustManagerFactory factory = CryptoHelper.getTrustManagerFactory(); 311 factory.init(ks); 312 final TrustManager[] trustManagers = factory.getTrustManagers(); 313 final X509TrustManager[] x509TrustManagers = 314 new X509TrustManager[trustManagers.length]; 315 for (int i=0; i < trustManagers.length; i++) 316 { 317 x509TrustManagers[i] = (X509TrustManager) trustManagers[i]; 318 } 319 return x509TrustManagers; 320 } 321 catch (final Exception e) 322 { 323 Debug.debugException(e); 324 325 throw new CertificateException( 326 ERR_TRUSTSTORE_CANNOT_GET_TRUST_MANAGERS.get(trustStoreFile, 327 trustStoreFormat, StaticUtils.getExceptionMessage(e)), 328 e); 329 } 330 } 331 332 333 334 /** 335 * Checks to determine whether the provided client certificate chain should be 336 * trusted. 337 * 338 * @param chain The client certificate chain for which to make the 339 * determination. 340 * @param authType The authentication type based on the client certificate. 341 * 342 * @throws CertificateException If the provided client certificate chain 343 * should not be trusted. 344 */ 345 @Override() 346 public void checkClientTrusted(@NotNull final X509Certificate[] chain, 347 @NotNull final String authType) 348 throws CertificateException 349 { 350 for (final X509TrustManager m : getTrustManagers(chain)) 351 { 352 m.checkClientTrusted(chain, authType); 353 } 354 } 355 356 357 358 /** 359 * Checks to determine whether the provided server certificate chain should be 360 * trusted. 361 * 362 * @param chain The server certificate chain for which to make the 363 * determination. 364 * @param authType The key exchange algorithm used. 365 * 366 * @throws CertificateException If the provided server certificate chain 367 * should not be trusted. 368 */ 369 @Override() 370 public void checkServerTrusted(@NotNull final X509Certificate[] chain, 371 @NotNull final String authType) 372 throws CertificateException 373 { 374 for (final X509TrustManager m : getTrustManagers(chain)) 375 { 376 m.checkServerTrusted(chain, authType); 377 } 378 } 379 380 381 382 /** 383 * Retrieves the accepted issuer certificates for this trust manager. This 384 * will always return an empty array. 385 * 386 * @return The accepted issuer certificates for this trust manager. 387 */ 388 @Override() 389 @NotNull() 390 public X509Certificate[] getAcceptedIssuers() 391 { 392 return NO_CERTIFICATES; 393 } 394}