View | Details | Raw Unified | Return to bug 49785
Collapse All | Expand All

(-)a/java/org/apache/catalina/realm/JNDIRealm.java (-2 / +274 lines)
Lines 17-27 Link Here
17
17
18
package org.apache.catalina.realm;
18
package org.apache.catalina.realm;
19
19
20
import java.io.IOException;
21
import java.lang.reflect.Constructor;
22
import java.lang.reflect.InvocationTargetException;
20
import java.net.URI;
23
import java.net.URI;
21
import java.net.URISyntaxException;
24
import java.net.URISyntaxException;
25
import java.security.KeyManagementException;
26
import java.security.NoSuchAlgorithmException;
22
import java.security.Principal;
27
import java.security.Principal;
23
import java.text.MessageFormat;
28
import java.text.MessageFormat;
24
import java.util.ArrayList;
29
import java.util.ArrayList;
30
import java.util.Arrays;
25
import java.util.Collections;
31
import java.util.Collections;
26
import java.util.HashMap;
32
import java.util.HashMap;
27
import java.util.Hashtable;
33
import java.util.Hashtable;
Lines 49-54 import javax.naming.directory.DirContext; Link Here
49
import javax.naming.directory.InitialDirContext;
55
import javax.naming.directory.InitialDirContext;
50
import javax.naming.directory.SearchControls;
56
import javax.naming.directory.SearchControls;
51
import javax.naming.directory.SearchResult;
57
import javax.naming.directory.SearchResult;
58
import javax.naming.ldap.InitialLdapContext;
59
import javax.naming.ldap.LdapContext;
60
import javax.naming.ldap.StartTlsRequest;
61
import javax.naming.ldap.StartTlsResponse;
62
import javax.net.ssl.HostnameVerifier;
63
import javax.net.ssl.SSLContext;
64
import javax.net.ssl.SSLSession;
65
import javax.net.ssl.SSLSocketFactory;
52
66
53
import org.apache.catalina.LifecycleException;
67
import org.apache.catalina.LifecycleException;
54
import org.ietf.jgss.GSSCredential;
68
import org.ietf.jgss.GSSCredential;
Lines 439-444 public class JNDIRealm extends RealmBase { Link Here
439
     */
453
     */
440
    protected String spnegoDelegationQop = "auth-conf";
454
    protected String spnegoDelegationQop = "auth-conf";
441
455
456
    /**
457
     * Whether to use TLS for connections
458
     */
459
    private boolean useStartTls = false;
460
461
    private StartTlsResponse tls = null;
462
463
    /**
464
     * The list of enabled cipher suites used for establishing tls connections.
465
     * <code>null</code> means to use the default cipher suites.
466
     */
467
    private String[] cipherSuites = null;
468
469
    /**
470
     * Verifier for hostnames in a StartTLS secured connection. <code>null</code>
471
     * means to use the default verifier.
472
     */
473
    private HostnameVerifier hostnameVerifier = null;
474
475
    /**
476
     * {@link SSLSocketFactory} to use when connection with StartTLS enabled.
477
     */
478
    private SSLSocketFactory sslSocketFactory = null;
479
442
    // ------------------------------------------------------------- Properties
480
    // ------------------------------------------------------------- Properties
443
481
444
    /**
482
    /**
Lines 1022-1027 public class JNDIRealm extends RealmBase { Link Here
1022
    }
1060
    }
1023
1061
1024
1062
1063
    /**
1064
     * @return flag whether to use StartTLS for connections to the ldap server
1065
     */
1066
    public boolean getUseStartTls() {
1067
        return useStartTls;
1068
    }
1069
1070
    /**
1071
     * Flag whether StartTLS should be used when connecting to the ldap server
1072
     *
1073
     * @param useStartTls
1074
     *            {@code true} when StartTLS should be used. Default is
1075
     *            {@code false}.
1076
     */
1077
    public void setUseStartTls(boolean useStartTls) {
1078
        this.useStartTls = useStartTls;
1079
    }
1080
1081
    /**
1082
     * @return list of the allowed cipher suites when connections are made using
1083
     *         StartTLS
1084
     */
1085
    private String[] getCipherSuitesArray() {
1086
        return cipherSuites;
1087
    }
1088
1089
    /**
1090
     * Set the allowed cipher suites when opening a connection using StartTLS.
1091
     * The cipher suites are expected as a comma separated list.
1092
     *
1093
     * @param suites
1094
     *            comma separated list of allowed cipher suites
1095
     */
1096
    public void setCipherSuites(String suites) {
1097
        if (suites == null || suites.trim().isEmpty()) {
1098
            containerLog.warn(sm.getString("jndiRealm.emptyCipherSuites"));
1099
            this.cipherSuites = null;
1100
        } else {
1101
            this.cipherSuites = suites.trim().split("\\s*,\\s*");
1102
            containerLog.debug(sm.getString("jndiRealm.cipherSuites",
1103
                    Arrays.asList(this.cipherSuites)));
1104
        }
1105
    }
1106
1107
    /**
1108
     * @return name of the {@link HostnameVerifier} class used for connections
1109
     *         using StartTLS, or the empty string, if the default verifier
1110
     *         should be used.
1111
     */
1112
    public String getHostnameVerifierClassName() {
1113
        if (this.hostnameVerifier == null) {
1114
            return "";
1115
        }
1116
        return this.hostnameVerifier.getClass().getCanonicalName();
1117
    }
1118
1119
    /**
1120
     * Set the {@link HostnameVerifier} to be used when opening connections
1121
     * using StartTLS. An instance of the given class name will be constructed
1122
     * using the default constructor.
1123
     *
1124
     * @param verifierClassName
1125
     *            class name of the {@link HostnameVerifier} to be constructed
1126
     */
1127
    public void setHostnameVerifierClassName(String verifierClassName) {
1128
        if (verifierClassName == null || verifierClassName.trim().equals("")) {
1129
            return;
1130
        }
1131
        try {
1132
            Object o = constructInstance(verifierClassName);
1133
            if (o instanceof HostnameVerifier) {
1134
                this.hostnameVerifier = (HostnameVerifier) o;
1135
            } else {
1136
                containerLog
1137
                        .warn(sm.getString("jndiRealm.invalidHostnameVerifier",
1138
                                verifierClassName));
1139
            }
1140
        } catch (ClassNotFoundException | NoSuchMethodException
1141
                | SecurityException | InstantiationException
1142
                | IllegalAccessException | IllegalArgumentException
1143
                | InvocationTargetException e) {
1144
            containerLog.warn(sm.getString("jndiRealm.invalidHostnameVerifier",
1145
                    verifierClassName), e);
1146
        }
1147
    }
1148
1149
    /**
1150
     * @return the {@link HostnameVerifier} to use for peer certificate
1151
     *         verification when opening connections using StartTLS.
1152
     */
1153
    public HostnameVerifier getHostnameVerifier() {
1154
        return this.getHostnameVerifier();
1155
    }
1156
1157
    /**
1158
     * Set the {@link SSLSocketFactory} to be used when opening connections
1159
     * using StartTLS. An instance of the factory with the given name will be
1160
     * created using the default constructor. The SSLSocketFactory can also be
1161
     * set using {@link JNDIRealm#setSslProtocol(String) setSslProtocol(String)}.
1162
     *
1163
     * @param factoryClassName
1164
     *            class name of the factory to be constructed
1165
     */
1166
    public void setSslSocketFactoryClassName(String factoryClassName) {
1167
        if (factoryClassName == null || factoryClassName.trim().equals("")) {
1168
            return;
1169
        }
1170
        try {
1171
            Object o = constructInstance(factoryClassName);
1172
            if (o instanceof SSLSocketFactory) {
1173
                this.sslSocketFactory = (SSLSocketFactory) o;
1174
            } else {
1175
                containerLog.warn(sm.getString(
1176
                        "jndiRealm.invalidSslSocketFactory", factoryClassName));
1177
            }
1178
        } catch (ClassNotFoundException | NoSuchMethodException
1179
                | SecurityException | InstantiationException
1180
                | IllegalAccessException | IllegalArgumentException
1181
                | InvocationTargetException e) {
1182
            containerLog.warn(sm.getString("jndiRealm.invalidSslSocketFactory",
1183
                    factoryClassName));
1184
        }
1185
    }
1186
1187
    /**
1188
     * Set the ssl protocol to be used for connections using StartTLS.
1189
     *
1190
     * @param protocol
1191
     *            one of the allowed ssl protocol names
1192
     */
1193
    public void setSslProtocol(String protocol) {
1194
        try {
1195
            SSLContext sslContext = SSLContext.getInstance(protocol);
1196
            sslContext.init(null, null, null);
1197
            this.sslSocketFactory = sslContext.getSocketFactory();
1198
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
1199
            List<String> allowedProtocols = Arrays
1200
                    .asList(getSupportedSslProtocols());
1201
            throw new IllegalArgumentException(
1202
                    sm.getString("jndiRealm.invalidSslProtocol", protocol,
1203
                            allowedProtocols), e);
1204
        }
1205
    }
1206
1207
    /**
1208
     * @return the list of supported ssl protocols by the default
1209
     *         {@link SSLContext}
1210
     */
1211
    private String[] getSupportedSslProtocols() {
1212
        try {
1213
            SSLContext sslContext = SSLContext.getDefault();
1214
            sslContext.init(null, null, null);
1215
            return sslContext.getSupportedSSLParameters().getProtocols();
1216
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
1217
            throw new RuntimeException(sm.getString("jndiRealm.exception"), e);
1218
        }
1219
    }
1220
1221
    private Object constructInstance(String className)
1222
            throws ClassNotFoundException, NoSuchMethodException,
1223
            InstantiationException, IllegalAccessException,
1224
            InvocationTargetException {
1225
        Class<?> clazz = getClass().getClassLoader().loadClass(
1226
                className);
1227
        Constructor<?> constructor = clazz.getConstructor();
1228
        return constructor.newInstance();
1229
    }
1230
1025
    // ---------------------------------------------------------- Realm Methods
1231
    // ---------------------------------------------------------- Realm Methods
1026
1232
1027
    /**
1233
    /**
Lines 1933-1938 public class JNDIRealm extends RealmBase { Link Here
1933
        if (context == null)
2139
        if (context == null)
1934
            return;
2140
            return;
1935
2141
2142
        // Close tls startResponse if used
2143
        if (tls != null) {
2144
            try {
2145
                tls.close();
2146
            } catch (IOException e) {
2147
                containerLog.error(sm.getString("jndiRealm.tlsClose"), e);
2148
            }
2149
        }
1936
        // Close our opened connection
2150
        // Close our opened connection
1937
        try {
2151
        try {
1938
            if (containerLog.isDebugEnabled())
2152
            if (containerLog.isDebugEnabled())
Lines 2125-2131 public class JNDIRealm extends RealmBase { Link Here
2125
        try {
2339
        try {
2126
2340
2127
            // Ensure that we have a directory context available
2341
            // Ensure that we have a directory context available
2128
            context = new InitialDirContext(getDirectoryContextEnvironment());
2342
            context = createDirContext(getDirectoryContextEnvironment());
2129
2343
2130
        } catch (Exception e) {
2344
        } catch (Exception e) {
2131
2345
Lines 2135-2141 public class JNDIRealm extends RealmBase { Link Here
2135
            containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
2349
            containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
2136
2350
2137
            // Try connecting to the alternate url.
2351
            // Try connecting to the alternate url.
2138
            context = new InitialDirContext(getDirectoryContextEnvironment());
2352
            context = createDirContext(getDirectoryContextEnvironment());
2139
2353
2140
        } finally {
2354
        } finally {
2141
2355
Lines 2149-2154 public class JNDIRealm extends RealmBase { Link Here
2149
2363
2150
    }
2364
    }
2151
2365
2366
    private DirContext createDirContext(Hashtable<String, String> env) throws NamingException {
2367
        if (useStartTls) {
2368
            return createTlsDirContext(env);
2369
        } else {
2370
            return new InitialDirContext(env);
2371
        }
2372
    }
2373
2374
    /**
2375
     * Create a tls enabled LdapContext and set the StartTlsResponse tls
2376
     * instance variable.
2377
     *
2378
     * @param env
2379
     *            Environment to use for context creation
2380
     * @return configured {@link LdapContext}
2381
     * @throws NamingException
2382
     *             when something goes wrong while negotiating the connection
2383
     */
2384
    private DirContext createTlsDirContext(
2385
            Hashtable<String, String> env) throws NamingException {
2386
        Map<String, Object> savedEnv = new HashMap<>();
2387
        for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION,
2388
                Context.SECURITY_CREDENTIALS, Context.SECURITY_PRINCIPAL,
2389
                Context.SECURITY_PROTOCOL)) {
2390
            Object entry = env.remove(key);
2391
            if (entry != null) {
2392
                savedEnv.put(key, entry);
2393
            }
2394
        }
