Search in sources :

Example 1 with KeyUsageExtension

use of com.unboundid.util.ssl.cert.KeyUsageExtension in project ldapsdk by pingidentity.

the class PromptTrustManagerProcessor method shouldPrompt.

/**
 * Indicates whether the trust manager should prompt about whether to trust
 * the provided certificate chain.
 *
 * @param  cacheKey                 The key that should be used to identify
 *                                  this certificate chain in the cache.  It
 *                                  should be an all-lowercase hexadecimal
 *                                  representation of the end certificate's
 *                                  subject.
 * @param  chain                    The certificate chain to be examined.  It
 *                                  must not be {@code null} or empty.
 * @param  isServerChain            Indicates whether the provided certificate
 *                                  chain was provided by a server (if
 *                                  {@code true}) or a client (if
 *                                  {@code false}).
 * @param  examineValidityDates     Indicates whether to examine the
 *                                  certificate's validity dates in the course
 *                                  of determining about whether to prompt
 *                                  about whether to trust the given
 *                                  certificate chain.
 * @param  acceptedCertificates     A cache of the certificates that have
 *                                  already been accepted.  The entries in the
 *                                  cache will be mapped from an all-lowercase
 *                                  hex representation of a previously-trusted
 *                                  certificate's signature to a Boolean value
 *                                  that indicates whether the certificate has
 *                                  been declared trusted even if the
 *                                  certificate is outside the validity
 *                                  window.
 * @param  expectedServerAddresses  A list containing the addresses that the
 *                                  client is expected to use to connect to a
 *                                  target server.  If this is {@code null} or
 *                                  empty, then no address validation will be
 *                                  performed.  This will be ignored if
 *                                  {@code isServerChain} is {@code false}.
 *
 * @return  An object pair in which the first element is a {@code Boolean}
 *          indicating whether the trust manager should prompt about whether
 *          to trust the certificate chain, and the second element is a
 *          (possibly empty) list of warning messages about the certificate
 *          chain that should be displayed to the user if they should be
 *          prompted.
 */
