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.core.kerberos;
21
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32
33 import org.apache.directory.server.core.interceptor.BaseInterceptor;
34 import org.apache.directory.server.core.interceptor.Interceptor;
35 import org.apache.directory.server.core.interceptor.NextInterceptor;
36 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
37 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
38 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
39 import org.apache.directory.server.core.normalization.NormalizationInterceptor;
40 import org.apache.directory.server.core.authn.AuthenticationInterceptor;
41 import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
42 import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
43 import org.apache.directory.server.core.exception.ExceptionInterceptor;
44 import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
45 import org.apache.directory.server.core.schema.SchemaInterceptor;
46 import org.apache.directory.server.core.subtree.SubentryInterceptor;
47 import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor;
48 import org.apache.directory.server.core.entry.ClonedServerEntry;
49 import org.apache.directory.server.core.entry.DefaultServerAttribute;
50 import org.apache.directory.server.core.entry.ServerAttribute;
51 import org.apache.directory.server.core.entry.ServerBinaryValue;
52 import org.apache.directory.server.core.entry.ServerEntry;
53 import org.apache.directory.server.core.entry.ServerModification;
54 import org.apache.directory.server.core.entry.ServerStringValue;
55 import org.apache.directory.server.core.event.EventInterceptor;
56 import org.apache.directory.server.core.trigger.TriggerInterceptor;
57 import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
58 import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
59 import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
60 import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
61 import org.apache.directory.server.kerberos.shared.io.encoder.EncryptionKeyEncoder;
62 import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
63 import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
64 import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
65 import org.apache.directory.server.schema.registries.Registries;
66 import org.apache.directory.shared.ldap.constants.SchemaConstants;
67 import org.apache.directory.shared.ldap.entry.EntryAttribute;
68 import org.apache.directory.shared.ldap.entry.Modification;
69 import org.apache.directory.shared.ldap.entry.ModificationOperation;
70 import org.apache.directory.shared.ldap.entry.Value;
71 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
72 import org.apache.directory.shared.ldap.name.LdapDN;
73 import org.apache.directory.shared.ldap.util.StringTools;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public class KeyDerivationInterceptor extends BaseInterceptor
90 {
91
92 private static final Logger log = LoggerFactory.getLogger( KeyDerivationInterceptor.class );
93
94
95 public static final String NAME = "keyDerivationService";
96
97
98
99
100 private static final Collection<String> USERLOOKUP_BYPASS;
101 static
102 {
103 Set<String> c = new HashSet<String>();
104 c.add( NormalizationInterceptor.class.getName() );
105 c.add( AuthenticationInterceptor.class.getName() );
106 c.add( AciAuthorizationInterceptor.class.getName() );
107 c.add( DefaultAuthorizationInterceptor.class.getName() );
108 c.add( ExceptionInterceptor.class.getName() );
109 c.add( OperationalAttributeInterceptor.class.getName() );
110 c.add( SchemaInterceptor.class.getName() );
111 c.add( SubentryInterceptor.class.getName() );
112 c.add( CollectiveAttributeInterceptor.class.getName() );
113 c.add( EventInterceptor.class.getName() );
114 c.add( TriggerInterceptor.class.getName() );
115 USERLOOKUP_BYPASS = Collections.unmodifiableCollection( c );
116 }
117
118
119
120
121
122
123
124
125 public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
126 {
127 LdapDN normName = addContext.getDn();
128
129 ServerEntry entry = addContext.getEntry();
130
131 if ( ( entry.get( SchemaConstants.USER_PASSWORD_AT ) != null ) &&
132 ( entry.get( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT ) != null ) )
133 {
134 log.debug( "Adding the entry '{}' for DN '{}'.", entry, normName.getUpName() );
135
136 ServerBinaryValue userPassword = (ServerBinaryValue)entry.get( SchemaConstants.USER_PASSWORD_AT ).get();
137 String strUserPassword = StringTools.utf8ToString( userPassword.get() );
138
139 if ( log.isDebugEnabled() )
140 {
141 StringBuffer sb = new StringBuffer();
142 sb.append( "'" + strUserPassword + "' ( " );
143 sb.append( userPassword );
144 sb.append( " )" );
145 log.debug( "Adding Attribute id : 'userPassword', Values : [ {} ]", sb.toString() );
146 }
147
148 Value<?> principalNameValue = entry.get( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT ).get();
149
150 String principalName = (String)principalNameValue.get();
151
152 log.debug( "Got principal '{}' with userPassword '{}'.", principalName, strUserPassword );
153
154 Map<EncryptionType, EncryptionKey> keys = generateKeys( principalName, strUserPassword );
155
156 entry.put( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT, principalName );
157 entry.put( KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT, "0" );
158
159 entry.put( getKeyAttribute( addContext.getSession().getDirectoryService().getRegistries(), keys ) );
160
161 log.debug( "Adding modified entry '{}' for DN '{}'.", entry, normName
162 .getUpName() );
163 }
164
165 next.add( addContext );
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179
180 public void modify( NextInterceptor next, ModifyOperationContext modContext ) throws Exception
181 {
182 ModifySubContext subContext = new ModifySubContext();
183
184 detectPasswordModification( modContext, subContext );
185
186 if ( subContext.getUserPassword() != null )
187 {
188 lookupPrincipalAttributes( modContext, subContext );
189 }
190
191 if ( subContext.isPrincipal() && subContext.hasValues() )
192 {
193 deriveKeys( modContext, subContext );
194 }
195
196 next.modify( modContext );
197 }
198
199
200
201
202
203
204
205
206
207
208 void detectPasswordModification( ModifyOperationContext modContext, ModifySubContext subContext )
209 throws Exception
210 {
211 List<Modification> mods = modContext.getModItems();
212
213 String operation = null;
214
215
216 for ( Modification mod:mods )
217 {
218 if ( log.isDebugEnabled() )
219 {
220 switch ( mod.getOperation() )
221 {
222 case ADD_ATTRIBUTE:
223 operation = "Adding";
224 break;
225
226 case REMOVE_ATTRIBUTE:
227 operation = "Removing";
228 break;
229
230 case REPLACE_ATTRIBUTE:
231 operation = "Replacing";
232 break;
233 }
234 }
235
236 ServerAttribute attr = (ServerAttribute)mod.getAttribute();
237
238 if ( attr.instanceOf( SchemaConstants.USER_PASSWORD_AT ) )
239 {
240 Object firstValue = attr.get();
241 String password = null;
242
243 if ( firstValue instanceof ServerStringValue )
244 {
245 password = ((ServerStringValue)firstValue).get();
246 log.debug( "{} Attribute id : 'userPassword', Values : [ '{}' ]", operation, password );
247 }
248 else if ( firstValue instanceof ServerBinaryValue )
249 {
250 password = StringTools.utf8ToString( ((ServerBinaryValue)firstValue).get() );
251
252 if ( log.isDebugEnabled() )
253 {
254 StringBuffer sb = new StringBuffer();
255 sb.append( "'" + password + "' ( " );
256 sb.append( StringTools.dumpBytes( ((ServerBinaryValue)firstValue).get() ).trim() );
257 sb.append( " )" );
258 log.debug( "{} Attribute id : 'userPassword', Values : [ {} ]", operation, sb.toString() );
259 }
260 }
261
262 subContext.setUserPassword( password );
263 log.debug( "Got userPassword '{}'.", subContext.getUserPassword() );
264 }
265
266 if ( attr.instanceOf( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT ) )
267 {
268 subContext.setPrincipalName( attr.getString() );
269 log.debug( "Got principal '{}'.", subContext.getPrincipalName() );
270 }
271 }
272 }
273
274
275
276
277
278
279
280
281
282 void lookupPrincipalAttributes( ModifyOperationContext modContext, ModifySubContext subContext )
283 throws Exception
284 {
285 LdapDN principalDn = modContext.getDn();
286
287 LookupOperationContext lookupContext = modContext.newLookupContext( principalDn );
288 lookupContext.setByPassed( USERLOOKUP_BYPASS );
289 lookupContext.setAttrsId( new String[]
290 {
291 SchemaConstants.OBJECT_CLASS_AT,
292 KerberosAttribute.KRB5_PRINCIPAL_NAME_AT,
293 KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT
294 } );
295
296 ClonedServerEntry userEntry = modContext.lookup( lookupContext );
297
298 if ( userEntry == null )
299 {
300 throw new LdapAuthenticationException( "Failed to authenticate user '" + principalDn + "'." );
301 }
302
303 EntryAttribute objectClass = userEntry.getOriginalEntry().get( SchemaConstants.OBJECT_CLASS_AT );
304
305 if ( !objectClass.contains( SchemaConstants.KRB5_PRINCIPAL_OC ) )
306 {
307 return;
308 }
309 else
310 {
311 subContext.isPrincipal( true );
312 log.debug( "DN {} is a Kerberos principal. Will attempt key derivation.", principalDn.getUpName() );
313 }
314
315 if ( subContext.getPrincipalName() == null )
316 {
317 EntryAttribute principalAttribute = userEntry.getOriginalEntry().get( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT );
318 String principalName = principalAttribute.getString();
319 subContext.setPrincipalName( principalName );
320 log.debug( "Found principal '{}' from lookup.", principalName );
321 }
322
323 EntryAttribute keyVersionNumberAttr = userEntry.getOriginalEntry().get( KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT );
324
325 if ( keyVersionNumberAttr == null )
326 {
327 subContext.setNewKeyVersionNumber( 0 );
328 log.debug( "Key version number was null, setting to 0." );
329 }
330 else
331 {
332 int oldKeyVersionNumber = Integer.valueOf( keyVersionNumberAttr.getString() );
333 int newKeyVersionNumber = oldKeyVersionNumber + 1;
334 subContext.setNewKeyVersionNumber( newKeyVersionNumber );
335 log.debug( "Found key version number '{}', setting to '{}'.", oldKeyVersionNumber, newKeyVersionNumber );
336 }
337 }
338
339
340
341
342
343
344
345
346
347
348 void deriveKeys( ModifyOperationContext modContext, ModifySubContext subContext ) throws Exception
349 {
350 List<Modification> mods = modContext.getModItems();
351
352 String principalName = subContext.getPrincipalName();
353 String userPassword = subContext.getUserPassword();
354 int kvno = subContext.getNewKeyVersionNumber();
355
356 log.debug( "Got principal '{}' with userPassword '{}'.", principalName, userPassword );
357
358 Map<EncryptionType, EncryptionKey> keys = generateKeys( principalName, userPassword );
359
360 List<Modification> newModsList = new ArrayList<Modification>();
361
362
363 for ( Modification mod:mods )
364 {
365 newModsList.add( mod );
366 }
367
368 AttributeTypeRegistry atRegistry = modContext.getSession()
369 .getDirectoryService().getRegistries().getAttributeTypeRegistry();
370
371
372 newModsList.add(
373 new ServerModification(
374 ModificationOperation.REPLACE_ATTRIBUTE,
375 new DefaultServerAttribute(
376 KerberosAttribute.KRB5_PRINCIPAL_NAME_AT,
377 atRegistry.lookup( KerberosAttribute.KRB5_PRINCIPAL_NAME_AT ),
378 principalName ) ) );
379 newModsList.add(
380 new ServerModification(
381 ModificationOperation.REPLACE_ATTRIBUTE,
382 new DefaultServerAttribute(
383 KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT,
384 atRegistry.lookup( KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT ),
385 Integer.toString( kvno ) ) ) );
386
387 ServerAttribute attribute = getKeyAttribute( modContext.getSession()
388 .getDirectoryService().getRegistries(), keys );
389 newModsList.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ) );
390
391 modContext.setModItems( newModsList );
392 }
393
394
395 private ServerAttribute getKeyAttribute( Registries registries, Map<EncryptionType, EncryptionKey> keys ) throws Exception
396 {
397 ServerAttribute keyAttribute =
398 new DefaultServerAttribute( KerberosAttribute.KRB5_KEY_AT,
399 registries.getAttributeTypeRegistry().lookup( KerberosAttribute.KRB5_KEY_AT ) );
400
401 Iterator<EncryptionKey> it = keys.values().iterator();
402
403 while ( it.hasNext() )
404 {
405 try
406 {
407 keyAttribute.add( EncryptionKeyEncoder.encode( it.next() ) );
408 }
409 catch ( IOException ioe )
410 {
411 log.error( "Error encoding EncryptionKey.", ioe );
412 }
413 }
414
415 return keyAttribute;
416 }
417
418
419 private Map<EncryptionType, EncryptionKey> generateKeys( String principalName, String userPassword )
420 {
421 if ( userPassword.equalsIgnoreCase( "randomKey" ) )
422 {
423
424 try
425 {
426 return RandomKeyFactory.getRandomKeys();
427 }
428 catch ( KerberosException ke )
429 {
430 log.debug( ke.getMessage(), ke );
431 return null;
432 }
433 }
434 else
435 {
436
437 return KerberosKeyFactory.getKerberosKeys( principalName, userPassword );
438 }
439 }
440
441 class ModifySubContext
442 {
443 private boolean isPrincipal = false;
444 private String principalName;
445 private String userPassword;
446 private int newKeyVersionNumber = -1;
447
448
449 boolean isPrincipal()
450 {
451 return isPrincipal;
452 }
453
454
455 void isPrincipal( boolean isPrincipal )
456 {
457 this.isPrincipal = isPrincipal;
458 }
459
460
461 String getPrincipalName()
462 {
463 return principalName;
464 }
465
466
467 void setPrincipalName( String principalName )
468 {
469 this.principalName = principalName;
470 }
471
472
473 String getUserPassword()
474 {
475 return userPassword;
476 }
477
478
479 void setUserPassword( String userPassword )
480 {
481 this.userPassword = userPassword;
482 }
483
484
485 int getNewKeyVersionNumber()
486 {
487 return newKeyVersionNumber;
488 }
489
490
491 void setNewKeyVersionNumber( int newKeyVersionNumber )
492 {
493 this.newKeyVersionNumber = newKeyVersionNumber;
494 }
495
496
497 boolean hasValues()
498 {
499 return userPassword != null && principalName != null && newKeyVersionNumber > -1;
500 }
501 }
502 }