diff --git a/src/main/java/com/fingerprint/Sealed.java b/src/main/java/com/fingerprint/Sealed.java index 497a352..9863e71 100644 --- a/src/main/java/com/fingerprint/Sealed.java +++ b/src/main/java/com/fingerprint/Sealed.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fingerprint.model.EventResponse; -import com.fingerprint.sdk.ApiClient; import javax.crypto.Cipher; import javax.crypto.spec.GCMParameterSpec; @@ -10,14 +9,13 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; -import java.util.logging.Logger; +import java.util.List; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; public class Sealed { - private static final Logger log = Logger.getLogger(ApiClient.class.getName()); - public enum DecryptionAlgorithm { AES_256_GCM } @@ -32,16 +30,43 @@ public DecryptionKey(byte[] key, DecryptionAlgorithm algorithm) { } } + public static class UnsealAggregateException extends Exception { + private final List unsealExceptions = new ArrayList<>(); + + public UnsealAggregateException() { + super("Failed to unseal with all decryption keys"); + } + + public void addUnsealException(UnsealException exception) { + unsealExceptions.add(exception); + } + + public List getUnsealExceptions() { + return unsealExceptions; + } + } + + public static class UnsealException extends Exception { + public final DecryptionKey decryptionKey; + + public final Exception exception; + + public UnsealException(String message, DecryptionKey decryptionKey, Exception exception) { + super(message); + this.decryptionKey = decryptionKey; + this.exception = exception; + } + } private static final byte[] SEAL_HEADER = new byte[]{(byte) 0x9E, (byte) 0x85, (byte) 0xDC, (byte) 0xED}; private static final int NONCE_LENGTH = 12; private static final int AUTH_TAG_LENGTH = 16; - public static byte[] unseal(byte[] sealed, DecryptionKey[] keys) throws IllegalArgumentException { + public static byte[] unseal(byte[] sealed, DecryptionKey[] keys) throws IllegalArgumentException, UnsealAggregateException { if (!Arrays.equals(Arrays.copyOf(sealed, SEAL_HEADER.length), SEAL_HEADER)) { throw new IllegalArgumentException("Invalid sealed data header"); } - int index = 0; + UnsealAggregateException aggregateException = new UnsealAggregateException(); for (DecryptionKey key : keys) { switch (key.algorithm) { @@ -49,7 +74,13 @@ public static byte[] unseal(byte[] sealed, DecryptionKey[] keys) throws IllegalA try { return decryptAes256Gcm(Arrays.copyOfRange(sealed, SEAL_HEADER.length, sealed.length), key.key); } catch (Exception exception) { - log.warning(String.format("Failed to decrypt with key: %d error: %s", index, exception.getMessage())); + aggregateException.addUnsealException( + new UnsealException( + "Failed to decrypt", + key, + exception + ) + ); } break; @@ -57,22 +88,19 @@ public static byte[] unseal(byte[] sealed, DecryptionKey[] keys) throws IllegalA default: throw new IllegalArgumentException("Invalid decryption algorithm"); } - - index++; } - throw new IllegalArgumentException("Invalid decryption keys"); + throw aggregateException; } /** * decrypts the sealed response with the provided keys. * * @param sealed Base64 encoded sealed data - * @param keys Decryption keys. The SDK will try to decrypt the result with each key until it succeeds. + * @param keys Decryption keys. The SDK will try to decrypt the result with each key until it succeeds. * @return EventResponse - * @throws Exception if the sealed data is invalid or if the decryption keys are invalid */ - public static EventResponse unsealEventResponse(byte[] sealed, DecryptionKey[] keys) throws Exception { + public static EventResponse unsealEventResponse(byte[] sealed, DecryptionKey[] keys) throws IllegalArgumentException, UnsealAggregateException, IOException { byte[] unsealed = unseal(sealed, keys); ObjectMapper mapper = ObjectMapperUtil.getObjectMapper(); diff --git a/src/test/java/com/fingerprint/SealedTest.java b/src/test/java/com/fingerprint/SealedTest.java index e836d24..67afbc0 100644 --- a/src/test/java/com/fingerprint/SealedTest.java +++ b/src/test/java/com/fingerprint/SealedTest.java @@ -6,8 +6,7 @@ import java.util.Base64; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class SealedTest { @@ -68,7 +67,7 @@ public void unsealEventResponseWithInvalidHeaderTest() throws Exception { public void unsealEventResponseWithInvalidKeysTest() throws Exception { byte[] sealedResult = Base64.getDecoder().decode("noXc7SXO+mqeAGrvBMgObi/S0fXTpP3zupk8qFqsO/1zdtWCD169iLA3VkkZh9ICHpZ0oWRzqG0M9/TnCeKFohgBLqDp6O0zEfXOv6i5q++aucItznQdLwrKLP+O0blfb4dWVI8/aSbd4ELAZuJJxj9bCoVZ1vk+ShbUXCRZTD30OIEAr3eiG9aw00y1UZIqMgX6CkFlU9L9OnKLsNsyomPIaRHTmgVTI5kNhrnVNyNsnzt9rY7fUD52DQxJILVPrUJ1Q+qW7VyNslzGYBPG0DyYlKbRAomKJDQIkdj/Uwa6bhSTq4XYNVvbk5AJ/dGwvsVdOnkMT2Ipd67KwbKfw5bqQj/cw6bj8Cp2FD4Dy4Ud4daBpPRsCyxBM2jOjVz1B/lAyrOp8BweXOXYugwdPyEn38MBZ5oL4D38jIwR/QiVnMHpERh93jtgwh9Abza6i4/zZaDAbPhtZLXSM5ztdctv8bAb63CppLU541Kf4OaLO3QLvfLRXK2n8bwEwzVAqQ22dyzt6/vPiRbZ5akh8JB6QFXG0QJF9DejsIspKF3JvOKjG2edmC9o+GfL3hwDBiihYXCGY9lElZICAdt+7rZm5UxMx7STrVKy81xcvfaIp1BwGh/HyMsJnkE8IczzRFpLlHGYuNDxdLoBjiifrmHvOCUDcV8UvhSV+UAZtAVejdNGo5G/bz0NF21HUO4pVRPu6RqZIs/aX4hlm6iO/0Ru00ct8pfadUIgRcephTuFC2fHyZxNBC6NApRtLSNLfzYTTo/uSjgcu6rLWiNo5G7yfrM45RXjalFEFzk75Z/fu9lCJJa5uLFgDNKlU+IaFjArfXJCll3apbZp4/LNKiU35ZlB7ZmjDTrji1wLep8iRVVEGht/DW00MTok7Zn7Fv+MlxgWmbZB3BuezwTmXb/fNw=="); - Exception thrown = assertThrows(Exception.class, () -> Sealed.unsealEventResponse( + assertThrows(Sealed.UnsealAggregateException.class, () -> Sealed.unsealEventResponse( sealedResult, new Sealed.DecryptionKey[]{ new Sealed.DecryptionKey( @@ -87,6 +86,5 @@ public void unsealEventResponseWithInvalidKeysTest() throws Exception { } )); - assertEquals("Invalid decryption keys", thrown.getMessage()); } }