You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by co...@apache.org on 2020/09/08 08:49:25 UTC

[directory-server] branch master updated: DIRSERVER-2327 Make the ReplayCache implementation configurable (#38)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/directory-server.git


The following commit(s) were added to refs/heads/master by this push:
     new 11a901f  DIRSERVER-2327 Make the ReplayCache implementation configurable (#38)
11a901f is described below

commit 11a901f880195b3faaf5f9a7c3352b8545c6bd38
Author: Josef Cacek <jo...@gmail.com>
AuthorDate: Tue Sep 8 10:49:16 2020 +0200

    DIRSERVER-2327 Make the ReplayCache implementation configurable (#38)
---
 .../directory/server/kerberos/KerberosConfig.java  |  20 +++
 .../directory/server/kerberos/kdc/KdcServer.java   |  39 ++++-
 .../kerberos/protocol/TGSReplayCacheTest.java      | 193 +++++++++++++++++++++
 .../server/annotations/CreateKdcServer.java        |   5 +
 .../server/factory/ServerAnnotationProcessor.java  |   1 +
 .../factory/CreateKdcServerAnnotationTest.java     |  35 +++-
 6 files changed, 287 insertions(+), 6 deletions(-)

diff --git a/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/KerberosConfig.java b/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/KerberosConfig.java
index 5237d9c..b3e2e9a 100644
--- a/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/KerberosConfig.java
+++ b/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/KerberosConfig.java
@@ -26,6 +26,8 @@ import java.util.Set;
 import javax.security.auth.kerberos.KerberosPrincipal;
 
 import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCacheImpl;
 import org.apache.directory.shared.kerberos.KerberosUtils;
 import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
 import org.apache.directory.shared.kerberos.codec.types.PrincipalNameType;
@@ -80,6 +82,9 @@ public class KerberosConfig
     public static final String[] DEFAULT_ENCRYPTION_TYPES = new String[]
         { "aes128-cts-hmac-sha1-96", "des-cbc-md5", "des3-cbc-sha1-kd" };
 
+    /** The default ReplayCache type */
+    public static final Class<? extends ReplayCache> DEFAULT_REPLAY_CACHE_TYPE = ReplayCacheImpl.class;
+
     /** The primary realm */
     private String primaryRealm = KerberosConfig.DEFAULT_REALM;
 
@@ -119,6 +124,9 @@ public class KerberosConfig
     /** Whether to verify the body checksum. */
     private boolean isBodyChecksumVerified = KerberosConfig.DEFAULT_VERIFY_BODY_CHECKSUM;
 
+    /** Replay cache implementing class. */
+    private Class<? extends ReplayCache> replayCacheType = KerberosConfig.DEFAULT_REPLAY_CACHE_TYPE;
+
     /** The encryption types. */
     private Set<EncryptionType> encryptionTypes;
 
@@ -419,6 +427,18 @@ public class KerberosConfig
     }
 
 
+    public Class<? extends ReplayCache> getReplayCacheType()
+    {
+        return replayCacheType;
+    }
+
+
+    public void setReplayCacheType( Class<? extends ReplayCache> replayCacheType )
+    {
+        this.replayCacheType = replayCacheType;
+    }
+
+
     public long getMinimumTicketLifetime()
     {
         return minimumTicketLifetime;
diff --git a/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/KdcServer.java b/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/KdcServer.java
index cd919da..c2a3696 100644
--- a/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/KdcServer.java
+++ b/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/KdcServer.java
@@ -29,7 +29,6 @@ import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer;
 import org.apache.directory.server.kerberos.protocol.KerberosProtocolHandler;
 import org.apache.directory.server.kerberos.protocol.codec.KerberosProtocolCodecFactory;
 import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
-import org.apache.directory.server.kerberos.shared.replay.ReplayCacheImpl;
 import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
 import org.apache.directory.server.protocol.shared.DirectoryBackedService;
 import org.apache.directory.server.protocol.shared.transport.TcpTransport;
@@ -89,6 +88,8 @@ public class KdcServer extends DirectoryBackedService
         this.config = config;
         super.setServiceName( SERVICE_NAME );
         super.setSearchBaseDn( config.getSearchBaseDn() );
+
+        initReplayCache();
     }
 
 
@@ -110,10 +111,6 @@ public class KdcServer extends DirectoryBackedService
 
         store = new DirectoryPrincipalStore( getDirectoryService(), new Dn( this.getSearchBaseDn() ) );
 
-        LOG.debug( "initializing the kerberos replay cache" );
-
-        replayCache = new ReplayCacheImpl( config.getAllowableClockSkew() );
-
         // Kerberos can use UDP or TCP
         for ( Transport transport : transports )
         {
@@ -255,4 +252,36 @@ public class KdcServer extends DirectoryBackedService
 
         return sb.toString();
     }
+
+    private void initReplayCache()
+    {
+        LOG.debug( "initializing the kerberos replay cache" );
+
+        Class<? extends ReplayCache> clazz = config.getReplayCacheType();
+        if ( clazz == null )
+        {
+            LOG.trace( "Kerberos replay cache is disabled" );
+            return;
+        }
+
+        LOG.debug( "Creating ReplayCache of type {}", clazz.getName() );
+        ReplayCache instance = null;
+        try
+        {
+            try
+            {
+                instance = clazz.getConstructor( Long.TYPE ).newInstance( config.getAllowableClockSkew() );
+            }
+            catch ( NoSuchMethodException e )
+            {
+                instance = clazz.newInstance();
+            }
+        }
+        catch ( Exception e )
+        {
+            LOG.error( "Failed to create the ReplayCache instance. No replay cache will be used!", e );
+        }
+        replayCache = instance;
+    }
+
 }
diff --git a/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/TGSReplayCacheTest.java b/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/TGSReplayCacheTest.java
new file mode 100644
index 0000000..2273068
--- /dev/null
+++ b/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/TGSReplayCacheTest.java
@@ -0,0 +1,193 @@
+/*
+ *  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.protocol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.text.ParseException;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.apache.directory.server.kerberos.KerberosConfig;
+import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.kerberos.protocol.AbstractAuthenticationServiceTest.KrbDummySession;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.apache.directory.shared.kerberos.codec.options.KdcOptions;
+import org.apache.directory.shared.kerberos.components.EncTicketPart;
+import org.apache.directory.shared.kerberos.components.EncryptionKey;
+import org.apache.directory.shared.kerberos.components.KdcReq;
+import org.apache.directory.shared.kerberos.components.KdcReqBody;
+import org.apache.directory.shared.kerberos.exceptions.ErrorType;
+import org.apache.directory.shared.kerberos.exceptions.KerberosException;
+import org.apache.directory.shared.kerberos.messages.KrbError;
+import org.apache.directory.shared.kerberos.messages.TgsRep;
+import org.apache.directory.shared.kerberos.messages.Ticket;
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Tests for configurable {@link ReplayCache}.
+ */
+public class TGSReplayCacheTest extends AbstractTicketGrantingServiceTest
+{
+    private KdcServer kdcServer;
+    private KerberosProtocolHandler handler;
+
+    /**
+     * Shutdown the Kerberos server
+     */
+    @After
+    public void shutDown()
+    {
+        kdcServer.stop();
+    }
+
+    /**
+     * Tests the replay cache is used by default.
+     */
+    @Test
+    public void testDefaultReplayCache() throws Exception
+    {
+        initKdcServer( new KerberosConfig() );
+
+        KdcReq message = createTgsRequest();
+
+        KrbDummySession session = new KrbDummySession();
+        handler.messageReceived( session, message );
+        assertEquals( "session.getMessage() instanceOf", TgsRep.class, session.getMessage().getClass() );
+
+        handler.messageReceived( session, message );
+        Object msg = session.getMessage();
+        assertEquals( "session.getMessage() instanceOf", KrbError.class, msg.getClass() );
+        KrbError error = (KrbError) msg;
+        assertEquals( "Replay not detected", ErrorType.KRB_AP_ERR_REPEAT, error.getErrorCode() );
+    }
+
+    /**
+     * Tests the replay cache is not used if the type is explicitly set to null.
+     */
+    @Test
+    public void testNullReplayCacheType() throws Exception
+    {
+        KerberosConfig config = new KerberosConfig();
+        config.setReplayCacheType( null );
+        initKdcServer( config );
+
+        KdcReq message = createTgsRequest();
+
+        KrbDummySession session = new KrbDummySession();
+        handler.messageReceived( session, message );
+        assertEquals( "session.getMessage() instanceOf", TgsRep.class, session.getMessage().getClass() );
+
+        handler.messageReceived( session, message );
+        assertEquals( "session.getMessage() instanceOf", TgsRep.class, session.getMessage().getClass() );
+    }
+
+    /**
+     * Tests that custom replay cache can be set.
+     */
+    @Test
+    public void testDummyReplayCacheType() throws Exception
+    {
+        KerberosConfig config = new KerberosConfig();
+        config.setReplayCacheType( DisabledReplayCache.class );
+        initKdcServer( config );
+
+        KdcReq message = createTgsRequest();
+
+        KrbDummySession session = new KrbDummySession();
+        handler.messageReceived( session, message );
+        assertEquals( "session.getMessage() instanceOf", TgsRep.class, session.getMessage().getClass() );
+
+        handler.messageReceived( session, message );
+        assertEquals( "session.getMessage() instanceOf", TgsRep.class, session.getMessage().getClass() );
+
+        assertTrue( "Incorrect cache implementation", DisabledReplayCache.replayTested );
+    }
+
+    private KdcReq createTgsRequest() throws KerberosException, ParseException, Exception
+    {
+        // Get the mutable ticket part.
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        EncTicketPart encTicketPart = getTicketArchetype( clientPrincipal );
+
+        // Seal the ticket for the server.
+        KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
+        String passPhrase = "randomKey";
+        EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
+        Ticket tgt = getTicket( encTicketPart, serverPrincipal, serverKey );
+
+        KdcReqBody kdcReqBody = new KdcReqBody();
+        kdcReqBody.setSName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
+        kdcReqBody.setRealm( "EXAMPLE.COM" );
+        kdcReqBody.setEType( kdcServer.getConfig().getEncryptionTypes() );
+        kdcReqBody.setNonce( random.nextInt() );
+        kdcReqBody.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        kdcReqBody.setTill( requestedEndTime );
+
+        KdcReq message = getKdcRequest( tgt, kdcReqBody );
+        return message;
+    }
+
+    /**
+     * Creates a new instance of {@link KdcServer}.
+     */
+    private void initKdcServer( KerberosConfig config )
+    {
+        config.setBodyChecksumVerified( false );
+        kdcServer = new KdcServer( config );
+        handler = new KerberosProtocolHandler( kdcServer, new MapPrincipalStoreImpl() );
+        lockBox = new CipherTextHandler();
+    }
+
+    public static class DisabledReplayCache implements ReplayCache
+    {
+
+        public static volatile boolean replayTested = false;
+
+        @Override
+        public boolean isReplay(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                int clientMicroSeconds)
+        {
+            replayTested = true;
+            return false;
+        }
+
+        @Override
+        public void save(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                int clientMicroSeconds)
+        {
+        }
+
+        @Override
+        public void clear()
+        {
+        }
+
+    }
+
+}
diff --git a/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateKdcServer.java b/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateKdcServer.java
index 86c6fb7..eb40d63 100644
--- a/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateKdcServer.java
+++ b/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateKdcServer.java
@@ -28,6 +28,8 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCacheImpl;
 
 
 /**
@@ -83,4 +85,7 @@ public @interface CreateKdcServer
     
     /** @return the DN of the search base for finding users and services */
     String searchBaseDn() default ServerDNConstants.USER_EXAMPLE_COM_DN;
+
+    /** @return the replay cache implementing class */
+    Class<? extends ReplayCache> replayCacheType() default ReplayCacheImpl.class;
 }
