com.virgilsecurity.sdk.securechat.KeysRotator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pfs Show documentation
Show all versions of pfs Show documentation
Virgil is a stack of security libraries (ECIES with Crypto Agility wrapped in Virgil Cryptogram) and all the necessary infrastructure to enable seamless, end-to-end encryption for any application, platform or device.
Learn about Virgil Java/Android SDK https://virgilsecurity.com/api-docs/java-android/quickstart
The newest version!
/*
* Copyright (c) 2017, Virgil Security, Inc.
*
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of virgil nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.virgilsecurity.sdk.securechat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;
import com.virgilsecurity.sdk.client.model.CardModel;
import com.virgilsecurity.sdk.pfs.VirgilPFSClient;
import com.virgilsecurity.sdk.pfs.model.response.CardStatus;
import com.virgilsecurity.sdk.securechat.keystorage.KeyAttrs;
import com.virgilsecurity.sdk.securechat.model.ExhaustInfo;
import com.virgilsecurity.sdk.securechat.model.ExhaustInfo.ExhaustInfoEntry;
import com.virgilsecurity.sdk.securechat.model.ExhaustInfo.SessionExhaustInfo;
import com.virgilsecurity.sdk.securechat.model.SessionState;
import com.virgilsecurity.sdk.securechat.utils.ArrayUtils;
import com.virgilsecurity.sdk.utils.ConvertionUtils;
/**
* This class provides key rotation functionality.
*
* @author Andrii Iakovenko
*
*/
public class KeysRotator {
private static final Logger log = Logger.getLogger(KeysRotator.class.getName());
static final int SECONDS_IN_DAY = 24 * 60 * 60;
private CardModel identityCard;
private int exhaustedOneTimeCardTtl;
private int expiredSessionTtl;
private int longTermKeysTtl;
private int expiredLongTermCardTtl;
private EphemeralCardsReplenisher ephemeralCardsReplenisher;
private SessionStorageManager sessionStorageManager;
private KeyStorageManager keyStorageManager;
private ExhaustInfoManager exhaustInfoManager;
private VirgilPFSClient pfsClient;
private Semaphore semaphore = new Semaphore(1);
public KeysRotator(CardModel card, int exhaustedOneTimeCardTtl, int expiredSessionTtl, int longTermKeysTtl,
int expiredLongTermCardTtl, EphemeralCardsReplenisher replenisher,
SessionStorageManager sessionStorageManager, KeyStorageManager keyStorageManager,
ExhaustInfoManager exhaustInfoManager, VirgilPFSClient pfsClient) {
this.identityCard = card;
this.exhaustedOneTimeCardTtl = exhaustedOneTimeCardTtl;
this.expiredSessionTtl = expiredSessionTtl;
this.longTermKeysTtl = longTermKeysTtl;
this.expiredLongTermCardTtl = expiredLongTermCardTtl;
this.ephemeralCardsReplenisher = replenisher;
this.sessionStorageManager = sessionStorageManager;
this.keyStorageManager = keyStorageManager;
this.exhaustInfoManager = exhaustInfoManager;
this.pfsClient = pfsClient;
}
private void cleanup() {
log.fine("Cleanup started.");
Date now = new Date();
Entry> entry = processExhaustedStuff(now);
ExhaustInfo updatedExhaustInfo = entry.getKey();
List otCardsToCheck = entry.getValue();
List exhaustedCardsIds = this.pfsClient.validateOneTimeCards(this.identityCard.getId(), otCardsToCheck);
this.updateExhaustInfo(now, updatedExhaustInfo, exhaustedCardsIds);
}
private Date minusSeconds(Date now, int ttl) {
Calendar cal = Calendar.getInstance();
cal.setTime(now);
cal.add(Calendar.SECOND, -ttl);
return cal.getTime();
}
private Entry> processExhaustedStuff(Date now) {
log.fine("Processing exhausted stuff.");
ExhaustInfo exhaustInfo = exhaustInfoManager.getKeysExhaustInfo();
List> allSessionStates = sessionStorageManager.getAllSessionsStates();
Map> keys = keyStorageManager.getAllKeysAttrs();
List otKeys = keys.get(KeyStorageManager.OT_KEYS);
List ltKeys = keys.get(KeyStorageManager.LT_KEYS);
List sessionKeys = keys.get(KeyStorageManager.SESSION_KEYS);
exhaustInfo = removeExpiredLtKeys(now, ltKeys, exhaustInfo);
exhaustInfo = removeOrphanedOtcs(now, otKeys, exhaustInfo);
List newOtKeysIds = new ArrayList<>(exhaustInfo.getOtc().size());
for (ExhaustInfoEntry infoEntry : exhaustInfo.getOtc()) {
newOtKeysIds.add(infoEntry.getIdentifier());
}
List otKeysIdsToCheck = new ArrayList<>();
for (KeyAttrs key : otKeys) {
if (!newOtKeysIds.contains(key.getName())) {
otKeysIdsToCheck.add(key.getName());
}
}
exhaustInfo = removeExpiredSessions(now, allSessionStates, exhaustInfo);
removeOrhpanedSessionKeys(sessionKeys, allSessionStates);
return new AbstractMap.SimpleEntry>(exhaustInfo, otKeysIdsToCheck);
}
private ExhaustInfo removeExpiredLtKeys(Date now, List ltKeys, ExhaustInfo exhaustInfo) {
log.fine("Removing expired ltc.");
// Remove lt keys that have expired some time ago
List ltcIdsToRemove = new ArrayList<>();
Date exDate = minusSeconds(now, this.expiredLongTermCardTtl);
for (ExhaustInfoEntry key : exhaustInfo.getLtc()) {
if (exDate.after(key.getExhaustDate())) {
ltcIdsToRemove.add(key.getIdentifier());
}
}
keyStorageManager.removeLtPrivateKeys(ltcIdsToRemove);
// Updated lt keys info
List ltKeysUpdated = new LinkedList<>();
for (KeyAttrs key : ltKeys) {
if (!ltcIdsToRemove.contains(key.getName())) {
ltKeysUpdated.add(key);
}
}
// Update exhaust info:
// Clear removed keys
List newLtKeys = new LinkedList<>();
for (ExhaustInfoEntry infoEntry : exhaustInfo.getLtc()) {
if (!ltcIdsToRemove.contains(infoEntry.getIdentifier())) {
newLtKeys.add(infoEntry);
}
}
// Add lt keys that have expired recently
List newLtKeysIds = new LinkedList<>();
for (ExhaustInfoEntry key : newLtKeys) {
newLtKeysIds.add(key.getIdentifier());
}
exDate = minusSeconds(now, this.longTermKeysTtl);
for (KeyAttrs key : ltKeysUpdated) {
if (exDate.after(key.getCreationDate()) && !newLtKeysIds.contains(key.getName())) {
ExhaustInfoEntry infoEntry = new ExhaustInfoEntry(key.getName(), now);
newLtKeys.add(infoEntry);
}
}
// Update exhaust info
return new ExhaustInfo(exhaustInfo.getOtc(), newLtKeys, exhaustInfo.getSessions());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private ExhaustInfo removeExpiredSessions(Date now, List> allSessions,
ExhaustInfo exhaustInfo) {
log.fine("Removing expired sessions.");
List sessionsIds = new LinkedList<>();
for (Map.Entry entry : allSessions) {
sessionsIds.add(entry.getValue().getSessionId());
}
// Remove expired sessions
Date exDate = minusSeconds(now, this.expiredSessionTtl);
// List sessionInfosToRemove = new LinkedList<>();
List> sessionStatesToRemove = new LinkedList<>();
List sessionIdsToRemove = new LinkedList<>();
for (SessionExhaustInfo sessionInfo : exhaustInfo.getSessions()) {
if (exDate.after(sessionInfo.getExhaustDate())
&& ArrayUtils.isInList(sessionsIds, sessionInfo.getIdentifier())) {
// sessionInfosToRemove.add(sessionInfo);
sessionIdsToRemove.add(sessionInfo.getIdentifier());
sessionStatesToRemove
.add(new AbstractMap.SimpleEntry(sessionInfo.getCardId(), sessionInfo.getIdentifier()));
}
}
keyStorageManager.removeSessionKeys(sessionIdsToRemove);
sessionStorageManager.removeSessionsStates(sessionStatesToRemove);
// Update sessions info
List> allSessionsUpdated = new LinkedList<>();
for (Entry entry : allSessions) {
if (!ArrayUtils.isInList(sessionIdsToRemove, entry.getValue().getSessionId())) {
allSessionsUpdated.add(entry);
}
}
// Update exhaust info:
// Clear removed keys
List newSessions = new ArrayList<>();
List newSessionsIds = new LinkedList<>();
for (SessionExhaustInfo sessionInfo : exhaustInfo.getSessions()) {
if (!ArrayUtils.isInList(sessionIdsToRemove, sessionInfo.getIdentifier())) {
newSessions.add(sessionInfo);
newSessionsIds.add(sessionInfo.getIdentifier());
}
}
// Add recently expired keys
for (Entry entry : allSessionsUpdated) {
SessionState session = entry.getValue();
if (session.isExpired(now) && !ArrayUtils.isInList(newSessionsIds, session.getSessionId())) {
newSessions.add(new SessionExhaustInfo(session.getSessionId(), entry.getKey(), now));
}
}
// Updated exhaust info
return new ExhaustInfo(exhaustInfo.getOtc(), exhaustInfo.getLtc(), newSessions);
}
private void removeOrhpanedSessionKeys(List sessionKeys, List> allSessions) {
log.fine("Removing orphaned session keys.");
List allSessionsIds = new LinkedList<>();
for (Entry entry : allSessions) {
allSessionsIds.add(entry.getValue().getSessionId());
}
List orphanedSessionKeysIds = new LinkedList<>();
for (KeyAttrs keyAttrs : sessionKeys) {
byte[] sessionId = ConvertionUtils.base64ToBytes(keyAttrs.getName());
if (!ArrayUtils.isInList(allSessionsIds, sessionId)) {
orphanedSessionKeysIds.add(sessionId);
}
}
if (!orphanedSessionKeysIds.isEmpty()) {
log.warning(String.format("WARNING: orphaned session keys found: %d", orphanedSessionKeysIds.size()));
keyStorageManager.removeSessionKeys(orphanedSessionKeysIds);
}
}
private ExhaustInfo removeOrphanedOtcs(Date now, List otKeys, ExhaustInfo exhaustInfo) {
log.fine("Removing orphaned otcs.");
List otKeysIds = new LinkedList<>();
for (KeyAttrs key : otKeys) {
otKeysIds.add(key.getName());
}
// Remove ot keys that have been used some time ago
Date exDate = minusSeconds(now, this.exhaustedOneTimeCardTtl);
List otcIdsToRemove = new LinkedList<>();
for (ExhaustInfoEntry infoEntry : exhaustInfo.getOtc()) {
if (exDate.after(infoEntry.getExhaustDate()) && otKeysIds.contains(infoEntry.getIdentifier())) {
otcIdsToRemove.add(infoEntry.getIdentifier());
}
}
if (!otcIdsToRemove.isEmpty()) {
log.warning(String.format("WARNING: orphaned otcs found: %d", otcIdsToRemove.size()));
keyStorageManager.removeOtPrivateKeys(otcIdsToRemove);
}
// Updated exhaust info
List newOtKeys = new LinkedList<>();
for (ExhaustInfoEntry infoEntry : exhaustInfo.getOtc()) {
if (otcIdsToRemove.contains(infoEntry.getIdentifier())) {
newOtKeys.add(infoEntry);
}
}
return new ExhaustInfo(newOtKeys, exhaustInfo.getLtc(), exhaustInfo.getSessions());
}
/**
* Rotate keys.
*
* @param desiredNumberOfCards
* the desired number of cards which should be available.
*/
public void rotateKeys(int desiredNumberOfCards) {
log.fine("Started keys' rotation");
try {
semaphore.acquire();
log.fine("Get OTC status.");
CardStatus status = pfsClient.getCardStatus(this.identityCard.getId());
int numberOfMissingCards = Math.max(desiredNumberOfCards - status.getActive(), 0);
// Cleanup
log.fine("Cleanup");
cleanup();
log.fine("Adding new cards.");
boolean addLtCard = !keyStorageManager.hasRelevantLtKey(this.longTermKeysTtl);
if (numberOfMissingCards > 0 || addLtCard) {
ephemeralCardsReplenisher.addCards(addLtCard, numberOfMissingCards);
}
} catch (InterruptedException e) {
log.severe("Rotate keys interrupted");
} finally {
semaphore.release();
}
}
private void updateExhaustInfo(Date now, ExhaustInfo exhaustInfo, List exhaustedCardsIds) {
List newOtc = exhaustInfo.getOtc();
for (String exhaustedCardsId : exhaustedCardsIds) {
ExhaustInfoEntry infoEntry = new ExhaustInfoEntry(exhaustedCardsId, now);
newOtc.add(infoEntry);
}
ExhaustInfo newExhaustInfo = new ExhaustInfo(newOtc, exhaustInfo.getLtc(), exhaustInfo.getSessions());
this.exhaustInfoManager.saveKeysExhaustInfo(newExhaustInfo);
}
}