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.authn;
21
22
23 import java.io.UnsupportedEncodingException;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.security.SecureRandom;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Set;
32
33 import javax.naming.Context;
34 import javax.naming.NamingException;
35
36 import org.apache.commons.collections.map.LRUMap;
37 import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
38 import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
39 import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor;
40 import org.apache.directory.server.core.entry.ServerEntry;
41 import org.apache.directory.server.core.entry.ServerStringValue;
42 import org.apache.directory.server.core.event.EventInterceptor;
43 import org.apache.directory.server.core.exception.ExceptionInterceptor;
44 import org.apache.directory.server.core.interceptor.context.BindOperationContext;
45 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
46 import org.apache.directory.server.core.normalization.NormalizationInterceptor;
47 import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
48 import org.apache.directory.server.core.schema.SchemaInterceptor;
49 import org.apache.directory.server.core.subtree.SubentryInterceptor;
50 import org.apache.directory.server.core.trigger.TriggerInterceptor;
51 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
52 import org.apache.directory.shared.ldap.constants.LdapSecurityConstants;
53 import org.apache.directory.shared.ldap.constants.SchemaConstants;
54 import org.apache.directory.shared.ldap.entry.EntryAttribute;
55 import org.apache.directory.shared.ldap.entry.Value;
56 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
57 import org.apache.directory.shared.ldap.name.LdapDN;
58 import org.apache.directory.shared.ldap.util.ArrayUtils;
59 import org.apache.directory.shared.ldap.util.Base64;
60 import org.apache.directory.shared.ldap.util.StringTools;
61 import org.apache.directory.shared.ldap.util.UnixCrypt;
62
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66
67
68
69
70
71
72
73
74
75
76
77 public class SimpleAuthenticator extends AbstractAuthenticator
78 {
79 private static final Logger LOG = LoggerFactory.getLogger( SimpleAuthenticator.class );
80
81
82 private static final boolean IS_DEBUG = LOG.isDebugEnabled();
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 private final LRUMap credentialCache;
101
102
103 private static final int DEFAULT_CACHE_SIZE = 100;
104
105
106
107
108
109 private static final Collection<String> USERLOOKUP_BYPASS;
110
111
112 static
113 {
114 Set<String> c = new HashSet<String>();
115 c.add( NormalizationInterceptor.class.getName() );
116 c.add( AuthenticationInterceptor.class.getName() );
117 c.add( AciAuthorizationInterceptor.class.getName() );
118 c.add( DefaultAuthorizationInterceptor.class.getName() );
119 c.add( ExceptionInterceptor.class.getName() );
120 c.add( OperationalAttributeInterceptor.class.getName() );
121 c.add( SchemaInterceptor.class.getName() );
122 c.add( SubentryInterceptor.class.getName() );
123 c.add( CollectiveAttributeInterceptor.class.getName() );
124 c.add( EventInterceptor.class.getName() );
125 c.add( TriggerInterceptor.class.getName() );
126 USERLOOKUP_BYPASS = Collections.unmodifiableCollection( c );
127 }
128
129
130
131
132
133
134 public SimpleAuthenticator()
135 {
136 super( AuthenticationLevel.SIMPLE.toString() );
137 credentialCache = new LRUMap( DEFAULT_CACHE_SIZE );
138 }
139
140
141
142
143
144
145 public SimpleAuthenticator( int cacheSize )
146 {
147 super( AuthenticationLevel.SIMPLE.toString() );
148
149 credentialCache = new LRUMap( cacheSize > 0 ? cacheSize : DEFAULT_CACHE_SIZE );
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171 private class EncryptionMethod
172 {
173 private byte[] salt;
174 private LdapSecurityConstants algorithm;
175
176 private EncryptionMethod( LdapSecurityConstants algorithm, byte[] salt )
177 {
178 this.algorithm = algorithm;
179 this.salt = salt;
180 }
181 }
182
183
184
185
186
187
188
189
190 private LdapPrincipal getStoredPassword( BindOperationContext opContext ) throws Exception
191 {
192 LdapPrincipal principal = null;
193
194 synchronized( credentialCache )
195 {
196 principal = ( LdapPrincipal ) credentialCache.get( opContext.getDn().getNormName() );
197 }
198
199 byte[] storedPassword;
200
201 if ( principal == null )
202 {
203
204
205 storedPassword = lookupUserPassword( opContext );
206
207
208
209
210
211
212 if ( storedPassword == null )
213 {
214 storedPassword = ArrayUtils.EMPTY_BYTE_ARRAY;
215 }
216
217
218 principal = new LdapPrincipal( opContext.getDn(), AuthenticationLevel.SIMPLE, storedPassword );
219
220
221 synchronized( credentialCache )
222 {
223 credentialCache.put( opContext.getDn().getNormName(), principal );
224 }
225 }
226
227 return principal;
228 }
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public LdapPrincipal authenticate( BindOperationContext opContext ) throws Exception
262 {
263 if ( IS_DEBUG )
264 {
265 LOG.debug( "Authenticating {}", opContext.getDn() );
266 }
267
268
269 byte[] credentials = opContext.getCredentials();
270
271 LdapPrincipal principal = getStoredPassword( opContext );
272
273
274 byte[] storedPassword = principal.getUserPassword();
275
276
277
278 if ( Arrays.equals( credentials, storedPassword ) )
279 {
280 if ( IS_DEBUG )
281 {
282 LOG.debug( "{} Authenticated", opContext.getDn() );
283 }
284
285 return principal;
286 }
287
288
289 LdapSecurityConstants algorithm = findAlgorithm( storedPassword );
290
291 if ( algorithm != null )
292 {
293 EncryptionMethod encryptionMethod = new EncryptionMethod( algorithm, null );
294
295
296
297
298
299
300 byte[] encryptedStored = splitCredentials( storedPassword, encryptionMethod );
301
302
303
304 byte[] userPassword = encryptPassword( credentials, encryptionMethod );
305
306
307 if ( Arrays.equals( userPassword, encryptedStored ) )
308 {
309 if ( IS_DEBUG )
310 {
311 LOG.debug( "{} Authenticated", opContext.getDn() );
312 }
313
314 return principal;
315 }
316 else
317 {
318
319 String message = "Password not correct for user '" + opContext.getDn().getUpName() + "'";
320 LOG.info( message );
321 throw new LdapAuthenticationException(message);
322 }
323 }
324 else
325 {
326
327 String message = "Password not correct for user '" + opContext.getDn().getUpName() + "'";
328 LOG.info( message );
329 throw new LdapAuthenticationException(message);
330 }
331 }
332
333
334 private static void split( byte[] all, int offset, byte[] left, byte[] right )
335 {
336 System.arraycopy( all, offset, left, 0, left.length );
337 System.arraycopy( all, offset + left.length, right, 0, right.length );
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352 private byte[] splitCredentials( byte[] credentials, EncryptionMethod encryptionMethod )
353 {
354 int pos = encryptionMethod.algorithm.getName().length() + 2;
355
356 switch ( encryptionMethod.algorithm )
357 {
358 case HASH_METHOD_MD5 :
359 case HASH_METHOD_SHA :
360 try
361 {
362
363
364 return Base64.decode( new String( credentials, pos, credentials.length - pos, "UTF-8" ).toCharArray() );
365 }
366 catch ( UnsupportedEncodingException uee )
367 {
368
369 return credentials;
370 }
371
372 case HASH_METHOD_SMD5 :
373 case HASH_METHOD_SSHA :
374 try
375 {
376
377
378
379
380 byte[] passwordAndSalt = Base64.decode( new String( credentials, pos, credentials.length - pos, "UTF-8" ).
381 toCharArray() );
382
383 encryptionMethod.salt = new byte[8];
384 byte[] password = new byte[passwordAndSalt.length - encryptionMethod.salt.length];
385 split( passwordAndSalt, 0, password, encryptionMethod.salt );
386
387 return password;
388 }
389 catch ( UnsupportedEncodingException uee )
390 {
391
392 return credentials;
393 }
394
395 case HASH_METHOD_CRYPT :
396
397
398
399 encryptionMethod.salt = new byte[2];
400 byte[] password = new byte[credentials.length - encryptionMethod.salt.length - pos];
401 split( credentials, pos, encryptionMethod.salt, password );
402
403 return password;
404
405 default :
406
407 return credentials;
408
409 }
410 }
411
412
413
414
415
416
417
418
419
420
421 private LdapSecurityConstants findAlgorithm( byte[] credentials )
422 {
423 if ( ( credentials == null ) || ( credentials.length == 0 ) )
424 {
425 return null;
426 }
427
428 if ( credentials[0] == '{' )
429 {
430
431 int pos = 1;
432
433 while ( pos < credentials.length )
434 {
435 if ( credentials[pos] == '}' )
436 {
437 break;
438 }
439
440 pos++;
441 }
442
443 if ( pos < credentials.length )
444 {
445 if ( pos == 1 )
446 {
447
448 return null;
449 }
450
451 String algorithm = new String( credentials, 1, pos - 1 ).toLowerCase();
452
453 return LdapSecurityConstants.getAlgorithm( algorithm );
454 }
455 else
456 {
457
458 return null;
459 }
460 }
461 else
462 {
463
464 return null;
465 }
466 }
467
468
469
470
471
472
473
474
475
476
477
478 private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt )
479 {
480 MessageDigest digest;
481
482 try
483 {
484 digest = MessageDigest.getInstance( algorithm.getName() );
485 }
486 catch ( NoSuchAlgorithmException e1 )
487 {
488 return null;
489 }
490
491 if ( salt != null )
492 {
493 digest.update( password );
494 digest.update( salt );
495 return digest.digest();
496 }
497 else
498 {
499 return digest.digest( password );
500 }
501 }
502
503
504 private byte[] encryptPassword( byte[] credentials, EncryptionMethod encryptionMethod )
505 {
506 byte[] salt = encryptionMethod.salt;
507
508 switch ( encryptionMethod.algorithm )
509 {
510 case HASH_METHOD_SHA :
511 case HASH_METHOD_SSHA :
512 return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt );
513
514 case HASH_METHOD_MD5 :
515 case HASH_METHOD_SMD5 :
516 return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt );
517
518 case HASH_METHOD_CRYPT :
519 if ( salt == null )
520 {
521 salt = new byte[2];
522 SecureRandom sr = new SecureRandom();
523 int i1 = sr.nextInt( 64 );
524 int i2 = sr.nextInt( 64 );
525
526 salt[0] = ( byte ) ( i1 < 12 ? ( i1 + '.' ) : i1 < 38 ? ( i1 + 'A' - 12 ) : ( i1 + 'a' - 38 ) );
527 salt[1] = ( byte ) ( i2 < 12 ? ( i2 + '.' ) : i2 < 38 ? ( i2 + 'A' - 12 ) : ( i2 + 'a' - 38 ) );
528 }
529
530 String saltWithCrypted = UnixCrypt.crypt( StringTools.utf8ToString( credentials ),
531 StringTools.utf8ToString( salt ) );
532 String crypted = saltWithCrypted.substring( 2 );
533
534 return StringTools.getBytesUtf8( crypted );
535
536 default :
537 return credentials;
538 }
539 }
540
541
542
543
544
545
546
547
548 private byte[] lookupUserPassword( BindOperationContext opContext ) throws Exception
549 {
550
551 ServerEntry userEntry;
552
553 try
554 {
555
556
557
558
559
560
561
562 LookupOperationContext lookupContext =
563 new LookupOperationContext( getDirectoryService().getAdminSession(), opContext.getDn() );
564 lookupContext.setByPassed( USERLOOKUP_BYPASS );
565 userEntry = getDirectoryService().getOperationManager().lookup( lookupContext );
566
567 if ( userEntry == null )
568 {
569 LdapDN dn = opContext.getDn();
570 String upDn = ( dn == null ? "" : dn.getUpName() );
571
572 throw new LdapAuthenticationException( "Failed to lookup user for authentication: "
573 + upDn );
574 }
575 }
576 catch ( Exception cause )
577 {
578 LOG.error( "Authentication error : " + cause.getMessage() );
579 LdapAuthenticationException e = new LdapAuthenticationException( cause.getMessage() );
580 e.setRootCause( e );
581 throw e;
582 }
583
584 Value<?> userPassword;
585
586 EntryAttribute userPasswordAttr = userEntry.get( SchemaConstants.USER_PASSWORD_AT );
587
588
589 if ( userPasswordAttr == null )
590 {
591 return StringTools.EMPTY_BYTES;
592 }
593 else
594 {
595 userPassword = userPasswordAttr.get();
596
597 if ( userPassword instanceof ServerStringValue )
598 {
599 return StringTools.getBytesUtf8( (String)userPassword.get() );
600 }
601 else
602 {
603 return (byte[])userPassword.get();
604 }
605 }
606 }
607
608
609
610
611
612
613
614
615
616
617
618
619 protected String getAlgorithmForHashedPassword( byte[] password ) throws IllegalArgumentException
620 {
621 String result = null;
622
623
624 String sPassword = StringTools.utf8ToString( password );
625 int rightParen = sPassword.indexOf( '}' );
626
627 if ( ( sPassword.length() > 2 ) &&
628 ( sPassword.charAt( 0 ) == '{' ) &&
629 ( rightParen > -1 ) )
630 {
631 String algorithm = sPassword.substring( 1, rightParen );
632
633 if ( LdapSecurityConstants.HASH_METHOD_CRYPT.getName().equalsIgnoreCase( algorithm ) )
634 {
635 return algorithm;
636 }
637
638 try
639 {
640 MessageDigest.getInstance( algorithm );
641 result = algorithm;
642 }
643 catch ( NoSuchAlgorithmException e )
644 {
645 LOG.warn( "Unknown message digest algorithm in password: " + algorithm, e );
646 }
647 }
648
649 return result;
650 }
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673 protected String createDigestedPassword( String algorithm, byte[] password ) throws IllegalArgumentException
674 {
675
676 try
677 {
678 if ( LdapSecurityConstants.HASH_METHOD_CRYPT.getName().equalsIgnoreCase( algorithm ) )
679 {
680 String saltWithCrypted = UnixCrypt.crypt( StringTools.utf8ToString( password ), "" );
681 String crypted = saltWithCrypted.substring( 2 );
682 return '{' + algorithm + '}' + Arrays.toString( StringTools.getBytesUtf8( crypted ) );
683 }
684 else
685 {
686 MessageDigest digest = MessageDigest.getInstance( algorithm );
687
688
689 byte[] fingerPrint = digest.digest( password );
690 char[] encoded = Base64.encode( fingerPrint );
691
692
693 return '{' + algorithm + '}' + new String( encoded );
694 }
695 }
696 catch ( NoSuchAlgorithmException nsae )
697 {
698 LOG.error( "Cannot create a digested password for algorithm '{}'", algorithm );
699 throw new IllegalArgumentException( nsae.getMessage() );
700 }
701 }
702
703
704
705
706
707
708 public void invalidateCache( LdapDN bindDn )
709 {
710 synchronized( credentialCache )
711 {
712 credentialCache.remove( bindDn.getNormName() );
713 }
714 }
715 }