001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.core.authn; 021 022 023 import java.io.UnsupportedEncodingException; 024 import java.security.MessageDigest; 025 import java.security.NoSuchAlgorithmException; 026 import java.security.SecureRandom; 027 import java.util.Arrays; 028 import java.util.Collection; 029 import java.util.Collections; 030 import java.util.HashSet; 031 import java.util.Set; 032 033 import javax.naming.Context; 034 035 import org.apache.commons.collections.map.LRUMap; 036 import org.apache.directory.server.core.LdapPrincipal; 037 import org.apache.directory.server.core.authz.AciAuthorizationInterceptor; 038 import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor; 039 import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor; 040 import org.apache.directory.server.core.event.EventInterceptor; 041 import org.apache.directory.server.core.exception.ExceptionInterceptor; 042 import org.apache.directory.server.core.interceptor.context.BindOperationContext; 043 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 044 import org.apache.directory.server.core.normalization.NormalizationInterceptor; 045 import org.apache.directory.server.core.operational.OperationalAttributeInterceptor; 046 import org.apache.directory.server.core.schema.SchemaInterceptor; 047 import org.apache.directory.server.core.subtree.SubentryInterceptor; 048 import org.apache.directory.server.core.trigger.TriggerInterceptor; 049 import org.apache.directory.server.i18n.I18n; 050 import org.apache.directory.shared.ldap.constants.AuthenticationLevel; 051 import org.apache.directory.shared.ldap.constants.LdapSecurityConstants; 052 import org.apache.directory.shared.ldap.constants.SchemaConstants; 053 import org.apache.directory.shared.ldap.entry.EntryAttribute; 054 import org.apache.directory.shared.ldap.entry.ServerEntry; 055 import org.apache.directory.shared.ldap.entry.Value; 056 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException; 057 import org.apache.directory.shared.ldap.name.DN; 058 import org.apache.directory.shared.ldap.util.ArrayUtils; 059 import org.apache.directory.shared.ldap.util.Base64; 060 import org.apache.directory.shared.ldap.util.StringTools; 061 import org.apache.directory.shared.ldap.util.UnixCrypt; 062 import org.slf4j.Logger; 063 import org.slf4j.LoggerFactory; 064 065 066 /** 067 * A simple {@link Authenticator} that authenticates clear text passwords 068 * contained within the <code>userPassword</code> attribute in DIT. If the 069 * password is stored with a one-way encryption applied (e.g. SHA), the password 070 * is hashed the same way before comparison. 071 * 072 * We use a cache to speedup authentication, where the DN/password are stored. 073 * 074 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 075 */ 076 public class SimpleAuthenticator extends AbstractAuthenticator 077 { 078 private static final Logger LOG = LoggerFactory.getLogger( SimpleAuthenticator.class ); 079 080 /** A speedup for logger in debug mode */ 081 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 082 083 /** The SHA1 hash length */ 084 private static final int SHA1_LENGTH = 20; 085 086 /** The MD5 hash length */ 087 private static final int MD5_LENGTH = 16; 088 089 /** 090 * A cache to store passwords. It's a speedup, we will be able to avoid backend lookups. 091 * 092 * Note that the backend also use a cache mechanism, but for performance gain, it's good 093 * to manage a cache here. The main problem is that when a user modify his password, we will 094 * have to update it at three different places : 095 * - in the backend, 096 * - in the partition cache, 097 * - in this cache. 098 * 099 * The update of the backend and partition cache is already correctly handled, so we will 100 * just have to offer an access to refresh the local cache. 101 * 102 * We need to be sure that frequently used passwords be always in cache, and not discarded. 103 * We will use a LRU cache for this purpose. 104 */ 105 private final LRUMap credentialCache; 106 107 /** Declare a default for this cache. 100 entries seems to be enough */ 108 private static final int DEFAULT_CACHE_SIZE = 100; 109 110 /** 111 * Define the interceptors we should *not* go through when we will have to request the backend 112 * about a userPassword. 113 */ 114 private static final Collection<String> USERLOOKUP_BYPASS; 115 116 static 117 { 118 Set<String> c = new HashSet<String>(); 119 c.add( NormalizationInterceptor.class.getName() ); 120 c.add( AuthenticationInterceptor.class.getName() ); 121 c.add( AciAuthorizationInterceptor.class.getName() ); 122 c.add( DefaultAuthorizationInterceptor.class.getName() ); 123 c.add( ExceptionInterceptor.class.getName() ); 124 c.add( OperationalAttributeInterceptor.class.getName() ); 125 c.add( SchemaInterceptor.class.getName() ); 126 c.add( SubentryInterceptor.class.getName() ); 127 c.add( CollectiveAttributeInterceptor.class.getName() ); 128 c.add( EventInterceptor.class.getName() ); 129 c.add( TriggerInterceptor.class.getName() ); 130 USERLOOKUP_BYPASS = Collections.unmodifiableCollection( c ); 131 } 132 133 134 /** 135 * Creates a new instance. 136 * @see AbstractAuthenticator 137 */ 138 public SimpleAuthenticator() 139 { 140 super( AuthenticationLevel.SIMPLE.toString() ); 141 credentialCache = new LRUMap( DEFAULT_CACHE_SIZE ); 142 } 143 144 145 /** 146 * Creates a new instance, with an initial cache size 147 * @param cacheSize the size of the credential cache 148 */ 149 public SimpleAuthenticator( int cacheSize ) 150 { 151 super( AuthenticationLevel.SIMPLE.toString() ); 152 153 credentialCache = new LRUMap( cacheSize > 0 ? cacheSize : DEFAULT_CACHE_SIZE ); 154 } 155 156 /** 157 * A private class to store all informations about the existing 158 * password found in the cache or get from the backend. 159 * 160 * This is necessary as we have to compute : 161 * - the used algorithm 162 * - the salt if any 163 * - the password itself. 164 * 165 * If we have a on-way encrypted password, it is stored using this 166 * format : 167 * {<algorithm>}<encrypted password> 168 * where the encrypted password format can be : 169 * - MD5/SHA : base64([<salt (4 or 8 bytes)>]<password>) 170 * - crypt : <salt (2 btytes)><password> 171 * 172 * Algorithm are currently MD5, SMD5, SHA, SSHA, CRYPT and empty 173 */ 174 private class EncryptionMethod 175 { 176 private byte[] salt; 177 private LdapSecurityConstants algorithm; 178 179 180 private EncryptionMethod( LdapSecurityConstants algorithm, byte[] salt ) 181 { 182 this.algorithm = algorithm; 183 this.salt = salt; 184 } 185 } 186 187 188 /** 189 * Get the password either from cache or from backend. 190 * @param principalDN The DN from which we want the password 191 * @return A byte array which can be empty if the password was not found 192 * @throws Exception If we have a problem during the lookup operation 193 */ 194 private LdapPrincipal getStoredPassword( BindOperationContext opContext ) throws Exception 195 { 196 LdapPrincipal principal = null; 197 198 synchronized ( credentialCache ) 199 { 200 principal = ( LdapPrincipal ) credentialCache.get( opContext.getDn().getNormName() ); 201 } 202 203 byte[] storedPassword; 204 205 if ( principal == null ) 206 { 207 // Not found in the cache 208 // Get the user password from the backend 209 storedPassword = lookupUserPassword( opContext ); 210 211 // Deal with the special case where the user didn't enter a password 212 // We will compare the empty array with the credentials. Sometime, 213 // a user does not set a password. This is bad, but there is nothing 214 // we can do against that, except education ... 215 if ( storedPassword == null ) 216 { 217 storedPassword = ArrayUtils.EMPTY_BYTE_ARRAY; 218 } 219 220 // Create the new principal before storing it in the cache 221 principal = new LdapPrincipal( opContext.getDn(), AuthenticationLevel.SIMPLE, storedPassword ); 222 223 // Now, update the local cache. 224 synchronized ( credentialCache ) 225 { 226 credentialCache.put( opContext.getDn().getNormName(), principal ); 227 } 228 } 229 230 return principal; 231 } 232 233 234 /** 235 * <p> 236 * Looks up <tt>userPassword</tt> attribute of the entry whose name is the 237 * value of {@link Context#SECURITY_PRINCIPAL} environment variable, and 238 * authenticates a user with the plain-text password. 239 * </p> 240 * We have at least 6 algorithms to encrypt the password : 241 * <ul> 242 * <li>- SHA</li> 243 * <li>- SHA-256</li> 244 * <li>- SSHA (salted SHA)</li> 245 * <li>- MD5</li> 246 * <li>- SMD5 (slated MD5)</li> 247 * <li>- crypt (unix crypt)</li> 248 * <li>- plain text, ie no encryption.</li> 249 * </ul> 250 * <p> 251 * If we get an encrypted password, it is prefixed by the used algorithm, between 252 * brackets : {SSHA}password ... 253 * </p> 254 * If the password is using SSHA, SMD5 or crypt, some 'salt' is added to the password : 255 * <ul> 256 * <li>- length(password) - 20, starting at 21th position for SSHA</li> 257 * <li>- length(password) - 16, starting at 16th position for SMD5</li> 258 * <li>- length(password) - 2, starting at 3rd position for crypt</li> 259 * </ul> 260 * <p> 261 * For (S)SHA, SHA-256 and (S)MD5, we have to transform the password from Base64 encoded text 262 * to a byte[] before comparing the password with the stored one. 263 * </p> 264 * <p> 265 * For crypt, we only have to remove the salt. 266 * </p> 267 * <p> 268 * At the end, we use the digest() method for (S)SHA and (S)MD5, the crypt() method for 269 * the CRYPT algorithm and a straight comparison for PLAIN TEXT passwords. 270 * </p> 271 * <p> 272 * The stored password is always using the unsalted form, and is stored as a bytes array. 273 * </p> 274 */ 275 public LdapPrincipal authenticate( BindOperationContext opContext ) throws Exception 276 { 277 if ( IS_DEBUG ) 278 { 279 LOG.debug( "Authenticating {}", opContext.getDn() ); 280 } 281 282 // ---- extract password from JNDI environment 283 byte[] credentials = opContext.getCredentials(); 284 285 LdapPrincipal principal = getStoredPassword( opContext ); 286 287 // Get the stored password, either from cache or from backend 288 byte[] storedPassword = principal.getUserPassword(); 289 290 // Short circuit for PLAIN TEXT passwords : we compare the byte array directly 291 // Are the passwords equal ? 292 if ( Arrays.equals( credentials, storedPassword ) ) 293 { 294 if ( IS_DEBUG ) 295 { 296 LOG.debug( "{} Authenticated", opContext.getDn() ); 297 } 298 299 return principal; 300 } 301 302 // Let's see if the stored password was encrypted 303 LdapSecurityConstants algorithm = findAlgorithm( storedPassword ); 304 305 if ( algorithm != null ) 306 { 307 EncryptionMethod encryptionMethod = new EncryptionMethod( algorithm, null ); 308 309 // Let's get the encrypted part of the stored password 310 // We should just keep the password, excluding the algorithm 311 // and the salt, if any. 312 // But we should also get the algorithm and salt to 313 // be able to encrypt the submitted user password in the next step 314 byte[] encryptedStored = splitCredentials( storedPassword, encryptionMethod ); 315 316 // Reuse the saltedPassword informations to construct the encrypted 317 // password given by the user. 318 byte[] userPassword = encryptPassword( credentials, encryptionMethod ); 319 320 // Now, compare the two passwords. 321 if ( Arrays.equals( userPassword, encryptedStored ) ) 322 { 323 if ( IS_DEBUG ) 324 { 325 LOG.debug( "{} Authenticated", opContext.getDn() ); 326 } 327 328 return principal; 329 } 330 else 331 { 332 // Bad password ... 333 String message = I18n.err( I18n.ERR_230, opContext.getDn().getName() ); 334 LOG.info( message ); 335 throw new LdapAuthenticationException( message ); 336 } 337 } 338 else 339 { 340 // Bad password ... 341 String message = I18n.err( I18n.ERR_230, opContext.getDn().getName() ); 342 LOG.info( message ); 343 throw new LdapAuthenticationException( message ); 344 } 345 } 346 347 348 private static void split( byte[] all, int offset, byte[] left, byte[] right ) 349 { 350 System.arraycopy( all, offset, left, 0, left.length ); 351 System.arraycopy( all, offset + left.length, right, 0, right.length ); 352 } 353 354 355 /** 356 * Decompose the stored password in an algorithm, an eventual salt 357 * and the password itself. 358 * 359 * If the algorithm is SHA, SSHA, MD5 or SMD5, the part following the algorithm 360 * is base64 encoded 361 * 362 * @param encryptionMethod The structure to feed 363 * @return The password 364 * @param credentials the credentials to split 365 */ 366 private byte[] splitCredentials( byte[] credentials, EncryptionMethod encryptionMethod ) 367 { 368 int algoLength = encryptionMethod.algorithm.getName().length() + 2; 369 370 switch ( encryptionMethod.algorithm ) 371 { 372 case HASH_METHOD_MD5: 373 case HASH_METHOD_SHA: 374 case HASH_METHOD_SHA256: 375 try 376 { 377 // We just have the password just after the algorithm, base64 encoded. 378 // Just decode the password and return it. 379 return Base64 380 .decode( new String( credentials, algoLength, credentials.length - algoLength, "UTF-8" ) 381 .toCharArray() ); 382 } 383 catch ( UnsupportedEncodingException uee ) 384 { 385 // do nothing 386 return credentials; 387 } 388 389 case HASH_METHOD_SMD5: 390 try 391 { 392 // The password is associated with a salt. Decompose it 393 // in two parts, after having decoded the password. 394 // The salt will be stored into the EncryptionMethod structure 395 // The salt is at the end of the credentials, and is 8 bytes long 396 byte[] passwordAndSalt = Base64.decode( new String( credentials, algoLength, credentials.length 397 - algoLength, "UTF-8" ).toCharArray() ); 398 399 int saltLength = passwordAndSalt.length - MD5_LENGTH; 400 encryptionMethod.salt = new byte[saltLength]; 401 byte[] password = new byte[MD5_LENGTH]; 402 split( passwordAndSalt, 0, password, encryptionMethod.salt ); 403 404 return password; 405 } 406 catch ( UnsupportedEncodingException uee ) 407 { 408 // do nothing 409 return credentials; 410 } 411 412 case HASH_METHOD_SSHA: 413 try 414 { 415 // The password is associated with a salt. Decompose it 416 // in two parts, after having decoded the password. 417 // The salt will be stored into the EncryptionMethod structure 418 // The salt is at the end of the credentials, and is 8 bytes long 419 byte[] passwordAndSalt = Base64.decode( new String( credentials, algoLength, credentials.length 420 - algoLength, "UTF-8" ).toCharArray() ); 421 422 int saltLength = passwordAndSalt.length - SHA1_LENGTH; 423 encryptionMethod.salt = new byte[saltLength]; 424 byte[] password = new byte[SHA1_LENGTH]; 425 split( passwordAndSalt, 0, password, encryptionMethod.salt ); 426 427 return password; 428 } 429 catch ( UnsupportedEncodingException uee ) 430 { 431 // do nothing 432 return credentials; 433 } 434 435 case HASH_METHOD_CRYPT: 436 // The password is associated with a salt. Decompose it 437 // in two parts, storing the salt into the EncryptionMethod structure. 438 // The salt comes first, not like for SSHA and SMD5, and is 2 bytes long 439 encryptionMethod.salt = new byte[2]; 440 byte[] password = new byte[credentials.length - encryptionMethod.salt.length - algoLength]; 441 split( credentials, algoLength, encryptionMethod.salt, password ); 442 443 return password; 444 445 default: 446 // unknown method 447 return credentials; 448 449 } 450 } 451 452 453 /** 454 * Get the algorithm from the stored password. 455 * It can be found on the beginning of the stored password, between 456 * curly brackets. 457 * @param credentials the credentials of the user 458 * @return the name of the algorithm to use 459 * TODO use an enum for the algorithm 460 */ 461 private LdapSecurityConstants findAlgorithm( byte[] credentials ) 462 { 463 if ( ( credentials == null ) || ( credentials.length == 0 ) ) 464 { 465 return null; 466 } 467 468 if ( credentials[0] == '{' ) 469 { 470 // get the algorithm 471 int pos = 1; 472 473 while ( pos < credentials.length ) 474 { 475 if ( credentials[pos] == '}' ) 476 { 477 break; 478 } 479 480 pos++; 481 } 482 483 if ( pos < credentials.length ) 484 { 485 if ( pos == 1 ) 486 { 487 // We don't have an algorithm : return the credentials as is 488 return null; 489 } 490 491 String algorithm = new String( credentials, 1, pos - 1 ).toLowerCase(); 492 493 return LdapSecurityConstants.getAlgorithm( algorithm ); 494 } 495 else 496 { 497 // We don't have an algorithm 498 return null; 499 } 500 } 501 else 502 { 503 // No '{algo}' part 504 return null; 505 } 506 } 507 508 509 /** 510 * Compute the hashed password given an algorithm, the credentials and 511 * an optional salt. 512 * 513 * @param algorithm the algorithm to use 514 * @param password the credentials 515 * @param salt the optional salt 516 * @return the digested credentials 517 */ 518 private static byte[] digest( LdapSecurityConstants algorithm, byte[] password, byte[] salt ) 519 { 520 MessageDigest digest; 521 522 try 523 { 524 digest = MessageDigest.getInstance( algorithm.getName() ); 525 } 526 catch ( NoSuchAlgorithmException e1 ) 527 { 528 return null; 529 } 530 531 if ( salt != null ) 532 { 533 digest.update( password ); 534 digest.update( salt ); 535 return digest.digest(); 536 } 537 else 538 { 539 return digest.digest( password ); 540 } 541 } 542 543 544 private byte[] encryptPassword( byte[] credentials, EncryptionMethod encryptionMethod ) 545 { 546 byte[] salt = encryptionMethod.salt; 547 548 switch ( encryptionMethod.algorithm ) 549 { 550 case HASH_METHOD_SHA: 551 case HASH_METHOD_SSHA: 552 return digest( LdapSecurityConstants.HASH_METHOD_SHA, credentials, salt ); 553 554 case HASH_METHOD_SHA256: 555 return digest( LdapSecurityConstants.HASH_METHOD_SHA256, credentials, salt ); 556 557 case HASH_METHOD_MD5: 558 case HASH_METHOD_SMD5: 559 return digest( LdapSecurityConstants.HASH_METHOD_MD5, credentials, salt ); 560 561 case HASH_METHOD_CRYPT: 562 if ( salt == null ) 563 { 564 salt = new byte[2]; 565 SecureRandom sr = new SecureRandom(); 566 int i1 = sr.nextInt( 64 ); 567 int i2 = sr.nextInt( 64 ); 568 569 salt[0] = ( byte ) ( i1 < 12 ? ( i1 + '.' ) : i1 < 38 ? ( i1 + 'A' - 12 ) : ( i1 + 'a' - 38 ) ); 570 salt[1] = ( byte ) ( i2 < 12 ? ( i2 + '.' ) : i2 < 38 ? ( i2 + 'A' - 12 ) : ( i2 + 'a' - 38 ) ); 571 } 572 573 String saltWithCrypted = UnixCrypt.crypt( StringTools.utf8ToString( credentials ), StringTools 574 .utf8ToString( salt ) ); 575 String crypted = saltWithCrypted.substring( 2 ); 576 577 return StringTools.getBytesUtf8( crypted ); 578 579 default: 580 return credentials; 581 } 582 } 583 584 585 /** 586 * Local function which request the password from the backend 587 * @param principalDn the principal to lookup 588 * @return the credentials from the backend 589 * @throws Exception if there are problems accessing backend 590 */ 591 private byte[] lookupUserPassword( BindOperationContext opContext ) throws Exception 592 { 593 // ---- lookup the principal entry's userPassword attribute 594 ServerEntry userEntry; 595 596 try 597 { 598 /* 599 * NOTE: at this point the BindOperationContext does not has a 600 * null session since the user has not yet authenticated so we 601 * cannot use opContext.lookup() yet. This is a very special 602 * case where we cannot rely on the opContext to perform a new 603 * sub operation. 604 */ 605 LookupOperationContext lookupContext = new LookupOperationContext( getDirectoryService().getAdminSession(), 606 opContext.getDn() ); 607 lookupContext.setByPassed( USERLOOKUP_BYPASS ); 608 userEntry = getDirectoryService().getOperationManager().lookup( lookupContext ); 609 610 if ( userEntry == null ) 611 { 612 DN dn = opContext.getDn(); 613 String upDn = ( dn == null ? "" : dn.getName() ); 614 615 throw new LdapAuthenticationException( I18n.err( I18n.ERR_231, upDn ) ); 616 } 617 } 618 catch ( Exception cause ) 619 { 620 LOG.error( I18n.err( I18n.ERR_6, cause.getLocalizedMessage() ) ); 621 LdapAuthenticationException e = new LdapAuthenticationException( cause.getLocalizedMessage() ); 622 e.initCause( e ); 623 throw e; 624 } 625 626 Value<?> userPassword; 627 628 EntryAttribute userPasswordAttr = userEntry.get( SchemaConstants.USER_PASSWORD_AT ); 629 630 // ---- assert that credentials match 631 if ( userPasswordAttr == null ) 632 { 633 return StringTools.EMPTY_BYTES; 634 } 635 else 636 { 637 userPassword = userPasswordAttr.get(); 638 639 return userPassword.getBytes(); 640 } 641 } 642 643 644 /** 645 * Get the algorithm of a password, which is stored in the form "{XYZ}...". 646 * The method returns null, if the argument is not in this form. It returns 647 * XYZ, if XYZ is an algorithm known to the MessageDigest class of 648 * java.security. 649 * 650 * @param password a byte[] 651 * @return included message digest alorithm, if any 652 * @throws IllegalArgumentException if the algorithm cannot be identified 653 */ 654 protected String getAlgorithmForHashedPassword( byte[] password ) throws IllegalArgumentException 655 { 656 String result = null; 657 658 // Check if password arg is string or byte[] 659 String sPassword = StringTools.utf8ToString( password ); 660 int rightParen = sPassword.indexOf( '}' ); 661 662 if ( ( sPassword.length() > 2 ) && ( sPassword.charAt( 0 ) == '{' ) && ( rightParen > -1 ) ) 663 { 664 String algorithm = sPassword.substring( 1, rightParen ); 665 666 if ( LdapSecurityConstants.HASH_METHOD_CRYPT.getName().equalsIgnoreCase( algorithm ) ) 667 { 668 return algorithm; 669 } 670 671 try 672 { 673 MessageDigest.getInstance( algorithm ); 674 result = algorithm; 675 } 676 catch ( NoSuchAlgorithmException e ) 677 { 678 LOG.warn( "Unknown message digest algorithm in password: " + algorithm, e ); 679 } 680 } 681 682 return result; 683 } 684 685 686 /** 687 * Creates a digested password. For a given hash algorithm and a password 688 * value, the algorithm is applied to the password, and the result is Base64 689 * encoded. The method returns a String which looks like "{XYZ}bbbbbbb", 690 * whereas XYZ is the name of the algorithm, and bbbbbbb is the Base64 691 * encoded value of XYZ applied to the password. 692 * 693 * @param algorithm 694 * an algorithm which is supported by 695 * java.security.MessageDigest, e.g. SHA 696 * @param password 697 * password value, a byte[] 698 * 699 * @return a digested password, which looks like 700 * {SHA}LhkDrSoM6qr0fW6hzlfOJQW61tc= 701 * 702 * @throws IllegalArgumentException 703 * if password is neither a String nor a byte[], or algorithm is 704 * not known to java.security.MessageDigest class 705 */ 706 protected String createDigestedPassword( String algorithm, byte[] password ) throws IllegalArgumentException 707 { 708 // create message digest object 709 try 710 { 711 if ( LdapSecurityConstants.HASH_METHOD_CRYPT.getName().equalsIgnoreCase( algorithm ) ) 712 { 713 String saltWithCrypted = UnixCrypt.crypt( StringTools.utf8ToString( password ), "" ); 714 String crypted = saltWithCrypted.substring( 2 ); 715 return '{' + algorithm + '}' + Arrays.toString( StringTools.getBytesUtf8( crypted ) ); 716 } 717 else 718 { 719 MessageDigest digest = MessageDigest.getInstance( algorithm ); 720 721 // calculate hashed value of password 722 byte[] fingerPrint = digest.digest( password ); 723 char[] encoded = Base64.encode( fingerPrint ); 724 725 // create return result of form "{alg}bbbbbbb" 726 return '{' + algorithm + '}' + new String( encoded ); 727 } 728 } 729 catch ( NoSuchAlgorithmException nsae ) 730 { 731 LOG.error( I18n.err( I18n.ERR_7, algorithm ) ); 732 throw new IllegalArgumentException( nsae.getLocalizedMessage() ); 733 } 734 } 735 736 737 /** 738 * Remove the principal form the cache. This is used when the user changes 739 * his password. 740 */ 741 public void invalidateCache( DN bindDn ) 742 { 743 synchronized ( credentialCache ) 744 { 745 credentialCache.remove( bindDn.getNormName() ); 746 } 747 } 748 }