You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by er...@apache.org on 2007/05/04 06:57:09 UTC
svn commit: r535094 - in
/directory/apacheds/branches/kerberos-encryption-types:
protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/
protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/
serv...
Author: erodriguez
Date: Thu May 3 21:57:08 2007
New Revision: 535094
URL: http://svn.apache.org/viewvc?view=rev&rev=535094
Log:
Added new "KeyDerivationService" interceptor:
o Additions and modifications to 'userPassword' result in Kerberos symmetric key derivation.
o If 'userPassword' is special keyword 'randomKey', random symmetric keys are generated.
o Added integration test demonstrating key derivation and random key generation when modifying 'userPassword' by LDAP protocol.
Added:
directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/
directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/
directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java (with props)
directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java (with props)
Added: directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java?view=auto&rev=535094
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java (added)
+++ directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java Thu May 3 21:57:08 2007
@@ -0,0 +1,365 @@
+/*
+ * 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.kerberos.shared.interceptors;
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+
+import org.apache.directory.server.core.authn.AuthenticationService;
+import org.apache.directory.server.core.authz.AuthorizationService;
+import org.apache.directory.server.core.authz.DefaultAuthorizationService;
+import org.apache.directory.server.core.collective.CollectiveAttributeService;
+import org.apache.directory.server.core.event.EventService;
+import org.apache.directory.server.core.exception.ExceptionService;
+import org.apache.directory.server.core.interceptor.BaseInterceptor;
+import org.apache.directory.server.core.interceptor.Interceptor;
+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.LookupOperationContext;
+import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
+import org.apache.directory.server.core.interceptor.context.OperationContext;
+import org.apache.directory.server.core.invocation.Invocation;
+import org.apache.directory.server.core.invocation.InvocationStack;
+import org.apache.directory.server.core.normalization.NormalizationService;
+import org.apache.directory.server.core.operational.OperationalAttributeService;
+import org.apache.directory.server.core.partition.PartitionNexusProxy;
+import org.apache.directory.server.core.referral.ReferralService;
+import org.apache.directory.server.core.schema.SchemaService;
+import org.apache.directory.server.core.subtree.SubentryService;
+import org.apache.directory.server.core.trigger.TriggerService;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
+import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
+import org.apache.directory.server.kerberos.shared.io.encoder.EncryptionKeyEncoder;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
+import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
+import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
+import org.apache.directory.shared.ldap.message.AttributeImpl;
+import org.apache.directory.shared.ldap.message.ModificationItemImpl;
+import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.directory.shared.ldap.util.AttributeUtils;
+import org.apache.directory.shared.ldap.util.StringTools;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An {@link Interceptor} that creates symmetric Kerberos keys for users. When a
+ * userPassword is added or modified, the userPassword and krb5PrincipalName are used
+ * to derive Kerberos keys. If the userPassword is the special keyword 'randomKey',
+ * a random key is generated and used as the Kerberos key.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class KeyDerivationService extends BaseInterceptor
+{
+ /** The log for this class. */
+ private static final Logger log = LoggerFactory.getLogger( KeyDerivationService.class );
+
+ /**
+ * Define the interceptors to bypass upon user lookup.
+ */
+ private static final Collection USERLOOKUP_BYPASS;
+ static
+ {
+ Set<String> c = new HashSet<String>();
+ c.add( NormalizationService.NAME );
+ c.add( AuthenticationService.NAME );
+ c.add( ReferralService.NAME );
+ c.add( AuthorizationService.NAME );
+ c.add( DefaultAuthorizationService.NAME );
+ c.add( ExceptionService.NAME );
+ c.add( OperationalAttributeService.NAME );
+ c.add( SchemaService.NAME );
+ c.add( SubentryService.NAME );
+ c.add( CollectiveAttributeService.NAME );
+ c.add( EventService.NAME );
+ c.add( TriggerService.NAME );
+ USERLOOKUP_BYPASS = Collections.unmodifiableCollection( c );
+ }
+
+
+ public void add( NextInterceptor next, OperationContext addContext ) throws NamingException
+ {
+ LdapDN normName = addContext.getDn();
+
+ Attributes entry = ( ( AddOperationContext ) addContext ).getEntry();
+
+ if ( entry.get( "userPassword" ) != null && entry.get( KerberosAttribute.PRINCIPAL ) != null )
+ {
+ log.debug( "Adding the entry " + AttributeUtils.toString( entry ) + " for DN = '" + normName.getUpName()
+ + "'" );
+
+ Object firstValue = entry.get( "userPassword" ).get();
+
+ if ( firstValue instanceof String )
+ {
+ log.debug( "Adding Attribute id : 'userPassword', Values : ['" + firstValue + "']" );
+ }
+ else if ( firstValue instanceof byte[] )
+ {
+ String string = StringTools.utf8ToString( ( byte[] ) firstValue );
+
+ StringBuffer sb = new StringBuffer();
+ sb.append( "'" + string + "' ( " );
+ sb.append( StringTools.dumpBytes( ( byte[] ) firstValue ).trim() );
+ log.debug( "Adding Attribute id : 'userPassword', Values : [ " + sb.toString() + " ) ]" );
+ firstValue = string;
+ }
+
+ String userPassword = ( String ) firstValue;
+ String principalName = ( String ) entry.get( KerberosAttribute.PRINCIPAL ).get();
+
+ log.debug( "Got principal " + principalName + " with userPassword " + userPassword );
+
+ EncryptionKey key = generateKey( principalName, userPassword, EncryptionType.DES_CBC_MD5 );
+
+ entry.put( KerberosAttribute.PRINCIPAL, principalName );
+ entry.put( KerberosAttribute.VERSION, Integer.toString( key.getKeyVersion() ) );
+ entry.put( KerberosAttribute.TYPE, Integer.toString( key.getKeyType().getOrdinal() ) );
+
+ Attribute keyAttribute = new AttributeImpl( KerberosAttribute.KEY );
+
+ try
+ {
+ keyAttribute.add( EncryptionKeyEncoder.encode( key ) );
+ }
+ catch ( IOException ioe )
+ {
+ ioe.printStackTrace();
+ }
+
+ keyAttribute.add( new byte[]
+ { ( byte ) 0x00 } );
+
+ entry.put( keyAttribute );
+
+ log.debug( "Adding modified entry " + AttributeUtils.toString( entry ) + " for DN = '"
+ + normName.getUpName() + "'" );
+
+ // Optionally discard userPassword.
+ }
+
+ next.add( addContext );
+ }
+
+
+ public void modify( NextInterceptor next, OperationContext opContext ) throws NamingException
+ {
+ LdapDN name = opContext.getDn();
+ ModifyOperationContext modContext = ( ModifyOperationContext ) opContext;
+
+ ModificationItemImpl[] mods = modContext.getModItems();
+
+ String userPassword = null;
+ String principalName = null;
+
+ for ( int ii = 0; ii < mods.length; ii++ )
+ {
+ Attribute attr = mods[ii].getAttribute();
+
+ if ( log.isDebugEnabled() )
+ {
+ String operation = null;
+
+ switch ( mods[ii].getModificationOp() )
+ {
+ case DirContext.ADD_ATTRIBUTE:
+ operation = "Adding";
+ break;
+ case DirContext.REMOVE_ATTRIBUTE:
+ operation = "Removing";
+ break;
+ case DirContext.REPLACE_ATTRIBUTE:
+ operation = "Replacing";
+ break;
+ }
+
+ log
+ .debug( operation + " for entry '" + name.getUpName() + "' the attribute "
+ + mods[ii].getAttribute() );
+ }
+
+ String attrId = attr.getID();
+
+ if ( attrId.equalsIgnoreCase( "userPassword" ) )
+ {
+ Object firstValue = attr.get();
+
+ if ( firstValue instanceof String )
+ {
+ log.debug( "Adding Attribute id : 'userPassword', Values : ['" + firstValue + "']" );
+ }
+ else if ( firstValue instanceof byte[] )
+ {
+ String string = StringTools.utf8ToString( ( byte[] ) firstValue );
+
+ StringBuffer sb = new StringBuffer();
+ sb.append( "'" + string + "' ( " );
+ sb.append( StringTools.dumpBytes( ( byte[] ) firstValue ).trim() );
+ log.debug( "Adding Attribute id : 'userPassword', Values : [ " + sb.toString() + " ) ]" );
+ firstValue = string;
+ }
+
+ userPassword = ( String ) firstValue;
+ log.debug( "Got userPassword " + userPassword + "." );
+ }
+
+ if ( attrId.equalsIgnoreCase( KerberosAttribute.PRINCIPAL ) )
+ {
+ principalName = ( String ) attr.get();
+ log.debug( "Got principal " + principalName + "." );
+ }
+ }
+
+ if ( userPassword != null && principalName != null )
+ {
+ log.debug( "Got principal " + principalName + " with userPassword " + userPassword );
+
+ List<ModificationItemImpl> newModsList = new ArrayList<ModificationItemImpl>();
+
+ EncryptionKey key = generateKey( principalName, userPassword, EncryptionType.DES_CBC_MD5 );
+
+ newModsList.add( new ModificationItemImpl( DirContext.REPLACE_ATTRIBUTE, new AttributeImpl(
+ KerberosAttribute.PRINCIPAL, principalName ) ) );
+ newModsList.add( new ModificationItemImpl( DirContext.REPLACE_ATTRIBUTE, new AttributeImpl(
+ KerberosAttribute.VERSION, Integer.toString( key.getKeyVersion() ) ) ) );
+ newModsList.add( new ModificationItemImpl( DirContext.REPLACE_ATTRIBUTE, new AttributeImpl(
+ KerberosAttribute.TYPE, Integer.toString( key.getKeyType().getOrdinal() ) ) ) );
+
+ Attribute keyAttribute = new AttributeImpl( KerberosAttribute.KEY );
+
+ try
+ {
+ keyAttribute.add( EncryptionKeyEncoder.encode( key ) );
+ }
+ catch ( IOException ioe )
+ {
+ ioe.printStackTrace();
+ }
+
+ keyAttribute.add( new byte[]
+ { ( byte ) 0x00 } );
+
+ newModsList.add( new ModificationItemImpl( DirContext.REPLACE_ATTRIBUTE, keyAttribute ) );
+
+ for ( int ii = 0; ii < mods.length; ii++ )
+ {
+ newModsList.add( mods[ii] );
+ }
+
+ mods = ( ModificationItemImpl[] ) newModsList.toArray( mods );
+
+ modContext.setModItems( mods );
+ }
+
+ next.modify( opContext );
+ }
+
+
+ /**
+ * Lookup the principal entry's krb5KeyVersionNumber attribute.
+ *
+ * @param principalDn
+ * @return The principal entry's krb5KeyVersionNumber attribute.
+ * @throws NamingException
+ */
+ protected int lookupKeyVersionNumber( LdapDN principalDn ) throws NamingException
+ {
+ Invocation invocation = InvocationStack.getInstance().peek();
+ PartitionNexusProxy proxy = invocation.getProxy();
+ Attributes userEntry;
+
+ try
+ {
+ LookupOperationContext lookupContext = new LookupOperationContext( new String[]
+ { KerberosAttribute.VERSION } );
+ lookupContext.setDn( principalDn );
+
+ userEntry = proxy.lookup( lookupContext, USERLOOKUP_BYPASS );
+
+ if ( userEntry == null )
+ {
+ throw new LdapAuthenticationException( "Failed to lookup user for authentication: " + principalDn );
+ }
+ }
+ catch ( Exception cause )
+ {
+ log.error( "Authentication error : " + cause.getMessage() );
+ LdapAuthenticationException e = new LdapAuthenticationException();
+ e.setRootCause( e );
+ throw e;
+ }
+
+ Integer keyVersionNumber;
+
+ Attribute keyVersionNumberAttr = userEntry.get( KerberosAttribute.VERSION );
+
+ if ( keyVersionNumberAttr == null )
+ {
+ keyVersionNumber = new Integer( 0 );
+ }
+ else
+ {
+ keyVersionNumber = new Integer( ( String ) keyVersionNumberAttr.get() );
+ }
+
+ return keyVersionNumber.intValue();
+ }
+
+
+ private EncryptionKey generateKey( String principalName, String userPassword, EncryptionType encryptionType )
+ {
+ if ( userPassword.equalsIgnoreCase( "randomKey" ) )
+ {
+ // Generate random key.
+ try
+ {
+ return RandomKeyFactory.getRandomKey( encryptionType );
+ }
+ catch ( KerberosException ke )
+ {
+ log.debug( ke.getMessage(), ke );
+ return null;
+ }
+ }
+ else
+ {
+ // Derive key based on password and principal name.
+ Map<EncryptionType, EncryptionKey> map = KerberosKeyFactory.getKerberosKeys( principalName, userPassword );
+
+ return map.get( encryptionType );
+ }
+ }
+}
Propchange: directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/KeyDerivationService.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java?view=auto&rev=535094
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java (added)
+++ directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java Thu May 3 21:57:08 2007
@@ -0,0 +1,439 @@
+/*
+ * 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;
+
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import javax.crypto.spec.DESKeySpec;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+import org.apache.directory.server.core.configuration.InterceptorConfiguration;
+import org.apache.directory.server.core.configuration.MutableInterceptorConfiguration;
+import org.apache.directory.server.core.configuration.MutablePartitionConfiguration;
+import org.apache.directory.server.core.configuration.PartitionConfiguration;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
+import org.apache.directory.server.kerberos.shared.interceptors.KeyDerivationService;
+import org.apache.directory.server.kerberos.shared.io.decoder.EncryptionKeyDecoder;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
+import org.apache.directory.server.kerberos.shared.store.KerberosAttribute;
+import org.apache.directory.server.unit.AbstractServerTest;
+import org.apache.directory.shared.ldap.message.AttributeImpl;
+import org.apache.directory.shared.ldap.message.AttributesImpl;
+import org.apache.directory.shared.ldap.message.ModificationItemImpl;
+import org.apache.mina.util.AvailablePortFinder;
+
+
+/**
+ * An {@link AbstractServerTest} testing the (@link {@link KeyDerivationService}'s
+ * ability to derive Kerberos symmetric keys based on userPassword and principal
+ * name and to generate random keys when the special keyword "randomKey" is used.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class KeyDerivationServiceTest extends AbstractServerTest
+{
+ private static final String RDN = "uid=hnelson,ou=users,dc=example,dc=com";
+
+ private DirContext ctx = null;
+
+
+ /**
+ * Set up a partition for EXAMPLE.COM, add the Key Derivation interceptor, enable
+ * the krb5kdc schema, and add a user principal to test authentication with.
+ */
+ public void setUp() throws Exception
+ {
+ configuration.setAllowAnonymousAccess( false );
+
+ Attributes attrs;
+ Set<PartitionConfiguration> pcfgs = new HashSet<PartitionConfiguration>();
+
+ MutablePartitionConfiguration pcfg;
+
+ // Add partition 'example'
+ pcfg = new MutablePartitionConfiguration();
+ pcfg.setName( "example" );
+ pcfg.setSuffix( "dc=example,dc=com" );
+
+ Set<Object> indexedAttrs = new HashSet<Object>();
+ indexedAttrs.add( "ou" );
+ indexedAttrs.add( "dc" );
+ indexedAttrs.add( "objectClass" );
+ pcfg.setIndexedAttributes( indexedAttrs );
+
+ attrs = new AttributesImpl( true );
+ Attribute attr = new AttributeImpl( "objectClass" );
+ attr.add( "top" );
+ attr.add( "domain" );
+ attrs.put( attr );
+ attr = new AttributeImpl( "dc" );
+ attr.add( "example" );
+ attrs.put( attr );
+ pcfg.setContextEntry( attrs );
+
+ pcfgs.add( pcfg );
+ configuration.setPartitionConfigurations( pcfgs );
+
+ MutableInterceptorConfiguration interceptorCfg = new MutableInterceptorConfiguration();
+ List<InterceptorConfiguration> list = configuration.getInterceptorConfigurations();
+
+ interceptorCfg.setName( "keyDerivationService" );
+ interceptorCfg.setInterceptor( new KeyDerivationService() );
+ list.add( interceptorCfg );
+ configuration.setInterceptorConfigurations( list );
+
+ doDelete( configuration.getWorkingDirectory() );
+ port = AvailablePortFinder.getNextAvailable( 1024 );
+ configuration.setLdapPort( port );
+ configuration.setShutdownHookEnabled( false );
+ setContexts( "uid=admin,ou=system", "secret" );
+
+ // -------------------------------------------------------------------
+ // Enable the krb5kdc schema
+ // -------------------------------------------------------------------
+
+ // check if krb5kdc is disabled
+ Attributes krb5kdcAttrs = schemaRoot.getAttributes( "cn=Krb5kdc" );
+ boolean isKrb5KdcDisabled = false;
+ if ( krb5kdcAttrs.get( "m-disabled" ) != null )
+ {
+ isKrb5KdcDisabled = ( ( String ) krb5kdcAttrs.get( "m-disabled" ).get() ).equalsIgnoreCase( "TRUE" );
+ }
+
+ // if krb5kdc is disabled then enable it
+ if ( isKrb5KdcDisabled )
+ {
+ Attribute disabled = new AttributeImpl( "m-disabled" );
+ ModificationItemImpl[] mods = new ModificationItemImpl[]
+ { new ModificationItemImpl( DirContext.REMOVE_ATTRIBUTE, disabled ) };
+ schemaRoot.modifyAttributes( "cn=Krb5kdc", mods );
+ }
+
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ env.put( "java.naming.provider.url", "ldap://localhost:" + port + "/dc=example,dc=com" );
+ env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ env.put( "java.naming.security.credentials", "secret" );
+ env.put( "java.naming.security.authentication", "simple" );
+ ctx = new InitialDirContext( env );
+
+ attrs = getOrgUnitAttributes( "users" );
+ DirContext users = ctx.createSubcontext( "ou=users", attrs );
+
+ attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "secret", "hnelson@EXAMPLE.COM" );
+ users.createSubcontext( "uid=hnelson", attrs );
+ }
+
+
+ /**
+ * Tests that the addition of an entry caused keys to be derived and added.
+ *
+ * @throws NamingException
+ * @throws IOException
+ */
+ public void testAddDerivedKeys() throws NamingException, IOException
+ {
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+ env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
+
+ env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+ env.put( Context.SECURITY_PRINCIPAL, "uid=hnelson,ou=users,dc=example,dc=com" );
+ env.put( Context.SECURITY_CREDENTIALS, "secret" );
+ env.put( "java.naming.ldap.attributes.binary", "krb5key" );
+
+ DirContext ctx = new InitialDirContext( env );
+
+ String[] attrIDs =
+ { "uid", "userPassword", "krb5Key" };
+
+ Attributes attributes = ctx.getAttributes( RDN, attrIDs );
+
+ String uid = null;
+
+ if ( attributes.get( "uid" ) != null )
+ {
+ uid = ( String ) attributes.get( "uid" ).get();
+ }
+
+ assertEquals( uid, "hnelson" );
+
+ byte[] userPassword = null;
+
+ if ( attributes.get( "userPassword" ) != null )
+ {
+ userPassword = ( byte[] ) attributes.get( "userPassword" ).get();
+ }
+
+ assertEquals( "Number of keys", 2, attributes.get( "krb5key" ).size() );
+
+ byte[] testPasswordBytes =
+ { ( byte ) 0x73, ( byte ) 0x65, ( byte ) 0x63, ( byte ) 0x72, ( byte ) 0x65, ( byte ) 0x74 };
+ assertTrue( Arrays.equals( userPassword, testPasswordBytes ) );
+
+ byte[] krb5key = ( byte[] ) attributes.get( "krb5key" ).get();
+
+ byte[] testKeyBytes =
+ { ( byte ) 0xF4, ( byte ) 0xA7, ( byte ) 0x13, ( byte ) 0x64, ( byte ) 0x8A, ( byte ) 0x61, ( byte ) 0xCE,
+ ( byte ) 0x5B };
+
+ byte[] encodedKey = new byte[]
+ { ( byte ) 0x30, ( byte ) 0x11, ( byte ) 0xA0, ( byte ) 0x03, ( byte ) 0x02, ( byte ) 0x01, ( byte ) 0x03,
+ ( byte ) 0xA1, ( byte ) 0x0A, ( byte ) 0x04, ( byte ) 0x08, ( byte ) 0xF4, ( byte ) 0xA7,
+ ( byte ) 0x13, ( byte ) 0x64, ( byte ) 0x8A, ( byte ) 0x61, ( byte ) 0xCE, ( byte ) 0x5B };
+
+ assertTrue( Arrays.equals( krb5key, encodedKey ) );
+
+ EncryptionKey encryptionKey = EncryptionKeyDecoder.decode( krb5key );
+
+ assertTrue( Arrays.equals( encryptionKey.getKeyValue(), testKeyBytes ) );
+ assertEquals( EncryptionType.DES_CBC_MD5, encryptionKey.getKeyType() );
+ }
+
+
+ /**
+ * Tests that the modification on an entry caused keys to be derived and modified.
+ *
+ * @throws NamingException
+ * @throws IOException
+ */
+ public void testModifyDerivedKeys() throws NamingException, IOException
+ {
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+ env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
+
+ env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+ env.put( Context.SECURITY_PRINCIPAL, "uid=hnelson,ou=users,dc=example,dc=com" );
+ env.put( Context.SECURITY_CREDENTIALS, "secret" );
+ env.put( "java.naming.ldap.attributes.binary", "krb5key" );
+
+ DirContext ctx = new InitialDirContext( env );
+
+ String newPrincipalName = "hnelson@EXAMPLE.COM";
+ String newUserPassword = "secretsecret";
+
+ // Modify password.
+ Attributes attributes = new AttributesImpl( true );
+ Attribute attr = new AttributeImpl( "userPassword", newUserPassword );
+ attributes.put( attr );
+ attr = new AttributeImpl( KerberosAttribute.PRINCIPAL, newPrincipalName );
+ attributes.put( attr );
+
+ DirContext person = ( DirContext ) ctx.lookup( RDN );
+ person.modifyAttributes( "", DirContext.REPLACE_ATTRIBUTE, attributes );
+
+ // Read again from directory.
+ person = ( DirContext ) ctx.lookup( RDN );
+ attributes = person.getAttributes( "" );
+
+ byte[] userPassword = null;
+
+ if ( attributes.get( "userPassword" ) != null )
+ {
+ userPassword = ( byte[] ) attributes.get( "userPassword" ).get();
+ }
+
+ assertEquals( "Number of keys", 2, attributes.get( "krb5key" ).size() );
+
+ byte[] testBytes =
+ { 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74 };
+ assertTrue( Arrays.equals( userPassword, testBytes ) );
+
+ byte[] krb5key = ( byte[] ) attributes.get( "krb5key" ).get();
+
+ byte[] testKeyBytes =
+ { ( byte ) 0x16, ( byte ) 0x4A, ( byte ) 0x6D, ( byte ) 0x89, ( byte ) 0x5D, ( byte ) 0x76, ( byte ) 0x0E,
+ ( byte ) 0x23 };
+
+ EncryptionKey encryptionKey = EncryptionKeyDecoder.decode( krb5key );
+
+ assertTrue( Arrays.equals( encryptionKey.getKeyValue(), testKeyBytes ) );
+ assertEquals( EncryptionType.DES_CBC_MD5, encryptionKey.getKeyType() );
+ }
+
+
+ /**
+ * Tests that the addition of an entry caused random keys to be derived and added.
+ *
+ * @throws NamingException
+ * @throws IOException
+ * @throws InvalidKeyException
+ */
+ public void testAddRandomKeys() throws NamingException, IOException, InvalidKeyException
+ {
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ env.put( "java.naming.provider.url", "ldap://localhost:" + port + "/ou=users,dc=example,dc=com" );
+ env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ env.put( "java.naming.security.credentials", "secret" );
+ env.put( "java.naming.security.authentication", "simple" );
+ env.put( "java.naming.ldap.attributes.binary", "krb5key" );
+ ctx = new InitialDirContext( env );
+
+ Attributes attrs = getPersonAttributes( "Quist", "Thomas Quist", "tquist", "randomKey", "tquist@EXAMPLE.COM" );
+ ctx.createSubcontext( "uid=tquist", attrs );
+
+ attrs = getPersonAttributes( "Fryer", "John Fryer", "jfryer", "randomKey", "jfryer@EXAMPLE.COM" );
+ ctx.createSubcontext( "uid=jfryer", attrs );
+
+ String[] attrIDs =
+ { "uid", "userPassword", "krb5Key" };
+
+ Attributes tquistAttrs = ctx.getAttributes( "uid=tquist", attrIDs );
+ Attributes jfryerAttrs = ctx.getAttributes( "uid=jfryer", attrIDs );
+
+ String uid = null;
+ byte[] userPassword = null;
+
+ if ( tquistAttrs.get( "uid" ) != null )
+ {
+ uid = ( String ) tquistAttrs.get( "uid" ).get();
+ }
+
+ assertEquals( "tquist", uid );
+
+ if ( tquistAttrs.get( "userPassword" ) != null )
+ {
+ userPassword = ( byte[] ) tquistAttrs.get( "userPassword" ).get();
+ }
+
+ // Bytes for "randomKey."
+ byte[] testPasswordBytes =
+ { ( byte ) 0x72, ( byte ) 0x61, ( byte ) 0x6E, ( byte ) 0x64, ( byte ) 0x6F, ( byte ) 0x6D, ( byte ) 0x4B,
+ ( byte ) 0x65, ( byte ) 0x79 };
+ assertTrue( Arrays.equals( testPasswordBytes, userPassword ) );
+
+ if ( jfryerAttrs.get( "uid" ) != null )
+ {
+ uid = ( String ) jfryerAttrs.get( "uid" ).get();
+ }
+
+ assertEquals( "jfryer", uid );
+
+ if ( jfryerAttrs.get( "userPassword" ) != null )
+ {
+ userPassword = ( byte[] ) jfryerAttrs.get( "userPassword" ).get();
+ }
+
+ assertTrue( Arrays.equals( testPasswordBytes, userPassword ) );
+
+ byte[] testKeyBytes =
+ { ( byte ) 0xF4, ( byte ) 0xA7, ( byte ) 0x13, ( byte ) 0x64, ( byte ) 0x8A, ( byte ) 0x61, ( byte ) 0xCE,
+ ( byte ) 0x5B };
+
+ byte[] tquistKey = ( byte[] ) tquistAttrs.get( "krb5key" ).get();
+ byte[] jfryerKey = ( byte[] ) jfryerAttrs.get( "krb5key" ).get();
+
+ EncryptionKey encryptionKey = EncryptionKeyDecoder.decode( tquistKey );
+ tquistKey = encryptionKey.getKeyValue();
+
+ assertEquals( EncryptionType.DES_CBC_MD5, encryptionKey.getKeyType() );
+
+ encryptionKey = EncryptionKeyDecoder.decode( jfryerKey );
+ jfryerKey = encryptionKey.getKeyValue();
+
+ assertEquals( EncryptionType.DES_CBC_MD5, encryptionKey.getKeyType() );
+
+ assertEquals( "Key length", 8, tquistKey.length );
+ assertEquals( "Key length", 8, jfryerKey.length );
+
+ assertFalse( Arrays.equals( testKeyBytes, tquistKey ) );
+ assertFalse( Arrays.equals( testKeyBytes, jfryerKey ) );
+ assertFalse( Arrays.equals( jfryerKey, tquistKey ) );
+
+ byte[] tquistDerivedKey =
+ { ( byte ) 0xFD, ( byte ) 0x7F, ( byte ) 0x6B, ( byte ) 0x83, ( byte ) 0xA4, ( byte ) 0x76, ( byte ) 0xC1,
+ ( byte ) 0xEA };
+ byte[] jfryerDerivedKey =
+ { ( byte ) 0xA4, ( byte ) 0x10, ( byte ) 0x3B, ( byte ) 0x49, ( byte ) 0xCE, ( byte ) 0x0B, ( byte ) 0xB5,
+ ( byte ) 0x07 };
+
+ assertFalse( Arrays.equals( tquistDerivedKey, tquistKey ) );
+ assertFalse( Arrays.equals( jfryerDerivedKey, jfryerKey ) );
+
+ assertTrue( DESKeySpec.isParityAdjusted( tquistKey, 0 ) );
+ assertTrue( DESKeySpec.isParityAdjusted( jfryerKey, 0 ) );
+ }
+
+
+ /**
+ * Tear down.
+ */
+ public void tearDown() throws Exception
+ {
+ ctx.close();
+ ctx = null;
+ super.tearDown();
+ }
+
+
+ /**
+ * Convenience method for creating a person.
+ */
+ protected Attributes getPersonAttributes( String sn, String cn, String uid, String userPassword, String principal )
+ {
+ Attributes attrs = new AttributesImpl();
+ Attribute ocls = new AttributeImpl( "objectClass" );
+ ocls.add( "top" );
+ ocls.add( "person" ); // sn $ cn
+ ocls.add( "inetOrgPerson" ); // uid
+ ocls.add( "krb5principal" );
+ ocls.add( "krb5kdcentry" );
+ attrs.put( ocls );
+ attrs.put( "cn", cn );
+ attrs.put( "sn", sn );
+ attrs.put( "uid", uid );
+ attrs.put( "userPassword", userPassword );
+ attrs.put( KerberosAttribute.PRINCIPAL, principal );
+ attrs.put( KerberosAttribute.VERSION, "0" );
+
+ return attrs;
+ }
+
+
+ /**
+ * Convenience method for creating an organizational unit.
+ */
+ protected Attributes getOrgUnitAttributes( String ou )
+ {
+ Attributes attrs = new AttributesImpl();
+ Attribute ocls = new AttributeImpl( "objectClass" );
+ ocls.add( "top" );
+ ocls.add( "organizationalUnit" );
+ attrs.put( ocls );
+ attrs.put( "ou", ou );
+
+ return attrs;
+ }
+}
Propchange: directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/KeyDerivationServiceTest.java
------------------------------------------------------------------------------
svn:eol-style = native