From 8877bae675fd3479e057191f78061ceb2b9d1532 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 9 Feb 2022 23:59:45 +0100 Subject: [PATCH] Implement detection of non-UTF8 passwords --- .../main/java/sop/cli/picocli/FileUtil.java | 4 +- .../java/sop/exception/SOPGPException.java | 6 --- sop-java/src/main/java/sop/util/UTF8Util.java | 39 +++++++++++++++++++ .../src/test/java/sop/util/UTF8UtilTest.java | 39 +++++++++++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 sop-java/src/main/java/sop/util/UTF8Util.java create mode 100644 sop-java/src/test/java/sop/util/UTF8UtilTest.java diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java b/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java index 3d4ac07..cbe2735 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java @@ -10,9 +10,9 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; -import java.nio.charset.Charset; import sop.exception.SOPGPException; +import sop.util.UTF8Util; public class FileUtil { @@ -106,7 +106,7 @@ public class FileUtil { while ((read = inputStream.read(buf)) != -1) { byteOut.write(buf, 0, read); } - return new String(byteOut.toByteArray(), Charset.forName("UTF8")); + return UTF8Util.decodeUTF8(byteOut.toByteArray()); } finally { inputStream.close(); } diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 6fa66d8..93f565e 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -4,8 +4,6 @@ package sop.exception; -import java.io.IOException; - public abstract class SOPGPException extends RuntimeException { public SOPGPException() { @@ -134,10 +132,6 @@ public abstract class SOPGPException extends RuntimeException { super(); } - public PasswordNotHumanReadable(String message, IOException e) { - super(message, e); - } - @Override public int getExitCode() { return EXIT_CODE; diff --git a/sop-java/src/main/java/sop/util/UTF8Util.java b/sop-java/src/main/java/sop/util/UTF8Util.java new file mode 100644 index 0000000..fa00b8e --- /dev/null +++ b/sop-java/src/main/java/sop/util/UTF8Util.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util; + +import sop.exception.SOPGPException; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; + +public class UTF8Util { + + private static final CharsetDecoder UTF8Decoder = Charset.forName("UTF8") + .newDecoder() + .onUnmappableCharacter(CodingErrorAction.REPORT) + .onMalformedInput(CodingErrorAction.REPORT); + + /** + * Detect non-valid UTF8 data. + * + * @see ante on StackOverflow + * @param data + * @return + */ + public static String decodeUTF8(byte[] data) { + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + try { + CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer); + return charBuffer.toString(); + } catch (CharacterCodingException e) { + throw new SOPGPException.PasswordNotHumanReadable(); + } + } +} diff --git a/sop-java/src/test/java/sop/util/UTF8UtilTest.java b/sop-java/src/test/java/sop/util/UTF8UtilTest.java new file mode 100644 index 0000000..775d273 --- /dev/null +++ b/sop-java/src/test/java/sop/util/UTF8UtilTest.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util; + +import org.junit.jupiter.api.Test; +import sop.exception.SOPGPException; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class UTF8UtilTest { + + @Test + public void testValidUtf8Decoding() { + String utf8String = "Hello, World\n"; + String decoded = UTF8Util.decodeUTF8(utf8String.getBytes(StandardCharsets.UTF_8)); + + assertEquals(utf8String, decoded); + } + + /** + * Test detection of non-uft8 data. + * @see + * Markus Kuhn's UTF8 decoder capability and stress test file + */ + @Test + public void testInvalidUtf8StringThrows() { + assertThrows(SOPGPException.PasswordNotHumanReadable.class, + () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xa0, (byte) 0xa1})); + assertThrows(SOPGPException.PasswordNotHumanReadable.class, + () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xc0, (byte) 0xaf})); + assertThrows(SOPGPException.PasswordNotHumanReadable.class, + () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0x80, (byte) 0xbf})); + } +}