View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   * 
19   */
20  package org.apache.directory.server.kerberos.shared.replay;
21  
22  
23  import java.io.Serializable;
24  import java.time.Duration;
25  
26  import javax.security.auth.kerberos.KerberosPrincipal;
27  
28  import org.apache.directory.shared.kerberos.KerberosTime;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import com.github.benmanes.caffeine.cache.Cache;
33  import com.github.benmanes.caffeine.cache.Caffeine;
34  
35  
36  /**
37   * "The replay cache will store at least the server name, along with the client name,
38   * time, and microsecond fields from the recently-seen authenticators, and if a
39   * matching tuple is found, the KRB_AP_ERR_REPEAT error is returned."
40   * 
41   * We will store the entries in Ehacache instance
42   * 
43   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
44   */
45  public class ReplayCacheImpl implements ReplayCache
46  {
47  
48      private static final Logger LOG = LoggerFactory.getLogger( ReplayCacheImpl.class );
49  
50      /** Caffeine based storage to store the entries */
51      Cache<String, Object> cache;
52  
53      /** default clock skew */
54      private static final long DEFAULT_CLOCK_SKEW = 5L * KerberosTime.MINUTE;
55  
56      /** The clock skew */
57      private long clockSkew = DEFAULT_CLOCK_SKEW;
58  
59      /**
60       * A structure to hold an entry
61       */
62      public static class ReplayCacheEntry implements Serializable
63      {
64          private static final long serialVersionUID = 1L;
65  
66          /** The server principal */
67          private KerberosPrincipal serverPrincipal;
68  
69          /** The client principal */
70          private KerberosPrincipal clientPrincipal;
71  
72          /** The client time */
73          private KerberosTime clientTime;
74  
75          /** The client micro seconds */
76          private int clientMicroSeconds;
77  
78  
79          /**
80           * Creates a new instance of ReplayCacheEntry.
81           * 
82           * @param serverPrincipal
83           * @param clientPrincipal
84           * @param clientTime
85           * @param clientMicroSeconds
86           */
87          public ReplayCacheEntry( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
88              KerberosTime clientTime, int clientMicroSeconds )
89          {
90              this.serverPrincipal = serverPrincipal;
91              this.clientPrincipal = clientPrincipal;
92              this.clientTime = clientTime;
93              this.clientMicroSeconds = clientMicroSeconds;
94          }
95  
96  
97          /**
98           * Returns whether this {@link ReplayCacheEntry} is equal to another {@link ReplayCacheEntry}.
99           * {@link ReplayCacheEntry}'s are equal when the server name, client name, client time, and
100          * the client microseconds are equal.
101          *
102          * @param that
103          * @return true if the ReplayCacheEntry's are equal.
104          */
105         public boolean equals( ReplayCacheEntry that )
106         {
107             return serverPrincipal.equals( that.serverPrincipal ) && clientPrincipal.equals( that.clientPrincipal )
108                 && clientTime.equals( that.clientTime ) && clientMicroSeconds == that.clientMicroSeconds;
109         }
110 
111 
112         /**
113          * Returns whether this {@link ReplayCacheEntry} is older than a given time.
114          *
115          * @param clockSkew
116          * @return true if the {@link ReplayCacheEntry}'s client time is outside the clock skew time.
117          */
118         public boolean isOutsideClockSkew( long clockSkew )
119         {
120             return !clientTime.isInClockSkew( clockSkew );
121         }
122 
123 
124         /**
125          * @return create a key to be used while storing in the cache
126          */
127         private String createKey()
128         {
129             StringBuilder sb = new StringBuilder();
130             sb.append( ( clientPrincipal == null ) ? "null" : clientPrincipal.getName() );
131             sb.append( '#' );
132             sb.append( ( serverPrincipal == null ) ? "null" : serverPrincipal.getName() );
133             sb.append( '#' );
134             sb.append( ( clientTime == null ) ? "null" : clientTime.getDate() );
135             sb.append( '#' );
136             sb.append( clientMicroSeconds );
137 
138             return sb.toString();
139         }
140     }
141 
142 
143     /**
144      * Creates a new instance of InMemoryReplayCache. Sets the
145      * delay between each cleaning run to 5 seconds. Sets the
146      * clockSkew to the given value
147      * 
148      * @param clockSkew the allowed skew (milliseconds)
149      */
150     public ReplayCacheImpl( long clockSkew )
151     {
152         this.clockSkew = clockSkew;
153         this.cache = Caffeine.newBuilder().expireAfterWrite( Duration.ofMillis( clockSkew )).build();
154     }
155 
156 
157     /**
158      * Check if an entry is a replay or not.
159      */
160     public synchronized boolean isReplay( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
161         KerberosTime clientTime, int clientMicroSeconds )
162     {
163         ReplayCacheEntry entry = new ReplayCacheEntry( serverPrincipal, 
164             clientPrincipal, clientTime, clientMicroSeconds );
165         ReplayCacheEntry found = ( ReplayCacheEntry ) cache.getIfPresent( entry.createKey() );
166 
167         if ( found == null )
168         {
169             return false;
170         }
171 
172         entry = found;
173 
174         return serverPrincipal.equals( entry.serverPrincipal ) &&
175             clientTime.equals( entry.clientTime ) &&
176             ( clientMicroSeconds == entry.clientMicroSeconds );
177     }
178 
179 
180     /**
181      * Add a new entry into the cache. A thread will clean all the timed out
182      * entries.
183      */
184     public synchronized void save( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal,
185         KerberosTime clientTime, int clientMicroSeconds )
186     {
187         ReplayCacheEntry entry = new ReplayCacheEntry( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds );
188 
189         cache.put( entry.createKey(), entry );
190     }
191 
192 
193     /**
194      * {@inheritDoc}
195      */
196     public void clear()
197     {
198         LOG.debug( "removing all the elements from cache" );
199         cache.invalidateAll();
200     }
201 }