2395
        LdapContext result = null;
2396
        try {
2397
            result = new InitialLdapContext(env, null);
2398
            tls = (StartTlsResponse) result
2399
                    .extendedOperation(new StartTlsRequest());
2400
            if (hostnameVerifier != null) {
2401
                tls.setHostnameVerifier(hostnameVerifier);
2402
            }
2403
            if (getCipherSuitesArray() != null) {
2404
                tls.setEnabledCipherSuites(getCipherSuitesArray());
2405
            }
2406
            try {
2407
                SSLSession negotiate = tls.negotiate(sslSocketFactory);
2408
                containerLog.debug(sm.getString("jndiRealm.negotiatedTls",
2409
                        negotiate.getProtocol()));
2410
            } catch (IOException e) {
2411
                throw new NamingException(e.getMessage());
2412
            }
2413
        } finally {
2414
            if (result != null) {
2415
                for (Map.Entry<String, Object> savedEntry : savedEnv.entrySet()) {
2416
                    result.addToEnvironment(savedEntry.getKey(),
2417
                            savedEntry.getValue());
2418
                }
2419
            }
2420
        }
2421
        return result;
2422
    }
2423
2152
    /**
2424
    /**
2153
     * Create our directory context configuration.
2425
     * Create our directory context configuration.
2154
     *
2426
     *
(-)a/java/org/apache/catalina/realm/LocalStrings.properties (+7 lines)
Lines 40-49 jdbcRealm.open=Exception opening database connection Link Here
40
jdbcRealm.open.invalidurl=Driver "{0}" does not support the url "{1}"
40
jdbcRealm.open.invalidurl=Driver "{0}" does not support the url "{1}"
41
jndiRealm.authenticateFailure=Username {0} NOT successfully authenticated
41
jndiRealm.authenticateFailure=Username {0} NOT successfully authenticated
42
jndiRealm.authenticateSuccess=Username {0} successfully authenticated
42
jndiRealm.authenticateSuccess=Username {0} successfully authenticated
43
jndiRealm.emptyCipherSuites=Empty String for cipher suites given. Using default cipher suites.
44
jndiRealm.cipherSuites=Enable [{0}] as cipher suites for tls connection.
43
jndiRealm.close=Exception closing directory server connection
45
jndiRealm.close=Exception closing directory server connection
44
jndiRealm.exception=Exception performing authentication
46
jndiRealm.exception=Exception performing authentication
45
jndiRealm.exception.retry=Exception performing authentication. Retrying...
47
jndiRealm.exception.retry=Exception performing authentication. Retrying...
48
jndiRealm.invalidHostnameVerifier="{0}" not a valid class name for a HostnameVerifier
49
jndiRealm.invalidSslProtocol=Given protocol "{0}" is invalid. It has to be one of {1}
50
jndiRealm.invalidSslSocketFactory="{0}" not a valid class name for a SSLSocketFactory
51
jndiRealm.negotiatedTls=Negotiated tls connection using protocol "{0}"
46
jndiRealm.open=Exception opening directory server connection
52
jndiRealm.open=Exception opening directory server connection
53
jndiRealm.tlsClose=Exception closing tls response
47
memoryRealm.authenticateFailure=Username {0} NOT successfully authenticated
54
memoryRealm.authenticateFailure=Username {0} NOT successfully authenticated
48
memoryRealm.authenticateSuccess=Username {0} successfully authenticated
55
memoryRealm.authenticateSuccess=Username {0} successfully authenticated
49
memoryRealm.loadExist=Memory database file {0} cannot be read
56
memoryRealm.loadExist=Memory database file {0} cannot be read
(-)a/webapps/docs/config/realm.xml (-1 / +35 lines)
Lines 412-417 Link Here
412
        can be used. If no value is given the providers default is used.</p>
412
        can be used. If no value is given the providers default is used.</p>
413
      </attribute>
413
      </attribute>
414
414
415
      <attribute name="cipherSuites" required="false">
416
        <p>Specify which cipher suites are allowed when trying to open
417
        a secured connection using StartTLS. The allowed cipher suites
418
        are specified by a comma separated list. The default is to use the
419
        cipher suites of the JVM.</p>
420
      </attribute>
421
415
      <attribute name="commonRole" required="false">
422
      <attribute name="commonRole" required="false">
416
        <p>A role name assigned to each successfully authenticated user in
423
        <p>A role name assigned to each successfully authenticated user in
417
        addition to the roles retrieved from LDAP. If not specified, only
424
        addition to the roles retrieved from LDAP. If not specified, only
Lines 468-473 Link Here
468
        <strong>CredentialHandler</strong> element instead.</p>
475
        <strong>CredentialHandler</strong> element instead.</p>
469
      </attribute>
476
      </attribute>
470
477
478
      <attribute name="hostnameVerifierClassName" required="false">
479
        <p>The name of the class to use for hostname verification when
480
        using StartTLS for securing the connection to the ldap server.
481
        The default constructor will be used to construct an instance of
482
        the verifier class. The default is to accept only those hostnames,
483
        that are valid according to the peer certificate of the ldap
484
        server.</p>
485
      </attribute>
486
471
      <attribute name="protocol" required="false">
487
      <attribute name="protocol" required="false">
472
         <p>A string specifying the security protocol to use. If not given
488
         <p>A string specifying the security protocol to use. If not given
473
         the providers default is used.</p>
489
         the providers default is used.</p>
Lines 577-582 Link Here
577
        <p>The default value is <code>auth-conf</code>.</p>
593
        <p>The default value is <code>auth-conf</code>.</p>
578
      </attribute>
594
      </attribute>
579
595
596
      <attribute name="sslProtocol" required="false">
597
        <p>Specifies which ssl protocol should be used, when connecting with
598
        StartTLS. The default is to let the jre decide. If you need even more
599
        control, you can specify the <code>SSLSocketFactory</code> to use.</p>
600
      </attribute>
601
602
      <attribute name="sslSocketFactory" required="false">
603
        <p>Specifies which <code>SSLSocketFactory</code> to use when connecting
604
        to the ldap server using StartTLS. An instance of the class will be
605
        constructed using the default constructor. If none class name is given
606
        the default jre <code>SSLSocketFactory</code> will be used.</p>
607
      </attribute>
608
580
      <attribute name="stripRealmForGss" required="false">
609
      <attribute name="stripRealmForGss" required="false">
581
        <p>When processing users authenticated via the GSS-API, this attribute
610
        <p>When processing users authenticated via the GSS-API, this attribute
582
        controls if any &quot;@...&quot; is removed from the end of the user
611
        controls if any &quot;@...&quot; is removed from the end of the user
Lines 682-687 Link Here
682
        expression.</p>
711
        expression.</p>
683
      </attribute>
712
      </attribute>
684
713
714
      <attribute name="useStartTls" required="false">
715
        <p>Set to <code>true</code> if you want to use StartTLS for securing
716
        the connection to the ldap server. The default value is <code>false</code>.
717
        </p>
718
      </attribute>
719
685
      <attribute name="X509UsernameRetrieverClassName" required="false">
720
      <attribute name="X509UsernameRetrieverClassName" required="false">
686
        <p>When using X509 client certificates, this specifies the class name
721
        <p>When using X509 client certificates, this specifies the class name
687
        that will be used to retrieve the user name from the certificate.
722
        that will be used to retrieve the user name from the certificate.
688
- 

Return to bug 49785