Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/pic-sure-hpds-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: pic-sure-hpds tests
on:
pull_request:
paths: ['services/pic-sure-hpds/**']
push:
branches: [main]
paths: ['services/pic-sure-hpds/**']
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0
with:
java-version: '25'
distribution: 'temurin'
cache: maven
- name: Test with Maven
run: mvn -B -pl services/pic-sure-hpds/common,services/pic-sure-hpds/data,services/pic-sure-hpds/etl,services/pic-sure-hpds/processing,services/pic-sure-hpds/service,services/pic-sure-hpds/genomic-processor,services/pic-sure-hpds/war,services/pic-sure-hpds/docker -am verify
env:
GITHUB_TOKEN: ${{ github.token }}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<module>services/picsure-dictionary/dictionaryweights</module>
<module>services/pic-sure-logging</module>
<module>services/pic-sure-auth-microapp</module>
<module>services/pic-sure-hpds</module>
<!-- pic-sure-legacy is deliberately NOT aggregated: it is quarantined
(Java 11/javax, own parent pom) and builds independently. -->
</modules>
Expand Down
2 changes: 1 addition & 1 deletion services/pic-sure-hpds/common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<artifactId>pic-sure-hpds</artifactId>
<groupId>edu.harvard.hms.dbmi.avillach.hpds</groupId>
<version>3.0.0-SNAPSHOT</version>
<version>${revision}</version>
</parent>

<artifactId>common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,89 +26,95 @@

public class Crypto {

public static final String DEFAULT_KEY_NAME = "DEFAULT";

// This needs to be set in a static initializer block to be overridable in tests.
private static final String DEFAULT_ENCRYPTION_KEY_PATH;
static{
DEFAULT_ENCRYPTION_KEY_PATH = "/opt/local/hpds/encryption_key";
}

private static final Logger LOGGER = LoggerFactory.getLogger(Crypto.class);

private static final HashMap<String, byte[]> keys = new HashMap<String, byte[]>();

public static void loadDefaultKey() {
loadKey(DEFAULT_KEY_NAME, DEFAULT_ENCRYPTION_KEY_PATH);
}

public static void loadKey(String keyName, String filePath) {
try {
setKey(keyName, IOUtils.toString(new FileInputStream(filePath), Charset.forName("UTF-8")).trim().getBytes());
LOGGER.info("****LOADED CRYPTO KEY****");
} catch (IOException e) {
LOGGER.error("****CRYPTO KEY NOT FOUND****", e);
}
}

public static byte[] encryptData(byte[] plaintextBytes) {
return encryptData(DEFAULT_KEY_NAME, plaintextBytes);
}

public static byte[] encryptData(String keyName, byte[] plaintextBytes) {
byte[] key = keys.get(keyName);
SecureRandom secureRandom = new SecureRandom();
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
byte[] cipherText;
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
cipherText = new byte[cipher.getOutputSize(plaintextBytes.length)];
cipher.doFinal(plaintextBytes, 0, plaintextBytes.length, cipherText, 0);
LOGGER.debug("Length of cipherText : " + cipherText.length);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
return cipherMessage;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("Exception while trying to encrypt data : ", e);
}
}

public static byte[] decryptData(byte[] encrypted) {
return decryptData(DEFAULT_KEY_NAME, encrypted);
}

public static byte[] decryptData(String keyName, byte[] encrypted) {
byte[] key = keys.get(keyName);
ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
int ivLength = byteBuffer.getInt();
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
return cipher.doFinal(cipherText);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("Exception caught trying to decrypt data : " + e, e);
}
}

private static void setKey(String keyName, byte[] key) {
keys.put(keyName, key);
}

public static boolean hasKey(String keyName) {
return keys.containsKey(keyName);
}
public static final String DEFAULT_KEY_NAME = "DEFAULT";

// This needs to be set in a static initializer block to be overridable in tests.
private static final String DEFAULT_ENCRYPTION_KEY_PATH;
static {
DEFAULT_ENCRYPTION_KEY_PATH = "/opt/local/hpds/encryption_key";
}

private static final Logger LOGGER = LoggerFactory.getLogger(Crypto.class);

private static final HashMap<String, byte[]> keys = new HashMap<String, byte[]>();

