InMemoryKeyStore.java

/*
 * @copyright defined in LICENSE.txt
 */

package hera.keystore;

import static org.slf4j.LoggerFactory.getLogger;

import hera.annotation.ApiAudience;
import hera.annotation.ApiStability;
import hera.api.model.Authentication;
import hera.api.model.EncryptedPrivateKey;
import hera.api.model.Identity;
import hera.exception.InvalidAuthenticationException;
import hera.exception.KeyStoreException;
import hera.key.AergoKey;
import hera.key.Signer;
import hera.util.Sha256Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;

@ApiAudience.Public
@ApiStability.Unstable
public class InMemoryKeyStore implements KeyStore {

  protected final transient Logger logger = getLogger(getClass());

  protected final Set<Identity> storedIdentities = new HashSet<>();
  protected final Map<String, EncryptedPrivateKey> hashedAuth2Encrypted = new HashMap<>();

  @Override
  public void save(final Authentication authentication, final AergoKey key) {
    try {
      logger.debug("Save with authentication: {}, key: {}", authentication, key);
      final String digested = digest(authentication);

      synchronized (this) {
        if (this.storedIdentities.contains(authentication.getIdentity())) {
          throw new InvalidAuthenticationException("Identity already exists");
        }
        if (this.hashedAuth2Encrypted.containsKey(digested)) {
          throw new InvalidAuthenticationException("Invalid authentication");
        }

        final EncryptedPrivateKey encryptedKey = key.export(authentication.getPassword());
        this.storedIdentities.add(authentication.getIdentity());
        this.hashedAuth2Encrypted.put(digested, encryptedKey);
      }
    } catch (InvalidAuthenticationException e) {
      throw e;
    } catch (final Exception e) {
      throw new KeyStoreException(e);
    }
  }

  @Override
  public Signer load(final Authentication authentication) {
    try {
      logger.debug("Unlock with authentication: {}", authentication);
      final String digested = digest(authentication);

      synchronized (this) {
        if (false == this.hashedAuth2Encrypted.containsKey(digested)) {
          throw new InvalidAuthenticationException("Invalid authentication");
        }

        final EncryptedPrivateKey encrypted = hashedAuth2Encrypted.get(digested);
        final AergoKey decrypted = AergoKey.of(encrypted, authentication.getPassword());
        return decrypted;
      }
    } catch (InvalidAuthenticationException e) {
      throw e;
    } catch (Exception e) {
      throw new KeyStoreException(e);
    }
  }

  @Override
  public void remove(final Authentication authentication) {
    try {
      logger.debug("Remove aergo key with authentication: {}", authentication);
      final String digested = digest(authentication);

      synchronized (this) {
        if (false == this.hashedAuth2Encrypted.containsKey(digested)) {
          throw new InvalidAuthenticationException("Invalid authentication");
        }

        this.storedIdentities.remove(authentication.getIdentity());
        this.hashedAuth2Encrypted.remove(digested);
      }
    } catch (InvalidAuthenticationException e) {
      throw e;
    } catch (final Exception e) {
      throw new KeyStoreException(e);
    }
  }

  @Override
  public EncryptedPrivateKey export(final Authentication authentication, final String password) {
    try {
      logger.debug("Export key with authentication: {}", authentication);
      final String digested = digest(authentication);

      synchronized (this) {
        if (false == hashedAuth2Encrypted.containsKey(digested)) {
          throw new InvalidAuthenticationException("Invalid authentication");
        }

        final EncryptedPrivateKey encrypted = hashedAuth2Encrypted.get(digested);
        final AergoKey decrypted = AergoKey.of(encrypted, authentication.getPassword());
        return decrypted.export(password);
      }
    } catch (InvalidAuthenticationException e) {
      throw e;
    } catch (final Exception e) {
      throw new KeyStoreException(e);
    }
  }

  @Override
  public List<Identity> listIdentities() {
    try {
      return new ArrayList<>(this.storedIdentities);
    } catch (final Exception e) {
      throw new KeyStoreException(e);
    }
  }

  @Override
  public void store(final String path, final char[] password) {
    // do nothing
  }

  protected String digest(final Authentication authentication) {
    final byte[] rawIdentity = authentication.getIdentity().getValue().getBytes();
    final byte[] rawPassword = authentication.getPassword().getBytes();
    final byte[] plaintext = new byte[rawIdentity.length + rawPassword.length];
    System.arraycopy(rawIdentity, 0, plaintext, 0, rawIdentity.length);
    System.arraycopy(rawPassword, 0, plaintext, rawIdentity.length, rawPassword.length);
    return new String(Sha256Utils.digest(plaintext));
  }

}