\ No newline at end of file
diff --git a/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java b/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java
index be226c6..70f736e 100644
--- a/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java
+++ b/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java
@@ -408,6 +408,7 @@ public final class ServerAnnotationProcessor
         kdcConfig.setPrimaryRealm( createKdcServer.primaryRealm() );
         kdcConfig.setMaximumTicketLifetime( createKdcServer.maxTicketLifetime() );
         kdcConfig.setMaximumRenewableLifetime( createKdcServer.maxRenewableLifetime() );
+        kdcConfig.setReplayCacheType( createKdcServer.replayCacheType() );
 
         KdcServer kdcServer = new KdcServer( kdcConfig );
 
diff --git a/server-annotations/src/test/java/org/apache/directory/server/factory/CreateKdcServerAnnotationTest.java b/server-annotations/src/test/java/org/apache/directory/server/factory/CreateKdcServerAnnotationTest.java
index fbc4610..dbb6b80 100644
--- a/server-annotations/src/test/java/org/apache/directory/server/factory/CreateKdcServerAnnotationTest.java
+++ b/server-annotations/src/test/java/org/apache/directory/server/factory/CreateKdcServerAnnotationTest.java
@@ -23,6 +23,8 @@ package org.apache.directory.server.factory;
 
 import static org.junit.Assert.assertEquals;
 