public static void loadDefaultKey() {
loadKey(DEFAULT_KEY_NAME, DEFAULT_ENCRYPTION_KEY_PATH);
}

public static void loadKey(String keyName, String filePath) {
try {
setKey(keyName, IOUtils.toString(new FileInputStream(filePath), Charset.forName("UTF-8")).trim().getBytes());
LOGGER.info("****LOADED CRYPTO KEY****");
} catch (IOException e) {
LOGGER.error("****CRYPTO KEY NOT FOUND****", e);
}
}

public static byte[] encryptData(byte[] plaintextBytes) {
return encryptData(DEFAULT_KEY_NAME, plaintextBytes);
}

public static byte[] encryptData(String keyName, byte[] plaintextBytes) {
byte[] key = keys.get(keyName);
SecureRandom secureRandom = new SecureRandom();
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; // NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
byte[] cipherText;
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); // 128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
cipherText = new byte[cipher.getOutputSize(plaintextBytes.length)];
cipher.doFinal(plaintextBytes, 0, plaintextBytes.length, cipherText, 0);
LOGGER.debug("Length of cipherText : " + cipherText.length);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
return cipherMessage;
} catch (
NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
| ShortBufferException | IllegalBlockSizeException | BadPaddingException e
) {
throw new RuntimeException("Exception while trying to encrypt data : ", e);
}
}

public static byte[] decryptData(byte[] encrypted) {
return decryptData(DEFAULT_KEY_NAME, encrypted);
}

public static byte[] decryptData(String keyName, byte[] encrypted) {
byte[] key = keys.get(keyName);
ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
int ivLength = byteBuffer.getInt();
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
return cipher.doFinal(cipherText);
} catch (
NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException
| IllegalBlockSizeException | BadPaddingException e
) {
throw new RuntimeException("Exception caught trying to decrypt data : " + e, e);
}
}

private static void setKey(String keyName, byte[] key) {
keys.put(keyName, key);
}

public static boolean hasKey(String keyName) {
return keys.containsKey(keyName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

public class NotEnoughMemoryException extends RuntimeException {

private static final long serialVersionUID = 2592915631853567560L;
private static final long serialVersionUID = 2592915631853567560L;

public NotEnoughMemoryException() {
super("Not enough available heap space to allocate results array."
+ "Please reduce ID_BATCH_SIZE or CACHE_SIZE system property or increase the heap available to this application. "
+ "This query will be retried up to 3 times.");
}
public NotEnoughMemoryException() {
super(
"Not enough available heap space to allocate results array."
+ "Please reduce ID_BATCH_SIZE or CACHE_SIZE system property or increase the heap available to this application. "
+ "This query will be retried up to 3 times."
);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package edu.harvard.hms.dbmi.avillach.hpds.exception;

public class ResultSetTooLargeException extends RuntimeException {
/**
* This exception is thrown when a result set would be larger in memory than
* Integer.MAX_INT bytes. This is a JVM limitation... but seriously people...
*
* You actually don't need that much data on the client side, work with us
* to implement whatever filtering you need.
*
* @param multiplesOfCapacity The number of pieces to split this result to actually have it work.
*/
public ResultSetTooLargeException(long multiplesOfCapacity) {
super("The result set is too large to handle in one request. "
+ "We have a plan to fix this limitation, but for now try "
+ "splitting your selected fields array in " + multiplesOfCapacity + " equal pieces "
+ "and running " + multiplesOfCapacity + " queries. Additionally, do you actually "
+ "need all this data? Do you have the compute resources "
+ "to handle processing it?");
}
/**
* This exception is thrown when a result set would be larger in memory than Integer.MAX_INT bytes. This is a JVM limitation... but
* seriously people...
*
* You actually don't need that much data on the client side, work with us to implement whatever filtering you need.
*
* @param multiplesOfCapacity The number of pieces to split this result to actually have it work.
*/
public ResultSetTooLargeException(long multiplesOfCapacity) {
super(
"The result set is too large to handle in one request. " + "We have a plan to fix this limitation, but for now try "
+ "splitting your selected fields array in " + multiplesOfCapacity + " equal pieces " + "and running " + multiplesOfCapacity
+ " queries. Additionally, do you actually " + "need all this data? Do you have the compute resources "
+ "to handle processing it?"
);
}
}
Loading
Loading