Author: kayyagari Date: Mon Nov 1 09:44:10 2010 New Revision: 1029585 URL: http://svn.apache.org/viewvc?rev=1029585&view=rev Log: o adding a new interceptor for hashing passwords (while performing add and modify operations) o associated test cases Added: directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/operations/add/PasswordHashingInterceptorTest.java directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/ directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/Md5PasswordHashingInterceptor.java directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/PasswordHashingInterceptor.java Added: directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/operations/add/PasswordHashingInterceptorTest.java URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/operations/add/PasswordHashingInterceptorTest.java?rev=1029585&view=auto ============================================================================== --- directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/operations/add/PasswordHashingInterceptorTest.java (added) +++ directory/apacheds/trunk/core-integ/src/test/java/org/apache/directory/server/core/operations/add/PasswordHashingInterceptorTest.java Mon Nov 1 09:44:10 2010 @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.server.core.operations.add; + + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.server.core.annotations.ApplyLdifs; +import org.apache.directory.server.core.annotations.CreateDS; +import org.apache.directory.server.core.authn.PasswordUtil; +import org.apache.directory.server.core.hash.Md5PasswordHashingInterceptor; +import org.apache.directory.server.core.integ.AbstractLdapTestUnit; +import org.apache.directory.server.core.integ.FrameworkRunner; +import org.apache.directory.server.core.integ.IntegrationUtils; +import org.apache.directory.shared.ldap.constants.LdapSecurityConstants; +import org.apache.directory.shared.ldap.constants.SchemaConstants; +import org.apache.directory.shared.ldap.entry.DefaultEntry; +import org.apache.directory.shared.ldap.entry.DefaultEntryAttribute; +import org.apache.directory.shared.ldap.entry.DefaultModification; +import org.apache.directory.shared.ldap.entry.Entry; +import org.apache.directory.shared.ldap.entry.EntryAttribute; +import org.apache.directory.shared.ldap.entry.Modification; +import org.apache.directory.shared.ldap.entry.ModificationOperation; +import org.apache.directory.shared.ldap.name.DN; +import org.apache.directory.shared.ldap.schema.AttributeType; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test case for checking PasswordHashingInterceptor. + * + * @author Apache Directory Project + */ +@RunWith(FrameworkRunner.class) +@CreateDS(name = "PasswordHashingInterceptorTest-DS", additionalInterceptors=Md5PasswordHashingInterceptor.class) +@ApplyLdifs( { + "dn: cn=test,ou=system", + "objectClass: person", + "cn: test", + "sn: sn_test", + "userPassword: secret" +}) +public class PasswordHashingInterceptorTest extends AbstractLdapTestUnit +{ + + @Test + public void testAddWithPlainPassword() throws Exception + { + LdapConnection connection = IntegrationUtils.getAdminConnection( service ); + + byte[] plainPwd = "secret".getBytes(); + DN dn = new DN( "cn=test,ou=system" ); + + Entry entry = connection.lookup( dn ); + EntryAttribute pwdAt = entry.get( SchemaConstants.USER_PASSWORD_AT ); + + assertFalse( Arrays.equals( plainPwd, pwdAt.getBytes() ) ); + assertTrue( PasswordUtil.compareCredentials( plainPwd, pwdAt.getBytes() ) ); + } + + + @Test + public void testModifyWithPlainPassword() throws Exception + { + LdapConnection connection = IntegrationUtils.getAdminConnection( service ); + + byte[] plainPwd = "newsecret".getBytes(); + DN dn = new DN( "cn=test,ou=system" ); + + AttributeType pwdAtType = service.getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.USER_PASSWORD_AT ); + + EntryAttribute pwdAt = new DefaultEntryAttribute( pwdAtType ); + pwdAt.add( plainPwd ); + + Modification mod = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, pwdAt ); + connection.modify( dn, mod ); + + Entry entry = connection.lookup( dn ); + pwdAt = entry.get( pwdAtType ); + + assertFalse( Arrays.equals( plainPwd, pwdAt.getBytes() ) ); + assertTrue( PasswordUtil.compareCredentials( plainPwd, pwdAt.getBytes() ) ); + } + + + @Test + public void testAddWithHashedPassword() throws Exception + { + LdapConnection connection = IntegrationUtils.getAdminConnection( service ); + + byte[] plainPwd = "secret".getBytes(); + byte[] hashedPwd = PasswordUtil.createStoragePassword( plainPwd, LdapSecurityConstants.HASH_METHOD_SSHA ); + + DN dn = new DN( "cn=testHash,ou=system" ); + Entry entry = new DefaultEntry( service.getSchemaManager(), dn ); + entry.add( "ObjectClass", "top", "person" ); + entry.add( "sn", "TEST" ); + entry.add( "cn", "testHash" ); + entry.add( SchemaConstants.USER_PASSWORD_AT, hashedPwd ); + + connection.add( entry ); + + entry = connection.lookup( dn ); + EntryAttribute pwdAt = entry.get( SchemaConstants.USER_PASSWORD_AT ); + assertTrue( Arrays.equals( hashedPwd, pwdAt.getBytes() ) ); + assertTrue( PasswordUtil.compareCredentials( plainPwd, pwdAt.getBytes() ) ); + } + + + @Test + public void testModifyWithHashedPassword() throws Exception + { + LdapConnection connection = IntegrationUtils.getAdminConnection( service ); + + byte[] plainPwd = "xyzsecret".getBytes(); + byte[] hashedPwd = PasswordUtil.createStoragePassword( plainPwd, LdapSecurityConstants.HASH_METHOD_SSHA256 ); + + DN dn = new DN( "cn=test,ou=system" ); + + AttributeType pwdAtType = service.getSchemaManager().lookupAttributeTypeRegistry( SchemaConstants.USER_PASSWORD_AT ); + + EntryAttribute pwdAt = new DefaultEntryAttribute( pwdAtType ); + pwdAt.add( hashedPwd ); + + Modification mod = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, pwdAt ); + connection.modify( dn, mod ); + + Entry entry = connection.lookup( dn ); + pwdAt = entry.get( pwdAtType ); + + assertTrue( Arrays.equals( hashedPwd, pwdAt.getBytes() ) ); + assertTrue( PasswordUtil.compareCredentials( plainPwd, pwdAt.getBytes() ) ); + } +} Added: directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/Md5PasswordHashingInterceptor.java URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/Md5PasswordHashingInterceptor.java?rev=1029585&view=auto ============================================================================== --- directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/Md5PasswordHashingInterceptor.java (added) +++ directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/Md5PasswordHashingInterceptor.java Mon Nov 1 09:44:10 2010 @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.server.core.hash; + +import org.apache.directory.shared.ldap.constants.LdapSecurityConstants; + +/** + * PasswordHashingInterceptor using MD5 hashing algorithm. + * + * @author Apache Directory Project + */ +public class Md5PasswordHashingInterceptor extends PasswordHashingInterceptor +{ + public Md5PasswordHashingInterceptor() + { + super( LdapSecurityConstants.HASH_METHOD_MD5 ); + } +} Added: directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/PasswordHashingInterceptor.java URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/PasswordHashingInterceptor.java?rev=1029585&view=auto ============================================================================== --- directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/PasswordHashingInterceptor.java (added) +++ directory/apacheds/trunk/core/src/main/java/org/apache/directory/server/core/hash/PasswordHashingInterceptor.java Mon Nov 1 09:44:10 2010 @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.directory.server.core.hash; + + +import java.util.List; + +import org.apache.directory.server.core.authn.PasswordUtil; +import org.apache.directory.server.core.interceptor.BaseInterceptor; +import org.apache.directory.server.core.interceptor.NextInterceptor; +import org.apache.directory.server.core.interceptor.context.AddOperationContext; +import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; +import org.apache.directory.shared.ldap.constants.LdapSecurityConstants; +import org.apache.directory.shared.ldap.constants.SchemaConstants; +import org.apache.directory.shared.ldap.entry.BinaryValue; +import org.apache.directory.shared.ldap.entry.Entry; +import org.apache.directory.shared.ldap.entry.EntryAttribute; +import org.apache.directory.shared.ldap.entry.Modification; +import org.apache.directory.shared.ldap.exception.LdapException; + + +/** + * An interceptor to hash plain text password according to the configured + * hashing algorithm. + * + * @author Apache Directory Project + */ +public class PasswordHashingInterceptor extends BaseInterceptor +{ + + /** the hashing algorithm to be used, if null then the password won't be changed */ + private LdapSecurityConstants algorithm; + + + /** + * Creates a new instance of PasswordHashingInterceptor which does not hash the passowrds. + */ + public PasswordHashingInterceptor() + { + this( null ); + } + + + /** + * + * Creates a new instance of PasswordHashingInterceptor which hashes the + * incoming non-hashed password using the given algorithm. + * If the password is found already hashed then it will skip hashing it. + * + * @param algorithm the name of the algorithm to be used + */ + public PasswordHashingInterceptor( LdapSecurityConstants algorithm ) + { + this.algorithm = algorithm; + } + + + @Override + public void add( NextInterceptor next, AddOperationContext addContext ) throws LdapException + { + if ( algorithm == null ) + { + next.add( addContext ); + return; + } + + Entry entry = addContext.getEntry(); + + EntryAttribute pwdAt = entry.get( SchemaConstants.USER_PASSWORD_AT ); + + includeHashedPassword( pwdAt ); + + next.add( addContext ); + } + + + @Override + public void modify( NextInterceptor next, ModifyOperationContext modifyContext ) throws LdapException + { + if ( algorithm == null ) + { + next.modify( modifyContext ); + return; + } + + List mods = modifyContext.getModItems(); + + for ( Modification mod : mods ) + { + String oid = mod.getAttribute().getAttributeType().getOid(); + + // check for modification on 'userPassword' AT + if ( SchemaConstants.USER_PASSWORD_AT_OID.equals( oid ) ) + { + includeHashedPassword( mod.getAttribute() ); + break; + } + } + + next.modify( modifyContext ); + } + + + /** + * hash the password if it was not already hashed + * + * @param pwdAt the password attribute + */ + private void includeHashedPassword( EntryAttribute pwdAt ) + { + if ( pwdAt == null ) + { + return; + } + + BinaryValue userPassword = ( BinaryValue ) pwdAt.get(); + + // check if the given password is already hashed + LdapSecurityConstants existingAlgo = PasswordUtil.findAlgorithm( userPassword.get() ); + + // if there exists NO algorithm, then hash the password + if ( existingAlgo == null ) + { + byte[] hashedPassword = PasswordUtil.createStoragePassword( userPassword.get(), algorithm ); + + pwdAt.clear(); + pwdAt.add( hashedPassword ); + } + } +}