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 */ 020package org.apache.directory.server.kerberos.shared.replay; 021 022 023import java.io.Serializable; 024import java.time.Duration; 025 026import javax.security.auth.kerberos.KerberosPrincipal; 027 028import org.apache.directory.shared.kerberos.KerberosTime; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import com.github.benmanes.caffeine.cache.Cache; 033import com.github.benmanes.caffeine.cache.Caffeine; 034 035 036/** 037 * "The replay cache will store at least the server name, along with the client name, 038 * time, and microsecond fields from the recently-seen authenticators, and if a 039 * matching tuple is found, the KRB_AP_ERR_REPEAT error is returned." 040 * 041 * We will store the entries in Ehacache instance 042 * 043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 044 */ 045public class ReplayCacheImpl implements ReplayCache 046{ 047 048 private static final Logger LOG = LoggerFactory.getLogger( ReplayCacheImpl.class ); 049 050 /** Caffeine based storage to store the entries */ 051 Cache<String, Object> cache; 052 053 /** default clock skew */ 054 private static final long DEFAULT_CLOCK_SKEW = 5L * KerberosTime.MINUTE; 055 056 /** The clock skew */ 057 private long clockSkew = DEFAULT_CLOCK_SKEW; 058 059 /** 060 * A structure to hold an entry 061 */ 062 public static class ReplayCacheEntry implements Serializable 063 { 064 private static final long serialVersionUID = 1L; 065 066 /** The server principal */ 067 private KerberosPrincipal serverPrincipal; 068 069 /** The client principal */ 070 private KerberosPrincipal clientPrincipal; 071 072 /** The client time */ 073 private KerberosTime clientTime; 074 075 /** The client micro seconds */ 076 private int clientMicroSeconds; 077 078 079 /** 080 * Creates a new instance of ReplayCacheEntry. 081 * 082 * @param serverPrincipal 083 * @param clientPrincipal 084 * @param clientTime 085 * @param clientMicroSeconds 086 */ 087 public ReplayCacheEntry( KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, 088 KerberosTime clientTime, int clientMicroSeconds ) 089 { 090 this.serverPrincipal = serverPrincipal; 091 this.clientPrincipal = clientPrincipal; 092 this.clientTime = clientTime; 093 this.clientMicroSeconds = clientMicroSeconds; 094 } 095 096 097 /** 098 * Returns whether this {@link ReplayCacheEntry} is equal to another {@link ReplayCacheEntry}. 099 * {@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}