package biz.karms.sinkit.ejb.impl; import biz.karms.sinkit.ejb.GSBService; import biz.karms.sinkit.ejb.ThreatType; import biz.karms.sinkit.ejb.cache.annotations.SinkitCache; import biz.karms.sinkit.ejb.cache.annotations.SinkitCacheName; import biz.karms.sinkit.ejb.cache.pojo.GSBRecord; import biz.karms.sinkit.ejb.gsb.GSBBlacklist; import biz.karms.sinkit.ejb.gsb.GSBClient; import biz.karms.sinkit.ejb.gsb.dto.FullHashLookupResponse; import biz.karms.sinkit.ejb.gsb.util.GSBCachePOJOFactory; import biz.karms.sinkit.ejb.gsb.util.GSBUtils; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.infinispan.client.hotrod.Flag; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.commons.util.concurrent.NotifyingFuture; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.inject.Inject; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Created by tom on 11/27/15. * * @author Tomas Kozel */ @Stateless public class GSBServiceEJB implements GSBService { private static final int PREFIX_LENGTH = 4; @Inject private Logger logger; @Inject @SinkitCache(SinkitCacheName.infinispan_gsb) private RemoteCache<String, GSBRecord> gsbCache; @EJB private GSBClient gsbClient; @PostConstruct public void setup() { if (gsbClient == null || gsbCache == null) { throw new IllegalStateException("GSB cache and client must not be null."); } } @Override public Set<ThreatType> lookup(final String ipOrFQDN) { if (ipOrFQDN == null) { throw new IllegalArgumentException("lookup: URL must not be null, cannot perform lookup."); } // canonization and prefix/suffix variants for lookup is done here final List<String> lookupVariants = GSBUtils.getLookupVariants(ipOrFQDN); // try to lookup for each variant until first match is found Set<String> gsbBlacklists; Set<ThreatType> coreBlacklists; for (String lookupVariant : lookupVariants) { gsbBlacklists = lookupSingleVariant(lookupVariant); if (gsbBlacklists != null && !gsbBlacklists.isEmpty()) { coreBlacklists = new HashSet<>(); for (String gsbBlacklist : gsbBlacklists) { coreBlacklists.add(GSBBlacklist.parseGSBName(gsbBlacklist).getThreatType()); } return coreBlacklists; } } return null; } private Set<String> lookupSingleVariant(final String lookupVariant) { final byte[] hash = DigestUtils.sha256(lookupVariant); final byte[] hashPrefix = ArrayUtils.subarray(hash, 0, PREFIX_LENGTH); final String fullHashString = Hex.encodeHexString(hash); final String hashStringPrefix = fullHashString.substring(0, PREFIX_LENGTH * 2); GSBRecord gsbRecord = gsbCache.withFlags(Flag.SKIP_CACHE_LOAD).get(hashStringPrefix); // if hash prefix is not in the cache then URL is not blacklisted for sure if (gsbRecord == null) { return null; } else { logger.log(Level.FINE, "lookup: hashPrefix " + hashStringPrefix + " was found in cache. It was made off: " + fullHashString + " which is lookupVariant: " + lookupVariant); } final HashMap<String, HashSet<String>> fullHashes; if (Calendar.getInstance().before(gsbRecord.getFullHashesExpireAt())) { logger.log(Level.FINE, "lookup: Full hashes for prefix " + hashStringPrefix + " are valid."); fullHashes = gsbRecord.getFullHashes(); } else { logger.log(Level.FINE, "lookup: Full hashes for prefix " + hashStringPrefix + " expired -> updating."); FullHashLookupResponse resposne = gsbClient.getFullHashes(hashPrefix); gsbRecord = GSBCachePOJOFactory.createFullHashes(resposne); gsbCache.put(hashStringPrefix, gsbRecord); fullHashes = gsbRecord.getFullHashes(); } // if fullHashes are empty then return null, i.e. no matched blacklists if (MapUtils.isEmpty(fullHashes)) { logger.log(Level.FINE, "lookup: Valid full hashes for prefix " + hashStringPrefix + " are empty."); return null; } else { final Set<String> matchedBlacklists = new HashSet<>(); for (String blacklist : fullHashes.keySet()) { final Set<String> fullHashesOnBlacklist = fullHashes.get(blacklist); if (fullHashesOnBlacklist != null && fullHashesOnBlacklist.contains(fullHashString)) { logger.log(Level.FINEST, "lookup: got hit for hash prefix " + hashStringPrefix + " and full hash " + fullHashString + ": " + blacklist); matchedBlacklists.add(blacklist); } } return matchedBlacklists; } } @Override public boolean putHashPrefix(final String hashPrefix) { if (hashPrefix == null) { logger.log(Level.SEVERE, "putHashPrefix: Got null hash prefix. Can't process this."); return false; } if (!gsbCache.containsKey(hashPrefix)) { final Calendar fullHashesExpireAt = Calendar.getInstance(); // set to past to enforce update full hashes when hit by lookup for the first time fullHashesExpireAt.add(Calendar.SECOND, -1); HashMap<String, HashSet<String>> fullHashes = new HashMap<>(); final GSBRecord gsbRecord = new GSBRecord(hashPrefix, fullHashesExpireAt, fullHashes); gsbCache.put(hashPrefix, gsbRecord); logger.log(Level.FINEST, "putHashPrefix: Hash prefix " + hashPrefix + " added into cache."); } else { //Should not have happened often logger.log(Level.FINE, "putHashPrefix: Hash prefix " + hashPrefix + " already contained in cache."); } return true; } @Override public boolean removeHashPrefix(final String hashPrefix) { if (hashPrefix == null) { logger.log(Level.SEVERE, "removeHashPrefix: Got null hash prefix. Can't process this."); return false; } gsbCache.remove(hashPrefix); return true; } // @Override // public boolean putFullHashes(String hashPrefix, int validSeconds, HashMap<String, HashSet<String>> fullHashes) { // if (hashPrefix == null || fullHashes == null) { // logger.log(Level.SEVERE, "putFullHashes: Hash prefix nor full hashes container cannot be null."); // return false; // } // // if (!gsbCache.containsKey(hashPrefix)) { // logger.log(Level.SEVERE, "putFullHashes: Hash prefix " + hashPrefix + " is not contained in cache. Must not proceed."); // return false; // } // Calendar fullHashesExpireAt = Calendar.getInstance(); // fullHashesExpireAt.add(Calendar.SECOND, validSeconds); // GSBRecord gsbRecord = new GSBRecord(hashPrefix, fullHashesExpireAt, fullHashes); // gsbCache.replace(hashPrefix, gsbRecord); // return true; // } @Override public boolean dropTheWholeCache(boolean async) { try { NotifyingFuture<Void> cleared = gsbCache.clearAsync(); if (!async) { cleared.get(); } return true; } catch (Exception ex) { ex.printStackTrace(); logger.log(Level.SEVERE, "dropTheWholeCache: Clearing cache went wrong."); return false; } } @Override public int getStats() { logger.log(Level.SEVERE, "Dangerous call to .size(), could OOM."); return gsbCache.size(); } }