From 4bd46579068977ef12efc8ac8cc65ed73c8be747 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:24:27 +0100 Subject: [PATCH 01/46] Add kotlin plugin --- build.gradle | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/build.gradle b/build.gradle index 56730fd..577c2aa 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,8 @@ buildscript { plugins { id 'ru.vyarus.animalsniffer' version '1.5.3' + id 'org.jetbrains.kotlin.jvm' version "1.8.10" + id 'com.diffplug.spotless' version '6.22.0' apply false } apply from: 'version.gradle' @@ -29,6 +31,8 @@ allprojects { apply plugin: 'eclipse' apply plugin: 'jacoco' apply plugin: 'checkstyle' + apply plugin: 'kotlin' + apply plugin: 'com.diffplug.spotless' // For non-cli modules enable android api compatibility check if (it.name.equals('sop-java')) { @@ -53,6 +57,12 @@ allprojects { toolVersion = '8.18' } + spotless { + kotlin { + ktfmt().dropboxStyle() + } + } + group 'org.pgpainless' description = "Stateless OpenPGP Protocol API for Java" version = shortVersion @@ -69,6 +79,13 @@ allprojects { reproducibleFileOrder = true } + // Compatibility of default implementations in kotlin interfaces with Java implementations. + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + freeCompilerArgs += ["-Xjvm-default=all-compatibility"] + } + } + project.ext { rootConfigDir = new File(rootDir, 'config') gitCommit = getGitCommit() From 0f5270c28da4f63c364a9f1d2561355a0f2f6fa9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:24:36 +0100 Subject: [PATCH 02/46] Kotlin conversion: ByteArrayAndResult --- .../src/main/java/sop/ByteArrayAndResult.java | 50 ------------------- .../src/main/kotlin/sop/ByteArrayAndResult.kt | 43 ++++++++++++++++ 2 files changed, 43 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/ByteArrayAndResult.java create mode 100644 sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt diff --git a/sop-java/src/main/java/sop/ByteArrayAndResult.java b/sop-java/src/main/java/sop/ByteArrayAndResult.java deleted file mode 100644 index fd2b39a..0000000 --- a/sop-java/src/main/java/sop/ByteArrayAndResult.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * Tuple of a byte array and associated result object. - * @param type of result - */ -public class ByteArrayAndResult { - - private final byte[] bytes; - private final T result; - - public ByteArrayAndResult(byte[] bytes, T result) { - this.bytes = bytes; - this.result = result; - } - - /** - * Return the byte array part. - * - * @return bytes - */ - public byte[] getBytes() { - return bytes; - } - - /** - * Return the result part. - * - * @return result - */ - public T getResult() { - return result; - } - - /** - * Return the byte array part as an {@link InputStream}. - * - * @return input stream - */ - public InputStream getInputStream() { - return new ByteArrayInputStream(getBytes()); - } -} diff --git a/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt b/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt new file mode 100644 index 0000000..021a2d9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.InputStream + +/** + * Tuple of a [ByteArray] and associated result object. + * + * @param bytes byte array + * @param result result object + * @param type of result + */ +data class ByteArrayAndResult(val bytes: ByteArray, val result: T) { + + /** + * [InputStream] returning the contents of [bytes]. + * + * @return input stream + */ + val inputStream: InputStream + get() = bytes.inputStream() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ByteArrayAndResult<*> + + if (!bytes.contentEquals(other.bytes)) return false + if (result != other.result) return false + + return true + } + + override fun hashCode(): Int { + var hashCode = bytes.contentHashCode() + hashCode = 31 * hashCode + (result?.hashCode() ?: 0) + return hashCode + } +} From 567571cf6cbc657e6f4713dea38bda7f34eb63ac Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:33:15 +0100 Subject: [PATCH 03/46] Kotlin conversion: SessionKey --- sop-java/src/main/java/sop/SessionKey.java | 80 ---------------------- sop-java/src/main/kotlin/sop/SessionKey.kt | 48 +++++++++++++ 2 files changed, 48 insertions(+), 80 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SessionKey.java create mode 100644 sop-java/src/main/kotlin/sop/SessionKey.kt diff --git a/sop-java/src/main/java/sop/SessionKey.java b/sop-java/src/main/java/sop/SessionKey.java deleted file mode 100644 index 722666d..0000000 --- a/sop-java/src/main/java/sop/SessionKey.java +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import sop.util.HexUtil; - -public class SessionKey { - - private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9A-F]+)$"); - - private final byte algorithm; - private final byte[] sessionKey; - - public SessionKey(byte algorithm, byte[] sessionKey) { - this.algorithm = algorithm; - this.sessionKey = sessionKey; - } - - /** - * Return the symmetric algorithm octet. - * - * @return algorithm id - */ - public byte getAlgorithm() { - return algorithm; - } - - /** - * Return the session key. - * - * @return session key - */ - public byte[] getKey() { - return sessionKey; - } - - @Override - public int hashCode() { - return getAlgorithm() * 17 + Arrays.hashCode(getKey()); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - if (this == other) { - return true; - } - if (!(other instanceof SessionKey)) { - return false; - } - - SessionKey otherKey = (SessionKey) other; - return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey()); - } - - public static SessionKey fromString(String string) { - string = string.trim().toUpperCase().replace("\n", ""); - Matcher matcher = PATTERN.matcher(string); - if (!matcher.matches()) { - throw new IllegalArgumentException("Provided session key does not match expected format."); - } - byte algorithm = Byte.parseByte(matcher.group(1)); - String key = matcher.group(2); - - return new SessionKey(algorithm, HexUtil.hexToBytes(key)); - } - - @Override - public String toString() { - return Integer.toString(getAlgorithm()) + ':' + HexUtil.bytesToHex(sessionKey); - } -} diff --git a/sop-java/src/main/kotlin/sop/SessionKey.kt b/sop-java/src/main/kotlin/sop/SessionKey.kt new file mode 100644 index 0000000..af230f3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SessionKey.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.HexUtil + +/** + * Class representing a symmetric session key. + * + * @param algorithm symmetric key algorithm ID + * @param key [ByteArray] containing the session key + */ +data class SessionKey(val algorithm: Byte, val key: ByteArray) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SessionKey + + if (algorithm != other.algorithm) return false + if (!key.contentEquals(other.key)) return false + + return true + } + + override fun hashCode(): Int { + var hashCode = algorithm.toInt() + hashCode = 31 * hashCode + key.contentHashCode() + return hashCode + } + + override fun toString(): String = "$algorithm:${HexUtil.bytesToHex(key)}" + + companion object { + + @JvmStatic private val PATTERN = "^(\\d):([0-9A-F]+)$".toPattern() + + @JvmStatic + fun fromString(string: String): SessionKey { + val matcher = PATTERN.matcher(string.trim().uppercase().replace("\n", "")) + require(matcher.matches()) { "Provided session key does not match expected format." } + return SessionKey(matcher.group(1).toByte(), HexUtil.hexToBytes(matcher.group(2))) + } + } +} From 6c5c4b3d981aa62f28dfde36a2e56821d279620f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:25:54 +0100 Subject: [PATCH 04/46] Kotlin conversion: Verification --- sop-java/src/main/java/sop/Verification.java | 222 ------------------- sop-java/src/main/kotlin/sop/Verification.kt | 76 +++++++ 2 files changed, 76 insertions(+), 222 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Verification.java create mode 100644 sop-java/src/main/kotlin/sop/Verification.kt diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java deleted file mode 100644 index 140e23b..0000000 --- a/sop-java/src/main/java/sop/Verification.java +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.enums.SignatureMode; -import sop.util.Optional; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.text.ParseException; -import java.util.Date; - -/** - * Class bundling information about a verified signature. - */ -public class Verification { - - private final Date creationTime; - private final String signingKeyFingerprint; - private final String signingCertFingerprint; - private final Optional signatureMode; - private final Optional description; - - private static final String MODE = "mode:"; - - /** - * Create a new {@link Verification} without mode and description. - * - * @param creationTime signature creation time - * @param signingKeyFingerprint fingerprint of the signing (sub-) key - * @param signingCertFingerprint fingerprint of the certificate - */ - public Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint) { - this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty()); - } - - /** - * Create a new {@link Verification}. - * - * @param creationTime signature creation time - * @param signingKeyFingerprint fingerprint of the signing (sub-) key - * @param signingCertFingerprint fingerprint of the certificate - * @param signatureMode signature mode (optional, may be
null
) - * @param description free-form description, e.g.
certificate from dkg.asc
(optional, may be
null
) - */ - public Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint, - @Nullable SignatureMode signatureMode, - @Nullable String description) { - this( - creationTime, - signingKeyFingerprint, - signingCertFingerprint, - Optional.ofNullable(signatureMode), - Optional.ofNullable(nullSafeTrim(description)) - ); - } - - private Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint, - @Nonnull Optional signatureMode, - @Nonnull Optional description) { - this.creationTime = creationTime; - this.signingKeyFingerprint = signingKeyFingerprint; - this.signingCertFingerprint = signingCertFingerprint; - this.signatureMode = signatureMode; - this.description = description; - } - - private static String nullSafeTrim(@Nullable String string) { - if (string == null) { - return null; - } - return string.trim(); - } - - @Nonnull - public static Verification fromString(@Nonnull String toString) { - String[] split = toString.trim().split(" "); - if (split.length < 3) { - throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'"); - } - - if (split.length == 3) { - return new Verification( - parseUTCDate(split[0]), // timestamp - split[1], // key FP - split[2] // cert FP - ); - } - - SignatureMode mode = null; - int index = 3; - if (split[index].startsWith(MODE)) { - mode = SignatureMode.valueOf(split[3].substring(MODE.length())); - index++; - } - - StringBuilder sb = new StringBuilder(); - for (int i = index; i < split.length; i++) { - if (sb.length() != 0) { - sb.append(' '); - } - sb.append(split[i]); - } - - return new Verification( - parseUTCDate(split[0]), // timestamp - split[1], // key FP - split[2], // cert FP - mode, // signature mode - sb.length() != 0 ? sb.toString() : null // description - ); - } - - 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. - * - * @return signature creation time - */ - @Nonnull - public Date getCreationTime() { - return creationTime; - } - - /** - * Return the fingerprint of the signing (sub)key. - * - * @return signing key fingerprint - */ - @Nonnull - public String getSigningKeyFingerprint() { - return signingKeyFingerprint; - } - - /** - * Return the fingerprint fo the signing certificate. - * - * @return signing certificate fingerprint - */ - @Nonnull - public String getSigningCertFingerprint() { - return signingCertFingerprint; - } - - /** - * Return the mode of the signature. - * Optional, may return
null
. - * - * @return signature mode - */ - @Nonnull - public Optional getSignatureMode() { - return signatureMode; - } - - /** - * Return an optional description. - * Optional, may return
null
. - * - * @return description - */ - @Nonnull - public Optional getDescription() { - return description; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder() - .append(UTCUtil.formatUTCDate(getCreationTime())) - .append(' ') - .append(getSigningKeyFingerprint()) - .append(' ') - .append(getSigningCertFingerprint()); - - if (signatureMode.isPresent()) { - sb.append(' ').append(MODE).append(signatureMode.get()); - } - - if (description.isPresent()) { - sb.append(' ').append(description.get()); - } - - return sb.toString(); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof Verification)) { - return false; - } - Verification other = (Verification) obj; - return toString().equals(other.toString()); - } -} diff --git a/sop-java/src/main/kotlin/sop/Verification.kt b/sop-java/src/main/kotlin/sop/Verification.kt new file mode 100644 index 0000000..20401a9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Verification.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.text.ParseException +import java.util.Date +import sop.enums.SignatureMode +import sop.util.Optional +import sop.util.UTCUtil + +data class Verification( + val creationTime: Date, + val signingKeyFingerprint: String, + val signingCertFingerprint: String, + val signatureMode: Optional, + val description: Optional +) { + @JvmOverloads + constructor( + creationTime: Date, + signingKeyFingerprint: String, + signingCertFingerprint: String, + signatureMode: SignatureMode? = null, + description: String? = null + ) : this( + creationTime, + signingKeyFingerprint, + signingCertFingerprint, + Optional.ofNullable(signatureMode), + Optional.ofNullable(description?.trim())) + + override fun toString(): String = + "${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" + + (if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") + + (if (description.isPresent) " ${description.get()}" else "") + + companion object { + @JvmStatic + fun fromString(string: String): Verification { + val split = string.trim().split(" ") + require(split.size >= 3) { + "Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'." + } + if (split.size == 3) { + return Verification(parseUTCDate(split[0]), split[1], split[2]) + } + + var index = 3 + val mode = + if (split[3].startsWith("mode:")) { + index += 1 + SignatureMode.valueOf(split[3].substring("mode:".length)) + } else null + + val description = split.subList(index, split.size).joinToString(" ").ifBlank { null } + + return Verification( + parseUTCDate(split[0]), + split[1], + split[2], + Optional.ofNullable(mode), + Optional.ofNullable(description)) + } + + @JvmStatic + private fun parseUTCDate(string: String): Date { + return try { + UTCUtil.parseUTCDate(string) + } catch (e: ParseException) { + throw IllegalArgumentException("Malformed UTC timestamp.", e) + } + } + } +} From ef4b01c6bd73d74a3b3d203ec5da2a0228431f4e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:42:13 +0100 Subject: [PATCH 05/46] Kotlin conversion: Profile --- sop-java/src/main/java/sop/Profile.java | 143 ------------------------ sop-java/src/main/kotlin/sop/Profile.kt | 87 ++++++++++++++ 2 files changed, 87 insertions(+), 143 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Profile.java create mode 100644 sop-java/src/main/kotlin/sop/Profile.kt diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java deleted file mode 100644 index 4ea9e71..0000000 --- a/sop-java/src/main/java/sop/Profile.java +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.util.Optional; -import sop.util.UTF8Util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Tuple class bundling a profile name and description. - * - * @see - * SOP Spec - Profile - */ -public class Profile { - - private final String name; - private final Optional description; - - /** - * Create a new {@link Profile} object. - * The {@link #toString()} representation MUST NOT exceed a length of 1000 bytes. - * - * @param name profile name - * @param description profile description - */ - public Profile(@Nonnull String name, @Nullable String description) { - if (name.trim().isEmpty()) { - throw new IllegalArgumentException("Name cannot be empty."); - } - if (name.contains(":")) { - throw new IllegalArgumentException("Name cannot contain ':'."); - } - if (name.contains(" ") || name.contains("\n") || name.contains("\t") || name.contains("\r")) { - throw new IllegalArgumentException("Name cannot contain whitespace characters."); - } - - this.name = name; - - if (description == null) { - this.description = Optional.ofEmpty(); - } else { - String trimmedDescription = description.trim(); - if (trimmedDescription.isEmpty()) { - this.description = Optional.ofEmpty(); - } else { - this.description = Optional.of(trimmedDescription); - } - } - - if (exceeds1000CharLineLimit(this)) { - throw new IllegalArgumentException("The line representation of a profile MUST NOT exceed 1000 bytes."); - } - } - - public Profile(String name) { - this(name, null); - } - - /** - * Parse a {@link Profile} from its string representation. - * - * @param string string representation - * @return profile - */ - public static Profile parse(String string) { - if (string.contains(": ")) { - // description after colon, e.g. "default: Use implementers recommendations." - String name = string.substring(0, string.indexOf(": ")); - String description = string.substring(string.indexOf(": ") + 2); - return new Profile(name, description.trim()); - } - - if (string.endsWith(":")) { - // empty description, e.g. "default:" - return new Profile(string.substring(0, string.length() - 1)); - } - - // no description - return new Profile(string.trim()); - } - - /** - * Return the name (also known as identifier) of the profile. - * A profile name is a UTF-8 string that has no whitespace in it. - * Similar to OpenPGP Notation names, profile names are divided into two namespaces: - * The IETF namespace and the user namespace. - * A profile name in the user namespace ends with the
@
character (0x40) followed by a DNS domain name. - * A profile name in the IETF namespace does not have an
@
character. - * A profile name in the user space is owned and controlled by the owner of the domain in the suffix. - * A profile name in the IETF namespace that begins with the string
rfc
should have semantics that hew as - * closely as possible to the referenced RFC. - * Similarly, a profile name in the IETF namespace that begins with the string
draft-
should have - * semantics that hew as closely as possible to the referenced Internet Draft. - * - * @return name - */ - @Nonnull - public String getName() { - return name; - } - - /** - * Return a free-form description of the profile. - * - * @return description - */ - @Nonnull - public Optional getDescription() { - return description; - } - - public boolean hasDescription() { - return description.isPresent(); - } - - /** - * Convert the profile into a String for displaying. - * - * @return string - */ - @Override - public String toString() { - if (getDescription().isEmpty()) { - return getName(); - } - return getName() + ": " + getDescription().get(); - } - - /** - * Test if the string representation of the profile exceeds the limit of 1000 bytes length. - * @param profile profile - * @return
true
if the profile exceeds 1000 bytes,
false
otherwise. - */ - private static boolean exceeds1000CharLineLimit(Profile profile) { - String line = profile.toString(); - return line.getBytes(UTF8Util.UTF8).length > 1000; - } -} diff --git a/sop-java/src/main/kotlin/sop/Profile.kt b/sop-java/src/main/kotlin/sop/Profile.kt new file mode 100644 index 0000000..2125c57 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Profile.kt @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.Optional +import sop.util.UTF8Util + +/** + * Tuple class bundling a profile name and description. + * + * @param name profile name. A profile name is a UTF-8 string that has no whitespace in it. Similar + * to OpenPGP Notation names, profile names are divided into two namespaces: The IETF namespace + * and the user namespace. A profile name in the user namespace ends with the `@` character (0x40) + * followed by a DNS domain name. A profile name in the IETF namespace does not have an `@` + * character. A profile name in the user space is owned and controlled by the owner of the domain + * in the suffix. A profile name in the IETF namespace that begins with the string `rfc` should + * have semantics that hew as closely as possible to the referenced RFC. Similarly, a profile name + * in the IETF namespace that begins with the string `draft-` should have semantics that hew as + * closely as possible to the referenced Internet Draft. + * @param description a free-form description of the profile. + * @see + * SOP Spec - Profile + */ +data class Profile(val name: String, val description: Optional) { + + @JvmOverloads + constructor( + name: String, + description: String? = null + ) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null })) + + init { + require(name.trim().isNotBlank()) { "Name cannot be empty." } + require(!name.contains(":")) { "Name cannot contain ':'." } + require(listOf(" ", "\n", "\t", "\r").none { name.contains(it) }) { + "Name cannot contain whitespace characters." + } + require(!exceeds1000CharLineLimit(this)) { + "The line representation of a profile MUST NOT exceed 1000 bytes." + } + } + + fun hasDescription() = description.isPresent + + /** + * Convert the profile into a String for displaying. + * + * @return string + */ + override fun toString(): String = + if (description.isEmpty) name else "$name: ${description.get()}" + + companion object { + + /** + * Parse a [Profile] from its string representation. + * + * @param string string representation + * @return profile + */ + @JvmStatic + fun parse(string: String): Profile { + return if (string.contains(": ")) { + Profile( + string.substring(0, string.indexOf(": ")), + string.substring(string.indexOf(": ") + 2).trim()) + } else if (string.endsWith(":")) { + Profile(string.substring(0, string.length - 1)) + } else { + Profile(string.trim()) + } + } + + /** + * Test if the string representation of the profile exceeds the limit of 1000 bytes length. + * + * @param profile profile + * @return `true` if the profile exceeds 1000 bytes, `false` otherwise. + */ + @JvmStatic + private fun exceeds1000CharLineLimit(profile: Profile): Boolean = + profile.toString().toByteArray(UTF8Util.UTF8).size > 1000 + } +} From 0cb5c74a11109cbe7a3b266a95f7655b8ef68441 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:48:23 +0100 Subject: [PATCH 06/46] Kotlin conversion: Optional --- sop-java/src/main/java/sop/util/Optional.java | 50 ------------------- sop-java/src/main/kotlin/sop/util/Optional.kt | 26 ++++++++++ 2 files changed, 26 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/Optional.java create mode 100644 sop-java/src/main/kotlin/sop/util/Optional.kt diff --git a/sop-java/src/main/java/sop/util/Optional.java b/sop-java/src/main/java/sop/util/Optional.java deleted file mode 100644 index 00eb201..0000000 --- a/sop-java/src/main/java/sop/util/Optional.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -/** - * Backport of java.util.Optional for older Android versions. - * - * @param item type - */ -public class Optional { - - private final T item; - - public Optional() { - this(null); - } - - public Optional(T item) { - this.item = item; - } - - public static Optional of(T item) { - if (item == null) { - throw new NullPointerException("Item cannot be null."); - } - return new Optional<>(item); - } - - public static Optional ofNullable(T item) { - return new Optional<>(item); - } - - public static Optional ofEmpty() { - return new Optional<>(null); - } - - public T get() { - return item; - } - - public boolean isPresent() { - return item != null; - } - - public boolean isEmpty() { - return item == null; - } -} diff --git a/sop-java/src/main/kotlin/sop/util/Optional.kt b/sop-java/src/main/kotlin/sop/util/Optional.kt new file mode 100644 index 0000000..0344d0b --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/Optional.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +/** + * Backport of java.util.Optional for older Android versions. + * + * @param item type + */ +data class Optional(val item: T? = null) { + + val isPresent: Boolean = item != null + val isEmpty: Boolean = item == null + + fun get() = item + + companion object { + @JvmStatic fun of(item: T) = Optional(item!!) + + @JvmStatic fun ofNullable(item: T?) = Optional(item) + + @JvmStatic fun ofEmpty() = Optional(null as T?) + } +} From bbe159e88c5442a61ae5dcbe7fca695f408cf785 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:56:12 +0100 Subject: [PATCH 07/46] Kotlin conversion: SigningResult --- sop-java/src/main/java/sop/SigningResult.java | 50 ------------------- sop-java/src/main/kotlin/sop/SigningResult.kt | 28 +++++++++++ 2 files changed, 28 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SigningResult.java create mode 100644 sop-java/src/main/kotlin/sop/SigningResult.kt diff --git a/sop-java/src/main/java/sop/SigningResult.java b/sop-java/src/main/java/sop/SigningResult.java deleted file mode 100644 index 1ea1ba8..0000000 --- a/sop-java/src/main/java/sop/SigningResult.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -/** - * This class contains various information about a signed message. - */ -public final class SigningResult { - - private final MicAlg micAlg; - - private SigningResult(MicAlg micAlg) { - this.micAlg = micAlg; - } - - /** - * Return a string identifying the digest mechanism used to create the signed message. - * This is useful for setting the micalg= parameter for the multipart/signed - * content type of a PGP/MIME object as described in section 5 of [RFC3156]. - *

- * If more than one signature was generated and different digest mechanisms were used, - * the value of the micalg object is an empty string. - * - * @return micalg - */ - public MicAlg getMicAlg() { - return micAlg; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private MicAlg micAlg; - - public Builder setMicAlg(MicAlg micAlg) { - this.micAlg = micAlg; - return this; - } - - public SigningResult build() { - SigningResult signingResult = new SigningResult(micAlg); - return signingResult; - } - } -} diff --git a/sop-java/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt new file mode 100644 index 0000000..29304ea --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +/** + * This class contains various information about a signed message. + * + * @param micAlg string identifying the digest mechanism used to create the signed message. This is + * useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME + * object as described in section 5 of [RFC3156]. If more than one signature was generated and + * different digest mechanisms were used, the value of the micalg object is an empty string. + */ +data class SigningResult(val micAlg: MicAlg) { + + class Builder internal constructor() { + private var micAlg = MicAlg.empty() + + fun setMicAlg(micAlg: MicAlg) = apply { this.micAlg = micAlg } + + fun build() = SigningResult(micAlg) + } + + companion object { + @JvmStatic fun builder() = Builder() + } +} From 9dbb93e13dd14083d48622b2ff2594db41ec9de8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:05:30 +0100 Subject: [PATCH 08/46] Kotlin conversion: MicAlg --- sop-java/src/main/java/sop/MicAlg.java | 55 ---------------------- sop-java/src/main/kotlin/sop/MicAlg.kt | 34 +++++++++++++ sop-java/src/test/java/sop/MicAlgTest.java | 2 +- 3 files changed, 35 insertions(+), 56 deletions(-) delete mode 100644 sop-java/src/main/java/sop/MicAlg.java create mode 100644 sop-java/src/main/kotlin/sop/MicAlg.kt diff --git a/sop-java/src/main/java/sop/MicAlg.java b/sop-java/src/main/java/sop/MicAlg.java deleted file mode 100644 index 5bee787..0000000 --- a/sop-java/src/main/java/sop/MicAlg.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.OutputStream; -import java.io.PrintWriter; - -public class MicAlg { - - private final String micAlg; - - public MicAlg(String micAlg) { - if (micAlg == null) { - throw new IllegalArgumentException("MicAlg String cannot be null."); - } - this.micAlg = micAlg; - } - - public static MicAlg empty() { - return new MicAlg(""); - } - - public static MicAlg fromHashAlgorithmId(int id) { - switch (id) { - case 1: - return new MicAlg("pgp-md5"); - case 2: - return new MicAlg("pgp-sha1"); - case 3: - return new MicAlg("pgp-ripemd160"); - case 8: - return new MicAlg("pgp-sha256"); - case 9: - return new MicAlg("pgp-sha384"); - case 10: - return new MicAlg("pgp-sha512"); - case 11: - return new MicAlg("pgp-sha224"); - default: - throw new IllegalArgumentException("Unsupported hash algorithm ID: " + id); - } - } - - public String getMicAlg() { - return micAlg; - } - - public void writeTo(OutputStream outputStream) { - PrintWriter pw = new PrintWriter(outputStream); - pw.write(getMicAlg()); - pw.close(); - } -} diff --git a/sop-java/src/main/kotlin/sop/MicAlg.kt b/sop-java/src/main/kotlin/sop/MicAlg.kt new file mode 100644 index 0000000..58ce7b5 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/MicAlg.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.OutputStream +import java.io.PrintWriter + +data class MicAlg(val micAlg: String) { + + fun writeTo(outputStream: OutputStream) { + PrintWriter(outputStream).use { it.write(micAlg) } + } + + companion object { + @JvmStatic fun empty() = MicAlg("") + + @JvmStatic + fun fromHashAlgorithmId(id: Int) = + when (id) { + 1 -> "pgp-md5" + 2 -> "pgp-sha1" + 3 -> "pgp-ripemd160" + 8 -> "pgp-sha256" + 9 -> "pgp-sha384" + 10 -> "pgp-sha512" + 11 -> "pgp-sha224" + 12 -> "pgp-sha3-256" + 14 -> "pgp-sha3-512" + else -> throw IllegalArgumentException("Unsupported hash algorithm ID: $id") + }.let { MicAlg(it) } + } +} diff --git a/sop-java/src/test/java/sop/MicAlgTest.java b/sop-java/src/test/java/sop/MicAlgTest.java index 16f54ef..7c6b30a 100644 --- a/sop-java/src/test/java/sop/MicAlgTest.java +++ b/sop-java/src/test/java/sop/MicAlgTest.java @@ -18,7 +18,7 @@ public class MicAlgTest { @Test public void constructorNullArgThrows() { - assertThrows(IllegalArgumentException.class, () -> new MicAlg(null)); + assertThrows(NullPointerException.class, () -> new MicAlg(null)); } @Test From e6562cecff7e8c444f8d410101a6a2c70378399f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:16:37 +0100 Subject: [PATCH 09/46] Kotlin conversion: Ready --- sop-java/src/main/java/sop/Ready.java | 45 ------------------------ sop-java/src/main/kotlin/sop/Ready.kt | 49 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Ready.java create mode 100644 sop-java/src/main/kotlin/sop/Ready.kt diff --git a/sop-java/src/main/java/sop/Ready.java b/sop-java/src/main/java/sop/Ready.java deleted file mode 100644 index 71ab26e..0000000 --- a/sop-java/src/main/java/sop/Ready.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public abstract class Ready { - - /** - * Write the data to the provided output stream. - * - * @param outputStream output stream - * @throws IOException in case of an IO error - */ - public abstract void writeTo(OutputStream outputStream) throws IOException; - - /** - * Return the data as a byte array by writing it to a {@link ByteArrayOutputStream} first and then returning - * the array. - * - * @return data as byte array - * @throws IOException in case of an IO error - */ - public byte[] getBytes() throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - writeTo(bytes); - return bytes.toByteArray(); - } - - /** - * Return an input stream containing the data. - * - * @return input stream - * @throws IOException in case of an IO error - */ - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(getBytes()); - } -} diff --git a/sop-java/src/main/kotlin/sop/Ready.kt b/sop-java/src/main/kotlin/sop/Ready.kt new file mode 100644 index 0000000..1eb3fb3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Ready.kt @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** Abstract class that encapsulates output data, waiting to be consumed. */ +abstract class Ready { + + /** + * Write the data to the provided output stream. + * + * @param outputStream output stream + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) abstract fun writeTo(outputStream: OutputStream) + + /** + * Return the data as a byte array by writing it to a [ByteArrayOutputStream] first and then + * returning the array. + * + * @return data as byte array + * @throws IOException in case of an IO error + */ + val bytes: ByteArray + @Throws(IOException::class) + get() = + ByteArrayOutputStream() + .let { + writeTo(it) + it + } + .toByteArray() + + /** + * Return an input stream containing the data. + * + * @return input stream + * @throws IOException in case of an IO error + */ + val inputStream: InputStream + @Throws(IOException::class) get() = ByteArrayInputStream(bytes) +} From a89e70c19e63362d52b0946aeacad73e6c1bfdc9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:23:26 +0100 Subject: [PATCH 10/46] Kotlin conversion: ReadyWithResult --- .../src/main/java/sop/ReadyWithResult.java | 41 ------------------- .../src/main/kotlin/sop/ReadyWithResult.kt | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 sop-java/src/main/java/sop/ReadyWithResult.java create mode 100644 sop-java/src/main/kotlin/sop/ReadyWithResult.kt diff --git a/sop-java/src/main/java/sop/ReadyWithResult.java b/sop-java/src/main/java/sop/ReadyWithResult.java deleted file mode 100644 index 9feedda..0000000 --- a/sop-java/src/main/java/sop/ReadyWithResult.java +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import sop.exception.SOPGPException; - -public abstract class ReadyWithResult { - - /** - * Write the data e.g. decrypted plaintext to the provided output stream and return the result of the - * processing operation. - * - * @param outputStream output stream - * @return result, eg. signatures - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature if there are no valid signatures found - */ - public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature; - - /** - * Return the data as a {@link ByteArrayAndResult}. - * Calling {@link ByteArrayAndResult#getBytes()} will give you access to the data as byte array, while - * {@link ByteArrayAndResult#getResult()} will grant access to the appended result. - * - * @return byte array and result - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature if there are no valid signatures found - */ - public ByteArrayAndResult toByteArrayAndResult() throws IOException, SOPGPException.NoSignature { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - T result = writeTo(bytes); - return new ByteArrayAndResult<>(bytes.toByteArray(), result); - } -} diff --git a/sop-java/src/main/kotlin/sop/ReadyWithResult.kt b/sop-java/src/main/kotlin/sop/ReadyWithResult.kt new file mode 100644 index 0000000..d309c76 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/ReadyWithResult.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream +import sop.exception.SOPGPException + +abstract class ReadyWithResult { + + /** + * Write the data e.g. decrypted plaintext to the provided output stream and return the result + * of the processing operation. + * + * @param outputStream output stream + * @return result, eg. signatures + * @throws IOException in case of an IO error + * @throws SOPGPException in case of a SOP protocol error + */ + @Throws(IOException::class, SOPGPException::class) + abstract fun writeTo(outputStream: OutputStream): T + + /** + * Return the data as a [ByteArrayAndResult]. Calling [ByteArrayAndResult.bytes] will give you + * access to the data as byte array, while [ByteArrayAndResult.result] will grant access to the + * appended result. + * + * @return byte array and result + * @throws IOException in case of an IO error + * @throws SOPGPException.NoSignature if there are no valid signatures found + */ + @Throws(IOException::class, SOPGPException::class) + fun toByteArrayAndResult() = + ByteArrayOutputStream().let { + val result = writeTo(it) + ByteArrayAndResult(it.toByteArray(), result) + } +} From 2391ffc9b2c5e6585f8eb24219e741eb7cfdd031 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:26:33 +0100 Subject: [PATCH 11/46] Kotlin conversion: DecryptionResult --- .../src/main/java/sop/DecryptionResult.java | 29 ------------------- .../src/main/kotlin/sop/DecryptionResult.kt | 16 ++++++++++ 2 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 sop-java/src/main/java/sop/DecryptionResult.java create mode 100644 sop-java/src/main/kotlin/sop/DecryptionResult.kt diff --git a/sop-java/src/main/java/sop/DecryptionResult.java b/sop-java/src/main/java/sop/DecryptionResult.java deleted file mode 100644 index 4f0e1ab..0000000 --- a/sop-java/src/main/java/sop/DecryptionResult.java +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import sop.util.Optional; - -public class DecryptionResult { - - private final Optional sessionKey; - private final List verifications; - - public DecryptionResult(SessionKey sessionKey, List verifications) { - this.sessionKey = Optional.ofNullable(sessionKey); - this.verifications = Collections.unmodifiableList(verifications); - } - - public Optional getSessionKey() { - return sessionKey; - } - - public List getVerifications() { - return new ArrayList<>(verifications); - } -} diff --git a/sop-java/src/main/kotlin/sop/DecryptionResult.kt b/sop-java/src/main/kotlin/sop/DecryptionResult.kt new file mode 100644 index 0000000..1ba98bb --- /dev/null +++ b/sop-java/src/main/kotlin/sop/DecryptionResult.kt @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.Optional + +data class DecryptionResult +internal constructor(val sessionKey: Optional, val verifications: List) { + + constructor( + sessionKey: SessionKey?, + verifications: List + ) : this(Optional.ofNullable(sessionKey), verifications) +} From dc23c8aa98a9f141af6051da1415b82dfe3a9f70 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:29:58 +0100 Subject: [PATCH 12/46] Kotlin conversion: Signatures --- sop-java/src/main/java/sop/Signatures.java | 21 --------------------- sop-java/src/main/kotlin/sop/Signatures.kt | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 21 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Signatures.java create mode 100644 sop-java/src/main/kotlin/sop/Signatures.kt diff --git a/sop-java/src/main/java/sop/Signatures.java b/sop-java/src/main/java/sop/Signatures.java deleted file mode 100644 index dd3f000..0000000 --- a/sop-java/src/main/java/sop/Signatures.java +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.IOException; -import java.io.OutputStream; - -public abstract class Signatures extends Ready { - - /** - * Write OpenPGP signatures to the provided output stream. - * - * @param signatureOutputStream output stream - * @throws IOException in case of an IO error - */ - @Override - public abstract void writeTo(OutputStream signatureOutputStream) throws IOException; - -} diff --git a/sop-java/src/main/kotlin/sop/Signatures.kt b/sop-java/src/main/kotlin/sop/Signatures.kt new file mode 100644 index 0000000..63aafe9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Signatures.kt @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.IOException +import java.io.OutputStream + +abstract class Signatures : Ready() { + + /** + * Write OpenPGP signatures to the provided output stream. + * + * @param outputStream signature output stream + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) abstract override fun writeTo(outputStream: OutputStream) +} From 31409b7949415b995c5a3a7cf52ad4af40b640bd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:38:53 +0100 Subject: [PATCH 13/46] Kotlin conversion: SOP --- sop-java/src/main/java/sop/SOP.java | 177 ---------------------------- sop-java/src/main/kotlin/sop/SOP.kt | 97 +++++++++++++++ 2 files changed, 97 insertions(+), 177 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SOP.java create mode 100644 sop-java/src/main/kotlin/sop/SOP.kt diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java deleted file mode 100644 index 1200e21..0000000 --- a/sop-java/src/main/java/sop/SOP.java +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.operation.Armor; -import sop.operation.ChangeKeyPassword; -import sop.operation.Dearmor; -import sop.operation.Decrypt; -import sop.operation.Encrypt; -import sop.operation.ExtractCert; -import sop.operation.GenerateKey; -import sop.operation.InlineDetach; -import sop.operation.InlineSign; -import sop.operation.InlineVerify; -import sop.operation.DetachedSign; -import sop.operation.DetachedVerify; -import sop.operation.ListProfiles; -import sop.operation.RevokeKey; -import sop.operation.Version; - -/** - * Stateless OpenPGP Interface. - * This class provides a stateless interface to various OpenPGP related operations. - *
- * Note: Subcommand objects acquired by calling any method of this interface are not intended for reuse. - * If you for example need to generate multiple keys, make a dedicated call to {@link #generateKey()} once per - * key generation. - */ -public interface SOP { - - /** - * Get information about the implementations name and version. - * - * @return version - */ - Version version(); - - /** - * Generate a secret key. - * Customize the operation using the builder {@link GenerateKey}. - * - * @return builder instance - */ - GenerateKey generateKey(); - - /** - * Extract a certificate (public key) from a secret key. - * Customize the operation using the builder {@link ExtractCert}. - * - * @return builder instance - */ - ExtractCert extractCert(); - - /** - * Create detached signatures. - * Customize the operation using the builder {@link DetachedSign}. - *

- * If you want to sign a message inline, use {@link #inlineSign()} instead. - * - * @return builder instance - */ - default DetachedSign sign() { - return detachedSign(); - } - - /** - * Create detached signatures. - * Customize the operation using the builder {@link DetachedSign}. - *

- * If you want to sign a message inline, use {@link #inlineSign()} instead. - * - * @return builder instance - */ - DetachedSign detachedSign(); - - /** - * Sign a message using inline signatures. - *

- * If you need to create detached signatures, use {@link #detachedSign()} instead. - * - * @return builder instance - */ - InlineSign inlineSign(); - - /** - * Verify detached signatures. - * Customize the operation using the builder {@link DetachedVerify}. - *

- * If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. - * - * @return builder instance - */ - default DetachedVerify verify() { - return detachedVerify(); - } - - /** - * Verify detached signatures. - * Customize the operation using the builder {@link DetachedVerify}. - *

- * If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. - * - * @return builder instance - */ - DetachedVerify detachedVerify(); - - /** - * Verify signatures of an inline-signed message. - *

- * If you need to verify detached signatures over a message, use {@link #detachedVerify()} instead. - * - * @return builder instance - */ - InlineVerify inlineVerify(); - - /** - * Detach signatures from an inline signed message. - * - * @return builder instance - */ - InlineDetach inlineDetach(); - - /** - * Encrypt a message. - * Customize the operation using the builder {@link Encrypt}. - * - * @return builder instance - */ - Encrypt encrypt(); - - /** - * Decrypt a message. - * Customize the operation using the builder {@link Decrypt}. - * - * @return builder instance - */ - Decrypt decrypt(); - - /** - * Convert binary OpenPGP data to ASCII. - * Customize the operation using the builder {@link Armor}. - * - * @return builder instance - */ - Armor armor(); - - /** - * Converts ASCII armored OpenPGP data to binary. - * Customize the operation using the builder {@link Dearmor}. - * - * @return builder instance - */ - Dearmor dearmor(); - - /** - * List supported {@link Profile Profiles} of a subcommand. - * - * @return builder instance - */ - ListProfiles listProfiles(); - - /** - * Revoke one or more secret keys. - * - * @return builder instance - */ - RevokeKey revokeKey(); - - /** - * Update a key's password. - * - * @return builder instance - */ - ChangeKeyPassword changeKeyPassword(); -} diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt new file mode 100644 index 0000000..e01763a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.operation.Armor +import sop.operation.ChangeKeyPassword +import sop.operation.Dearmor +import sop.operation.Decrypt +import sop.operation.DetachedSign +import sop.operation.DetachedVerify +import sop.operation.Encrypt +import sop.operation.ExtractCert +import sop.operation.GenerateKey +import sop.operation.InlineDetach +import sop.operation.InlineSign +import sop.operation.InlineVerify +import sop.operation.ListProfiles +import sop.operation.RevokeKey +import sop.operation.Version + +/** + * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related + * operations. Note: Subcommand objects acquired by calling any method of this interface are not + * intended for reuse. If you for example need to generate multiple keys, make a dedicated call to + * [generateKey] once per key generation. + */ +interface SOP { + + /** Get information about the implementations name and version. */ + fun version(): Version + + /** Generate a secret key. */ + fun generateKey(): GenerateKey + + /** Extract a certificate (public key) from a secret key. */ + fun extractCert(): ExtractCert + + /** + * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. + */ + fun sign(): DetachedSign = detachedSign() + + /** + * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. + */ + fun detachedSign(): DetachedSign + + /** + * Sign a message using inline signatures. If you need to create detached signatures, use + * [detachedSign] instead. + */ + fun inlineSign(): InlineSign + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun verify(): DetachedVerify = detachedVerify() + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun detachedVerify(): DetachedVerify + + /** + * Verify signatures of an inline-signed message. If you need to verify detached signatures over + * a message, use [detachedVerify] instead. + */ + fun inlineVerify(): InlineVerify + + /** Detach signatures from an inline signed message. */ + fun inlineDetach(): InlineDetach + + /** Encrypt a message. */ + fun encrypt(): Encrypt + + /** Decrypt a message. */ + fun decrypt(): Decrypt + + /** Convert binary OpenPGP data to ASCII. */ + fun armor(): Armor + + /** Converts ASCII armored OpenPGP data to binary. */ + fun dearmor(): Dearmor + + /** List supported [Profiles][Profile] of a subcommand. */ + fun listProfiles(): ListProfiles + + /** Revoke one or more secret keys. */ + fun revokeKey(): RevokeKey + + /** Update a key's password. */ + fun changeKeyPassword(): ChangeKeyPassword +} From e68d6df57f86da9b4881205fe7f6b455d25c8690 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:50:46 +0100 Subject: [PATCH 14/46] Kotlin conversion: AbstractSign --- .../main/java/sop/operation/AbstractSign.java | 85 ------------------- .../main/kotlin/sop/operation/AbstractSign.kt | 79 +++++++++++++++++ 2 files changed, 79 insertions(+), 85 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/AbstractSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/AbstractSign.kt diff --git a/sop-java/src/main/java/sop/operation/AbstractSign.java b/sop-java/src/main/java/sop/operation/AbstractSign.java deleted file mode 100644 index 508d741..0000000 --- a/sop-java/src/main/java/sop/operation/AbstractSign.java +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface AbstractSign { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - T noArmor(); - - /** - * Add one or more signing keys. - * - * @param key input stream containing encoded keys - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - T key(InputStream key) - throws SOPGPException.KeyCannotSign, - SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Add one or more signing keys. - * - * @param key byte array containing encoded keys - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default T key(byte[] key) - throws SOPGPException.KeyCannotSign, - SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return key(new ByteArrayInputStream(key)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable - */ - default T withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable - */ - T withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - -} diff --git a/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt new file mode 100644 index 0000000..0258432 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.KeyCannotSign +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.exception.SOPGPException.UnsupportedAsymmetricAlgo +import sop.exception.SOPGPException.UnsupportedOption +import sop.util.UTF8Util + +/** + * Interface for signing operations. + * + * @param builder subclass + */ +interface AbstractSign { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): T + + /** + * Add one or more signing keys. + * + * @param key input stream containing encoded keys + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws BadData if the [InputStream] does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun key(key: InputStream): T + + /** + * Add one or more signing keys. + * + * @param key byte array containing encoded keys + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun key(key: ByteArray): T = key(key.inputStream()) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the provided passphrase is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): T = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the provided passphrase is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): T +} From 08ddc5d8a529aa1acfb65b7e849a82b2ca596534 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:51:30 +0100 Subject: [PATCH 15/46] Kotlin conversion: AbstractVerify --- .../java/sop/operation/AbstractVerify.java | 68 ------------------- .../kotlin/sop/operation/AbstractVerify.kt | 57 ++++++++++++++++ 2 files changed, 57 insertions(+), 68 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/AbstractVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt diff --git a/sop-java/src/main/java/sop/operation/AbstractVerify.java b/sop-java/src/main/java/sop/operation/AbstractVerify.java deleted file mode 100644 index 51d84b7..0000000 --- a/sop-java/src/main/java/sop/operation/AbstractVerify.java +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -/** - * Common API methods shared between verification of inline signatures ({@link InlineVerify}) - * and verification of detached signatures ({@link DetachedVerify}). - * - * @param Builder type ({@link DetachedVerify}, {@link InlineVerify}) - */ -public interface AbstractVerify { - - /** - * Makes the SOP implementation consider signatures before this date invalid. - * - * @param timestamp timestamp - * @return builder instance - */ - T notBefore(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Makes the SOP implementation consider signatures after this date invalid. - * - * @param timestamp timestamp - * @return builder instance - */ - T notAfter(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Add one or more verification cert. - * - * @param cert input stream containing the encoded certs - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - T cert(InputStream cert) - throws SOPGPException.BadData, - IOException; - - /** - * Add one or more verification cert. - * - * @param cert byte array containing the encoded certs - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - default T cert(byte[] cert) - throws SOPGPException.BadData, - IOException { - return cert(new ByteArrayInputStream(cert)); - } - -} diff --git a/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt b/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt new file mode 100644 index 0000000..3554ea3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import java.util.* +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +/** + * Common API methods shared between verification of inline signatures ([InlineVerify]) and + * verification of detached signatures ([DetachedVerify]). + * + * @param Builder type ([DetachedVerify], [InlineVerify]) + */ +interface AbstractVerify { + + /** + * Makes the SOP implementation consider signatures before this date invalid. + * + * @param timestamp timestamp + * @return builder instance + */ + @Throws(UnsupportedOption::class) fun notBefore(timestamp: Date): T + + /** + * Makes the SOP implementation consider signatures after this date invalid. + * + * @param timestamp timestamp + * @return builder instance + */ + @Throws(UnsupportedOption::class) fun notAfter(timestamp: Date): T + + /** + * Add one or more verification cert. + * + * @param cert input stream containing the encoded certs + * @return builder instance + * @throws BadData if the input stream does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun cert(cert: InputStream): T + + /** + * Add one or more verification cert. + * + * @param cert byte array containing the encoded certs + * @return builder instance + * @throws BadData if the byte array does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun cert(cert: ByteArray): T = cert(cert.inputStream()) +} From 4a123a198078f81c80640716d51c574a956dccda Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:54:24 +0100 Subject: [PATCH 16/46] Kotlin conversion: Armor --- .../src/main/java/sop/operation/Armor.java | 53 ------------------- .../src/main/kotlin/sop/operation/Armor.kt | 46 ++++++++++++++++ 2 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Armor.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Armor.kt diff --git a/sop-java/src/main/java/sop/operation/Armor.java b/sop-java/src/main/java/sop/operation/Armor.java deleted file mode 100644 index a625808..0000000 --- a/sop-java/src/main/java/sop/operation/Armor.java +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.enums.ArmorLabel; -import sop.exception.SOPGPException; - -public interface Armor { - - /** - * Overrides automatic detection of label. - * - * @param label armor label - * @return builder instance - */ - Armor label(ArmorLabel label) - throws SOPGPException.UnsupportedOption; - - /** - * Armor the provided data. - * - * @param data input stream of unarmored OpenPGP data - * @return armored data - * - * @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken - * @throws IOException in case of an IO error - */ - Ready data(InputStream data) - throws SOPGPException.BadData, - IOException; - - /** - * Armor the provided data. - * - * @param data unarmored OpenPGP data - * @return armored data - * - * @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken - * @throws IOException in case of an IO error - */ - default Ready data(byte[] data) - throws SOPGPException.BadData, - IOException { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Armor.kt b/sop-java/src/main/kotlin/sop/operation/Armor.kt new file mode 100644 index 0000000..e89708b --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Armor.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.enums.ArmorLabel +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +interface Armor { + + /** + * Overrides automatic detection of label. + * + * @param label armor label + * @return builder instance + */ + @Deprecated("Use of armor labels is deprecated and will be removed in a future release.") + @Throws(UnsupportedOption::class) + fun label(label: ArmorLabel): Armor + + /** + * Armor the provided data. + * + * @param data input stream of unarmored OpenPGP data + * @return armored data + * @throws BadData if the data appears to be OpenPGP packets, but those are broken + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready + + /** + * Armor the provided data. + * + * @param data unarmored OpenPGP data + * @return armored data + * @throws BadData if the data appears to be OpenPGP packets, but those are broken + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) +} From 34e1d8992f2b6adb626dd25dd6c940a233a687bc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:00:49 +0100 Subject: [PATCH 17/46] Kotlin conversion: ChangeKeyPassword --- .../java/sop/operation/ChangeKeyPassword.java | 83 ---------------- .../kotlin/sop/operation/ChangeKeyPassword.kt | 95 +++++++++++++++++++ 2 files changed, 95 insertions(+), 83 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ChangeKeyPassword.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt diff --git a/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java b/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java deleted file mode 100644 index 460a20a..0000000 --- a/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.CharacterCodingException; - -public interface ChangeKeyPassword { - - /** - * Disable ASCII armoring of the output. - * - * @return builder instance - */ - ChangeKeyPassword noArmor(); - - default ChangeKeyPassword oldKeyPassphrase(byte[] password) { - try { - return oldKeyPassphrase(UTF8Util.decodeUTF8(password)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); - } - } - - /** - * Provide a passphrase to unlock the secret key. - * This method can be provided multiple times to provide separate passphrases that are tried as a - * means to unlock any secret key material encountered. - * - * @param oldPassphrase old passphrase - * @return builder instance - */ - ChangeKeyPassword oldKeyPassphrase(String oldPassphrase); - - /** - * Provide a passphrase to re-lock the secret key with. - * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. - * If this method is not called, the key material will not be protected. - * - * @param newPassphrase new passphrase - * @return builder instance - */ - default ChangeKeyPassword newKeyPassphrase(byte[] newPassphrase) { - try { - return newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); - } - } - - /** - * Provide a passphrase to re-lock the secret key with. - * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. - * If this method is not called, the key material will not be protected. - * - * @param newPassphrase new passphrase - * @return builder instance - */ - ChangeKeyPassword newKeyPassphrase(String newPassphrase); - - default Ready keys(byte[] keys) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { - return keys(new ByteArrayInputStream(keys)); - } - - /** - * Provide the key material. - * - * @param inputStream input stream of secret key material - * @return ready - * - * @throws sop.exception.SOPGPException.KeyIsProtected if any (sub-) key encountered cannot be unlocked. - * @throws sop.exception.SOPGPException.BadData if the key material is malformed - */ - Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData; - -} diff --git a/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt new file mode 100644 index 0000000..224e0f4 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.KeyIsProtected +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.util.UTF8Util + +interface ChangeKeyPassword { + + /** + * Disable ASCII armoring of the output. + * + * @return builder instance + */ + fun noArmor(): ChangeKeyPassword + + /** + * Provide a passphrase to unlock the secret key. This method can be provided multiple times to + * provide separate passphrases that are tried as a means to unlock any secret key material + * encountered. + * + * @param oldPassphrase old passphrase + * @return builder instance + */ + @Throws(PasswordNotHumanReadable::class) + fun oldKeyPassphrase(oldPassphrase: ByteArray): ChangeKeyPassword = + try { + oldKeyPassphrase(UTF8Util.decodeUTF8(oldPassphrase)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") + } + + /** + * Provide a passphrase to unlock the secret key. This method can be provided multiple times to + * provide separate passphrases that are tried as a means to unlock any secret key material + * encountered. + * + * @param oldPassphrase old passphrase + * @return builder instance + */ + fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword + + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + @Throws(PasswordNotHumanReadable::class) + fun newKeyPassphrase(newPassphrase: ByteArray): ChangeKeyPassword = + try { + newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") + } + + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + fun newKeyPassphrase(newPassphrase: String): ChangeKeyPassword + + /** + * Provide the key material. + * + * @param keys input stream of secret key material + * @return ready + * @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked. + * @throws BadData if the key material is malformed + */ + @Throws(KeyIsProtected::class, BadData::class) + fun keys(keys: ByteArray): Ready = keys(keys.inputStream()) + + /** + * Provide the key material. + * + * @param keys input stream of secret key material + * @return ready + * @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked. + * @throws BadData if the key material is malformed + */ + @Throws(KeyIsProtected::class, BadData::class) fun keys(keys: InputStream): Ready +} From 39c222dfc893d780d8daaee8fd47854ecb17d143 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:03:18 +0100 Subject: [PATCH 18/46] Kotlin conversion: Dearmor --- .../src/main/java/sop/operation/Dearmor.java | 59 ------------------- .../src/main/kotlin/sop/operation/Dearmor.kt | 46 +++++++++++++++ 2 files changed, 46 insertions(+), 59 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Dearmor.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Dearmor.kt diff --git a/sop-java/src/main/java/sop/operation/Dearmor.java b/sop-java/src/main/java/sop/operation/Dearmor.java deleted file mode 100644 index 524dc8c..0000000 --- a/sop-java/src/main/java/sop/operation/Dearmor.java +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -public interface Dearmor { - - /** - * Dearmor armored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - Ready data(InputStream data) - throws SOPGPException.BadData, - IOException; - - /** - * Dearmor armored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - default Ready data(byte[] data) - throws SOPGPException.BadData, - IOException { - return data(new ByteArrayInputStream(data)); - } - - /** - * Dearmor amored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - default Ready data(String data) - throws SOPGPException.BadData, - IOException { - return data(data.getBytes(UTF8Util.UTF8)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Dearmor.kt b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt new file mode 100644 index 0000000..cc5e98d --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData +import sop.util.UTF8Util + +interface Dearmor { + + /** + * Dearmor armored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready + + /** + * Dearmor armored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) + + /** + * Dearmor amored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: String): Ready = data(data.toByteArray(UTF8Util.UTF8)) +} From 91a861b5c366d54ddff4b1625e920e0e75d476cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:08:03 +0100 Subject: [PATCH 19/46] Kotlin conversion: Decrypt --- .../src/main/java/sop/operation/Decrypt.java | 193 ------------------ .../src/main/kotlin/sop/operation/Decrypt.kt | 169 +++++++++++++++ 2 files changed, 169 insertions(+), 193 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Decrypt.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Decrypt.kt diff --git a/sop-java/src/main/java/sop/operation/Decrypt.java b/sop-java/src/main/java/sop/operation/Decrypt.java deleted file mode 100644 index 0123bbc..0000000 --- a/sop-java/src/main/java/sop/operation/Decrypt.java +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -public interface Decrypt { - - /** - * Makes the SOP consider signatures before this date invalid. - * - * @param timestamp timestamp - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt verifyNotBefore(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Makes the SOP consider signatures after this date invalid. - * - * @param timestamp timestamp - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt verifyNotAfter(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Adds one or more verification cert. - * - * @param cert input stream containing the cert(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} doesn't provide an OpenPGP certificate - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Decrypt verifyWithCert(InputStream cert) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Adds one or more verification cert. - * - * @param cert byte array containing the cert(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array doesn't contain an OpenPGP certificate - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default Decrypt verifyWithCert(byte[] cert) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return verifyWithCert(new ByteArrayInputStream(cert)); - } - - /** - * Tries to decrypt with the given session key. - * - * @param sessionKey session key - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt withSessionKey(SessionKey sessionKey) - throws SOPGPException.UnsupportedOption; - - /** - * Tries to decrypt with the given password. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt withPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Adds one or more decryption key. - * - * @param key input stream containing the key(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Decrypt withKey(InputStream key) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Adds one or more decryption key. - * - * @param key byte array containing the key(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default Decrypt withKey(byte[] key) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return withKey(new ByteArrayInputStream(key)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - default Decrypt withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - Decrypt withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - - /** - * Decrypts the given ciphertext, returning verification results and plaintext. - * @param ciphertext ciphertext - * @return ready with result - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP message - * @throws sop.exception.SOPGPException.MissingArg if an argument required for decryption was not provided - * @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason - * @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) - * @throws IOException in case of an IO error - */ - ReadyWithResult ciphertext(InputStream ciphertext) - throws SOPGPException.BadData, - SOPGPException.MissingArg, - SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, - IOException; - - /** - * Decrypts the given ciphertext, returning verification results and plaintext. - * @param ciphertext ciphertext - * @return ready with result - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an encrypted OpenPGP message - * @throws sop.exception.SOPGPException.MissingArg in case of missing decryption method (password or key required) - * @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason - * @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) - * @throws IOException in case of an IO error - */ - default ReadyWithResult ciphertext(byte[] ciphertext) - throws SOPGPException.BadData, - SOPGPException.MissingArg, - SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, - IOException { - return ciphertext(new ByteArrayInputStream(ciphertext)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt new file mode 100644 index 0000000..9c24c54 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import java.util.* +import sop.DecryptionResult +import sop.ReadyWithResult +import sop.SessionKey +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface Decrypt { + + /** + * Makes the SOP consider signatures before this date invalid. + * + * @param timestamp timestamp + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun verifyNotBefore(timestamp: Date): Decrypt + + /** + * Makes the SOP consider signatures after this date invalid. + * + * @param timestamp timestamp + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun verifyNotAfter(timestamp: Date): Decrypt + + /** + * Adds one or more verification cert. + * + * @param cert input stream containing the cert(s) + * @return builder instance + * @throws BadData if the [InputStream] doesn't provide an OpenPGP certificate + * @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun verifyWithCert(cert: InputStream): Decrypt + + /** + * Adds one or more verification cert. + * + * @param cert byte array containing the cert(s) + * @return builder instance + * @throws BadData if the byte array doesn't contain an OpenPGP certificate + * @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun verifyWithCert(cert: ByteArray): Decrypt = verifyWithCert(cert.inputStream()) + + /** + * Tries to decrypt with the given session key. + * + * @param sessionKey session key + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun withSessionKey(sessionKey: SessionKey): Decrypt + + /** + * Tries to decrypt with the given password. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if this option is not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withPassword(password: String): Decrypt + + /** + * Adds one or more decryption key. + * + * @param key input stream containing the key(s) + * @return builder instance + * @throws BadData if the [InputStream] does not provide an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun withKey(key: InputStream): Decrypt + + /** + * Adds one or more decryption key. + * + * @param key byte array containing the key(s) + * @return builder instance + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun withKey(key: ByteArray): Decrypt { + return withKey(key.inputStream()) + } + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): Decrypt { + return withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + } + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): Decrypt + + /** + * Decrypts the given ciphertext, returning verification results and plaintext. + * + * @param ciphertext ciphertext + * @return ready with result + * @throws BadData if the [InputStream] does not provide an OpenPGP message + * @throws MissingArg if an argument required for decryption was not provided + * @throws CannotDecrypt in case decryption fails for some reason + * @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) + * @throws IOException in case of an IO error + */ + @Throws( + BadData::class, + MissingArg::class, + CannotDecrypt::class, + KeyIsProtected::class, + IOException::class) + fun ciphertext(ciphertext: InputStream): ReadyWithResult + + /** + * Decrypts the given ciphertext, returning verification results and plaintext. + * + * @param ciphertext ciphertext + * @return ready with result + * @throws BadData if the byte array does not contain an encrypted OpenPGP message + * @throws MissingArg in case of missing decryption method (password or key required) + * @throws CannotDecrypt in case decryption fails for some reason + * @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) + * @throws IOException in case of an IO error + */ + @Throws( + BadData::class, + MissingArg::class, + CannotDecrypt::class, + KeyIsProtected::class, + IOException::class) + fun ciphertext(ciphertext: ByteArray): ReadyWithResult { + return ciphertext(ciphertext.inputStream()) + } +} From 4dc1779a06be4aadffdd39855cb6e4da081cde00 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:10:06 +0100 Subject: [PATCH 20/46] Kotlin conversion: DetachedSign --- .../main/java/sop/operation/DetachedSign.java | 61 ------------------- .../main/kotlin/sop/operation/DetachedSign.kt | 50 +++++++++++++++ 2 files changed, 50 insertions(+), 61 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/DetachedSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/DetachedSign.kt diff --git a/sop-java/src/main/java/sop/operation/DetachedSign.java b/sop-java/src/main/java/sop/operation/DetachedSign.java deleted file mode 100644 index 745077d..0000000 --- a/sop-java/src/main/java/sop/operation/DetachedSign.java +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.ReadyWithResult; -import sop.SigningResult; -import sop.enums.SignAs; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface DetachedSign extends AbstractSign { - - /** - * Sets the signature mode. - * Note: This method has to be called before {@link #key(InputStream)} is called. - * - * @param mode signature mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - DetachedSign mode(SignAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Signs data. - * - * @param data input stream containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - ReadyWithResult data(InputStream data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText; - - /** - * Signs data. - * - * @param data byte array containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - default ReadyWithResult data(byte[] data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt new file mode 100644 index 0000000..c0e62dd --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.SigningResult +import sop.enums.SignAs +import sop.exception.SOPGPException.* + +interface DetachedSign : AbstractSign { + + /** + * Sets the signature mode. Note: This method has to be called before [key] is called. + * + * @param mode signature mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: SignAs): DetachedSign + + /** + * Signs data. + * + * @param data input stream containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: InputStream): ReadyWithResult + + /** + * Signs data. + * + * @param data byte array containing data + * @return ready + * @throws IOException in case of an IO error + * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be + * unlocked + * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data + * was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: ByteArray): ReadyWithResult = data(data.inputStream()) +} From ee6975c7d3ab039b917aef476de44af7f542bef2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:11:11 +0100 Subject: [PATCH 21/46] Decrypt: Use return statement --- sop-java/src/main/kotlin/sop/operation/Decrypt.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt index 9c24c54..ae228e9 100644 --- a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt @@ -99,9 +99,7 @@ interface Decrypt { * @throws IOException in case of an IO error */ @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) - fun withKey(key: ByteArray): Decrypt { - return withKey(key.inputStream()) - } + fun withKey(key: ByteArray): Decrypt = withKey(key.inputStream()) /** * Provide the decryption password for the secret key. @@ -112,9 +110,8 @@ interface Decrypt { * @throws PasswordNotHumanReadable if the password is not human-readable */ @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) - fun withKeyPassword(password: String): Decrypt { - return withKeyPassword(password.toByteArray(UTF8Util.UTF8)) - } + fun withKeyPassword(password: String): Decrypt = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) /** * Provide the decryption password for the secret key. @@ -163,7 +160,6 @@ interface Decrypt { CannotDecrypt::class, KeyIsProtected::class, IOException::class) - fun ciphertext(ciphertext: ByteArray): ReadyWithResult { - return ciphertext(ciphertext.inputStream()) - } + fun ciphertext(ciphertext: ByteArray): ReadyWithResult = + ciphertext(ciphertext.inputStream()) } From e681090757312dedcc90cc9fc35ee7f64118c99d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:12:56 +0100 Subject: [PATCH 22/46] Kotlin conversion DetachedVerify --- .../java/sop/operation/DetachedVerify.java | 45 ------------------- .../kotlin/sop/operation/DetachedVerify.kt | 36 +++++++++++++++ 2 files changed, 36 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/DetachedVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt diff --git a/sop-java/src/main/java/sop/operation/DetachedVerify.java b/sop-java/src/main/java/sop/operation/DetachedVerify.java deleted file mode 100644 index 9dee870..0000000 --- a/sop-java/src/main/java/sop/operation/DetachedVerify.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * API for verifying detached signatures. - */ -public interface DetachedVerify extends AbstractVerify, VerifySignatures { - - /** - * Provides the detached signatures. - * @param signatures input stream containing encoded, detached signatures. - * - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain OpenPGP signatures - * @throws IOException in case of an IO error - */ - VerifySignatures signatures(InputStream signatures) - throws SOPGPException.BadData, - IOException; - - /** - * Provides the detached signatures. - * @param signatures byte array containing encoded, detached signatures. - * - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain OpenPGP signatures - * @throws IOException in case of an IO error - */ - default VerifySignatures signatures(byte[] signatures) - throws SOPGPException.BadData, - IOException { - return signatures(new ByteArrayInputStream(signatures)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt new file mode 100644 index 0000000..2dd9218 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException.BadData + +interface DetachedVerify : AbstractVerify, VerifySignatures { + + /** + * Provides the detached signatures. + * + * @param signatures input stream containing encoded, detached signatures. + * @return builder instance + * @throws BadData if the input stream does not contain OpenPGP signatures + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun signatures(signatures: InputStream): VerifySignatures + + /** + * Provides the detached signatures. + * + * @param signatures byte array containing encoded, detached signatures. + * @return builder instance + * @throws BadData if the byte array does not contain OpenPGP signatures + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun signatures(signatures: ByteArray): VerifySignatures = + signatures(ByteArrayInputStream(signatures)) +} From 41db9d2ac77d7b8e5c2fe79ae02ec36efb6bc240 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:15:56 +0100 Subject: [PATCH 23/46] Kotlin conversion: Encrypt --- .../src/main/java/sop/operation/Encrypt.java | 193 ------------------ .../src/main/kotlin/sop/operation/Encrypt.kt | 166 +++++++++++++++ 2 files changed, 166 insertions(+), 193 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Encrypt.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Encrypt.kt diff --git a/sop-java/src/main/java/sop/operation/Encrypt.java b/sop-java/src/main/java/sop/operation/Encrypt.java deleted file mode 100644 index b380d32..0000000 --- a/sop-java/src/main/java/sop/operation/Encrypt.java +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Profile; -import sop.Ready; -import sop.enums.EncryptAs; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface Encrypt { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - Encrypt noArmor(); - - /** - * Sets encryption mode. - * - * @param mode mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Encrypt mode(EncryptAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Adds the signer key. - * - * @param key input stream containing the encoded signer key - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - * @throws IOException in case of an IO error - */ - Encrypt signWith(InputStream key) - throws SOPGPException.KeyCannotSign, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException; - - /** - * Adds the signer key. - * - * @param key byte array containing the encoded signer key - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws IOException in case of an IO error - */ - default Encrypt signWith(byte[] key) - throws SOPGPException.KeyCannotSign, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException { - return signWith(new ByteArrayInputStream(key)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported - */ - default Encrypt withKeyPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported - */ - Encrypt withKeyPassword(byte[] password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Encrypt with the given password. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Encrypt withPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Encrypt with the given cert. - * - * @param cert input stream containing the encoded cert. - * @return builder instance - * - * @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - Encrypt withCert(InputStream cert) - throws SOPGPException.CertCannotEncrypt, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException; - - /** - * Encrypt with the given cert. - * - * @param cert byte array containing the encoded cert. - * @return builder instance - * - * @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - default Encrypt withCert(byte[] cert) - throws SOPGPException.CertCannotEncrypt, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException { - return withCert(new ByteArrayInputStream(cert)); - } - - /** - * Pass in a profile. - * - * @param profile profile - * @return builder instance - */ - default Encrypt profile(Profile profile) { - return profile(profile.getName()); - } - - /** - * Pass in a profile identifier. - * - * @param profileName profile identifier - * @return builder instance - */ - Encrypt profile(String profileName); - - /** - * Encrypt the given data yielding the ciphertext. - * @param plaintext plaintext - * @return input stream containing the ciphertext - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - */ - Ready plaintext(InputStream plaintext) - throws IOException, - SOPGPException.KeyIsProtected; - - /** - * Encrypt the given data yielding the ciphertext. - * @param plaintext plaintext - * @return input stream containing the ciphertext - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - */ - default Ready plaintext(byte[] plaintext) - throws IOException, - SOPGPException.KeyIsProtected { - return plaintext(new ByteArrayInputStream(plaintext)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt new file mode 100644 index 0000000..ad30149 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.Profile +import sop.Ready +import sop.enums.EncryptAs +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface Encrypt { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): Encrypt + + /** + * Sets encryption mode. + * + * @param mode mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: EncryptAs): Encrypt + + /** + * Adds the signer key. + * + * @param key input stream containing the encoded signer key + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws BadData if the [InputStream] does not contain an OpenPGP key + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) + fun signWith(key: InputStream): Encrypt + + /** + * Adds the signer key. + * + * @param key byte array containing the encoded signer key + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) + fun signWith(key: ByteArray): Encrypt = signWith(ByteArrayInputStream(key)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key password are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: String): Encrypt = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key password are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): Encrypt + + /** + * Encrypt with the given password. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if this option is not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withPassword(password: String): Encrypt + + /** + * Encrypt with the given cert. + * + * @param cert input stream containing the encoded cert. + * @return builder instance + * @throws CertCannotEncrypt if the certificate is not encryption capable + * @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm + * @throws BadData if the [InputStream] does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws( + CertCannotEncrypt::class, + UnsupportedAsymmetricAlgo::class, + BadData::class, + IOException::class) + fun withCert(cert: InputStream): Encrypt + + /** + * Encrypt with the given cert. + * + * @param cert byte array containing the encoded cert. + * @return builder instance + * @throws CertCannotEncrypt if the certificate is not encryption capable + * @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm + * @throws BadData if the byte array does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws( + CertCannotEncrypt::class, + UnsupportedAsymmetricAlgo::class, + BadData::class, + IOException::class) + fun withCert(cert: ByteArray): Encrypt = withCert(ByteArrayInputStream(cert)) + + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + fun profile(profile: Profile): Encrypt = profile(profile.name) + + /** + * Pass in a profile identifier. + * + * @param profileName profile identifier + * @return builder instance + */ + fun profile(profileName: String): Encrypt + + /** + * Encrypt the given data yielding the ciphertext. + * + * @param plaintext plaintext + * @return input stream containing the ciphertext + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + */ + @Throws(IOException::class, KeyIsProtected::class) fun plaintext(plaintext: InputStream): Ready + + /** + * Encrypt the given data yielding the ciphertext. + * + * @param plaintext plaintext + * @return input stream containing the ciphertext + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + */ + @Throws(IOException::class, KeyIsProtected::class) + fun plaintext(plaintext: ByteArray): Ready = plaintext(ByteArrayInputStream(plaintext)) +} From 653675f730788593566aba75879870d4e39f652e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:17:12 +0100 Subject: [PATCH 24/46] Kotlin conversion: ExtractCert --- .../main/java/sop/operation/ExtractCert.java | 50 ------------------- .../main/kotlin/sop/operation/ExtractCert.kt | 42 ++++++++++++++++ 2 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ExtractCert.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ExtractCert.kt diff --git a/sop-java/src/main/java/sop/operation/ExtractCert.java b/sop-java/src/main/java/sop/operation/ExtractCert.java deleted file mode 100644 index a862d33..0000000 --- a/sop-java/src/main/java/sop/operation/ExtractCert.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.exception.SOPGPException; - -public interface ExtractCert { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - ExtractCert noArmor(); - - /** - * Extract the cert(s) from the provided key(s). - * - * @param keyInputStream input stream containing the encoding of one or more OpenPGP keys - * @return result containing the encoding of the keys certs - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - */ - Ready key(InputStream keyInputStream) - throws IOException, - SOPGPException.BadData; - - /** - * Extract the cert(s) from the provided key(s). - * - * @param key byte array containing the encoding of one or more OpenPGP key - * @return result containing the encoding of the keys certs - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - */ - default Ready key(byte[] key) - throws IOException, - SOPGPException.BadData { - return key(new ByteArrayInputStream(key)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt new file mode 100644 index 0000000..bd48e08 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData + +interface ExtractCert { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): ExtractCert + + /** + * Extract the cert(s) from the provided key(s). + * + * @param keyInputStream input stream containing the encoding of one or more OpenPGP keys + * @return result containing the encoding of the keys certs + * @throws IOException in case of an IO error + * @throws BadData if the [InputStream] does not contain an OpenPGP key + */ + @Throws(IOException::class, BadData::class) fun key(keyInputStream: InputStream): Ready + + /** + * Extract the cert(s) from the provided key(s). + * + * @param key byte array containing the encoding of one or more OpenPGP key + * @return result containing the encoding of the keys certs + * @throws IOException in case of an IO error + * @throws BadData if the byte array does not contain an OpenPGP key + */ + @Throws(IOException::class, BadData::class) + fun key(key: ByteArray): Ready = key(ByteArrayInputStream(key)) +} From 3e6ebe1cc44968b1ecad4d6244fc43f874a2aa64 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:19:08 +0100 Subject: [PATCH 25/46] Kotlin conversion: GenerateKey --- .../main/java/sop/operation/GenerateKey.java | 103 ------------------ .../main/kotlin/sop/operation/GenerateKey.kt | 91 ++++++++++++++++ 2 files changed, 91 insertions(+), 103 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/GenerateKey.java create mode 100644 sop-java/src/main/kotlin/sop/operation/GenerateKey.kt diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java deleted file mode 100644 index 77afea1..0000000 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.CharacterCodingException; - -import sop.Profile; -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -public interface GenerateKey { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - GenerateKey noArmor(); - - /** - * Adds a user-id. - * - * @param userId user-id - * @return builder instance - */ - GenerateKey userId(String userId); - - /** - * Set a password for the key. - * - * @param password password to protect the key - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - GenerateKey withKeyPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Set a password for the key. - * - * @param password password to protect the key - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - */ - default GenerateKey withKeyPassword(byte[] password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption { - try { - return withKeyPassword(UTF8Util.decodeUTF8(password)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable(); - } - } - - /** - * Pass in a profile. - * - * @param profile profile - * @return builder instance - */ - default GenerateKey profile(Profile profile) { - return profile(profile.getName()); - } - - /** - * Pass in a profile identifier. - * - * @param profile profile identifier - * @return builder instance - */ - GenerateKey profile(String profile); - - /** - * If this options is set, the generated key will not be capable of encryption / decryption. - * - * @return builder instance - */ - GenerateKey signingOnly(); - - /** - * Generate the OpenPGP key and return it encoded as an {@link InputStream}. - * - * @return key - * - * @throws sop.exception.SOPGPException.MissingArg if no user-id was provided - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Ready generate() - throws SOPGPException.MissingArg, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; -} diff --git a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt new file mode 100644 index 0000000..3b83b99 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import sop.Profile +import sop.Ready +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface GenerateKey { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): GenerateKey + + /** + * Adds a user-id. + * + * @param userId user-id + * @return builder instance + */ + fun userId(userId: String): GenerateKey + + /** + * Set a password for the key. + * + * @param password password to protect the key + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: String): GenerateKey + + /** + * Set a password for the key. + * + * @param password password to protect the key + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key passwords are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): GenerateKey = + try { + withKeyPassword(UTF8Util.decodeUTF8(password)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable() + } + + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + fun profile(profile: Profile): GenerateKey = profile(profile.name) + + /** + * Pass in a profile identifier. + * + * @param profile profile identifier + * @return builder instance + */ + fun profile(profile: String): GenerateKey + + /** + * If this options is set, the generated key will not be capable of encryption / decryption. + * + * @return builder instance + */ + fun signingOnly(): GenerateKey + + /** + * Generate the OpenPGP key and return it encoded as an [InputStream]. + * + * @return key + * @throws MissingArg if no user-id was provided + * @throws UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric + * algorithm + * @throws IOException in case of an IO error + */ + @Throws(MissingArg::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun generate(): Ready +} From 8df4a520bdb6c2e37ea81cf71f0cf7e8b422eb7e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:20:29 +0100 Subject: [PATCH 26/46] Kotlin conversion: InlineDetach --- .../main/java/sop/operation/InlineDetach.java | 52 ------------------- .../main/kotlin/sop/operation/InlineDetach.kt | 43 +++++++++++++++ 2 files changed, 43 insertions(+), 52 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineDetach.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineDetach.kt diff --git a/sop-java/src/main/java/sop/operation/InlineDetach.java b/sop-java/src/main/java/sop/operation/InlineDetach.java deleted file mode 100644 index aba40b1..0000000 --- a/sop-java/src/main/java/sop/operation/InlineDetach.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.ReadyWithResult; -import sop.Signatures; -import sop.exception.SOPGPException; - -/** - * Split cleartext signed messages up into data and signatures. - */ -public interface InlineDetach { - - /** - * Do not wrap the signatures in ASCII armor. - * @return builder - */ - InlineDetach noArmor(); - - /** - * Detach the provided signed message from its signatures. - * - * @param messageInputStream input stream containing the signed message - * @return result containing the detached message - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain a signed message - */ - ReadyWithResult message(InputStream messageInputStream) - throws IOException, - SOPGPException.BadData; - - /** - * Detach the provided cleartext signed message from its signatures. - * - * @param message byte array containing the signed message - * @return result containing the detached message - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain a signed message - */ - default ReadyWithResult message(byte[] message) - throws IOException, - SOPGPException.BadData { - return message(new ByteArrayInputStream(message)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt new file mode 100644 index 0000000..941a9bf --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.Signatures +import sop.exception.SOPGPException.BadData + +interface InlineDetach { + + /** + * Do not wrap the signatures in ASCII armor. + * + * @return builder + */ + fun noArmor(): InlineDetach + + /** + * Detach the provided signed message from its signatures. + * + * @param messageInputStream input stream containing the signed message + * @return result containing the detached message + * @throws IOException in case of an IO error + * @throws BadData if the input stream does not contain a signed message + */ + @Throws(IOException::class, BadData::class) + fun message(messageInputStream: InputStream): ReadyWithResult + + /** + * Detach the provided cleartext signed message from its signatures. + * + * @param message byte array containing the signed message + * @return result containing the detached message + * @throws IOException in case of an IO error + * @throws BadData if the byte array does not contain a signed message + */ + @Throws(IOException::class, BadData::class) + fun message(message: ByteArray): ReadyWithResult = message(message.inputStream()) +} From 9283f81c56662d63c565dfd2aaac14868c4bbc3d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:22:12 +0100 Subject: [PATCH 27/46] Replace ByteArrayInputStream with inputStream() --- sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt | 4 +--- sop-java/src/main/kotlin/sop/operation/Encrypt.kt | 7 +++---- sop-java/src/main/kotlin/sop/operation/ExtractCert.kt | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt index 2dd9218..d899b54 100644 --- a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt +++ b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.exception.SOPGPException.BadData @@ -31,6 +30,5 @@ interface DetachedVerify : AbstractVerify, VerifySignatures { * @throws IOException in case of an IO error */ @Throws(BadData::class, IOException::class) - fun signatures(signatures: ByteArray): VerifySignatures = - signatures(ByteArrayInputStream(signatures)) + fun signatures(signatures: ByteArray): VerifySignatures = signatures(signatures.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt index ad30149..0daebee 100644 --- a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.Profile @@ -57,7 +56,7 @@ interface Encrypt { */ @Throws( KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) - fun signWith(key: ByteArray): Encrypt = signWith(ByteArrayInputStream(key)) + fun signWith(key: ByteArray): Encrypt = signWith(key.inputStream()) /** * Provide the password for the secret key used for signing. @@ -125,7 +124,7 @@ interface Encrypt { UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) - fun withCert(cert: ByteArray): Encrypt = withCert(ByteArrayInputStream(cert)) + fun withCert(cert: ByteArray): Encrypt = withCert(cert.inputStream()) /** * Pass in a profile. @@ -162,5 +161,5 @@ interface Encrypt { * @throws KeyIsProtected if at least one signing key cannot be unlocked */ @Throws(IOException::class, KeyIsProtected::class) - fun plaintext(plaintext: ByteArray): Ready = plaintext(ByteArrayInputStream(plaintext)) + fun plaintext(plaintext: ByteArray): Ready = plaintext(plaintext.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt index bd48e08..e2ce1cc 100644 --- a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt +++ b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.Ready @@ -38,5 +37,5 @@ interface ExtractCert { * @throws BadData if the byte array does not contain an OpenPGP key */ @Throws(IOException::class, BadData::class) - fun key(key: ByteArray): Ready = key(ByteArrayInputStream(key)) + fun key(key: ByteArray): Ready = key(key.inputStream()) } From be0ceb0886a9b6a51b51a4519b4098e761ad0cf3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:25:25 +0100 Subject: [PATCH 28/46] Kotlin conversion: InlineSign --- .../main/java/sop/operation/InlineSign.java | 60 ------------------- .../main/kotlin/sop/operation/InlineSign.kt | 47 +++++++++++++++ 2 files changed, 47 insertions(+), 60 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineSign.kt diff --git a/sop-java/src/main/java/sop/operation/InlineSign.java b/sop-java/src/main/java/sop/operation/InlineSign.java deleted file mode 100644 index d45aebd..0000000 --- a/sop-java/src/main/java/sop/operation/InlineSign.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.enums.InlineSignAs; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface InlineSign extends AbstractSign { - - /** - * Sets the signature mode. - * Note: This method has to be called before {@link #key(InputStream)} is called. - * - * @param mode signature mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - InlineSign mode(InlineSignAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Signs data. - * - * @param data input stream containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - Ready data(InputStream data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText; - - /** - * Signs data. - * - * @param data byte array containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - default Ready data(byte[] data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineSign.kt b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt new file mode 100644 index 0000000..11b5668 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.enums.InlineSignAs +import sop.exception.SOPGPException.* + +interface InlineSign : AbstractSign { + + /** + * Sets the signature mode. Note: This method has to be called before [.key] is called. + * + * @param mode signature mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: InlineSignAs): InlineSign + + /** + * Signs data. + * + * @param data input stream containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: InputStream): Ready + + /** + * Signs data. + * + * @param data byte array containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) +} From 6c14f249bb66ba8ca019c8235fe2c21112baf876 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:25:47 +0100 Subject: [PATCH 29/46] Kotlin conversion: InlineVerify --- .../main/java/sop/operation/InlineVerify.java | 54 ------------------- .../main/kotlin/sop/operation/InlineVerify.kt | 42 +++++++++++++++ 2 files changed, 42 insertions(+), 54 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineVerify.kt diff --git a/sop-java/src/main/java/sop/operation/InlineVerify.java b/sop-java/src/main/java/sop/operation/InlineVerify.java deleted file mode 100644 index ac662a0..0000000 --- a/sop-java/src/main/java/sop/operation/InlineVerify.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.ReadyWithResult; -import sop.Verification; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -/** - * API for verification of inline-signed messages. - */ -public interface InlineVerify extends AbstractVerify { - - /** - * Provide the inline-signed data. - * The result can be used to write the plaintext message out and to get the verifications. - * - * @param data signed data - * @return list of signature verifications - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - ReadyWithResult> data(InputStream data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData; - - /** - * Provide the inline-signed data. - * The result can be used to write the plaintext message out and to get the verifications. - * - * @param data signed data - * @return list of signature verifications - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - default ReadyWithResult> data(byte[] data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt new file mode 100644 index 0000000..c16b269 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.Verification +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.NoSignature + +/** API for verification of inline-signed messages. */ +interface InlineVerify : AbstractVerify { + + /** + * Provide the inline-signed data. The result can be used to write the plaintext message out and + * to get the verifications. + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: InputStream): ReadyWithResult> + + /** + * Provide the inline-signed data. The result can be used to write the plaintext message out and + * to get the verifications. + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: ByteArray): ReadyWithResult> = data(data.inputStream()) +} From 145cadef4f7557a52529eaea1a04eb3bcb484868 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:27:17 +0100 Subject: [PATCH 30/46] Kotlin conversion: ListProfiles --- .../main/java/sop/operation/ListProfiles.java | 43 ------------------- .../main/kotlin/sop/operation/ListProfiles.kt | 34 +++++++++++++++ 2 files changed, 34 insertions(+), 43 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ListProfiles.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ListProfiles.kt diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java deleted file mode 100644 index 0c17bd6..0000000 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Profile; - -import java.util.List; - -/** - * Subcommand to list supported profiles of other subcommands. - */ -public interface ListProfiles { - - /** - * Provide the name of the subcommand for which profiles shall be listed. - * The returned list of profiles MUST NOT contain more than 4 entries. - * - * @param command command name (e.g.

generate-key
) - * @return list of profiles. - */ - List subcommand(String command); - - /** - * Return a list of {@link Profile Profiles} supported by the {@link GenerateKey} implementation. - * - * @return profiles - */ - default List generateKey() { - return subcommand("generate-key"); - } - - /** - * Return a list of {@link Profile Profiles} supported by the {@link Encrypt} implementation. - * - * @return profiles - */ - default List encrypt() { - return subcommand("encrypt"); - } - -} diff --git a/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt new file mode 100644 index 0000000..315faf2 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Profile + +/** Subcommand to list supported profiles of other subcommands. */ +interface ListProfiles { + + /** + * Provide the name of the subcommand for which profiles shall be listed. The returned list of + * profiles MUST NOT contain more than 4 entries. + * + * @param command command name (e.g. `generate-key`) + * @return list of profiles. + */ + fun subcommand(command: String): List + + /** + * Return a list of [Profiles][Profile] supported by the [GenerateKey] implementation. + * + * @return profiles + */ + fun generateKey(): List = subcommand("generate-key") + + /** + * Return a list of [Profiles][Profile] supported by the [Encrypt] implementation. + * + * @return profiles + */ + fun encrypt(): List = subcommand("encrypt") +} From 0ee4638bebf88f28fc434f9c78b6fef45c8a877a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:28:59 +0100 Subject: [PATCH 31/46] Kotlin conversion: RevokeKey --- .../main/java/sop/operation/RevokeKey.java | 54 ------------------- .../main/kotlin/sop/operation/RevokeKey.kt | 48 +++++++++++++++++ 2 files changed, 48 insertions(+), 54 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/RevokeKey.java create mode 100644 sop-java/src/main/kotlin/sop/operation/RevokeKey.kt diff --git a/sop-java/src/main/java/sop/operation/RevokeKey.java b/sop-java/src/main/java/sop/operation/RevokeKey.java deleted file mode 100644 index 3ceb5b3..0000000 --- a/sop-java/src/main/java/sop/operation/RevokeKey.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -public interface RevokeKey { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - RevokeKey noArmor(); - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - default RevokeKey withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - RevokeKey withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - - default Ready keys(byte[] bytes) { - return keys(new ByteArrayInputStream(bytes)); - } - - Ready keys(InputStream keys); -} diff --git a/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt new file mode 100644 index 0000000..f3cbe5c --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.exception.SOPGPException.UnsupportedOption +import sop.util.UTF8Util + +interface RevokeKey { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): RevokeKey + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): RevokeKey = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): RevokeKey + + fun keys(bytes: ByteArray): Ready = keys(bytes.inputStream()) + + fun keys(keys: InputStream): Ready +} From a8c2e72ef589bdeea16df71c2901842b6fdd5220 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:30:24 +0100 Subject: [PATCH 32/46] Kotlin conversion: VerifySignatures --- .../java/sop/operation/VerifySignatures.java | 46 ------------------- .../kotlin/sop/operation/VerifySignatures.kt | 38 +++++++++++++++ 2 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/VerifySignatures.java create mode 100644 sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt diff --git a/sop-java/src/main/java/sop/operation/VerifySignatures.java b/sop-java/src/main/java/sop/operation/VerifySignatures.java deleted file mode 100644 index 5181514..0000000 --- a/sop-java/src/main/java/sop/operation/VerifySignatures.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import sop.Verification; -import sop.exception.SOPGPException; - -public interface VerifySignatures { - - /** - * Provide the signed data (without signatures). - * - * @param data signed data - * @return list of signature verifications - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no valid signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - List data(InputStream data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData; - - /** - * Provide the signed data (without signatures). - * - * @param data signed data - * @return list of signature verifications - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no valid signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - default List data(byte[] data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt new file mode 100644 index 0000000..b75e4a5 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Verification +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.NoSignature + +interface VerifySignatures { + + /** + * Provide the signed data (without signatures). + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no valid signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: InputStream): List + + /** + * Provide the signed data (without signatures). + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no valid signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: ByteArray): List = data(data.inputStream()) +} From d0ee9c2066ce0d72cbbd5fb9bcbdf09fbaaa3f17 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:38:34 +0100 Subject: [PATCH 33/46] Kotlin conversion: Version --- .../src/main/java/sop/operation/Version.java | 109 ------------------ .../main/java/sop/operation/package-info.java | 9 -- .../src/main/kotlin/sop/operation/Version.kt | 100 ++++++++++++++++ 3 files changed, 100 insertions(+), 118 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Version.java delete mode 100644 sop-java/src/main/java/sop/operation/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Version.kt diff --git a/sop-java/src/main/java/sop/operation/Version.java b/sop-java/src/main/java/sop/operation/Version.java deleted file mode 100644 index b6d66b9..0000000 --- a/sop-java/src/main/java/sop/operation/Version.java +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -public interface Version { - - /** - * Return the implementations name. - * e.g. "SOP", - * - * @return implementation name - */ - String getName(); - - /** - * Return the implementations short version string. - * e.g. "1.0" - * - * @return version string - */ - String getVersion(); - - /** - * Return version information about the used OpenPGP backend. - * e.g. "Bouncycastle 1.70" - * - * @return backend version string - */ - String getBackendVersion(); - - /** - * Return an extended version string containing multiple lines of version information. - * The first line MUST match the information produced by {@link #getName()} and {@link #getVersion()}, but the rest of the text - * has no defined structure. - * Example: - *
-     *     "SOP 1.0
-     *     Awesome PGP!
-     *     Using Bouncycastle 1.70
-     *     LibFoo 1.2.2
-     *     See https://pgp.example.org/sop/ for more information"
-     * 
- * - * @return extended version string - */ - String getExtendedVersion(); - - /** - * Return the revision of the SOP specification that this implementation is implementing, for example, - *
draft-dkg-openpgp-stateless-cli-06
. - * If the implementation targets a specific draft but the implementer knows the implementation is incomplete, - * it should prefix the draft title with a "~" (TILDE, U+007E), for example: - *
~draft-dkg-openpgp-stateless-cli-06
. - * The implementation MAY emit additional text about its relationship to the targeted draft on the lines following - * the versioned title. - * - * @return implemented SOP spec version - */ - default String getSopSpecVersion() { - StringBuilder sb = new StringBuilder(); - if (isSopSpecImplementationIncomplete()) { - sb.append('~'); - } - - sb.append(getSopSpecRevisionName()); - - if (getSopSpecImplementationRemarks() != null) { - sb.append('\n') - .append('\n') - .append(getSopSpecImplementationRemarks()); - } - - return sb.toString(); - } - - /** - * Return the version number of the latest targeted SOP spec revision. - * - * @return SOP spec revision number - */ - int getSopSpecRevisionNumber(); - - /** - * Return the name of the latest targeted revision of the SOP spec. - * - * @return SOP spec revision string - */ - default String getSopSpecRevisionName() { - return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecRevisionNumber()); - } - - /** - * Return
true
, if this implementation of the SOP spec is known to be incomplete or defective. - * - * @return true if incomplete, false otherwise - */ - boolean isSopSpecImplementationIncomplete(); - - /** - * Return free-form text containing remarks about the completeness of the SOP implementation. - * If there are no remarks, this method returns
null
. - * - * @return remarks or null - */ - String getSopSpecImplementationRemarks(); - -} diff --git a/sop-java/src/main/java/sop/operation/package-info.java b/sop-java/src/main/java/sop/operation/package-info.java deleted file mode 100644 index dde4d5b..0000000 --- a/sop-java/src/main/java/sop/operation/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Different cryptographic operations. - */ -package sop.operation; diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt new file mode 100644 index 0000000..9b3bd8a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +interface Version { + + /** + * Return the implementations name. e.g. `SOP`, + * + * @return implementation name + */ + fun getName(): String + + /** + * Return the implementations short version string. e.g. `1.0` + * + * @return version string + */ + fun getVersion(): String + + /** + * Return version information about the used OpenPGP backend. e.g. `Bouncycastle 1.70` + * + * @return backend version string + */ + fun getBackendVersion(): String + + /** + * Return an extended version string containing multiple lines of version information. The first + * line MUST match the information produced by [getName] and [getVersion], but the rest of the + * text has no defined structure. Example: + * ``` + * "SOP 1.0 + * Awesome PGP! + * Using Bouncycastle 1.70 + * LibFoo 1.2.2 + * See https://pgp.example.org/sop/ for more information" + * ``` + * + * @return extended version string + */ + fun getExtendedVersion(): String + + /** + * Return the revision of the SOP specification that this implementation is implementing, for + * example, `draft-dkg-openpgp-stateless-cli-06`. If the implementation targets a specific draft + * but the implementer knows the implementation is incomplete, it should prefix the draft title + * with a `~` (TILDE, U+007E), for example: `~draft-dkg-openpgp-stateless-cli-06`. The + * implementation MAY emit additional text about its relationship to the targeted draft on the + * lines following the versioned title. + * + * @return implemented SOP spec version + */ + fun getSopSpecVersion(): String { + return buildString { + if (isSopSpecImplementationIncomplete()) append('~') + append(getSopSpecRevisionName()) + if (getSopSpecImplementationRemarks() != null) { + append('\n') + append('\n') + append(getSopSpecImplementationRemarks()) + } + } + } + + /** + * Return the version number of the latest targeted SOP spec revision. + * + * @return SOP spec revision number + */ + fun getSopSpecRevisionNumber(): Int + + /** + * Return the name of the latest targeted revision of the SOP spec. + * + * @return SOP spec revision string + */ + fun getSopSpecRevisionName(): String = buildString { + append("draft-dkg-openpgp-stateless-cli-") + append(String.format("%02d", getSopSpecRevisionNumber())) + } + + /** + * Return
true
, if this implementation of the SOP spec is known to be incomplete or + * defective. + * + * @return true if incomplete, false otherwise + */ + fun isSopSpecImplementationIncomplete(): Boolean + + /** + * Return free-form text containing remarks about the completeness of the SOP implementation. If + * there are no remarks, this method returns
null
. + * + * @return remarks or null + */ + fun getSopSpecImplementationRemarks(): String? +} From 049c18c17ba3907057c65f39dbfa5bf57a0f2960 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:45:56 +0100 Subject: [PATCH 34/46] Fix sop-java-picocli jar task --- sop-java-picocli/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 664c385..438ef50 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -38,6 +38,7 @@ application { jar { dependsOn(":sop-java:jar") + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) manifest { attributes 'Main-Class': "$mainClassName" From 4b9e2c206f18bcec162bb3a82674c151a61239fc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:46:41 +0100 Subject: [PATCH 35/46] Fix DecryptionResult constructor --- sop-java/src/main/kotlin/sop/DecryptionResult.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/DecryptionResult.kt b/sop-java/src/main/kotlin/sop/DecryptionResult.kt index 1ba98bb..b653297 100644 --- a/sop-java/src/main/kotlin/sop/DecryptionResult.kt +++ b/sop-java/src/main/kotlin/sop/DecryptionResult.kt @@ -6,11 +6,10 @@ package sop import sop.util.Optional -data class DecryptionResult -internal constructor(val sessionKey: Optional, val verifications: List) { +class DecryptionResult(sessionKey: SessionKey?, val verifications: List) { + val sessionKey: Optional - constructor( - sessionKey: SessionKey?, - verifications: List - ) : this(Optional.ofNullable(sessionKey), verifications) + init { + this.sessionKey = Optional.ofNullable(sessionKey) + } } From d5c0d4e390ccd0b0c0171c621eed497d8c897a3b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:47:14 +0100 Subject: [PATCH 36/46] Kotlin conversion: ArmorLabel --- .../src/main/java/sop/enums/ArmorLabel.java | 19 ------------------- .../src/main/kotlin/sop/enums/ArmorLabel.kt | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 19 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/ArmorLabel.java create mode 100644 sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt diff --git a/sop-java/src/main/java/sop/enums/ArmorLabel.java b/sop-java/src/main/java/sop/enums/ArmorLabel.java deleted file mode 100644 index bb97e84..0000000 --- a/sop-java/src/main/java/sop/enums/ArmorLabel.java +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum ArmorLabel { - Auto, - Sig, - Key, - Cert, - Message, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt b/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt new file mode 100644 index 0000000..8b4e2cd --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +@Deprecated("Use of armor labels is deprecated.") +enum class ArmorLabel { + auto, + sig, + key, + cert, + message +} From 1c290e0c8f4cae89108adcae5d79334513c49822 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:48:48 +0100 Subject: [PATCH 37/46] Kotlin conversion: EncryptAs --- sop-java/src/main/java/sop/enums/EncryptAs.java | 16 ---------------- sop-java/src/main/kotlin/sop/enums/EncryptAs.kt | 10 ++++++++++ .../testsuite/operation/EncryptDecryptTest.java | 4 ++-- 3 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/EncryptAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/EncryptAs.kt diff --git a/sop-java/src/main/java/sop/enums/EncryptAs.java b/sop-java/src/main/java/sop/enums/EncryptAs.java deleted file mode 100644 index 7e7d4d1..0000000 --- a/sop-java/src/main/java/sop/enums/EncryptAs.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum EncryptAs { - Binary, - Text, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt b/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt new file mode 100644 index 0000000..0b6aa8e --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class EncryptAs { + binary, + text +} 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 0c382dc..9138f64 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -142,7 +142,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(EncryptAs.Binary) + .mode(EncryptAs.binary) .plaintext(message) .getBytes(); @@ -173,7 +173,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(EncryptAs.Text) + .mode(EncryptAs.text) .plaintext(message) .getBytes(); From be6be3deac79a922f24285d45b024438b22b3b04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:50:09 +0100 Subject: [PATCH 38/46] Kotlin conversion: InlineSignAs --- .../src/main/java/sop/enums/InlineSignAs.java | 24 ------------------- .../src/main/kotlin/sop/enums/InlineSignAs.kt | 17 +++++++++++++ 2 files changed, 17 insertions(+), 24 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/InlineSignAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt diff --git a/sop-java/src/main/java/sop/enums/InlineSignAs.java b/sop-java/src/main/java/sop/enums/InlineSignAs.java deleted file mode 100644 index c1097df..0000000 --- a/sop-java/src/main/java/sop/enums/InlineSignAs.java +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum InlineSignAs { - - /** - * Signature is made over the binary message. - */ - binary, - - /** - * Signature is made over the message in text mode. - */ - text, - - /** - * Signature is made using the Cleartext Signature Framework. - */ - clearsigned, -} - diff --git a/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt b/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt new file mode 100644 index 0000000..3440edf --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class InlineSignAs { + + /** Signature is made over the binary message. */ + binary, + + /** Signature is made over the message in text mode. */ + text, + + /** Signature is made using the Cleartext Signature Framework. */ + clearsigned +} From 30c369d24a10b50b64064963b643e0cd203efec9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:51:16 +0100 Subject: [PATCH 39/46] Kotlin conversion: SignAs --- sop-java/src/main/java/sop/enums/SignAs.java | 23 ------------------- sop-java/src/main/kotlin/sop/enums/SignAs.kt | 12 ++++++++++ .../DetachedSignDetachedVerifyTest.java | 2 +- 3 files changed, 13 insertions(+), 24 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/SignAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/SignAs.kt diff --git a/sop-java/src/main/java/sop/enums/SignAs.java b/sop-java/src/main/java/sop/enums/SignAs.java deleted file mode 100644 index 1174098..0000000 --- a/sop-java/src/main/java/sop/enums/SignAs.java +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum SignAs { - /** - * Signature is made over the binary message. - */ - Binary, - - /** - * Signature is made over the message in text mode. - */ - Text, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/SignAs.kt b/sop-java/src/main/kotlin/sop/enums/SignAs.kt new file mode 100644 index 0000000..832e831 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/SignAs.kt @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class SignAs { + /** Signature is made over the binary message. */ + binary, + /** Signature is made over the message in text mode. */ + text +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java index e715c14..e404599 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -62,7 +62,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] signature = sop.detachedSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(SignAs.Text) + .mode(SignAs.text) .data(message) .toByteArrayAndResult() .getBytes(); From 01f98df80b4f5e6757b887fe99b3c1efc21ceb10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:52:45 +0100 Subject: [PATCH 40/46] Kotlin conversion: SignatureMode --- .../main/java/sop/enums/SignatureMode.java | 25 ------------------- .../src/main/java/sop/enums/package-info.java | 9 ------- .../main/kotlin/sop/enums/SignatureMode.kt | 18 +++++++++++++ 3 files changed, 18 insertions(+), 34 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/SignatureMode.java delete mode 100644 sop-java/src/main/java/sop/enums/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/enums/SignatureMode.kt diff --git a/sop-java/src/main/java/sop/enums/SignatureMode.java b/sop-java/src/main/java/sop/enums/SignatureMode.java deleted file mode 100644 index 71ce7d8..0000000 --- a/sop-java/src/main/java/sop/enums/SignatureMode.java +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -/** - * Enum referencing relevant signature types. - * - * @see - * RFC4880 §5.2.1 - Signature Types - */ -public enum SignatureMode { - /** - * Signature of a binary document (
0x00
). - */ - binary, - - /** - * Signature of a canonical text document (
0x01
). - */ - text - - // Other Signature Types are irrelevant. -} diff --git a/sop-java/src/main/java/sop/enums/package-info.java b/sop-java/src/main/java/sop/enums/package-info.java deleted file mode 100644 index 67148d3..0000000 --- a/sop-java/src/main/java/sop/enums/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Enumerations. - */ -package sop.enums; diff --git a/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt b/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt new file mode 100644 index 0000000..7213195 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +/** + * Enum referencing relevant signature types. + * + * @see RFC4880 §5.2.1 - Signature + * Types + */ +enum class SignatureMode { + /** Signature of a binary document (type `0x00`). */ + binary, + /** Signature of a canonical text document (type `0x01`). */ + text +} From b7007cc0070659c58e0e9cad520acd58b6b07342 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:00:21 +0100 Subject: [PATCH 41/46] Kotlin conversion: HexUtil --- sop-java/src/main/java/sop/util/HexUtil.java | 47 -------------------- sop-java/src/main/kotlin/sop/util/HexUtil.kt | 38 ++++++++++++++++ 2 files changed, 38 insertions(+), 47 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/HexUtil.java create mode 100644 sop-java/src/main/kotlin/sop/util/HexUtil.kt diff --git a/sop-java/src/main/java/sop/util/HexUtil.java b/sop-java/src/main/java/sop/util/HexUtil.java deleted file mode 100644 index 9b88f53..0000000 --- a/sop-java/src/main/java/sop/util/HexUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Paul Schaub, @maybeWeCouldStealAVan, @Dave L. -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -public class HexUtil { - - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - - /** - * Encode a byte array to a hex string. - * - * @see - * How to convert a byte array to a hex string in Java? - * @param bytes bytes - * @return hex encoding - */ - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars); - } - - /** - * Decode a hex string into a byte array. - * - * @see - * Convert a string representation of a hex dump to a byte array using Java? - * @param s hex string - * @return decoded byte array - */ - public static byte[] hexToBytes(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } -} diff --git a/sop-java/src/main/kotlin/sop/util/HexUtil.kt b/sop-java/src/main/kotlin/sop/util/HexUtil.kt new file mode 100644 index 0000000..f372137 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/HexUtil.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +class HexUtil { + + companion object { + /** + * Encode a byte array to a hex string. + * + * @param bytes bytes + * @return hex encoding + * @see + * [Convert Byte Arrays to Hex Strings in Kotlin](https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings) + */ + @JvmStatic fun bytesToHex(bytes: ByteArray): String = bytes.toHex() + + /** + * Decode a hex string into a byte array. + * + * @param s hex string + * @return decoded byte array + * @see + * [Kotlin convert hex string to ByteArray](https://stackoverflow.com/a/66614516/11150851) + */ + @JvmStatic fun hexToBytes(s: String): ByteArray = s.decodeHex() + } +} + +fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Hex encoding must have even number of digits." } + + return chunked(2).map { it.toInt(16).toByte() }.toByteArray() +} + +fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02X".format(eachByte) } From 05886228df497c18ce94ffd756fc2075e48b0a95 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:07:13 +0100 Subject: [PATCH 42/46] Kotlin conversion: ProxyOutputStream --- .../main/java/sop/util/ProxyOutputStream.java | 80 ------------------- .../main/kotlin/sop/util/ProxyOutputStream.kt | 75 +++++++++++++++++ 2 files changed, 75 insertions(+), 80 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/ProxyOutputStream.java create mode 100644 sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt diff --git a/sop-java/src/main/java/sop/util/ProxyOutputStream.java b/sop-java/src/main/java/sop/util/ProxyOutputStream.java deleted file mode 100644 index ed24fc2..0000000 --- a/sop-java/src/main/java/sop/util/ProxyOutputStream.java +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * {@link OutputStream} that buffers data being written into it, until its underlying output stream is being replaced. - * At that point, first all the buffered data is being written to the underlying stream, followed by any successive - * data that may get written to the {@link ProxyOutputStream}. - *

- * This class is useful if we need to provide an {@link OutputStream} at one point in time when the final - * target output stream is not yet known. - */ -public class ProxyOutputStream extends OutputStream { - - private final ByteArrayOutputStream buffer; - private OutputStream swapped; - - public ProxyOutputStream() { - this.buffer = new ByteArrayOutputStream(); - } - - public synchronized void replaceOutputStream(OutputStream underlying) throws IOException { - if (underlying == null) { - throw new NullPointerException("Underlying OutputStream cannot be null."); - } - this.swapped = underlying; - - byte[] bufferBytes = buffer.toByteArray(); - swapped.write(bufferBytes); - } - - @Override - public synchronized void write(byte[] b) throws IOException { - if (swapped == null) { - buffer.write(b); - } else { - swapped.write(b); - } - } - - @Override - public synchronized void write(byte[] b, int off, int len) throws IOException { - if (swapped == null) { - buffer.write(b, off, len); - } else { - swapped.write(b, off, len); - } - } - - @Override - public synchronized void flush() throws IOException { - buffer.flush(); - if (swapped != null) { - swapped.flush(); - } - } - - @Override - public synchronized void close() throws IOException { - buffer.close(); - if (swapped != null) { - swapped.close(); - } - } - - @Override - public synchronized void write(int i) throws IOException { - if (swapped == null) { - buffer.write(i); - } else { - swapped.write(i); - } - } -} diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt new file mode 100644 index 0000000..142f7b3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream + +/** + * [OutputStream] that buffers data being written into it, until its underlying output stream is + * being replaced. At that point, first all the buffered data is being written to the underlying + * stream, followed by any successive data that may get written to the [ProxyOutputStream]. This + * class is useful if we need to provide an [OutputStream] at one point in time when the final + * target output stream is not yet known. + */ +class ProxyOutputStream { + private val buffer = ByteArrayOutputStream() + private var swapped: OutputStream? = null + + @Synchronized + fun replaceOutputStream(underlying: OutputStream) { + this.swapped = underlying + swapped!!.write(buffer.toByteArray()) + } + + @Synchronized + @Throws(IOException::class) + fun write(b: ByteArray) { + if (swapped == null) { + buffer.write(b) + } else { + swapped!!.write(b) + } + } + + @Synchronized + @Throws(IOException::class) + fun write(b: ByteArray, off: Int, len: Int) { + if (swapped == null) { + buffer.write(b, off, len) + } else { + swapped!!.write(b, off, len) + } + } + + @Synchronized + @Throws(IOException::class) + fun flush() { + buffer.flush() + if (swapped != null) { + swapped!!.flush() + } + } + + @Synchronized + @Throws(IOException::class) + fun close() { + buffer.close() + if (swapped != null) { + swapped!!.close() + } + } + + @Synchronized + @Throws(IOException::class) + fun write(i: Int) { + if (swapped == null) { + buffer.write(i) + } else { + swapped!!.write(i) + } + } +} From 25a33611fdab6eba7cb3625677d37ccd5c16fd7d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:10:46 +0100 Subject: [PATCH 43/46] Kotlin conversion: UTCUtil --- sop-java/src/main/java/sop/util/UTCUtil.java | 65 -------------------- sop-java/src/main/kotlin/sop/util/UTCUtil.kt | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 65 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/UTCUtil.java create mode 100644 sop-java/src/main/kotlin/sop/util/UTCUtil.kt diff --git a/sop-java/src/main/java/sop/util/UTCUtil.java b/sop-java/src/main/java/sop/util/UTCUtil.java deleted file mode 100644 index 96a6b9c..0000000 --- a/sop-java/src/main/java/sop/util/UTCUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import javax.annotation.Nonnull; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -/** - * Utility class to parse and format dates as ISO-8601 UTC timestamps. - */ -public class UTCUtil { - - public static final SimpleDateFormat UTC_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - public static final SimpleDateFormat[] UTC_PARSERS = new SimpleDateFormat[] { - UTC_FORMATTER, - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), - new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'") - }; - - static { - for (SimpleDateFormat f : UTC_PARSERS) { - f.setTimeZone(TimeZone.getTimeZone("UTC")); - } - } - /** - * Parse an ISO-8601 UTC timestamp from a string. - * - * @param dateString string - * @return date - * @throws ParseException if the date string is malformed and cannot be parsed - */ - @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 - } - } - // No parser worked, so we throw the store exception - throw exception; - } - - /** - * Format a date as ISO-8601 UTC timestamp. - * - * @param date date - * @return timestamp string - */ - public static String formatUTCDate(Date date) { - return UTC_FORMATTER.format(date); - } -} diff --git a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt new file mode 100644 index 0000000..8924f25 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class UTCUtil { + + companion object { + + @JvmStatic val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + @JvmStatic + val UTC_PARSERS = + arrayOf( + UTC_FORMATTER, + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), + SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"), + SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")) + .onEach { fmt -> fmt.timeZone = TimeZone.getTimeZone("UTC") } + + /** + * Parse an ISO-8601 UTC timestamp from a string. + * + * @param dateString string + * @return date + * @throws ParseException if the date string is malformed and cannot be parsed + */ + @JvmStatic + @Throws(ParseException::class) + fun parseUTCDate(dateString: String): Date { + var exception: ParseException? = null + for (parser in UTC_PARSERS) { + try { + return parser.parse(dateString) + } catch (e: ParseException) { + // Store first exception (that of UTC_FORMATTER) to throw if we fail to parse + // the date + if (exception == null) { + exception = e + } + // Try next parser + } + } + throw exception!! + } + + /** + * Format a date as ISO-8601 UTC timestamp. + * + * @param date date + * @return timestamp string + */ + @JvmStatic + fun formatUTCDate(date: Date): String { + return UTC_FORMATTER.format(date) + } + } +} From e1a6ffd07a7e081e207f84d105344013f9535b17 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:18:39 +0100 Subject: [PATCH 44/46] Use @JvmField annotation --- sop-java/src/main/kotlin/sop/util/UTCUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt index 8924f25..4ae13bc 100644 --- a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt +++ b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt @@ -12,8 +12,8 @@ class UTCUtil { companion object { - @JvmStatic val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") - @JvmStatic + @JvmField val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + @JvmField val UTC_PARSERS = arrayOf( UTC_FORMATTER, From 94b428ef62cc9735fad20cca4560eb14af6a2b41 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:18:48 +0100 Subject: [PATCH 45/46] Kotlin conversion: UTF8Util --- sop-java/src/main/java/sop/util/UTF8Util.java | 37 ------------------- .../src/main/java/sop/util/package-info.java | 8 ---- sop-java/src/main/kotlin/sop/util/UTF8Util.kt | 37 +++++++++++++++++++ 3 files changed, 37 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/UTF8Util.java delete mode 100644 sop-java/src/main/java/sop/util/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/util/UTF8Util.kt diff --git a/sop-java/src/main/java/sop/util/UTF8Util.java b/sop-java/src/main/java/sop/util/UTF8Util.java deleted file mode 100644 index 1b4941b..0000000 --- a/sop-java/src/main/java/sop/util/UTF8Util.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -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 { - - public static final Charset UTF8 = Charset.forName("UTF8"); - private static final CharsetDecoder UTF8Decoder = UTF8 - .newDecoder() - .onUnmappableCharacter(CodingErrorAction.REPORT) - .onMalformedInput(CodingErrorAction.REPORT); - - /** - * Detect non-valid UTF8 data. - * - * @see ante on StackOverflow - * @param data utf-8 encoded bytes - * - * @return decoded string - * @throws CharacterCodingException if the input data does not resemble UTF8 - */ - public static String decodeUTF8(byte[] data) - throws CharacterCodingException { - ByteBuffer byteBuffer = ByteBuffer.wrap(data); - CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer); - return charBuffer.toString(); - } -} diff --git a/sop-java/src/main/java/sop/util/package-info.java b/sop-java/src/main/java/sop/util/package-info.java deleted file mode 100644 index 3dd9fc1..0000000 --- a/sop-java/src/main/java/sop/util/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Utility classes. - */ -package sop.util; diff --git a/sop-java/src/main/kotlin/sop/util/UTF8Util.kt b/sop-java/src/main/kotlin/sop/util/UTF8Util.kt new file mode 100644 index 0000000..770f32c --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/UTF8Util.kt @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.nio.ByteBuffer +import java.nio.charset.Charset +import java.nio.charset.CodingErrorAction + +class UTF8Util { + companion object { + @JvmField val UTF8: Charset = Charset.forName("UTF8") + + @JvmStatic + private val UTF8Decoder = + UTF8.newDecoder() + .onUnmappableCharacter(CodingErrorAction.REPORT) + .onMalformedInput(CodingErrorAction.REPORT) + + /** + * Detect non-valid UTF8 data. + * + * @param data utf-8 encoded bytes + * @return decoded string + * @throws CharacterCodingException if the input data does not resemble UTF8 + * @see [ante on StackOverflow](https://stackoverflow.com/a/1471193) + */ + @JvmStatic + @Throws(CharacterCodingException::class) + fun decodeUTF8(data: ByteArray): String { + val byteBuffer = ByteBuffer.wrap(data) + val charBuffer = UTF8Decoder.decode(byteBuffer) + return charBuffer.toString() + } + } +} From 7824ee92c536f990de01c63d07c2dbfd68f4f579 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:27:47 +0100 Subject: [PATCH 46/46] Kotlin conversion: SOPGPException --- .../java/sop/exception/SOPGPException.java | 473 ------------------ .../main/java/sop/exception/package-info.java | 9 - sop-java/src/main/java/sop/package-info.java | 8 - .../kotlin/sop/exception/SOPGPException.kt | 308 ++++++++++++ 4 files changed, 308 insertions(+), 490 deletions(-) delete mode 100644 sop-java/src/main/java/sop/exception/SOPGPException.java delete mode 100644 sop-java/src/main/java/sop/exception/package-info.java delete mode 100644 sop-java/src/main/java/sop/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/exception/SOPGPException.kt diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java deleted file mode 100644 index 1d13065..0000000 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ /dev/null @@ -1,473 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.exception; - -public abstract class SOPGPException extends RuntimeException { - - public SOPGPException() { - super(); - } - - public SOPGPException(String message) { - super(message); - } - - public SOPGPException(Throwable e) { - super(e); - } - - public SOPGPException(String message, Throwable cause) { - super(message, cause); - } - - public abstract int getExitCode(); - - /** - * No acceptable signatures found (sop verify, inline-verify). - */ - public static class NoSignature extends SOPGPException { - - public static final int EXIT_CODE = 3; - - public NoSignature() { - this("No verifiable signature found."); - } - - public NoSignature(String message) { - super(message); - } - - public NoSignature(String errorMsg, NoSignature e) { - super(errorMsg, e); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign). - */ - public static class UnsupportedAsymmetricAlgo extends SOPGPException { - - public static final int EXIT_CODE = 13; - - public UnsupportedAsymmetricAlgo(String message, Throwable e) { - super(message, e); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Certificate not encryption capable (e,g, expired, revoked, unacceptable usage). - */ - public static class CertCannotEncrypt extends SOPGPException { - public static final int EXIT_CODE = 17; - - public CertCannotEncrypt(String message, Throwable cause) { - super(message, cause); - } - - public CertCannotEncrypt(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Missing required argument. - */ - public static class MissingArg extends SOPGPException { - - public static final int EXIT_CODE = 19; - - public MissingArg() { - - } - - public MissingArg(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Incomplete verification instructions (sop decrypt). - */ - public static class IncompleteVerification extends SOPGPException { - - public static final int EXIT_CODE = 23; - - public IncompleteVerification(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unable to decrypt (sop decrypt). - */ - public static class CannotDecrypt extends SOPGPException { - - public static final int EXIT_CODE = 29; - - public CannotDecrypt() { - - } - - public CannotDecrypt(String errorMsg, Throwable e) { - super(errorMsg, e); - } - - public CannotDecrypt(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Non-UTF-8 or otherwise unreliable password (sop encrypt). - */ - public static class PasswordNotHumanReadable extends SOPGPException { - - public static final int EXIT_CODE = 31; - - public PasswordNotHumanReadable() { - super(); - } - - public PasswordNotHumanReadable(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unsupported option. - */ - public static class UnsupportedOption extends SOPGPException { - - public static final int EXIT_CODE = 37; - - public UnsupportedOption(String message) { - super(message); - } - - public UnsupportedOption(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Invalid data type (no secret key where KEYS expected, etc.). - */ - public static class BadData extends SOPGPException { - - public static final int EXIT_CODE = 41; - - public BadData(String message) { - super(message); - } - - public BadData(Throwable throwable) { - super(throwable); - } - - public BadData(String message, Throwable throwable) { - super(message, throwable); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Non-Text input where text expected. - */ - public static class ExpectedText extends SOPGPException { - - public static final int EXIT_CODE = 53; - - public ExpectedText() { - super(); - } - - public ExpectedText(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Output file already exists. - */ - public static class OutputExists extends SOPGPException { - - public static final int EXIT_CODE = 59; - - public OutputExists(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Input file does not exist. - */ - public static class MissingInput extends SOPGPException { - - public static final int EXIT_CODE = 61; - - public MissingInput(String message, Throwable cause) { - super(message, cause); - } - - public MissingInput(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * A KEYS input is protected (locked) with a password and sop failed to unlock it. - */ - public static class KeyIsProtected extends SOPGPException { - - public static final int EXIT_CODE = 67; - - public KeyIsProtected() { - super(); - } - - public KeyIsProtected(String message) { - super(message); - } - - public KeyIsProtected(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unsupported subcommand. - */ - public static class UnsupportedSubcommand extends SOPGPException { - - public static final int EXIT_CODE = 69; - - public UnsupportedSubcommand(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * An indirect parameter is a special designator (it starts with @), but sop does not know how to handle the prefix. - */ - public static class UnsupportedSpecialPrefix extends SOPGPException { - - public static final int EXIT_CODE = 71; - - public UnsupportedSpecialPrefix(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Exception that gets thrown if a special designator (starting with @) is given, but the filesystem contains - * a file matching the designator. - *

- * E.g.

@ENV:FOO
is given, but
./@ENV:FOO
exists on the filesystem. - */ - public static class AmbiguousInput extends SOPGPException { - - public static final int EXIT_CODE = 73; - - public AmbiguousInput(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Key not signature-capable (e.g. expired, revoked, unacceptable usage flags). - */ - public static class KeyCannotSign extends SOPGPException { - - public static final int EXIT_CODE = 79; - - public KeyCannotSign() { - super(); - } - - public KeyCannotSign(String message) { - super(message); - } - - public KeyCannotSign(String s, Throwable throwable) { - super(s, throwable); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * User provided incompatible options (e.g. "--as=clearsigned --no-armor"). - */ - public static class IncompatibleOptions extends SOPGPException { - - public static final int EXIT_CODE = 83; - - public IncompatibleOptions() { - super(); - } - - public IncompatibleOptions(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * The user provided a subcommand with an unsupported profile ("--profile=XYZ"), - * or the user tried to list profiles of a subcommand that does not support profiles at all. - */ - public static class UnsupportedProfile extends SOPGPException { - - public static final int EXIT_CODE = 89; - - private final String subcommand; - private final String profile; - - /** - * Create an exception signalling a subcommand that does not support any profiles. - * - * @param subcommand subcommand - */ - public UnsupportedProfile(String subcommand) { - super("Subcommand '" + subcommand + "' does not support any profiles."); - this.subcommand = subcommand; - this.profile = null; - } - - /** - * Create an exception signalling a subcommand does not support a specific profile. - * - * @param subcommand subcommand - * @param profile unsupported profile - */ - public UnsupportedProfile(String subcommand, String profile) { - super("Subcommand '" + subcommand + "' does not support profile '" + profile + "'."); - this.subcommand = subcommand; - this.profile = profile; - } - - /** - * Wrap an exception into another instance with a possibly translated error message. - * - * @param errorMsg error message - * @param e exception - */ - public UnsupportedProfile(String errorMsg, UnsupportedProfile e) { - super(errorMsg, e); - this.subcommand = e.getSubcommand(); - this.profile = e.getProfile(); - } - - /** - * Return the subcommand name. - * - * @return subcommand - */ - public String getSubcommand() { - return subcommand; - } - - /** - * Return the profile name. - * May return
null
. - * - * @return profile name - */ - public String getProfile() { - return profile; - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } -} diff --git a/sop-java/src/main/java/sop/exception/package-info.java b/sop-java/src/main/java/sop/exception/package-info.java deleted file mode 100644 index 4abc562..0000000 --- a/sop-java/src/main/java/sop/exception/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Exception classes. - */ -package sop.exception; diff --git a/sop-java/src/main/java/sop/package-info.java b/sop-java/src/main/java/sop/package-info.java deleted file mode 100644 index 5ad4f52..0000000 --- a/sop-java/src/main/java/sop/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - */ -package sop; diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt new file mode 100644 index 0000000..2473258 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -0,0 +1,308 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.exception + +abstract class SOPGPException : RuntimeException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor(message: String, cause: Throwable) : super(message, cause) + + abstract fun getExitCode(): Int + + /** No acceptable signatures found (sop verify, inline-verify). */ + class NoSignature : SOPGPException { + @JvmOverloads + constructor(message: String = "No verifiable signature found.") : super(message) + + constructor(errorMsg: String, e: NoSignature) : super(errorMsg, e) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 3 + } + } + + /** Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign). */ + class UnsupportedAsymmetricAlgo(message: String, e: Throwable) : SOPGPException(message, e) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 13 + } + } + + /** Certificate not encryption capable (e,g, expired, revoked, unacceptable usage). */ + class CertCannotEncrypt : SOPGPException { + constructor(message: String, cause: Throwable) : super(message, cause) + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 17 + } + } + + /** Missing required argument. */ + class MissingArg : SOPGPException { + constructor() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 19 + } + } + + /** Incomplete verification instructions (sop decrypt). */ + class IncompleteVerification(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 23 + } + } + + /** Unable to decrypt (sop decrypt). */ + class CannotDecrypt : SOPGPException { + constructor() + + constructor(errorMsg: String, e: Throwable) : super(errorMsg, e) + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 29 + } + } + + /** Non-UTF-8 or otherwise unreliable password (sop encrypt). */ + class PasswordNotHumanReadable : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 31 + } + } + + /** Unsupported option. */ + class UnsupportedOption : SOPGPException { + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 37 + } + } + + /** Invalid data type (no secret key where KEYS expected, etc.). */ + class BadData : SOPGPException { + constructor(message: String) : super(message) + + constructor(throwable: Throwable) : super(throwable) + + constructor(message: String, throwable: Throwable) : super(message, throwable) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 41 + } + } + + /** Non-Text input where text expected. */ + class ExpectedText : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 53 + } + } + + /** Output file already exists. */ + class OutputExists(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 59 + } + } + + /** Input file does not exist. */ + class MissingInput : SOPGPException { + constructor(message: String, cause: Throwable) : super(message, cause) + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 61 + } + } + + /** A KEYS input is protected (locked) with a password and sop failed to unlock it. */ + class KeyIsProtected : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 67 + } + } + + /** Unsupported subcommand. */ + class UnsupportedSubcommand(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 69 + } + } + + /** + * An indirect parameter is a special designator (it starts with @), but sop does not know how + * to handle the prefix. + */ + class UnsupportedSpecialPrefix(errorMsg: String) : SOPGPException(errorMsg) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 71 + } + } + + /** + * Exception that gets thrown if a special designator (starting with @) is given, but the + * filesystem contains a file matching the designator. + * + * E.g.
@ENV:FOO
is given, but
./@ENV:FOO
exists on the filesystem. + */ + class AmbiguousInput(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 73 + } + } + + /** Key not signature-capable (e.g. expired, revoked, unacceptable usage flags). */ + class KeyCannotSign : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + constructor(s: String, throwable: Throwable) : super(s, throwable) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 79 + } + } + + /** User provided incompatible options (e.g. "--as=clearsigned --no-armor"). */ + class IncompatibleOptions : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 83 + } + } + + /** + * The user provided a subcommand with an unsupported profile ("--profile=XYZ"), or the user + * tried to list profiles of a subcommand that does not support profiles at all. + */ + class UnsupportedProfile : SOPGPException { + /** + * Return the subcommand name. + * + * @return subcommand + */ + val subcommand: String + + /** + * Return the profile name. May return `null`. + * + * @return profile name + */ + val profile: String? + + /** + * Create an exception signalling a subcommand that does not support any profiles. + * + * @param subcommand subcommand + */ + constructor( + subcommand: String + ) : super("Subcommand '$subcommand' does not support any profiles.") { + this.subcommand = subcommand + profile = null + } + + /** + * Create an exception signalling a subcommand does not support a specific profile. + * + * @param subcommand subcommand + * @param profile unsupported profile + */ + constructor( + subcommand: String, + profile: String + ) : super("Subcommand '$subcommand' does not support profile '$profile'.") { + this.subcommand = subcommand + this.profile = profile + } + + /** + * Wrap an exception into another instance with a possibly translated error message. + * + * @param errorMsg error message + * @param e exception + */ + constructor(errorMsg: String, e: UnsupportedProfile) : super(errorMsg, e) { + subcommand = e.subcommand + profile = e.profile + } + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 89 + } + } +}