+import javax.security.auth.kerberos.KerberosPrincipal;
+
 import org.apache.directory.api.util.FileUtils;
 import org.apache.directory.server.annotations.CreateKdcServer;
 import org.apache.directory.server.annotations.CreateTransport;
@@ -31,6 +33,8 @@ import org.apache.directory.server.core.api.DirectoryService;
 import org.apache.directory.server.core.factory.DSAnnotationProcessor;
 import org.apache.directory.server.kerberos.KerberosConfig;
 import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.shared.kerberos.KerberosTime;
 import org.apache.mina.util.AvailablePortFinder;
 import org.junit.Test;
 
@@ -47,6 +51,7 @@ public class CreateKdcServerAnnotationTest
         kdcPrincipal = "krbtgt/apache.org@apache.org",
         maxTicketLifetime = 1000,
         maxRenewableLifetime = 2000,
+        replayCacheType = DisabledReplayCache.class,
         transports =
             {
                 @CreateTransport(protocol = "TCP"),
@@ -69,6 +74,7 @@ public class CreateKdcServerAnnotationTest
         assertEquals( "krbtgt/apache.org@apache.org", config.getServicePrincipal().getName() );
         assertEquals( 1000, config.getMaximumTicketLifetime() );
         assertEquals( 2000, config.getMaximumRenewableLifetime() );
+        assertEquals( DisabledReplayCache.class, config.getReplayCacheType() );
         
         server.stop();
         directoryService.shutdown();
@@ -101,11 +107,38 @@ public class CreateKdcServerAnnotationTest
         assertEquals( "krbtgt/apache.org@apache.org", config.getServicePrincipal().getName() );
         assertEquals( 1000, config.getMaximumTicketLifetime() );
         assertEquals( 2000, config.getMaximumRenewableLifetime() );
+        assertEquals( KerberosConfig.DEFAULT_REPLAY_CACHE_TYPE, config.getReplayCacheType() );
         
         server.stop();
         directoryService.shutdown();
 
         FileUtils.deleteDirectory( directoryService.getInstanceLayout().getInstanceDirectory() );
     }
-    
+
+    /**
+     * Empty {@link ReplayCache} implementation which doesn't cache.
+     */
+    public static class DisabledReplayCache implements ReplayCache
+    {
+
+        @Override
+        public boolean isReplay(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                int clientMicroSeconds)
+        {
+            return false;
+        }
+
+        @Override
+        public void save(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+                int clientMicroSeconds)
+        {
+        }
+
+        @Override
+        public void clear()
+        {
+        }
+
+    }
+
 }