Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2008-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2008-2019 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util.ssl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.X509TrustManager;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.ssl.cert.CertException;
import static com.unboundid.util.ssl.SSLMessages.*;
/**
* This class provides an SSL trust manager that will interactively prompt the
* user to determine whether to trust any certificate that is presented to it.
* It provides the ability to cache information about certificates that had been
* previously trusted so that the user is not prompted about the same
* certificate repeatedly, and it can be configured to store trusted
* certificates in a file so that the trust information can be persisted.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class PromptTrustManager
implements X509TrustManager
{
/**
* A pre-allocated empty certificate array.
*/
private static final X509Certificate[] NO_CERTIFICATES =
new X509Certificate[0];
// Indicates whether to examine the validity dates for the certificate in
// addition to whether the certificate has been previously trusted.
private final boolean examineValidityDates;
// The set of previously-accepted certificates. The certificates will be
// mapped from an all-lowercase hexadecimal string representation of the
// certificate signature to a flag that indicates whether the certificate has
// already been manually trusted even if it is outside of the validity window.
private final ConcurrentHashMap acceptedCerts;
// The input stream from which the user input will be read.
private final InputStream in;
// A list of the addresses that the client is expected to use to connect to
// one of the target servers.
private final List expectedAddresses;
// The print stream that will be used to display the prompt.
private final PrintStream out;
// The path to the file to which the set of accepted certificates should be
// persisted.
private final String acceptedCertsFile;
/**
* Creates a new instance of this prompt trust manager. It will cache trust
* information in memory but not on disk.
*/
public PromptTrustManager()
{
this(null, true, null, null);
}
/**
* Creates a new instance of this prompt trust manager. It may optionally
* cache trust information on disk.
*
* @param acceptedCertsFile The path to a file in which the certificates
* that have been previously accepted will be
* cached. It may be {@code null} if the cache
* should only be maintained in memory.
*/
public PromptTrustManager(final String acceptedCertsFile)
{
this(acceptedCertsFile, true, null, null);
}
/**
* Creates a new instance of this prompt trust manager. It may optionally
* cache trust information on disk, and may also be configured to examine or
* ignore validity dates.
*
* @param acceptedCertsFile The path to a file in which the certificates
* that have been previously accepted will be
* cached. It may be {@code null} if the cache
* should only be maintained in memory.
* @param examineValidityDates Indicates whether to reject certificates if
* the current time is outside the validity
* window for the certificate.
* @param in The input stream that will be used to read
* input from the user. If this is {@code null}
* then {@code System.in} will be used.
* @param out The print stream that will be used to display
* the prompt to the user. If this is
* {@code null} then System.out will be used.
*/
public PromptTrustManager(final String acceptedCertsFile,
final boolean examineValidityDates,
final InputStream in, final PrintStream out)
{
this(acceptedCertsFile, examineValidityDates,
Collections.emptyList(), in, out);
}
/**
* Creates a new instance of this prompt trust manager. It may optionally
* cache trust information on disk, and may also be configured to examine or
* ignore validity dates.
*
* @param acceptedCertsFile The path to a file in which the certificates
* that have been previously accepted will be
* cached. It may be {@code null} if the cache
* should only be maintained in memory.
* @param examineValidityDates Indicates whether to reject certificates if
* the current time is outside the validity
* window for the certificate.
* @param expectedAddress An optional address that the client is
* expected to use to connect to the target
* server. This may be {@code null} if no
* expected address is available, if this trust
* manager is only expected to be used to
* validate client certificates, or if no server
* address validation should be performed. If a
* non-{@code null} value is provided, then the
* trust manager may issue a warning if the
* certificate does not contain that address.
* @param in The input stream that will be used to read
* input from the user. If this is {@code null}
* then {@code System.in} will be used.
* @param out The print stream that will be used to display
* the prompt to the user. If this is
* {@code null} then System.out will be used.
*/
public PromptTrustManager(final String acceptedCertsFile,
final boolean examineValidityDates,
final String expectedAddress, final InputStream in,
final PrintStream out)
{
this(acceptedCertsFile, examineValidityDates,
(expectedAddress == null)
? Collections.emptyList()
: Collections.singletonList(expectedAddress),
in, out);
}
/**
* Creates a new instance of this prompt trust manager. It may optionally
* cache trust information on disk, and may also be configured to examine or
* ignore validity dates.
*
* @param acceptedCertsFile The path to a file in which the certificates
* that have been previously accepted will be
* cached. It may be {@code null} if the cache
* should only be maintained in memory.
* @param examineValidityDates Indicates whether to reject certificates if
* the current time is outside the validity
* window for the certificate.
* @param expectedAddresses An optional collection of the addresses that
* the client is expected to use to connect to
* one of the target servers. This may be
* {@code null} or empty if no expected
* addresses are available, if this trust
* manager is only expected to be used to
* validate client certificates, or if no server
* address validation should be performed. If a
* non-empty collection is provided, then the
* trust manager may issue a warning if the
* certificate does not contain any of these
* addresses.
* @param in The input stream that will be used to read
* input from the user. If this is {@code null}
* then {@code System.in} will be used.
* @param out The print stream that will be used to display
* the prompt to the user. If this is
* {@code null} then System.out will be used.
*/
public PromptTrustManager(final String acceptedCertsFile,
final boolean examineValidityDates,
final Collection expectedAddresses,
final InputStream in, final PrintStream out)
{
this.acceptedCertsFile = acceptedCertsFile;
this.examineValidityDates = examineValidityDates;
if (expectedAddresses == null)
{
this.expectedAddresses = Collections.emptyList();
}
else
{
this.expectedAddresses =
Collections.unmodifiableList(new ArrayList<>(expectedAddresses));
}
if (in == null)
{
this.in = System.in;
}
else
{
this.in = in;
}
if (out == null)
{
this.out = System.out;
}
else
{
this.out = out;
}
acceptedCerts = new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20));
if (acceptedCertsFile != null)
{
BufferedReader r = null;
try
{
final File f = new File(acceptedCertsFile);
if (f.exists())
{
r = new BufferedReader(new FileReader(f));
while (true)
{
final String line = r.readLine();
if (line == null)
{
break;
}
acceptedCerts.put(line, false);
}
}
}
catch (final Exception e)
{
Debug.debugException(e);
}
finally
{
if (r != null)
{
try
{
r.close();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
}
}
/**
* Writes an updated copy of the trusted certificate cache to disk.
*
* @throws IOException If a problem occurs.
*/
private void writeCacheFile()
throws IOException
{
final File tempFile = new File(acceptedCertsFile + ".new");
BufferedWriter w = null;
try
{
w = new BufferedWriter(new FileWriter(tempFile));
for (final String certBytes : acceptedCerts.keySet())
{
w.write(certBytes);
w.newLine();
}
}
finally
{
if (w != null)
{
w.close();
}
}
final File cacheFile = new File(acceptedCertsFile);
if (cacheFile.exists())
{
final File oldFile = new File(acceptedCertsFile + ".previous");
if (oldFile.exists())
{
Files.delete(oldFile.toPath());
}
Files.move(cacheFile.toPath(), oldFile.toPath());
}
Files.move(tempFile.toPath(), cacheFile.toPath());
}
/**
* Indicates whether this trust manager would interactively prompt the user
* about whether to trust the provided certificate chain.
*
* @param chain The chain of certificates for which to make the
* determination.
*
* @return {@code true} if this trust manger would interactively prompt the
* user about whether to trust the certificate chain, or
* {@code false} if not (e.g., because the certificate is already
* known to be trusted).
*/
public synchronized boolean wouldPrompt(final X509Certificate[] chain)
{
try
{
final String cacheKey = getCacheKey(chain[0]);
return PromptTrustManagerProcessor.shouldPrompt(cacheKey,
convertChain(chain), false, examineValidityDates, acceptedCerts,
null).getFirst();
}
catch (final Exception e)
{
Debug.debugException(e);
return false;
}
}
/**
* Performs the necessary validity check for the provided certificate array.
*
* @param chain The chain of certificates for which to make the
* determination.
* @param serverCert Indicates whether the certificate was presented as a
* server certificate or as a client certificate.
*
* @throws CertificateException If the provided certificate chain should not
* be trusted.
*/
private synchronized void checkCertificateChain(final X509Certificate[] chain,
final boolean serverCert)
throws CertificateException
{
final com.unboundid.util.ssl.cert.X509Certificate[] convertedChain =
convertChain(chain);
final String cacheKey = getCacheKey(chain[0]);
final ObjectPair> shouldPromptResult =
PromptTrustManagerProcessor.shouldPrompt(cacheKey, convertedChain,
serverCert, examineValidityDates, acceptedCerts,
expectedAddresses);
if (! shouldPromptResult.getFirst())
{
return;
}
if (serverCert)
{
out.println(INFO_PROMPT_SERVER_HEADING.get());
}
else
{
out.println(INFO_PROMPT_CLIENT_HEADING.get());
}
out.println();
out.println(" " +
INFO_PROMPT_SUBJECT.get(convertedChain[0].getSubjectDN()));
out.println(" " +
INFO_PROMPT_VALID_FROM.get(PromptTrustManagerProcessor.formatDate(
convertedChain[0].getNotBeforeDate())));
out.println(" " +
INFO_PROMPT_VALID_TO.get(PromptTrustManagerProcessor.formatDate(
convertedChain[0].getNotAfterDate())));
try
{
final byte[] sha1Fingerprint = convertedChain[0].getSHA1Fingerprint();
final StringBuilder buffer = new StringBuilder();
StaticUtils.toHex(sha1Fingerprint, ":", buffer);
out.println(" " + INFO_PROMPT_SHA1_FINGERPRINT.get(buffer));
}
catch (final Exception e)
{
Debug.debugException(e);
}
try
{
final byte[] sha256Fingerprint = convertedChain[0].getSHA256Fingerprint();
final StringBuilder buffer = new StringBuilder();
StaticUtils.toHex(sha256Fingerprint, ":", buffer);
out.println(" " + INFO_PROMPT_SHA256_FINGERPRINT.get(buffer));
}
catch (final Exception e)
{
Debug.debugException(e);
}
for (int i=1; i < chain.length; i++)
{
out.println(" -");
out.println(" " +
INFO_PROMPT_ISSUER_SUBJECT.get(i, convertedChain[i].getSubjectDN()));
out.println(" " +
INFO_PROMPT_VALID_FROM.get(PromptTrustManagerProcessor.formatDate(
convertedChain[i].getNotBeforeDate())));
out.println(" " +
INFO_PROMPT_VALID_TO.get(PromptTrustManagerProcessor.formatDate(
convertedChain[i].getNotAfterDate())));
try
{
final byte[] sha1Fingerprint = convertedChain[i].getSHA1Fingerprint();
final StringBuilder buffer = new StringBuilder();
StaticUtils.toHex(sha1Fingerprint, ":", buffer);
out.println(" " + INFO_PROMPT_SHA1_FINGERPRINT.get(buffer));
}
catch (final Exception e)
{
Debug.debugException(e);
}
try
{
final byte[] sha256Fingerprint =
convertedChain[i].getSHA256Fingerprint();
final StringBuilder buffer = new StringBuilder();
StaticUtils.toHex(sha256Fingerprint, ":", buffer);
out.println(" " + INFO_PROMPT_SHA256_FINGERPRINT.get(buffer));
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
for (final String warningMessage : shouldPromptResult.getSecond())
{
out.println();
for (final String line :
StaticUtils.wrapLine(warningMessage,
(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1)))
{
out.println(line);
}
}
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
while (true)
{
try
{
out.println();
out.print(INFO_PROMPT_MESSAGE.get() + ' ');
out.flush();
final String line = reader.readLine();
if (line == null)
{
// The input stream has been closed, so we can't prompt for trust,
// and should assume it is not trusted.
throw new CertificateException(
ERR_CERTIFICATE_REJECTED_BY_END_OF_STREAM.get(
SSLUtil.certificateToString(chain[0])));
}
else if (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"))
{
// The certificate should be considered trusted.
break;
}
else if (line.equalsIgnoreCase("n") || line.equalsIgnoreCase("no"))
{
// The certificate should not be trusted.
throw new CertificateException(
ERR_CERTIFICATE_REJECTED_BY_USER.get(
SSLUtil.certificateToString(chain[0])));
}
}
catch (final CertificateException ce)
{
throw ce;
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
boolean isOutsideValidityWindow = false;
for (final com.unboundid.util.ssl.cert.X509Certificate c : convertedChain)
{
if (! c.isWithinValidityWindow())
{
isOutsideValidityWindow = true;
break;
}
}
acceptedCerts.put(cacheKey, isOutsideValidityWindow);
if (acceptedCertsFile != null)
{
try
{
writeCacheFile();
}
catch (final Exception e)
{
Debug.debugException(e);
}
}
}
/**
* Indicate whether to prompt about certificates contained in the cache if the
* current time is outside the validity window for the certificate.
*
* @return {@code true} if the certificate validity time should be examined
* for cached certificates and the user should be prompted if they
* are expired or not yet valid, or {@code false} if cached
* certificates should be accepted even outside of the validity
* window.
*/
public boolean examineValidityDates()
{
return examineValidityDates;
}
/**
* Retrieves a list of the addresses that the client is expected to use to
* communicate with the server, if available.
*
* @return A list of the addresses that the client is expected to use to
* communicate with the server, or an empty list if this is not
* available or applicable.
*/
public List getExpectedAddresses()
{
return expectedAddresses;
}
/**
* Checks to determine whether the provided client certificate chain should be
* trusted.
*
* @param chain The client certificate chain for which to make the
* determination.
* @param authType The authentication type based on the client certificate.
*
* @throws CertificateException If the provided client certificate chain
* should not be trusted.
*/
@Override()
public void checkClientTrusted(final X509Certificate[] chain,
final String authType)
throws CertificateException
{
checkCertificateChain(chain, false);
}
/**
* Checks to determine whether the provided server certificate chain should be
* trusted.
*
* @param chain The server certificate chain for which to make the
* determination.
* @param authType The key exchange algorithm used.
*
* @throws CertificateException If the provided server certificate chain
* should not be trusted.
*/
@Override()
public void checkServerTrusted(final X509Certificate[] chain,
final String authType)
throws CertificateException
{
checkCertificateChain(chain, true);
}
/**
* Retrieves the accepted issuer certificates for this trust manager. This
* will always return an empty array.
*
* @return The accepted issuer certificates for this trust manager.
*/
@Override()
public X509Certificate[] getAcceptedIssuers()
{
return NO_CERTIFICATES;
}
/**
* Retrieves the cache key used to identify the provided certificate in the
* map of accepted certificates.
*
* @param certificate The certificate for which to get the cache key.
*
* @return The generated cache key.
*/
static String getCacheKey(final Certificate certificate)
{
final X509Certificate x509Certificate = (X509Certificate) certificate;
return StaticUtils.toLowerCase(
StaticUtils.toHex(x509Certificate.getSignature()));
}
/**
* Converts the provided certificate chain from Java's representation of
* X.509 certificates to the LDAP SDK's version.
*
* @param chain The chain to be converted.
*
* @return The converted certificate chain.
*
* @throws CertificateException If a problem occurs while performing the
* conversion.
*/
static com.unboundid.util.ssl.cert.X509Certificate[]
convertChain(final Certificate[] chain)
throws CertificateException
{
final com.unboundid.util.ssl.cert.X509Certificate[] convertedChain =
new com.unboundid.util.ssl.cert.X509Certificate[chain.length];
for (int i=0; i < chain.length; i++)
{
try
{
convertedChain[i] = new com.unboundid.util.ssl.cert.X509Certificate(
chain[i].getEncoded());
}
catch (final CertException ce)
{
Debug.debugException(ce);
throw new CertificateException(ce.getMessage(), ce);
}
}
return convertedChain;
}
}