diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index d155e9c..9aec5a7 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -18,6 +18,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.ParseException; import java.util.Collection; import java.util.Date; import java.util.Locale; @@ -246,21 +247,36 @@ public abstract class AbstractSopCmd implements Runnable { } public Date parseNotAfter(String notAfter) { - Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter); - if (date == null) { + if (notAfter.equals("now")) { + return new Date(); + } + + if (notAfter.equals("-")) { + return END_OF_TIME; + } + + try { + return UTCUtil.parseUTCDate(notAfter); + } catch (ParseException e) { String errorMsg = getMsg("sop.error.input.malformed_not_after"); throw new IllegalArgumentException(errorMsg); } - return date; } public Date parseNotBefore(String notBefore) { - Date date = notBefore.equals("now") ? new Date() : notBefore.equals("-") ? BEGINNING_OF_TIME : UTCUtil.parseUTCDate(notBefore); - if (date == null) { + if (notBefore.equals("now")) { + return new Date(); + } + + if (notBefore.equals("-")) { + return BEGINNING_OF_TIME; + } + + try { + return UTCUtil.parseUTCDate(notBefore); + } catch (ParseException e) { String errorMsg = getMsg("sop.error.input.malformed_not_before"); throw new IllegalArgumentException(errorMsg); } - return date; } - } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index b445a6d..e3dd198 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.text.ParseException; import java.util.Collections; import java.util.Date; @@ -187,7 +188,7 @@ public class DecryptCmdTest { } @Test - public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { + public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException, ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -281,7 +282,7 @@ public class DecryptCmdTest { } @Test - public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData { + public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, ParseException { File certFile = File.createTempFile("verify-out-cert", ".asc"); File verifyOut = new File(certFile.getParent(), "verify-out.txt"); if (verifyOut.exists()) { diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java index c33cf74..50a8043 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.text.ParseException; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -41,7 +42,7 @@ public class VerifyCmdTest { PrintStream originalSout; @BeforeEach - public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException { + public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException, ParseException { originalSout = System.out; detachedVerify = mock(DetachedVerify.class); @@ -73,7 +74,7 @@ public class VerifyCmdTest { } @Test - public void notAfter_passedDown() throws SOPGPException.UnsupportedOption { + public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); verify(detachedVerify, times(1)).notAfter(date); @@ -100,7 +101,7 @@ public class VerifyCmdTest { } @Test - public void notBefore_passedDown() throws SOPGPException.UnsupportedOption { + public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); verify(detachedVerify, times(1)).notBefore(date); @@ -178,7 +179,7 @@ public class VerifyCmdTest { } @Test - public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { + public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData, ParseException { when(detachedVerify.data((InputStream) any())).thenReturn(Arrays.asList( new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"), "EB85BB5FA33A75E15E944E63F231550C4F47E38E", diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index d48a0b7..140e23b 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -10,6 +10,7 @@ import sop.util.UTCUtil; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.text.ParseException; import java.util.Date; /** @@ -89,7 +90,7 @@ public class Verification { if (split.length == 3) { return new Verification( - UTCUtil.parseUTCDate(split[0]), // timestamp + parseUTCDate(split[0]), // timestamp split[1], // key FP split[2] // cert FP ); @@ -111,7 +112,7 @@ public class Verification { } return new Verification( - UTCUtil.parseUTCDate(split[0]), // timestamp + parseUTCDate(split[0]), // timestamp split[1], // key FP split[2], // cert FP mode, // signature mode @@ -119,6 +120,14 @@ public class Verification { ); } + private static Date parseUTCDate(String utcFormatted) { + try { + return UTCUtil.parseUTCDate(utcFormatted); + } catch (ParseException e) { + throw new IllegalArgumentException("Malformed UTC timestamp.", e); + } + } + /** * Return the signatures' creation time. * diff --git a/sop-java/src/main/java/sop/util/UTCUtil.java b/sop-java/src/main/java/sop/util/UTCUtil.java index 8ef7e77..3849258 100644 --- a/sop-java/src/main/java/sop/util/UTCUtil.java +++ b/sop-java/src/main/java/sop/util/UTCUtil.java @@ -4,6 +4,7 @@ package sop.util; +import javax.annotation.Nonnull; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -33,15 +34,22 @@ public class UTCUtil { * @param dateString string * @return date */ - public static Date parseUTCDate(String dateString) { + @Nonnull + public static Date parseUTCDate(String dateString) throws ParseException { + ParseException exception = null; for (SimpleDateFormat parser : UTC_PARSERS) { try { return parser.parse(dateString); } catch (ParseException e) { + // Store first exception (that of UTC_FORMATTER) to throw if we fail to parse the date + if (exception == null) { + exception = e; + } // Try next parser } } - return null; + // No parser worked, so we throw the store exception + throw exception; } /** diff --git a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java b/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java index 8ae1859..a1413b4 100644 --- a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java +++ b/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Collections; import java.util.List; @@ -18,7 +19,7 @@ import sop.Verification; public class ByteArrayAndResultTest { @Test - public void testCreationAndGetters() { + public void testCreationAndGetters() throws ParseException { byte[] bytes = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); List result = Collections.singletonList( new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"), diff --git a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java b/sop-java/src/test/java/sop/util/ReadyWithResultTest.java index 97841fa..1207c5c 100644 --- a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java +++ b/sop-java/src/test/java/sop/util/ReadyWithResultTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Collections; import java.util.List; @@ -22,7 +23,7 @@ import sop.exception.SOPGPException; public class ReadyWithResultTest { @Test - public void testReadyWithResult() throws SOPGPException.NoSignature, IOException { + public void testReadyWithResult() throws SOPGPException.NoSignature, IOException, ParseException { byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); List result = Collections.singletonList( new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"), diff --git a/sop-java/src/test/java/sop/util/UTCUtilTest.java b/sop-java/src/test/java/sop/util/UTCUtilTest.java index 18de817..f6ccbc8 100644 --- a/sop-java/src/test/java/sop/util/UTCUtilTest.java +++ b/sop-java/src/test/java/sop/util/UTCUtilTest.java @@ -4,12 +4,13 @@ package sop.util; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; +import java.text.ParseException; import java.util.Date; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Test parsing some date examples from the stateless OpenPGP CLI spec. @@ -19,30 +20,29 @@ import org.junit.jupiter.api.Test; public class UTCUtilTest { @Test - public void parseExample1() { + public void parseExample1() throws ParseException { String timestamp = "2019-10-29T12:11:04+00:00"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date)); } @Test - public void parseExample2() { + public void parseExample2() throws ParseException { String timestamp = "2019-10-24T23:48:29Z"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-24T23:48:29Z", UTCUtil.formatUTCDate(date)); } @Test - public void parseExample3() { + public void parseExample3() throws ParseException { String timestamp = "20191029T121104Z"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date)); } @Test - public void invalidDateReturnsNull() { + public void invalidDateThrows() { String invalidTimestamp = "foobar"; - Date expectNull = UTCUtil.parseUTCDate(invalidTimestamp); - assertNull(expectNull); + assertThrows(ParseException.class, () -> UTCUtil.parseUTCDate(invalidTimestamp)); } } diff --git a/sop-java/src/test/java/sop/util/VerificationTest.java b/sop-java/src/test/java/sop/util/VerificationTest.java index b292688..c4f864e 100644 --- a/sop-java/src/test/java/sop/util/VerificationTest.java +++ b/sop-java/src/test/java/sop/util/VerificationTest.java @@ -9,6 +9,7 @@ import sop.Verification; import sop.enums.SignatureMode; import sop.testsuite.assertions.VerificationAssert; +import java.text.ParseException; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class VerificationTest { @Test - public void limitedConstructorTest() { + public void limitedConstructorTest() throws ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -32,7 +33,7 @@ public class VerificationTest { } @Test - public void limitedParsingTest() { + public void limitedParsingTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); @@ -44,7 +45,7 @@ public class VerificationTest { } @Test - public void parsingWithModeTest() { + public void parsingWithModeTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:text"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); @@ -56,7 +57,7 @@ public class VerificationTest { } @Test - public void extendedConstructorTest() { + public void extendedConstructorTest() throws ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -73,7 +74,7 @@ public class VerificationTest { } @Test - public void extendedParsingTest() { + public void extendedParsingTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); diff --git a/sop-java/src/testFixtures/java/sop/testsuite/TestData.java b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java index e1d35c2..386c411 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/TestData.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java @@ -7,6 +7,7 @@ package sop.testsuite; import sop.util.UTCUtil; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Date; public class TestData { @@ -59,7 +60,7 @@ public class TestData { "sfcfswMA\n" + "=RDAo\n" + "-----END PGP MESSAGE-----"; - public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T17:20:47Z"); // signature over PLAINTEXT public static final String ALICE_DETACHED_SIGNED_MESSAGE = "-----BEGIN PGP SIGNATURE-----\n" + "\n" + @@ -68,7 +69,7 @@ public class TestData { "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + "=bxPN\n" + "-----END PGP SIGNATURE-----"; - public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T16:57:57Z"); // 'Bob' key from draft-bre-openpgp-samples-00 public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -421,4 +422,12 @@ public class TestData { public static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); public static final byte[] END_PGP_SIGNATURE = "-----END PGP SIGNATURE-----".getBytes(StandardCharsets.UTF_8); public static final byte[] BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n".getBytes(StandardCharsets.UTF_8); + + private static Date parseUTCDate(String utcFormatted) { + try { + return UTCUtil.parseUTCDate(utcFormatted); + } catch (ParseException e) { + throw new IllegalArgumentException("Malformed UTC timestamp.", e); + } + } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 23f117e..0c382dc 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -21,6 +21,7 @@ import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Date; import java.util.List; import java.util.stream.Stream; @@ -231,7 +232,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void decryptVerifyNotAfterTest(SOP sop) { + public void decryptVerifyNotAfterTest(SOP sop) throws ParseException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + @@ -265,7 +266,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void decryptVerifyNotBeforeTest(SOP sop) { + public void decryptVerifyNotBeforeTest(SOP sop) throws ParseException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" +