1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.server.ssl;
21
22
23 import java.io.ByteArrayInputStream;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.security.KeyStore;
27 import java.security.cert.Certificate;
28 import java.security.cert.CertificateFactory;
29 import java.util.ArrayList;
30 import java.util.HashSet;
31 import java.util.Hashtable;
32 import java.util.List;
33 import java.util.Set;
34
35 import javax.naming.AuthenticationNotSupportedException;
36 import javax.naming.Context;
37 import javax.naming.NameNotFoundException;
38 import javax.naming.NamingEnumeration;
39 import javax.naming.directory.Attributes;
40 import javax.naming.directory.BasicAttribute;
41 import javax.naming.directory.BasicAttributes;
42 import javax.naming.directory.DirContext;
43 import javax.naming.directory.ModificationItem;
44 import javax.naming.directory.SearchControls;
45 import javax.naming.directory.SearchResult;
46 import javax.naming.ldap.InitialLdapContext;
47 import javax.naming.ldap.LdapContext;
48 import javax.naming.ldap.StartTlsRequest;
49 import javax.naming.ldap.StartTlsResponse;
50 import javax.net.ssl.HostnameVerifier;
51 import javax.net.ssl.SSLSession;
52
53 import org.apache.directory.server.core.CoreSession;
54 import org.apache.directory.server.core.entry.ClonedServerEntry;
55 import org.apache.directory.server.core.integ.Level;
56 import org.apache.directory.server.core.integ.annotations.CleanupLevel;
57 import org.apache.directory.server.integ.ServerIntegrationUtils;
58 import org.apache.directory.server.integ.SiRunner;
59 import org.apache.directory.server.ldap.LdapService;
60 import org.apache.directory.shared.ldap.name.LdapDN;
61 import org.junit.After;
62 import org.junit.Before;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68
69 import static org.junit.Assert.assertNotNull;
70 import static org.junit.Assert.assertEquals;
71 import static org.junit.Assert.assertTrue;
72 import static org.junit.Assert.fail;
73
74
75
76
77
78
79
80
81
82
83
84
85
86 @RunWith ( SiRunner.class )
87 @CleanupLevel ( Level.SUITE )
88 public class StartTlsIT
89 {
90 private static final Logger LOG = LoggerFactory.getLogger( StartTlsIT.class );
91 private static final String[] CERT_IDS = new String[] { "userCertificate" };
92 private static final int CONNECT_ITERATIONS = 10;
93 private static final boolean VERBOSE = false;
94 private File ksFile;
95
96
97 public static LdapService ldapService;
98 boolean oldConfidentialityRequiredValue;
99
100
101
102
103
104
105
106
107
108
109
110
111 @Before
112 public void installKeyStoreWithCertificate() throws Exception
113 {
114 if ( ksFile != null && ksFile.exists() )
115 {
116 ksFile.delete();
117 }
118
119 ksFile = File.createTempFile( "testStore", "ks" );
120
121 CoreSession session = ldapService.getDirectoryService().getAdminSession();
122 ClonedServerEntry entry = session.lookup( new LdapDN( "uid=admin,ou=system" ), CERT_IDS );
123 byte[] userCertificate = entry.get( CERT_IDS[0] ).getBytes();
124 assertNotNull( userCertificate );
125
126 ByteArrayInputStream in = new ByteArrayInputStream( userCertificate );
127 CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
128 Certificate cert = factory.generateCertificate( in );
129 KeyStore ks = KeyStore.getInstance( KeyStore.getDefaultType() );
130 ks.load( null, null );
131 ks.setCertificateEntry( "apacheds", cert );
132 ks.store( new FileOutputStream( ksFile ), "changeit".toCharArray() );
133 LOG.debug( "Keystore file installed: {}", ksFile.getAbsolutePath() );
134
135 oldConfidentialityRequiredValue = ldapService.isConfidentialityRequired();
136 }
137
138
139
140
141
142 @After
143 public void deleteKeyStore() throws Exception
144 {
145 if ( ksFile != null && ksFile.exists() )
146 {
147 ksFile.delete();
148 }
149
150 LOG.debug( "Keystore file deleted: {}", ksFile.getAbsolutePath() );
151 ldapService.setConfidentialityRequired( oldConfidentialityRequiredValue );
152 }
153
154
155 private LdapContext getSecuredContext() throws Exception
156 {
157 System.setProperty ( "javax.net.ssl.trustStore", ksFile.getAbsolutePath() );
158 System.setProperty ( "javax.net.ssl.keyStore", ksFile.getAbsolutePath() );
159 System.setProperty ( "javax.net.ssl.keyStorePassword", "changeit" );
160 LOG.debug( "testStartTls() test starting ... " );
161
162
163 Hashtable<String, Object> env = new Hashtable<String,Object>();
164 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
165
166
167 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
168
169
170 LOG.debug( "About to get initial context" );
171 LdapContext ctx = new InitialLdapContext( env, null );
172
173
174 LOG.debug( "About send startTls extended operation" );
175 StartTlsResponse tls = ( StartTlsResponse ) ctx.extendedOperation( new StartTlsRequest() );
176 LOG.debug( "Extended operation issued" );
177 tls.setHostnameVerifier( new HostnameVerifier() {
178 public boolean verify( String hostname, SSLSession session )
179 {
180 return true;
181 }
182 } );
183 LOG.debug( "TLS negotion about to begin" );
184 tls.negotiate();
185 return ctx;
186 }
187
188
189
190
191
192 @Test
193 public void testConfidentiality() throws Exception
194 {
195 ldapService.setConfidentialityRequired( true );
196
197
198
199
200
201 try
202 {
203 ServerIntegrationUtils.getWiredContext( ldapService );
204 fail( "Should not get here due to violation of confidentiality requirements" );
205 }
206 catch( AuthenticationNotSupportedException e )
207 {
208 }
209
210
211
212
213
214 LdapContext ctx = getSecuredContext();
215 assertNotNull( ctx );
216
217
218
219
220
221 ctx.addToEnvironment( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
222 ctx.addToEnvironment( Context.SECURITY_CREDENTIALS, "secret" );
223 ctx.addToEnvironment( Context.SECURITY_AUTHENTICATION, "simple" );
224 ctx.reconnect( null );
225
226
227
228
229
230 NamingEnumeration<SearchResult> results = ctx.search( "ou=system", "(objectClass=*)", new SearchControls() );
231 Set<String> names = new HashSet<String>();
232 while( results.hasMore() )
233 {
234 names.add( results.next().getName() );
235 }
236 results.close();
237 assertTrue( names.contains( "prefNodeName=sysPrefRoot" ) );
238 assertTrue( names.contains( "ou=users" ) );
239 assertTrue( names.contains( "ou=configuration" ) );
240 assertTrue( names.contains( "uid=admin" ) );
241 assertTrue( names.contains( "ou=groups" ) );
242
243
244
245
246
247 Attributes attrs = new BasicAttributes( "objectClass", "person", true );
248 attrs.put( "sn", "foo" );
249 attrs.put( "cn", "foo bar" );
250 ctx.createSubcontext( "cn=foo bar,ou=system", attrs );
251 assertNotNull( ctx.lookup( "cn=foo bar,ou=system" ) );
252
253
254
255
256
257 ModificationItem[] mods = new ModificationItem[] {
258 new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute( "cn", "fbar" ) )
259 };
260 ctx.modifyAttributes( "cn=foo bar,ou=system", mods );
261 Attributes reread = ( Attributes ) ctx.getAttributes( "cn=foo bar,ou=system" );
262 assertTrue( reread.get( "cn" ).contains( "fbar" ) );
263
264
265
266
267
268 ctx.rename( "cn=foo bar,ou=system", "cn=fbar,ou=system" );
269 try
270 {
271 ctx.getAttributes( "cn=foo bar,ou=system" );
272 fail( "old name of renamed entry should not be found" );
273 }
274 catch ( NameNotFoundException e )
275 {
276 }
277 reread = ( Attributes ) ctx.getAttributes( "cn=fbar,ou=system" );
278 assertTrue( reread.get( "cn" ).contains( "fbar" ) );
279
280
281
282
283
284 ctx.destroySubcontext( "cn=fbar,ou=system" );
285 try
286 {
287 ctx.getAttributes( "cn=fbar,ou=system" );
288 fail( "deleted entry should not be found" );
289 }
290 catch ( NameNotFoundException e )
291 {
292 }
293
294 ctx.close();
295 }
296
297
298 private void search( int ii, LdapContext securedContext ) throws Exception
299 {
300 SearchControls controls = new SearchControls();
301 controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
302
303 if ( VERBOSE )
304 {
305 System.out.println( "Searching on " + ii + "-th iteration:" );
306 }
307
308 List<String> results = new ArrayList<String>();
309 NamingEnumeration<SearchResult> ne = securedContext.search( "ou=system", "(objectClass=*)", controls );
310 while ( ne.hasMore() )
311 {
312 String dn = ne.next().getNameInNamespace();
313 results.add( dn );
314
315 if ( VERBOSE )
316 {
317 System.out.println( "\tSearch Result = " + dn );
318 }
319 }
320 ne.close();
321
322 assertEquals( "ou=system", results.get( 0 ) );
323 assertEquals( "uid=admin,ou=system", results.get( 1 ) );
324 assertEquals( "ou=users,ou=system", results.get( 2 ) );
325 assertEquals( "ou=groups,ou=system", results.get( 3 ) );
326 assertEquals( "cn=Administrators,ou=groups,ou=system", results.get( 4 ) );
327 assertEquals( "ou=configuration,ou=system", results.get( 5 ) );
328 assertEquals( "ou=partitions,ou=configuration,ou=system", results.get( 6 ) );
329 assertEquals( "ou=services,ou=configuration,ou=system", results.get( 7 ) );
330 assertEquals( "ou=interceptors,ou=configuration,ou=system", results.get( 8 ) );
331 assertEquals( "prefNodeName=sysPrefRoot,ou=system", results.get( 9 ) );
332 }
333
334
335
336
337
338
339
340
341
342
343
344 @Test
345 public void testStartTls() throws Exception
346 {
347 for ( int ii = 0; ii < CONNECT_ITERATIONS; ii++ )
348 {
349 if ( VERBOSE )
350 {
351 System.out.println( "Performing " + ii + "-th iteration to connect via StartTLS." );
352 }
353
354 System.setProperty ( "javax.net.ssl.trustStore", ksFile.getAbsolutePath() );
355 System.setProperty ( "javax.net.ssl.keyStore", ksFile.getAbsolutePath() );
356 System.setProperty ( "javax.net.ssl.keyStorePassword", "changeit" );
357 LOG.debug( "testStartTls() test starting ... " );
358
359
360 Hashtable<String, Object> env = new Hashtable<String,Object>();
361 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
362 env.put( "java.naming.security.principal", "uid=admin,ou=system" );
363 env.put( "java.naming.security.credentials", "secret" );
364 env.put( "java.naming.security.authentication", "simple" );
365
366
367 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
368
369
370 LOG.debug( "About to get initial context" );
371 LdapContext ctx = new InitialLdapContext( env, null );
372
373
374 LOG.debug( "About send startTls extended operation" );
375 StartTlsResponse tls = ( StartTlsResponse ) ctx.extendedOperation( new StartTlsRequest() );
376 LOG.debug( "Extended operation issued" );
377 tls.setHostnameVerifier( new HostnameVerifier() {
378 public boolean verify( String hostname, SSLSession session )
379 {
380 return true;
381 }
382 } );
383 LOG.debug( "TLS negotion about to begin" );
384 tls.negotiate();
385
386 search( ii, ctx );
387
388 tls.close();
389 ctx.close();
390 }
391 }
392 }