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 |
* |