Skip to content

Commit

Permalink
CryptoService now use a Supplier<String> to get its password
Browse files Browse the repository at this point in the history
We added a simple 'SensitiveString' that encrypts/obfuscates the String using
AES in order to minimize the chances of the clear unlock password gets swapped
to disk.
  • Loading branch information
ar committed Jun 18, 2018
1 parent 70a6c23 commit b247ae2
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
* Provides AES encryption service
Expand Down Expand Up @@ -78,7 +79,7 @@ public final class CryptoService extends QBeanSupport implements Runnable {
private Random rnd = new SecureRandom();
private long ttl;
private long duration;
private char[] unlock;
private Supplier<String> unlock;

/**
* Encrypts data using the current key
Expand Down Expand Up @@ -127,7 +128,7 @@ public byte[] aesDecrypt (UUID jobId, UUID keyId, byte[] encoded) throws Excepti
UUID xid = xor(jobId, keyId);
SecretKey sk = keys.rdp(xid);
if (sk == null && unlock != null) {
loadKey(jobId, keyId, unlock);
loadKey(jobId, keyId, unlock.get().toCharArray());
sk = keys.rdp(xid);
}
if (sk == null) {
Expand Down Expand Up @@ -168,16 +169,16 @@ public boolean unloadKey (UUID jobId, UUID keyId) {
/**
* Unlock the CryptoService
*/
public boolean unlock (char[] password) {
public boolean unlock (Supplier<String> passwordSupplier) {
try {
if (isLocked()) {
// attempt encrypt/decrypt
UUID id = UUID.randomUUID();
SecretKey sk = generateKey();
byte[] b = pgpEncrypt(id.toString(), sk.getEncoded());
PGPHelper.decrypt(b, privKeyRing, password);
PGPHelper.decrypt(b, privKeyRing, passwordSupplier.get().toCharArray());
sem.acquire();
this.unlock = password;
this.unlock = passwordSupplier;
sem.release();
}
return true;
Expand Down Expand Up @@ -229,8 +230,13 @@ public void setConfiguration (Configuration cfg) throws ConfigurationException {
ttl = cfg.getLong("ttl", 3600000L);
duration = cfg.getLong("duration", 86400000L);
String unlockPassword = cfg.get("unlock-password", null);
if (unlockPassword != null)
unlock (unlockPassword.toCharArray());
if (unlockPassword != null) {
try {
unlock (new SensitiveString(unlockPassword));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new ConfigurationException(e);
}
}
}

private SecretKey generateKey() throws NoSuchAlgorithmException {
Expand Down Expand Up @@ -269,8 +275,10 @@ private void registerKey(String k, String v) throws Exception {
Logger.log(evt);
}

private SecretKey getKey (UUID keyId, char[] password) throws Exception {
password = password != null ? password : unlock;
private SecretKey getKey (UUID keyId, char[] passPhrase) throws Exception {
if (passPhrase == null && unlock == null)
throw new SecurityException("Passphrase not available");
passPhrase = passPhrase != null ? passPhrase : unlock.get().toCharArray();

String v = (String) DB.execWithTransaction(db -> {
SysConfigManager mgr = new SysConfigManager(db, "key.");
Expand All @@ -282,12 +290,12 @@ private SecretKey getKey (UUID keyId, char[] password) throws Exception {
byte[] key = PGPHelper.decrypt(
v.getBytes(),
privKeyRing,
password != null ? password : unlock);
passPhrase
);
return new SecretKeySpec(key, 0, key.length, "AES");

}


private byte[] decrypt (SecretKey sk, IvParameterSpec iv, byte[] cryptogram)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, NoSuchProviderException, InvalidAlgorithmParameterException
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2018 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.crypto;

import org.jpos.iso.ISOUtil;
import org.jpos.security.SystemSeed;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.nio.ByteBuffer;
import java.security.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;

public class SensitiveString implements Supplier<String> {
private SecretKey key;
private byte[] encoded;
private static Random rnd;
private static final String AES = "AES/CBC/PKCS5Padding";

static {
try {
rnd = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage());
}
}


public SensitiveString(String s) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException, InvalidAlgorithmParameterException {
key = generateKey();
encoded = encrypt(s.getBytes());
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SensitiveString that = (SensitiveString) o;
return this.get().equals(that.get());
}

private SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
int maxKeyLength = Cipher.getMaxAllowedKeyLength(keyGen.getAlgorithm());
keyGen.init(maxKeyLength == Integer.MAX_VALUE ? 256 : maxKeyLength);
return keyGen.generateKey();
}

private byte[] encrypt(byte[] b) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
final Cipher cipher = Cipher.getInstance(AES);
final byte[] iv = randomIV();
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] enc = cipher.doFinal(b);
ByteBuffer buf = ByteBuffer.allocate(iv.length + enc.length);
buf.put(ISOUtil.xor(iv, SystemSeed.getSeed(iv.length, iv.length)));
buf.put(enc);
return buf.array();
}
private byte[] decrypt(byte[] encoded)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
IllegalBlockSizeException, NoSuchProviderException, InvalidAlgorithmParameterException
{
byte[] iv = new byte[16];
byte[] cryptogram = new byte[encoded.length - iv.length];
System.arraycopy(encoded, 0, iv, 0, iv.length);
System.arraycopy(encoded, iv.length, cryptogram, 0, cryptogram.length);
final Cipher cipher = Cipher.getInstance(AES);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ISOUtil.xor(iv, SystemSeed.getSeed(iv.length, iv.length))));
return cipher.doFinal(cryptogram);
}

private byte[] randomIV() {
final byte[] b = new byte[16];
rnd.nextBytes(b);
return b;
}

@Override
public String get() {
try {
return new String(decrypt(encoded));
} catch (NoSuchPaddingException | NoSuchAlgorithmException | BadPaddingException | InvalidKeyException | NoSuchProviderException | IllegalBlockSizeException | InvalidAlgorithmParameterException e) {
throw new AssertionError(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@
package org.jpos.q2.cli.crypto;

import org.jpos.crypto.CryptoService;
import org.jpos.crypto.SensitiveString;
import org.jpos.q2.CLICommand;
import org.jpos.q2.CLIContext;
import org.jpos.util.NameRegistrar;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

@SuppressWarnings("unused")
public class UNLOCK implements CLICommand {
private CryptoService cs;
Expand Down Expand Up @@ -50,7 +59,7 @@ private void usage (CLIContext cli) {
cli.println ("Usage: UNLOCK");
}

private boolean unlock (CLIContext cli) {
return cs.unlock(cli.getReader().readLine("Password: ", '*').toCharArray());
private boolean unlock (CLIContext cli) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException, InvalidKeyException {
return cs.unlock(new SensitiveString(cli.getReader().readLine("Password: ", '*')));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.jpos.crypto;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class SensitiveStringTest {
@Test
public void testSS() throws Exception {
String s = this.toString();
for (int i=0; i<15; i++) {
SensitiveString ss = new SensitiveString(s);
assertEquals("Should be equal", s, ss.get());
s = s + System.lineSeparator() + s;
}
}

@Test
public void testSSEquals() throws Exception {
String s = "The quick brown fox jumps over the lazy dog";
SensitiveString ss0 = new SensitiveString(s);
SensitiveString ss1 = new SensitiveString(s);
assertEquals ("Equals should be true", ss0, ss1);
}
}

0 comments on commit b247ae2

Please sign in to comment.