From 8a6e3b7ae9c25f31a5e48034171aff0e9a9ec2c2 Mon Sep 17 00:00:00 2001
From: Chris Ridd <chris.ridd@forgerock.com>
Date: Tue, 12 Jul 2016 15:19:29 +0000
Subject: [PATCH] OPENDJ-2855 Check Subject Alt Names in the CheckHostName TrustManager
---
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert7.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert2.pem | 19 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert11.pem | 19 +
opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java | 217 ++++++++++++
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert13.pem | 20 +
opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties | 9
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert10.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert6.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert4.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert1.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert8.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert12.pem | 18 +
opendj-core/src/test/java/org/forgerock/opendj/ldap/TrustManagersTestCase.java | 218 ++++++++++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java | 282 ++++++++++++++--
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert9.pem | 19 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert3.pem | 18 +
opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert5.pem | 18 +
17 files changed, 922 insertions(+), 43 deletions(-)
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java
new file mode 100644
index 0000000..ab2c845
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java
@@ -0,0 +1,217 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Portions copyright 2016 ForgeRock AS.
+ */
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.forgerock.opendj.ldap;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p><b>InetAddress</b> validation and conversion routines (<code>java.net.InetAddress</code>).</p>
+ *
+ * <p>This class provides methods to validate a candidate IP address.
+ *
+ * @since Validator 1.4
+ */
+final class InetAddressValidator {
+
+ private static final int IPV4_MAX_OCTET_VALUE = 255;
+
+ private static final int MAX_UNSIGNED_SHORT = 0xffff;
+
+ private static final int BASE_16 = 16;
+
+ private static final String IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
+
+ // Max number of hex groups (separated by :) in an IPV6 address
+ private static final int IPV6_MAX_HEX_GROUPS = 8;
+
+ // Max hex digits in each IPv6 group
+ private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
+
+ /** IPv4 regex pattern. */
+ private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
+
+ /** Private constructor. */
+ private InetAddressValidator() {
+ }
+
+ /**
+ * Checks if the specified string is a valid IP address.
+ * @param inetAddress the string to validate
+ * @return true if the string validates as an IP address
+ */
+ public static boolean isValid(String inetAddress) {
+ return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress);
+ }
+
+ /**
+ * Validates an IPv4 address. Returns true if valid.
+ * @param inet4Address the IPv4 address to validate
+ * @return true if the argument contains a valid IPv4 address
+ */
+ public static boolean isValidInet4Address(String inet4Address) {
+ // verify that address conforms to generic IPv4 format
+ String[] groups = match(inet4Address);
+
+ if (groups == null) {
+ return false;
+ }
+
+ // verify that address subgroups are legal
+ for (String ipSegment : groups) {
+ if (ipSegment == null || ipSegment.length() == 0) {
+ return false;
+ }
+
+ int iIpSegment;
+
+ try {
+ iIpSegment = Integer.parseInt(ipSegment);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+
+ if (iIpSegment > IPV4_MAX_OCTET_VALUE) {
+ return false;
+ }
+
+ if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate a value against the set of regular expressions returning the array of matched groups. From the Apache
+ * RegexValidator class.
+ *
+ * @param value The value to validate
+ * @return String array of the matched groups, of {@code null} if invalid.
+ */
+ private static String[] match(String value) {
+ if (value == null) {
+ return null;
+ }
+ Matcher matcher = IPV4_PATTERN.matcher(value);
+ if (matcher.matches()) {
+ int count = matcher.groupCount();
+ String[] groups = new String[count];
+ for (int j = 0; j < count; j++) {
+ groups[j] = matcher.group(j + 1);
+ }
+ return groups;
+ }
+ return null;
+ }
+
+ /**
+ * Validates an IPv6 address. Returns true if valid.
+ * @param inet6Address the IPv6 address to validate
+ * @return true if the argument contains a valid IPv6 address
+ *
+ * @since 1.4.1
+ */
+ public static boolean isValidInet6Address(String inet6Address) {
+ boolean containsCompressedZeroes = inet6Address.contains("::");
+ if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) {
+ return false;
+ }
+ if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::"))
+ || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) {
+ return false;
+ }
+ String[] octets = inet6Address.split(":");
+ if (containsCompressedZeroes) {
+ List<String> octetList = new ArrayList<>(Arrays.asList(octets));
+ if (inet6Address.endsWith("::")) {
+ // String.split() drops ending empty segments
+ octetList.add("");
+ } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) {
+ octetList.remove(0);
+ }
+ octets = octetList.toArray(new String[octetList.size()]);
+ }
+ if (octets.length > IPV6_MAX_HEX_GROUPS) {
+ return false;
+ }
+ int validOctets = 0;
+ int emptyOctets = 0;
+ for (int index = 0; index < octets.length; index++) {
+ String octet = octets[index];
+ if (octet.length() == 0) {
+ emptyOctets++;
+ if (emptyOctets > 1) {
+ return false;
+ }
+ } else {
+ emptyOctets = 0;
+ if (octet.contains(".")) { // contains is Java 1.5+
+ if (!inet6Address.endsWith(octet)) {
+ return false;
+ }
+ if (index > octets.length - 1 || index > 6) { // CHECKSTYLE IGNORE MagicNumber
+ // IPV4 occupies last two octets
+ return false;
+ }
+ if (!isValidInet4Address(octet)) {
+ return false;
+ }
+ validOctets += 2;
+ continue;
+ }
+ if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
+ return false;
+ }
+ int octetInt;
+ try {
+ octetInt = Integer.valueOf(octet, BASE_16);
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
+ return false;
+ }
+ }
+ validOctets++;
+ }
+ if (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java
index 4770a4c..aa375a1 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/TrustManagers.java
@@ -19,30 +19,48 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import java.util.List;
+import java.util.Set;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.util.Reject;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_CERT_NO_MATCH_IP;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_CERT_NO_MATCH_DNS;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_CERT_NO_MATCH_ALLOTHERS;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_CERT_NO_MATCH_SUBJECT;
+
+
/** This class contains methods for creating common types of trust manager. */
public final class TrustManagers {
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
/**
- * An X509TrustManager which rejects certificate chains whose subject DN
- * does not match a specified host name.
+ * An X509TrustManager which rejects certificate chains whose subject alternative names do not match the specified
+ * host name or IP address. The check may fall back to checking a hostname in the left-most CN of the certificate
+ * subject for backwards compatibility.
*/
private static final class CheckHostName implements X509TrustManager {
@@ -75,15 +93,191 @@
}
/**
- * Checks whether a host name matches the provided pattern. It accepts
- * the use of wildcards in the pattern, e.g. {@code *.example.com}.
+ * Look in the SubjectAlternativeName for DNS names (wildcards are allowed) and IP addresses, and potentially
+ * fall back to checking CN in the subjectDN.
+ * <p>
+ * If DNS names and IP addresses do not match, and other SubjectAlternativeNames are present and critical, do
+ * not fall back checking CN.
+ * </p>
+ * <p>
+ * If DNS names and IP addresses do not match and the SubjectAlternativeNames are non-critical, fall back to
+ * checking CN.
+ * </p>
+ * @param chain X.509 certificate chain from the server
+ */
+ private void verifyHostName(final X509Certificate[] chain) throws CertificateException {
+ final X500Principal principal = chain[0].getSubjectX500Principal();
+ try {
+ final List<String> dnsNamePatterns = new ArrayList<>(0);
+ final List<String> ipAddresses = new ArrayList<>(0);
+ final List<Object> allOthers = new ArrayList<>(0);
+ getSanGeneralNames(chain[0], dnsNamePatterns, ipAddresses, allOthers);
+ final boolean sanIsCritical = getSanCriticality(chain[0]);
+
+ final InetAddress hostAddress = toIpAddress(hostName);
+ if (hostAddress != null) {
+ if (verifyIpAddresses(hostAddress, ipAddresses, principal, sanIsCritical)) {
+ return;
+ }
+ } else {
+ if (verifyDnsNamePatterns(hostName, dnsNamePatterns, principal, sanIsCritical)) {
+ return;
+ }
+ }
+ if (!allOthers.isEmpty() && sanIsCritical) {
+ throw new CertificateException(ERR_CERT_NO_MATCH_ALLOTHERS.get(principal, hostName).toString());
+ }
+
+ final DN dn = DN.valueOf(principal.getName(), Schema.getCoreSchema());
+ final String certSubjectHostName = getLowestCommonName(dn);
+ /* Backwards compatibility: check wildcards in cn */
+ if (hostNameMatchesPattern(hostName, certSubjectHostName)) {
+ return;
+ }
+ throw new CertificateException(ERR_CERT_NO_MATCH_SUBJECT.get(principal, hostName).toString());
+ } catch (final CertificateException e) {
+ logger.warn(LocalizableMessage.raw("Certificate verification problem for: " + principal), e);
+ throw e;
+ }
+ }
+
+ /**
+ * Collect the general names from a certificate's SubjectAlternativeName extension.
+ *
+ * General Names can contain: dnsNames, ipAddresses, rfc822Names, x400Addresses, directoryNames, ediPartyNames,
+ * uniformResourceIdentifiers, registeredIDs (OID), or otherNames (anything). See
+ * {@link X509Certificate#getSubjectAlternativeNames()} for details on how these values are encoded. We separate
+ * the dnsNames and ipAddresses (which we can try to match) from everything else (which we do not try to match.)
+ *
+ * @param subject certificate
+ * @param dnsNames list where the dnsNames will be added (may be empty)
+ * @param ipAddresses list where the ipAddresses will be added (may be empty)
+ * @param allOthers list where all other general names will be added (may be empty)
+ */
+ private void getSanGeneralNames(X509Certificate subject,
+ List<String> dnsNames, List<String> ipAddresses,
+ List<Object> allOthers) {
+ try {
+ Collection<List<?>> sans = subject.getSubjectAlternativeNames();
+ if (sans == null) {
+ return;
+ }
+ for (List<?> san : sans) {
+ switch ((Integer) san.get(0)) {
+ case 2:
+ dnsNames.add((String) san.get(1));
+ break;
+ case 7:
+ ipAddresses.add((String) san.get(1));
+ break;
+ default:
+ allOthers.add(san.get(1));
+ break;
+ }
+ }
+ } catch (CertificateParsingException e) {
+ /* do nothing */
+ }
+ }
+
+ /**
+ * Get the ASN.1 criticality of the SubjectAlternativeName extension.
+ *
+ * @param subject X509Certificate to check
+ * @return {@code true} if a subject alt name was found and was marked critical, {@code false} otherwise.
+ */
+ private boolean getSanCriticality(X509Certificate subject) {
+ Set<String> critSet = subject.getCriticalExtensionOIDs();
+ return critSet != null && critSet.contains("2.5.29.17");
+ }
+
+ /**
+ * Convert to an IP address without performing a DNS lookup.
+ *
+ * @param hostName either an IP address string, or a host name
+ * @return {@code InetAddress} if hostName was an IPv4 or IPv6 address, or {@code null}
+ */
+ private static InetAddress toIpAddress(String hostName) {
+ try {
+ if (InetAddressValidator.isValid(hostName)) {
+ return InetAddress.getByName(hostName);
+ }
+ } catch (UnknownHostException e) {
+ /* do nothing */
+ }
+ return null;
+ }
+
+ /**
+ * Verify an IP address in the list of IP addresses.
+ *
+ * @param hostAddress IP address from the user
+ * @param ipAddresses List of IP addresses from the certificate (may be empty)
+ * @param principal Subject name from the certificate
+ * @param failureIsCritical Should a verification failure throw a {@link CertificateException}
+ * @return {@code true} if the address is verified, {@code false} if the address was not verified.
+ * @throws CertificateException if verification fails and {@code failureIsCritical} is {@code true}.
+ */
+ private boolean verifyIpAddresses(InetAddress hostAddress, List<String> ipAddresses, X500Principal principal,
+ boolean failureIsCritical) throws CertificateException {
+ if (!ipAddresses.isEmpty()) {
+ for (String address : ipAddresses) {
+ try {
+ if (InetAddress.getByName(address).equals(hostAddress)) {
+ return true;
+ }
+ } catch (UnknownHostException e) {
+ // do nothing
+ }
+ }
+ if (failureIsCritical) {
+ // RFC 5280 mentions:
+ /* If the subject field
+ * contains an empty sequence, then the issuing CA MUST include a
+ * subjectAltName extension that is marked as critical. When including
+ * the subjectAltName extension in a certificate that has a non-empty
+ * subject distinguished name, conforming CAs SHOULD mark the
+ * subjectAltName extension as non-critical.
+ */
+ // Since SAN is critical, the subject is empty, so we cannot perform the next check anyway
+ throw new CertificateException(ERR_CERT_NO_MATCH_IP.get(principal, hostName).toString());
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Verify a hostname in the list of DNS name patterns.
+ *
+ * @param hostName Host name from the user
+ * @param dnsNamePatterns List of DNS name patterns from the certificate (may be empty)
+ * @param principal Subject name from the certificate
+ * @param failureIsCritical Should a verification failure throw a {@link CertificateException}
+ * @return {@code true} if the address is verified, {@code false} if the address was not verified.
+ * @throws CertificateException If verification fails and {@code failureIsCritical} is {@code true}
+ */
+ private boolean verifyDnsNamePatterns(String hostName, List<String> dnsNamePatterns, X500Principal principal,
+ boolean failureIsCritical) throws CertificateException {
+ for (String namePattern : dnsNamePatterns) {
+ if (hostNameMatchesPattern(hostName, namePattern)) {
+ return true;
+ }
+ }
+ if (failureIsCritical) {
+ throw new CertificateException(ERR_CERT_NO_MATCH_DNS.get(principal, hostName).toString());
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether a host name matches the provided pattern. It accepts the use of wildcards in the pattern,
+ * e.g. {@code *.example.com}.
*
* @param hostName
* The host name.
* @param pattern
- * The host name pattern, which may contain wild cards.
- * @return {@code true} if the host name matched the pattern, otherwise
- * {@code false}.
+ * The host name pattern, which may contain wildcards.
+ * @return {@code true} if the host name matched the pattern, otherwise {@code false}.
*/
private boolean hostNameMatchesPattern(final String hostName, final String pattern) {
final String[] nameElements = hostName.split("\\.");
@@ -100,24 +294,22 @@
return hostMatch;
}
- private void verifyHostName(final X509Certificate[] chain) {
- try {
- // TODO: NPE if root DN.
- final DN dn =
- DN.valueOf(chain[0].getSubjectX500Principal().getName(), Schema
- .getCoreSchema());
- final String certSubjectHostName =
- dn.iterator().next().iterator().next().getAttributeValue().toString();
- if (!hostNameMatchesPattern(hostName, certSubjectHostName)) {
- throw new CertificateException(
- "The host name contained in the certificate chain subject DN \'"
- + chain[0].getSubjectX500Principal()
- + "' does not match the host name \'" + hostName + "'");
+ /**
+ * Find the lowest (left-most) cn in the DN, and return its value.
+ *
+ * @param subject the DN being searched
+ * @return the cn value, or {@code null} if no cn was found
+ */
+ private String getLowestCommonName(DN subject) {
+ AttributeType cn = Schema.getDefaultSchema().getAttributeType("cn");
+ for (RDN rdn : subject) {
+ for (AVA ava : rdn) {
+ if (ava.getAttributeType().equals(cn)) {
+ return ava.getAttributeValue().toString();
+ }
}
- } catch (final Throwable t) {
- LOG.log(Level.WARNING, "Error parsing subject dn: "
- + chain[0].getSubjectX500Principal(), t);
}
+ return null;
}
}
@@ -155,16 +347,14 @@
try {
c.checkValidity(currentDate);
} catch (final CertificateExpiredException e) {
- LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \""
- + c.getSubjectDN().getName() + "\" because it" + " expired on "
- + String.valueOf(c.getNotAfter()));
-
+ logger.warn(LocalizableMessage.raw(
+ "Refusing to trust security certificate \'%s\' because it expired on %s",
+ c.getSubjectDN().getName(), String.valueOf(c.getNotAfter())));
throw e;
} catch (final CertificateNotYetValidException e) {
- LOG.log(Level.WARNING, "Refusing to trust security" + " certificate \""
- + c.getSubjectDN().getName() + "\" because it" + " is not valid until "
- + String.valueOf(c.getNotBefore()));
-
+ logger.warn(LocalizableMessage.raw(
+ "Refusing to trust security certificate \'%s\' because it is not valid until %s",
+ c.getSubjectDN().getName(), String.valueOf(c.getNotBefore())));
throw e;
}
}
@@ -226,23 +416,29 @@
}
}
- private static final Logger LOG = Logger.getLogger(TrustManagers.class.getName());
-
/**
- * Wraps the provided {@code X509TrustManager} by adding additional
- * validation which rejects certificate chains whose subject DN does not
- * match the specified host name pattern. The pattern may contain
- * wild-cards, for example {@code *.example.com}.
+ * Wraps the provided {@code X509TrustManager} by adding additional validation which rejects certificate chains
+ * whose subject alternative names do not match the specified host name or IP address. The check may fall back to
+ * checking a hostname in the left-most CN of the subjectDN for backwards compatibility.
+ *
+ * If the {@code hostName} is an IP address, only the {@code ipAddresses} field of the subject alternative name
+ * will be checked. Similarly if {@code hostName} is not an IP address, only the {@code dnsNames} of the subject
+ * alternative name will be checked.
+ *
+ * Host names can be matched using wild cards, for example {@code *.example.com}.
+ *
+ * If a critical subject alternative name doesn't match, verification will not fall back to checking the subjectDN
+ * and will <b>fail</b>. If a critical subject alternative name doesn't match and it contains other kinds of general
+ * names that cannot be checked verification will also <b>fail</b>.
*
* @param hostName
- * A host name which the RDN value contained in
- * certificate subject DNs must match.
+ * The IP address or hostname used to connect to the LDAP server which will be matched against the
+ * subject alternative name and possibly the subjectDN as described above.
* @param trustManager
* The trust manager to be wrapped.
* @return The wrapped trust manager.
* @throws NullPointerException
- * If {@code trustManager} or {@code hostNamePattern} was
- * {@code null}.
+ * If {@code trustManager} or {@code hostName} was {@code null}.
*/
public static X509TrustManager checkHostName(final String hostName,
final X509TrustManager trustManager) {
diff --git a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
index dfb8df2..8c52e87 100644
--- a/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-core/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -1681,3 +1681,12 @@
because the connection timeout period of %d ms was exceeded
LOAD_BALANCER_EVENT_LISTENER_LOG_ONLINE=Connection factory '%s' is now operational
LOAD_BALANCER_EVENT_LISTENER_LOG_OFFLINE=Connection factory '%s' is no longer operational: %s
+
+ERR_CERT_NO_MATCH_IP=The IP addresses in the critical subject alt name extension for '%s' \
+ do not match the address '%s'
+ERR_CERT_NO_MATCH_DNS=The DNS names in the critical subject alt name extension for '%s' \
+ do not match the host name '%s'
+ERR_CERT_NO_MATCH_ALLOTHERS=None of the remaining names in the critical subject alt name extension for '%s' \
+ can be matched against '%s'
+ERR_CERT_NO_MATCH_SUBJECT=The host name contained in the subject DN '%s' \
+ does not match the host name '%s'
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/TrustManagersTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/TrustManagersTestCase.java
new file mode 100644
index 0000000..17b5256
--- /dev/null
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/TrustManagersTestCase.java
@@ -0,0 +1,218 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+
+/**
+ * This class defines a set of tests for the
+ * {@link org.forgerock.opendj.ldap.TrustManagers} class.
+ */
+@SuppressWarnings("javadoc")
+public class TrustManagersTestCase extends SdkTestCase {
+ /**
+ * X509TrustManager test data provider with a variety of hostnames in subjects and subject alt names.
+ *
+ * @return The array of test data.
+ */
+ @DataProvider(name = "hostnameTestData")
+ public Object[][] createHostnameTestData() throws Exception {
+ // see comments in test cases for descriptions
+ X509Certificate cert1 = getTestCertificate("cert1.pem");
+ X509Certificate cert2 = getTestCertificate("cert2.pem");
+ X509Certificate cert3 = getTestCertificate("cert3.pem");
+ X509Certificate cert4 = getTestCertificate("cert4.pem");
+ X509Certificate cert5 = getTestCertificate("cert5.pem");
+ X509Certificate cert6 = getTestCertificate("cert6.pem");
+ X509Certificate cert7 = getTestCertificate("cert7.pem");
+ X509Certificate cert8 = getTestCertificate("cert8.pem");
+ X509Certificate cert9 = getTestCertificate("cert9.pem");
+ X509Certificate cert10 = getTestCertificate("cert10.pem");
+ X509Certificate cert11 = getTestCertificate("cert11.pem");
+ X509Certificate cert12 = getTestCertificate("cert12.pem");
+ X509Certificate cert13 = getTestCertificate("cert13.pem");
+ // @formatter:off
+ return new Object[][] {
+ /*
+ * cert1:
+ * subject "cn=ldap.example.com,o=OpenDJ"
+ * san (none)
+ */
+ { cert1, "ldap.example.com", true },
+ { cert1, "ldap2.example.com", false },
+ { cert1, "192.168.0.1", false },
+ { cert1, "2001:db8::1:0:0:1", false },
+ { cert1, "*.example.com", false },
+ /*
+ * cert2:
+ * subject "email=info,cn=ldap.example.com,o=OpenDJ"
+ * san (none)
+ */
+ { cert2, "ldap.example.com", true },
+ { cert2, "ldap2.example.com", false },
+ { cert2, "192.168.0.1", false },
+ { cert2, "2001:db8::1:0:0:1", false },
+ { cert2, "info", false },
+ /*
+ * cert3:
+ * subject "cn=ldap.example.com,o=OpenDJ"
+ * san [ dnsName "ldap.example.org"] critical
+ */
+ { cert3, "ldap.example.org", true },
+ { cert3, "ldap.example.com", false }, // critical so no fall back to testing subject DN
+ { cert3, "ldap2.example.org", false },
+ { cert3, "192.168.0.1", false },
+ { cert3, "2001:db8::1:0:0:1", false },
+ /*
+ * cert4:
+ * subject "cn=ldap.example.com,o=OpenDJ"
+ * san [ dnsName "ldap.example.org"] non-critical
+ */
+ { cert4, "ldap.example.org", true },
+ { cert4, "ldap.example.com", true }, // falls back to testing subject DN
+ { cert4, "ldap2.example.org", false },
+ { cert4, "192.168.0.1", false },
+ { cert4, "2001:db8::1:0:0:1", false },
+ /*
+ * cert5:
+ * subject "cn=server,o=OpenDJ"
+ * san [ dnsName "ldap1.example.com", "ldap2.example.com" ] critical
+ */
+ { cert5, "ldap.example.com", false },
+ { cert5, "server", false },
+ { cert5, "ldap1.example.com", true },
+ { cert5, "ldap2.example.com", true },
+ /*
+ * cert6:
+ * subject "cn=server,o=OpenDJ"
+ * san [ dnsName "*.example.com" ] critical
+ */
+ { cert6, "ldap.example.com", true },
+ { cert6, "ldap10.example.com", true },
+ { cert6, "ldap.dev.example.com", false },
+ { cert6, "server", false },
+ /*
+ * cert7:
+ * subject "cn=*.example.com,o=OpenDJ"
+ * san (none)
+ */
+ { cert7, "ldap1.example.com", true },
+ { cert7, "ldap2.example.com", true },
+ { cert7, "ldap.dev.example.com", false },
+ { cert7, "192.168.0.1", false },
+ { cert7, "2001:db8::1:0:0:1", false },
+ { cert7, "ldap.example.org", false },
+ /*
+ * cert8:
+ * subject "cn=server,o=OpenDJ"
+ * san [ dnsName "ldap.example.com", ip "192.168.0.1" ] critical
+ */
+ { cert8, "ldap.example.com", true },
+ { cert8, "192.168.0.1", true },
+ { cert8, "ldap2.example.com", false },
+ { cert8, "192.168.0.2", false },
+ { cert8, "2001:db8::1:0:0:1", false },
+ { cert8, "server", false },
+ /*
+ * cert9:
+ * subject "cn=server,o=OpenDJ"
+ * san [ ip "2001:db8::1:0:0:1" ] critical
+ */
+ { cert9, "2001:db8::1:0:0:1", true },
+ { cert9, "ldap.example.com", false },
+ { cert9, "server", false },
+ /*
+ * cert10:
+ * subject "cn=server,o=OpenDJ"
+ * san [ email "info@forgerock.org" ] critical
+ */
+ { cert10, "ldap.example.com", false },
+ { cert10, "server", false },
+ /*
+ * cert11:
+ * subject "cn=ldap.example.com,o=OpenDJ"
+ * san [ uri "ldap://ldap.example.com ] critical
+ */
+ { cert11, "ldap.example.com", false },
+ /*
+ * cert12:
+ * subject "cn=server,o=OpenDJ"
+ * san [ dns "ldap.example.com", uri "ldap://ldap.example.com" ] non-critical
+ */
+ { cert12, "ldap.example.com", true },
+ { cert12, "server", true },
+ /*
+ * cert13:
+ * subject ""
+ * san [ dns "ldap.example.com" ] critical
+ */
+ { cert13, "ldap.example.com", true },
+ { cert13, "server", false },
+
+ };
+ // @formatter:on
+ }
+
+ /**
+ * Disables logging before the tests.
+ */
+ @BeforeClass
+ public void disableLogging() {
+ TestCaseUtils.setDefaultLogLevel(Level.SEVERE);
+ }
+
+ /**
+ * Re-enable logging after the tests.
+ */
+ @AfterClass
+ public void enableLogging() {
+ TestCaseUtils.setDefaultLogLevel(Level.INFO);
+ }
+
+ private X509Certificate getTestCertificate(String filename) throws Exception {
+ String path = TestCaseUtils.getTestFilePath("org.forgerock.opendj.ldap.TrustManagers" + File.separator
+ + filename);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ try (InputStream is = new FileInputStream(path)) {
+ return (X509Certificate) cf.generateCertificate(is);
+ }
+ }
+
+ @Test(dataProvider = "hostnameTestData")
+ public void testHostnames(X509Certificate subject, String hostname, boolean expectedResult)
+ throws Exception {
+ X509TrustManager mgr = TrustManagers.checkHostName(hostname, TrustManagers.trustAll());
+ try {
+ mgr.checkServerTrusted(new X509Certificate[] { subject }, "RSA");
+ Assert.assertTrue(expectedResult);
+ } catch (CertificateException ce) {
+ Assert.assertFalse(expectedResult);
+ }
+ }
+}
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert1.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert1.pem
new file mode 100644
index 0000000..d7a53f1
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert1.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIEV3pVNjANBgkqhkiG9w0BAQsFADAsMQ8wDQYDVQQKDAZP
+cGVuREoxGTAXBgNVBAMMEGxkYXAuZXhhbXBsZS5jb20wHhcNMTYwNzA0MTIyMzM4
+WhcNMTcwNzA0MTIyMzM4WjAsMQ8wDQYDVQQKDAZPcGVuREoxGTAXBgNVBAMMEGxk
+YXAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCA
+Z4cWPDd5Usv6InfpewuYN2UQE20JvHmWT35OlObXxogk4SDncmuxqux8YozkA/5U
+Pa9hYFroVVpsa84x173tg01AsiBotxjBzdDfmue7NF/hDdlg6mYYuIDAi94f84Pi
+Y7z+/Sm0vFfKRZoRXJtmjnGZJBFzPi5L5xEzb94YvMhdqJPNRI2ihNSG4fGFRyq6
+1S4y2p8Hj6AeCwVjFVOjwtry4IG469EQbqrz5ZbPa4iJ0zNZNE51mGXDJA5B/ht9
+8DAGGae8rB3Eab5VGERjPJpyNNnU+irJVlr6Uef6iCguPoZyYc1LPOkaIynUey54
+RWLyPXvvxiXUIpNMotaTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABoPGkJAiEpg
+buPJsltgY4mfKLzRZw0RmTIh+zhm8aI06owAOtdN9x7tnh5bb7ZEPv3toP2AoCNl
+aWb2Y3EqpZvITXhDHw9F40UwW6sQapnlGjGZMgmttD2z/5ozeYhKWyNQgg/7nwMd
+atsZNmd4s/ePRPQzq2DoXQs+8Hp/jhvlVdU6/B8pUO4M6tT4gGGVHrB4cb0oBqW/
+xeoXYAhiHiYx6KNxXoRy6y9/TbIock0nd6BoXTepl4vziPVnHFW3Mw6CKUnzUC5A
+wKTmAR5Ja5Nu0grcJjrLvUDH6nSlVHR3xORH4Xbbqt8cUXgebWWiz6VoRnVa8iLs
+UbwVVuiYQrs=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert10.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert10.pem
new file mode 100644
index 0000000..bd350f1
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert10.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC5jCCAc6gAwIBAgIEV3pryzANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKDAZP
+cGVuREoxDzANBgNVBAMMBnNlcnZlcjAeFw0xNjA3MDQxNDAwMjVaFw0xNzA3MDQx
+NDAwMjVaMCIxDzANBgNVBAoMBk9wZW5ESjEPMA0GA1UEAwwGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlLXw1BOnRttU0MnIm56yBubRgyIp
+HqC2EZXwbqAb6WpFe72P7CsN1+UbpzfHJl6JV7bpKNbHXB1nA1e1W9zdTBy/j+GJ
+VYctZuXJQ453ehi8c3C8iv3SkFDgcw0+B8d3JWpFVTTaEMCJpwN/z06/3Kh9dDBM
+qelQqYOY2skRpMErJB7KJGqxhhoiBDl32b2hICszH0+637RlywfACe42CytBR2/i
+yzvRULN8fccGpHDvDg3XPZKP+wNcImJT70u0meCvN4HG3jdm1zakCQ5+6fHvzJp9
+rIzRZ6J7KDbC1oT/NUCWTeB+S2IexuS+8+KULNmWu2JQGyVEXTyI/7+/QQIDAQAB
+oyQwIjAgBgNVHREBAf8EFjAUgRJpbmZvQGZvcmdlcm9jay5vcmcwDQYJKoZIhvcN
+AQELBQADggEBAGrzyIPsp6fMHz3lfvY9uTO3+6J4FiXq5073WjdW100tO97lGvhW
+DyyCBDhDQHtvIqiNSkyE11vtuvVqxQAXujwsuzfSKhXZai7BSpfoz28QefV3Kn7F
++5gR+YG6e0xrBgsNO6ZupiHxGq0jocsge/HHRXYjZutqK+LUZFUBtAuaeJ6+Nqkw
+f8cMNWjQbqNKokqhK71NMnG4GLXg0/7lqlX8I0zs8jgKonlgsd1gQzWadqFSUjcW
+4g2xN8oHsCR4Z5K3hkqnrwuwNn2ca9byo3qU6vLRm/ShPphZ1I+Le9l9TZRNko33
+hccRusqQau4Rd2p0Un5cl5sSVS7Si0JUBZQ=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert11.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert11.pem
new file mode 100644
index 0000000..0c11a2d
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert11.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIEV3qLJTANBgkqhkiG9w0BAQsFADAsMQ8wDQYDVQQKDAZP
+cGVuREoxGTAXBgNVBAMMEGxkYXAuZXhhbXBsZS5jb20wHhcNMTYwNzA0MTYxNDE3
+WhcNMTcwNzA0MTYxNDE3WjAsMQ8wDQYDVQQKDAZPcGVuREoxGTAXBgNVBAMMEGxk
+YXAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+EJt4dE7WU1Wi91Wvuy7fo33knGHJpPbLAmkBptRnJebKfpcz6Aiq9/P6ZTcPni/U
+2b8JF7PW3TioASx48ns3cpeLLSpG6oveWmf6SSjnrUn6yaOE607inULVCW8Slhrh
+5fU6gTppWqB3Ar2xeNLZI7HQBhoAKtFb4KNr5dhI++hMxA5W9u1rZ1xAMNavFn70
+XA/WBYM/DlAqSrbIICTNMuXdrYA6uvCv8ZM6U9Brms7qKDArLUUNDkRT64xJ3ajg
+AkaJ9KoPfRRx/T/VWDV7i3H5l+hrOBQ+tsc0I19XUvBam+BZFesQa6ImB555iTfb
+Ft9uBeoQ8ZyZGT2xdEshAgMBAAGjKTAnMCUGA1UdEQEB/wQbMBmGF2xkYXA6Ly9s
+ZGFwLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBw220f176kgZeDf3Pq
+/KIR0F32e4d1V1lgQmgqYkyuYWRTK2NLnDA1IytgCttdLHJgpOGT2FqqC3GDUiGY
+MOQBt3ktSoxFGm9aIgLvD4Vu2i/szKaA8WyVexExGLsuYCaRx8NVHPgpVf2xAuWV
+8qpkkAlyqk9epHrxsIKttu8H44DY/bxSbYsWOBMIXnLnZiKvD/UIzTyX2yg1mZsD
+UvF4Gpo0vgUMKE1qhLxh+9bgEoOMWb2BeTvdeJUimd7DdYC1sb7WvC+IeigACtVm
+/J19maobzW+GkHq79Oyx4i7cwTc5Fo6wYDIuAgN49NO9SzjVchtSI78VQ6DuyiER
+ztUD
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert12.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert12.pem
new file mode 100644
index 0000000..3d2ca89
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert12.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+jCCAeKgAwIBAgIEV3qL4jANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKDAZP
+cGVuREoxDzANBgNVBAMMBnNlcnZlcjAeFw0xNjA3MDQxNjE4MDdaFw0xNzA3MDQx
+NjE4MDdaMCIxDzANBgNVBAoMBk9wZW5ESjEPMA0GA1UEAwwGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAohD42B3LG+3SntrBn4a5rxcDfcTG
+HZadjScjwFZ4qosB0lGw+bO6UKLhHCKMbdoXMYqSWIxPiu/hySae8ecM7++PQsYN
+GZb4zn/cpJWJizlknhzhG4byUG+xsarGeV6/ayu8zLHMgelWWExvgeKmjeUCWgLs
+xDm23U7m34tbrN+nfyrx3yzc1gtczPehwd7Y3jY+JY4FiOCsHpaitqDKZM2nEg+K
+JuXjx0mhENh1qLOcUQax0AWCRh6B5nXCPSV469ed8zz9QyZfR7Lb5WcWOE95PSZM
+aEMJBR9vDZfxGjE2k4WipOCKhGjPBUsR4zazGvRfgNeyRsblCChE/enTgwIDAQAB
+ozgwNjA0BgNVHREELTArghBsZGFwLmV4YW1wbGUuY29thhdsZGFwOi8vbGRhcC5l
+eGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAbvL8blsqkZ+qF8qM1o5G6cCY
+w49+4PQGh2mkseJokj7e4w7lfFk3o5jNMEWktig15qir2QWGSecHtBJajWTkw9Fe
+/OF74ptdKgBYKxsAF7IFQdozs+NUOc0q7tMp2L4g9rpOTk46EPRHT7zTo0sViNBR
+9/0vRsIsBx5HtC9h5f0cucF8ykB7Sibk6wCmubBZWbpm5Hk5aPfq4ui03/SAe79/
+e5Cu+1dBx6qJSA5XTG4D1IDizQVKRRTglVwV/jVuG49GDekIg/lVBYTDTD0Ld42+
+gCslJrRnw9QbbmejWyn2oZxIVwnCQwCzTZ4NunnOx4fD6OLzAMNDLMWloagAqw==
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert13.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert13.pem
new file mode 100644
index 0000000..a750b79
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert13.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIJALfTcCMjgUSOMA0GCSqGSIb3DQEBCwUAMD0xCzAJBgNV
+BAYTAkdCMQ0wCwYDVQQIDARBdm9uMRIwEAYDVQQKDAlGb3JnZVJvY2sxCzAJBgNV
+BAMMAkNBMB4XDTE2MDcwNjE0MzExMVoXDTE3MDcwNjE0MzExMVowADCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN3Y2kkWv0wVNfhVowOCl+4crxsYRAaf
+RHPJERTfsLpAhdpHS4VdUTCcwfTmqGn3YQ427wnnl07DDN5YRokfRJArdnfifuoT
+/0UWa434AjK5CO9YiZ5BGt9bAoA4jdJqCR5pbW07dA0dAb5XEwirWWW5w/d4N3Eq
+26vRMXh7iDLYzuJ2rLDauq4gkdqvYwRi0WfuP7dl41nfxufGdt5vOMot/8akXO2t
+kqBJFCpdw5GKILQRo8sf4GaqTYdG0+eplO/rf+JOMWWO9bD6oEzxXk2gYyM5dvbd
+h/cv9AE+wBnYXvsW2tK0/emCt1dbVj8i4tPIRBD5IVW+SpO4MKo86AkCAwEAAaNt
+MGswCQYDVR0TBAIwADAdBgNVHQ4EFgQUD27k4aisMchWtHCnAFhHa03malYwHwYD
+VR0jBBgwFoAUl/cZwoNkFVFCD3lJ6lxuyNxMi0UwHgYDVR0RAQH/BBQwEoIQbGRh
+cC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAXj+Hx2EVswhqpdyW1jN0
+E3dxNDCeG3DHJsHmcbPMbTtpPT2xvT/6SNr8+FoLZAtw+ibDR+NFU1H7UGK+bpjh
+CfUkArmzbYun3FOkLYliMW/ffqr8lMpeqTuRkrOpAkqqo0dCrT2/ucraFkOhypfk
+scaeQq7pTLnjICZ8g9Yu2ru78q4nd5a7s5p/xQ1Om2oy/L+as4fcrNImHomvhfAg
+IPr0sX1G8ftlvRRfIzaZORqXB1raeLCXvVt6opyrxTyeWz8Lld3w+FkKwt3YXFA8
+Td6tR4JA9TTMlnwaR8dbBSfaPuVrVfoBh5KDOVXji/fER/gKUPaiH48h1rJVucF4
+Yg==
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert2.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert2.pem
new file mode 100644
index 0000000..588fcc0
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIESjmh0DANBgkqhkiG9w0BAQsFADBBMQ8wDQYDVQQKEwZP
+cGVuREoxGTAXBgNVBAMTEGxkYXAuZXhhbXBsZS5jb20xEzARBgkqhkiG9w0BCQEW
+BGluZm8wHhcNMTYwNzA0MTMxMzIwWhcNMTYxMDAyMTMxMzIwWjBBMQ8wDQYDVQQK
+EwZPcGVuREoxGTAXBgNVBAMTEGxkYXAuZXhhbXBsZS5jb20xEzARBgkqhkiG9w0B
+CQEWBGluZm8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjYZcndShB
+ByrBA80MRnW2RNMbGWCkxDZthpAFwsqQQPXxlKZ7gA/dbIG8ijQximW638mfjcu4
+YdViJ2XTafG0G0r1rKD+wl8IQPbWokMBSYWYrXcxN9KEediSL53CAyBqGHJ5TrZD
+S0mI5xiWWPxlSuCBdxCvAip8vOuFoLnkeiOdntGkG7lJDkT+3hPIJ0d/ol1ZB7VI
+YYkuxJcWZIQw/vhxYFsZ7aBhyxyyMqczhhMKJySwSr3o/Fe/HciLnALMDLXBrifz
+BOAjlp3uC6miUPSqUZJGNPR+eaMe7Tj6jTwa1C0EZ3sLmFCt+BUVr2+PrA4AoGDt
+9g7r3bkvIpyBAgMBAAGjITAfMB0GA1UdDgQWBBRQn86+hZEpcuM9o+xiDkQA0zHu
+dzANBgkqhkiG9w0BAQsFAAOCAQEAQxUaRbsig9JC0HaXlB1gAsy4NYBKMtcOrNhl
+eG0SCGGaW2SyWbLOgtfC/256J/CU42RhD6QpMuNQgq701NzkpxOl3rQC3jCLocge
+ECAKOe6T20pBhjY7ihgPyJFmsuGZe5bi2Qc0ZLsAe1rKn3t5Gas2WNrU6KwqmsbS
+QwKJJ9wAW4SP+GmHZ16Kz+xuZLxE5DY5dw4MOd25w4bagmMrr61JxXblTT76466q
+hfBtuEvxzSn2E7DGj7xP0h7X4nfixME8O+qnE+Xd9mIjZNhd5DkXjKy8ZsOzsdFR
+qLZmcpcyMFIgrN9/mjRvaNgf19B8lcgx9fM9frzIeOiv+lYFMw==
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert3.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert3.pem
new file mode 100644
index 0000000..47117a2
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert3.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+DCCAeCgAwIBAgIEV3pVWTANBgkqhkiG9w0BAQsFADAsMQ8wDQYDVQQKDAZP
+cGVuREoxGTAXBgNVBAMMEGxkYXAuZXhhbXBsZS5jb20wHhcNMTYwNzA0MTIyNDM1
+WhcNMTcwNzA0MTIyNDM1WjAsMQ8wDQYDVQQKDAZPcGVuREoxGTAXBgNVBAMMEGxk
+YXAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCI
++e9AysfcqtTpz78ULs4zs61UmxpflvysBKgc4Vo+fQFEfxLDb2q1i8Pf3Mv40Q9t
+9xCcSDYV8FFbKRinv15vVLZ/nHa1V7q2yqWuDF7P0pyEDjnVgmOOL+/VMHCz80GE
+UKpLvmHjOJoPJ1Xz+x5rhWFrOQAYrAsJBWEDJJ7kL9ixjajFK6z2iK7ufTvoks3B
+AYEYpPj8e7ngM4UpcPM9KsWU91QlV91Skfxyv5vH/JmdItYnGbv7o/6rTAdfkhZl
+6fS8ToK4rWxjl/W9Y9fQcPX1/jB1C5x8GQDFsiSzGosP4L6Rh8rwOArv2cDN/Igp
+T92Iv5fN1Sn/t4jWjVl5AgMBAAGjIjAgMB4GA1UdEQEB/wQUMBKCEGxkYXAuZXhh
+bXBsZS5vcmcwDQYJKoZIhvcNAQELBQADggEBADKziDxHdbMLfAglpGxpHvU68/9F
+yqUSFLamRxAYM+bnxskVf6vJG4Htq0XHrNOGBTpEIacd9txMJkyuqaTcNE9xQmPS
+xMnv5nTQumq65AD3MJvXBFpG+kumVv5PKvHVIfWykAatvQ2M7w/dDwvjLkB6LdrY
+Y1sW3fyri9GhDzXR996Cdlfx+MOpB9f/hmDJzzCbYEwopVJ1tTl0JrqR8Rwih2MS
+Kiv7ECkubGnphgwN6+ztLepmbDG2sCevWNAewp49NeCiZBwg3uhpvuxqjFn8zzkB
+VhuXCNfVkXZIv3tVnr8cdyKqIG/WOchhho00gfdI7S76aOlF6kvCAUPEP5k=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert4.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert4.pem
new file mode 100644
index 0000000..289d410
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert4.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9TCCAd2gAwIBAgIEV3pdzTANBgkqhkiG9w0BAQsFADAsMQ8wDQYDVQQKDAZP
+cGVuREoxGTAXBgNVBAMMEGxkYXAuZXhhbXBsZS5jb20wHhcNMTYwNzA0MTMwMDMw
+WhcNMTcwNzA0MTMwMDMwWjAsMQ8wDQYDVQQKDAZPcGVuREoxGTAXBgNVBAMMEGxk
+YXAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCP
+Dbagq6CxseV2HqrjKjj2Er/qsDqk9CVncEZsJL6kV2/S8Umj6w/vOxtsw06DiAV6
+Yvs64JDptSPg2ip0pI8CxqO2b7DOA7qwwp8ng0Uw4U6WYGn4tt5zFC/F9hORwFTC
+uN3Qm2k+hwCQdIJq5yTEAoq8qNJvUUqCJ7bCcJbNp4qQuFh7gUcqjAAYjc42ABOw
+C4NVnfqpHnFa0GibIGZZgQaPWatCWC/+KScJZavH+I6I+kNHqP+0XEvkFYBp/BM/
+s8Bg+2rNrrsK1ikq4oS9zOfjcOU2a+1CDKidazxdVYc0Y1p326o74bVYE0vcPE1e
+cgb8Sc91wgXml7DDbN9PAgMBAAGjHzAdMBsGA1UdEQQUMBKCEGxkYXAuZXhhbXBs
+ZS5vcmcwDQYJKoZIhvcNAQELBQADggEBACio2XKfLsmOH3e1uYhBiqE9+CbuQz6u
+nyMO2BaRK69rg1703AI+G6tYf9rC32Lqmt9wGd/IOHcucytPXcq9LQE99f5CP8Pt
+D82mBjhbNA4fb/Xf++DprfmywTqU92U5uMUyaGw0DN7v4qqRUeG45O5Mbi/7kAqb
+cxYlxwVE5wy816X8wDCa1PvkOFvVO8pVdzhxx7L0jRhEAuqEyCpvlZbDHpvF/K+6
+vskoLM42ctrUBi+rFj/5oHw/qw7jhEulMJoQ5sDXxr9+eMbvzszeVJ695ILnBPDQ
+7J7Dmodsk2LMF6r1AJX+f7jmOjGn9uw2upSRTkdI+TIAZKhvGjiQw00=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert5.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert5.pem
new file mode 100644
index 0000000..91f45eb
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert5.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+DCCAeCgAwIBAgIEV3peTjANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKDAZP
+cGVuREoxDzANBgNVBAMMBnNlcnZlcjAeFw0xNjA3MDQxMzAyNTNaFw0xNzA3MDQx
+MzAyNTNaMCIxDzANBgNVBAoMBk9wZW5ESjEPMA0GA1UEAwwGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApxPO9rpEUKWOGJmOW6lqPEkYR8C6
+C/NCB+qR7+oPYRMyllaAN8zqc2FZ1gKeDkv4QmI0T8Eg+9tHbRpjkc72tE9nkPFw
+IWjhUpfxcbdBmQST7gPlEYj/AVdmd1m25BOJg9YNuMYftyHTRli1ULYOz7AWwTpi
+w8v9uT7q2dwMxkbkECa5Y5E0zyuIadlvZKz1GnH1O6/5PKSgMk9qP6X3Bx+QYyq2
+X3SfPSrJ5V16h/mIaVgz17OIm8aVOAPaC+b/a9PsIWBF1IapQeBodLc9E+1uvjjI
+AyyBibKJreditG8LRiAr+0bqMPrtYm5X7vKtq6Q+l0ryB5T+V8x92eBrAwIDAQAB
+ozYwNDAyBgNVHREBAf8EKDAmghFsZGFwMS5leGFtcGxlLmNvbYIRbGRhcDIuZXhh
+bXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAABh4JgGss1aZ3qc6SmkvYSalmFp
+rQJahuwzGBaxDiAKpkfhPRy7g09xzHyWy/3zrYXTURwqqJnWujPfv4h/tJQt3ptl
+MW9AzmAYVVBY4u14TdJ/zl67iRukS6SKtN94r67e+lwECxWCD/jheJVbl5/mGcvO
+O6uGTc/cXahpwb31Osv8qGQyJLi+rc6x7vMMG3tvAss45RmSkWiKzmITNa9eWqOS
+EOercFRlLdaTQ/0FpnMpsO0Ejl1w71I4XXeZ7Ay2fHclBm6S016nMgWqUU9QziFG
+wc1mrBP6RKmP+1v0sCO8gXlbzuhnqDSDTBlw4xGZ++Ej3mpIxaX9ZOkqz4k=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert6.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert6.pem
new file mode 100644
index 0000000..d7346cd
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert6.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4TCCAcmgAwIBAgIEV3peuzANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKDAZP
+cGVuREoxDzANBgNVBAMMBnNlcnZlcjAeFw0xNjA3MDQxMzA0MjJaFw0xNzA3MDQx
+MzA0MjJaMCIxDzANBgNVBAoMBk9wZW5ESjEPMA0GA1UEAwwGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg5KTCDHu3LhIF/9XlRaVvgFFjfc4
+D+npzCFYLujbNmY1G/I9s5o7Be0/9W/1OPZjGO4Wb8fAMfV1qgMGE4XR40+UGtmx
+MkBPmVPSV8mgCrGRPYGXkSgvJv8X3lVf3ldbqJOKcaF70AwLrATlqNbCyWweoby8
+PSzjlDSDGTUAa1XTj7ACgdcrqq6OdL8N2sqM4C+vnC7GAaqdCq/wQaPpshC6c3gB
+lo9+t2hqy3ob/ltnsRA4hvmyB5JJ9LNsfmvhzIGpX/fX1bc13+Katfk9sXIEcn0A
+zTUKY9vVppsxpSbhaaM2x0neBhcJDT/BnGBA7NO/42uc8njciyqhuevOUwIDAQAB
+ox8wHTAbBgNVHREBAf8EETAPgg0qLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUA
+A4IBAQB3wyyQjHIU2YCTG59d8C1D8Y6Mb5loodCYkTFqZU4HPaGi8qsBLdzeYvkZ
+ntaVogm0jRGAgWlgKwo2IS5a9smctWGK65bLb1Ts3h65Ft0mhMZk4MNUli/yKM+v
+zQqRymRRHgj50juaJYiKLWICH6l66AwUPnuZ2pGLDUSxdWTRvmVsfzamySOba+yz
+u577MQMXXwybZVhWs6ZZbJ5yVqkXEkFzKgBRPOI5y+reeLRqEfWEb0J+N0h3rYIE
+lj01B7EdUQBqle0slB/TUqZkFH+WOv5DX7TwRHOb/qAgIl9EraLxOM/UI/UVUKA6
+97y8hmW/nDrEz1rqEYDr/Fj4/s5l
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert7.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert7.pem
new file mode 100644
index 0000000..b5dd8f9
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert7.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIICzjCCAbagAwIBAgIEV3pfHDANBgkqhkiG9w0BAQsFADApMQ8wDQYDVQQKDAZP
+cGVuREoxFjAUBgNVBAMMDSouZXhhbXBsZS5jb20wHhcNMTYwNzA0MTMwNTQ3WhcN
+MTcwNzA0MTMwNTQ3WjApMQ8wDQYDVQQKDAZPcGVuREoxFjAUBgNVBAMMDSouZXhh
+bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCLrepSmClz
+yVV5IYWQ8DGq3K4VgfithXm6pY4ruoH5nwfJ3NbOQukyOOJ8VLvPvbSGMFDeSvtp
+dzKfNeYB06mVV7ASvOJZPzD+Teemr6p2+sT/KosstxkrtgWJHRsyVajyMIYNtLh9
+2EpLBGYdM4LpzVWmOjNep7it3zH7xqL6IGesI7DVKfv2dQy0RnMV2+B2etDpE5Gi
+duxnVHUxBUrC1FxbIvWoAG0H1ijgP2YqGlZCaOGRsBSajGs1Vfhz8hTarzTY/Bgz
+66ZDB8t9PM2L7LN73S9QRSGiblloBNLPdbE+cus5t46mNgXswnCv4C9zHqtY+yPf
+MYYzSDzJm1dbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAgS3k/+GL7JC20KYzkk
+jvhDjRCT8xmNNHOsJEPBjGDmj+24s4TwqQdJJ6GmyNM5h5bCPbDaUBExZaagTurl
+LLTbqT0UhnlICsFye8/CqP1H9zRneZxVE9U6XIZQCvuhUcvxaGUqYFwa8Fj31R0i
+tQQO0WaghxduMDaXb/WVCA94PuTpuPtzLcchdTogO+2nPOvG7AG0YeWhvWkpn/Go
+a9FoCx0+BM37UV0eG6rqmrt8dQTx/IdoickNfCuu4qQFzvM2ct0SJrmb4VWMPP2J
+QemHQ1nPRFtZenPEc29bSsYbhmauX5Z4N4+soAzGR0Q5By4/11On+w2oAGcz31eg
+NuA=
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert8.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert8.pem
new file mode 100644
index 0000000..f0ffa22
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert8.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6jCCAdKgAwIBAgIEV3pfizANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKDAZP
+cGVuREoxDzANBgNVBAMMBnNlcnZlcjAeFw0xNjA3MDQxMzA4MDNaFw0xNzA3MDQx
+MzA4MDNaMCIxDzANBgNVBAoMBk9wZW5ESjEPMA0GA1UEAwwGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqceuUbp2niP3eAla+fMlzX+Kt/zA
+Xfo/UiY/C+qdcryvuINm4d37+EVW8PUTr9uBKUvsWBUHJ36r7LGCSDlU+3aS+/Du
+OFBArKwfayAb8oLwcbWbFRuG406yqBAIIOTFpON/5PATZ1oljyYGEUbPMjPCN5K4
+pBSIbLU/yugWPWg0Kx1VtwrRIiRydSu5QVvGA8vDX4+IsDH2UQSgzz8suRLQvbKk
+4NroCEzjsmRUQhs0z4vuVPM+4ybMvD13IIrL18SCr8b1drMgeBhXtcD1Z62v/tm0
++qFq3JLc+9Gsf2uJACFOtTwuI8LkUAODbVy7UWL1vW3X02GXq27JXYTZfQIDAQAB
+oygwJjAkBgNVHREBAf8EGjAYghBsZGFwLmV4YW1wbGUuY29thwTAqAABMA0GCSqG
+SIb3DQEBCwUAA4IBAQAYdVC67IoywXFAUqsCVh2gxSA7Sfu7MCcccJyjG+XxkJYx
+S6xuEbvLf8IdG1/VRpO6V07AHSsUz/O2XOwNudfyb59Syvz+Y/OTZaw0w68N+eZp
+/F5RWrfzY6anDRRcRDy/uG5PAz7IEdgYyfwCITIOZimHzH1GkBfuGgHSRDktH4q7
+pvRrxqJ5S4NDqcUYu0KWvTbkFZeh+a0h5FRdlCW2B9HhXkb6OpoZmHVKpwfOanPQ
+x4ztQkGgGJpSHv5cqFNvmpTFxA07ZXFXBWnRQS59pjOgbLiLUxvTyY4a0yfKDIrU
+7yj7TEow4LBs2r1zXPVuEIs1bt+4W1pmyUHf463V
+-----END CERTIFICATE-----
diff --git a/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert9.pem b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert9.pem
new file mode 100644
index 0000000..a93c423
--- /dev/null
+++ b/opendj-core/src/test/resources/org.forgerock.opendj.ldap.TrustManagers/cert9.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIEJkgy6zANBgkqhkiG9w0BAQsFADAiMQ8wDQYDVQQKEwZP
+cGVuREoxDzANBgNVBAMTBnNlcnZlcjAeFw0xNjA3MDQxMzQ3MTNaFw0xNjEwMDIx
+MzQ3MTNaMCIxDzANBgNVBAoTBk9wZW5ESjEPMA0GA1UEAxMGc2VydmVyMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl63LuujQxWqIy7DiDsEDIKDuSTrx
+Bw3F0qsn5XoIpXpTqye9z5Ux/CA4GzSTJFiTEGnThqBQUiegz8XHxQRXg0WPddsB
+tQVrxRZ1SGnpv0rqX0izdz8Wl1kN6cczzyU06A4Fyx5uDUBfa6vQcLzcRM87Ycsn
+cENnB2JUA0G/RRP7dxrpMtln7+iEFr3thAHdmbXodJ9WTpi3tXSYzzPaSLWAbycc
+dooMdiQi0rdbYhz6s4FWN22w2i39rIuQKNiiDntJSW+psFXEKCa4exAktEM3vZ/V
+WKZGQasN2Ja2BNf9BSq8c1LGuBlPh1eCjFhxKLrdZl7dfxr7Snk8TLZbjwIDAQAB
+o0EwPzAeBgNVHREBAf8EFDAShxAgAQ24AAAAAAABAAAAAAABMB0GA1UdDgQWBBQ7
+y7Tq2SHqsFQGFA5QjvLJ3hkj3TANBgkqhkiG9w0BAQsFAAOCAQEAbZtb1IMVogE/
+ZJcdgRked8pknyhAun2xtaL6ob3PEWHFqeK8WThbvRO5j8FWvcVYMX9OXOJCQJVu
+iAvRxZQcRvbu2ApefrvoYQKYFr5Wr9cf8lKe3ggbeqcj48SNNOF/WMq+q4Yv9C8G
+cBlxJQvRR4lubXd3FC/xM+YElPJxldMqSZsz4TvtEJYd+lfI3fR0PW8D7svNdUEp
+7osvwsA5re2/s9/yyVM/dNUr2ud1lbnA0U5ECRyIHn0V32E0Qmg+yk9rayu1mmbp
+Y2M8f3DXVm8tLlpSyV4qKnotgQ09Ip5sQAcKB2k8fuDkjtxJKf/lo4EuXim4skYZ
+xP+LN/FvNw==
+-----END CERTIFICATE-----
--
Gitblit v1.10.0