@NotNull()
static ObjectPair<Boolean, List<String>> shouldPrompt(@NotNull final String cacheKey, @NotNull final X509Certificate[] chain, final boolean isServerChain, final boolean examineValidityDates, @NotNull final Map<String, Boolean> acceptedCertificates, @Nullable final List<String> expectedServerAddresses) {
    // See if any of the certificates is outside the validity window.
    boolean outsideValidityWindow = false;
    final List<String> warningMessages = new ArrayList<>(5);
    final long currentTime = System.currentTimeMillis();
    for (int i = 0; i < chain.length; i++) {
        if (!chain[i].isWithinValidityWindow(currentTime)) {
            outsideValidityWindow = true;
            final String identifier;
            if (i == 0) {
                if (isServerChain) {
                    identifier = WARN_PROMPT_PROCESSOR_LABEL_SERVER.get();
                } else {
                    identifier = WARN_PROMPT_PROCESSOR_LABEL_CLIENT.get();
                }
            } else {
                identifier = WARN_PROMPT_PROCESSOR_LABEL_ISSUER.get();
            }
            if (currentTime > chain[i].getNotAfterTime()) {
                final long expiredSecondsAgo = Math.round(((currentTime - chain[i].getNotAfterTime()) / 1000.0d));
                warningMessages.add(WARN_PROMPT_PROCESSOR_CERT_EXPIRED.get(identifier, String.valueOf(chain[i].getSubjectDN()), formatDate(chain[i].getNotAfterDate()), StaticUtils.secondsToHumanReadableDuration(expiredSecondsAgo)));
            } else {
                final long secondsUntilValid = Math.round(((chain[i].getNotBeforeTime() - currentTime) / 1000.0d));
                warningMessages.add(WARN_PROMPT_PROCESSOR_CERT_NOT_YET_VALID.get(identifier, String.valueOf(chain[i].getSubjectDN()), formatDate(chain[i].getNotBeforeDate()), StaticUtils.secondsToHumanReadableDuration(secondsUntilValid)));
            }
        }
    }
    // If the certificate at the head of the chain has an extended key usage
    // extension, then make sure it includes either the serverAuth usage (for a
    // server certificate) or the clientAuth usage (for a client certificate).
    SubjectAlternativeNameExtension san = null;
    for (final X509CertificateExtension extension : chain[0].getExtensions()) {
        if (extension instanceof ExtendedKeyUsageExtension) {
            final ExtendedKeyUsageExtension eku = (ExtendedKeyUsageExtension) extension;
            if (isServerChain) {
                if (!eku.getKeyPurposeIDs().contains(ExtendedKeyUsageID.TLS_SERVER_AUTHENTICATION.getOID())) {
                    warningMessages.add(WARN_PROMPT_PROCESSOR_EKU_MISSING_SERVER_AUTH.get(chain[0].getSubjectDN()));
                }
            } else {
                if (!eku.getKeyPurposeIDs().contains(ExtendedKeyUsageID.TLS_CLIENT_AUTHENTICATION.getOID())) {
                    warningMessages.add(WARN_PROMPT_PROCESSOR_EKU_MISSING_CLIENT_AUTH.get(chain[0].getSubjectDN()));
                }
            }
        } else if (extension instanceof SubjectAlternativeNameExtension) {
            san = (SubjectAlternativeNameExtension) extension;
        }
    }
    // extensions in the issuer certificates.
    if (chain.length == 1) {
        if (chain[0].isSelfSigned()) {
            warningMessages.add(WARN_PROMPT_PROCESSOR_CERT_IS_SELF_SIGNED.get());
            try {
                chain[0].verifySignature(chain[0]);
            } catch (final CertException ce) {
                Debug.debugException(ce);
                warningMessages.add(ce.getMessage());
            }
        } else {
            warningMessages.add(WARN_PROMPT_PROCESSOR_CHAIN_NOT_COMPLETE.get(chain[0].getSubjectDN()));
        }
    } else {
        for (int i = 1; i < chain.length; i++) {
            if (chain[i].isIssuerFor(chain[i - 1])) {
                try {
                    chain[i - 1].verifySignature(chain[i]);
                } catch (final CertException ce) {
                    Debug.debugException(ce);
                    warningMessages.add(ce.getMessage());
                }
            } else {
                warningMessages.add(WARN_PROMPT_PROCESSOR_CHAIN_ISSUER_MISMATCH.get(chain[i].getSubjectDN(), chain[i - 1].getSubjectDN()));
            }
            BasicConstraintsExtension bc = null;
            KeyUsageExtension ku = null;
            for (final X509CertificateExtension extension : chain[i].getExtensions()) {
                if (extension instanceof BasicConstraintsExtension) {
                    bc = (BasicConstraintsExtension) extension;
                } else if (extension instanceof KeyUsageExtension) {
                    ku = (KeyUsageExtension) extension;
                }
            }
            if (bc == null) {
                warningMessages.add(WARN_PROMPT_PROCESSOR_NO_BC_EXTENSION.get(chain[i].getSubjectDN()));
            } else if (!bc.isCA()) {
                warningMessages.add(WARN_PROMPT_PROCESSOR_BC_NOT_CA.get(chain[i].getSubjectDN()));
            } else if ((bc.getPathLengthConstraint() != null) && ((i - 1) > bc.getPathLengthConstraint())) {
                if (bc.getPathLengthConstraint() == 0) {
                    warningMessages.add(WARN_PROMPT_PROCESSOR_BC_DISALLOWED_INTERMEDIATE.get(chain[i].getSubjectDN()));
                } else {
                    warningMessages.add(WARN_PROMPT_PROCESSOR_BC_TOO_MANY_INTERMEDIATES.get(chain[i].getSubjectDN(), bc.getPathLengthConstraint(), (i - 1)));
                }
            }
            if ((ku != null) && (!ku.isKeyCertSignBitSet())) {
                warningMessages.add(WARN_PROMPT_PROCESSOR_KU_NO_KEY_CERT_SIGN.get(chain[i].getSubjectDN()));
            }
        }
        if (chain[chain.length - 1].isSelfSigned()) {
            try {
                chain[chain.length - 1].verifySignature(chain[chain.length - 1]);
            } catch (final CertException ce) {
                Debug.debugException(ce);
                warningMessages.add(ce.getMessage());
            }
        } else {
            warningMessages.add(WARN_PROMPT_PROCESSOR_CHAIN_NOT_COMPLETE.get(chain[chain.length - 1].getSubjectDN()));
        }
    }
    // addresses.
    if (isServerChain && (expectedServerAddresses != null) && (!expectedServerAddresses.isEmpty())) {
        // Get the CN attribute from the certificate subject.
        boolean hasAllowedAddress = false;
        final StringBuilder addressBuffer = new StringBuilder();
        for (final RDN rdn : chain[0].getSubjectDN().getRDNs()) {
            final String[] names = rdn.getAttributeNames();
            for (int i = 0; i < names.length; i++) {
                if (names[i].equalsIgnoreCase("cn") || names[i].equalsIgnoreCase("commonName") || names[i].equalsIgnoreCase("2.5.4.3")) {
                    final String cnValue = rdn.getAttributeValues()[i];
                    final String lowerCNValue = StaticUtils.toLowerCase(cnValue);
                    if (isHostnameOrIPAddress(lowerCNValue)) {
                        commaAppend(addressBuffer, cnValue);
                        if (isAllowedHostnameOrIPAddress(lowerCNValue, expectedServerAddresses)) {
                            hasAllowedAddress = true;
                            break;
                        }
                    }
                }
            }
            if (hasAllowedAddress) {
                break;
            }
        }
        // check its DNS names.
        if ((!hasAllowedAddress) && (san != null)) {
            for (final String dnsName : san.getDNSNames()) {
                commaAppend(addressBuffer, dnsName);
                if (isAllowedHostnameOrIPAddress(dnsName, expectedServerAddresses)) {
                    hasAllowedAddress = true;
                    break;
                }
            }
            if (!hasAllowedAddress) {
                for (final InetAddress ipAddress : san.getIPAddresses()) {
                    commaAppend(addressBuffer, ipAddress.getHostAddress());
                    if (isAllowedIPAddress(ipAddress, expectedServerAddresses)) {
                        hasAllowedAddress = true;
                        break;
                    }
                }
            }
        }
        if (!hasAllowedAddress) {
            if (addressBuffer.length() == 0) {
            // The certificate doesn't indicate the server with which it should be
            // used.  This isn't desirable, but we won't warn about it.
            } else if (addressBuffer.indexOf(",") > 0) {
                warningMessages.add(WARN_PROMPT_PROCESSOR_MULTIPLE_ADDRESSES_NOT_MATCHED.get(chain[0].getSubjectDN(), addressBuffer));
            } else {
                warningMessages.add(WARN_PROMPT_PROCESSOR_SINGLE_ADDRESS_NOT_MATCHED.get(chain[0].getSubjectDN(), addressBuffer));
            }
        }
    }
    // See if the provided certificate is in the cache.  If not, then we will
    // definitely prompt.  If the cache indicates that the certificate has been
    // accepted even outside the validity window, then we will not prompt.
    // Otherwise, we'll prompt only if the certificate is outside the validity
    // window.
    final Boolean acceptedEvenWithBadValidity = acceptedCertificates.get(cacheKey);
    if (acceptedEvenWithBadValidity == null) {
        return new ObjectPair<>(Boolean.TRUE, warningMessages);
    } else if (acceptedEvenWithBadValidity) {
        return new ObjectPair<>(Boolean.FALSE, warningMessages);
    } else {
        return new ObjectPair<>(outsideValidityWindow, warningMessages);
    }
}
Also used : ExtendedKeyUsageExtension(com.unboundid.util.ssl.cert.ExtendedKeyUsageExtension) X509CertificateExtension(com.unboundid.util.ssl.cert.X509CertificateExtension) SubjectAlternativeNameExtension(com.unboundid.util.ssl.cert.SubjectAlternativeNameExtension) ArrayList(java.util.ArrayList) CertException(com.unboundid.util.ssl.cert.CertException) BasicConstraintsExtension(com.unboundid.util.ssl.cert.BasicConstraintsExtension) RDN(com.unboundid.ldap.sdk.RDN) InetAddress(java.net.InetAddress) KeyUsageExtension(com.unboundid.util.ssl.cert.KeyUsageExtension) ExtendedKeyUsageExtension(com.unboundid.util.ssl.cert.ExtendedKeyUsageExtension) ObjectPair(com.unboundid.util.ObjectPair) NotNull(com.unboundid.util.NotNull)

Aggregations

RDN (com.unboundid.ldap.sdk.RDN)1 NotNull (com.unboundid.util.NotNull)1 ObjectPair (com.unboundid.util.ObjectPair)1 BasicConstraintsExtension (com.unboundid.util.ssl.cert.BasicConstraintsExtension)1 CertException (com.unboundid.util.ssl.cert.CertException)1 ExtendedKeyUsageExtension (com.unboundid.util.ssl.cert.ExtendedKeyUsageExtension)1 KeyUsageExtension (com.unboundid.util.ssl.cert.KeyUsageExtension)1 SubjectAlternativeNameExtension (com.unboundid.util.ssl.cert.SubjectAlternativeNameExtension)1 X509CertificateExtension (com.unboundid.util.ssl.cert.X509CertificateExtension)1 InetAddress (java.net.InetAddress)1 ArrayList (java.util.ArrayList)1